openlang

Хелпер, перенесённый из Java, в Groovy вызывает другую перегрузку

Java-класс хелпера переехал в Groovy на стороне сборочного скрипта, и тот же вызов ушёл в другой метод. По порядку.

class Printer {
    void show(Object o) { println 'object' }
    void show(String s) { println 'string' }

    void run(Object x) { show(x) }
}

new Printer().run('hi')

В Java объявленный тип x - это Object, поэтому show(Object) выбирается при компиляции и печатается object. Groovy выбирает метод во время выполнения по фактическому типу аргумента, поэтому тот же код печатает string. В документации это описано как multi-methods, так что это не баг.

Путает третий случай. Стоит повесить на класс @CompileStatic, и снова печатается object. То есть один и тот же исходник вызывает разные методы в зависимости от одной аннотации. Есть ещё несколько хелперов с перегрузками по типам, и хочется понять, вешают ли @CompileStatic на файл или на класс, или правильнее вообще избавиться от перегрузок.

Разговор продолжается 3

Следуя доводам
  1. Ссылка на ответ

    Решает режим компиляции вызывающего кода, а не класса, где объявлен show. В вашем примере это один и тот же класс, поэтому разница не видна. Перенесите обе перегрузки в утилитный класс с @CompileStatic и вызовите их из динамического скрипта, и вызов в скрипте всё равно диспетчеризуется во время выполнения, так что печатается string. Аннотация должна покрывать код, который делает вызов.

    Поэтому аннотация на класс помогает, только когда вызывающие живут в этом же классе. Для хелперов, которые зовут из сборочных скриптов, я бы скорее убрал неоднозначность, чем обходил её аннотациями. Две перегрузки, различающиеся лишь подтипом, как раз то место, где статический и динамический выбор расходятся. Два имени или один метод, который сам ветвится по типу, дают один ответ в обоих режимах, а цена - чуть более длинное имя в месте вызова.

  2. Ссылка на ответ

    Я тоже за разные имена. Но есть ещё вариант. switch в Groovy всегда принимал класс в case, так что ветвиться по типу во время выполнения можно вообще без опоры на диспетчеризацию.

    void show(Object o) {
        switch (o) {
            case String:
                println 'string'
                break
            default:
                println 'object'
        }
    }

    Метод один, выбирать не из чего, поэтому string печатается и с @CompileStatic, и без него. Если когда-нибудь придётся вернуть это в Java, получится паттерн с instanceof почти строка в строку. То, что методов становится не два, а один, для меня плюс сам по себе.

Другие стороны того же вопроса

  1. Единицы измерения как дешёвые идентификаторы

    Для целочисленных идентификаторов я пробую единицы измерения вместо объединений с одним случаем, и хочется услышать второе мн…

    Ответы 1fsharp
  2. Пустая ячейка равна и нулю, и пустой строке

    Достался макрос, который помечает строки заказов с нулевым количеством, и он пометил заодно все пустые строки. Проверка была…

    Ответы 7vba
  3. Проверка JSON до размеченного объединения

    Утверждение типа только говорит компилятору, во что верить. Внешний ответ приходит байтами, и пока его никто не проверил, нич…

    Ответы 9typescript