Подтвердить что ты не робот

Ошибка Nashorn при вызове перегруженного метода с параметром varargs

Предположим, что следующий API:

package nashorn.test;

public class API {

    public static void test(String string) {
        throw new RuntimeException("Don't call this");
    }

    public static void test(Integer... args) {
        System.out.println("OK");
    }
}

Следующий фрагмент кода Nashorn JavaScript завершится неудачно:

var API = Java.type("nashorn.test.API");
API.test(1);

Первый метод будет вызываться вместо второго. Это ошибка в двигателе Nashorn?

Для записи эта проблема была ранее сообщена в пользовательской группе jOOQ, где перегрузка методов и varargs используются в значительной степени, и где эта проблема может вызвать много беда.

О боксе

Может возникнуть подозрение, что это может иметь отношение к боксу. Это не так. Проблема также возникает, когда я делаю

public class API {

    public static void test(String string) {
        throw new RuntimeException("Don't call this");
    }

    public static void test(Integer... args) {
        System.out.println("OK");
    }

    public static void test(MyType... args) {
        System.out.println("OK");
    }
}

и

public class MyType {
}

И затем:

var API = Java.type("nashorn.test.API");
var MyType = Java.type("nashorn.test.MyType");

API.test(new MyType());
4b9b3361

Ответ 1

Как парень, который написал механизм разрешения перегрузки для Nashorn, я всегда очарован угловыми случаями, с которыми сталкиваются люди. К лучшему или худшему, вот как это получается:

Решение метода перегрузки Nashorn позволяет максимально точно подбирать спецификацию языка Java (JLS), но также допускает преобразования JavaScript. JLS говорит, что при выборе метода для вызова перегруженного имени методы переменной arity могут рассматриваться для вызова только тогда, когда не применяется применимый метод фиксированной arity. Обычно при вызове с Java test(String) не применимо к вызову с int, поэтому метод test(Integer...) будет вызван. Однако, поскольку JavaScript фактически допускает неявное преобразование числа в строку, он применим и рассматривается перед любыми методами переменной arity. Отсюда наблюдаемое поведение. Arity превосходит неконверсию. Если вы добавили метод test(int), он будет вызван перед методом String, поскольку он фиксирует arity и более специфичен, чем String.

Можно утверждать, что мы должны изменить алгоритм выбора метода. Многое было уделено этому, так как еще до проекта Nashorn (даже когда я разрабатывал Dynalink самостоятельно). Текущий код (как это реализовано в библиотеке Dynalink, над которой на самом деле основан Насборн) следует за JLS к письму, и в отсутствие конверсий по языковому типу будут выбирать те же методы, что и Java. Однако, как только вы начнете расслаблять свою систему типов, все начинает тонко меняться, и чем больше вы ее расслабляете, тем больше они будут меняться (и JavaScript релаксирует много), и любое изменение алгоритма выбора будет иметь некоторые другие Боюсь, странное поведение, с которым кто-то столкнется... он просто поставляется с системой расслабленного типа. Например:

  • Если мы допустили, что varargs следует рассматривать вместе с fixargs, нам нужно будет выработать "более конкретную" связь между различными методами arity, что не существует в JLS и, следовательно, несовместимо с ним, и будет иногда вызывать varargs, если в противном случае JLS будет вызывать вызов fixargs.
  • Если мы запретили JS-разрешенные преобразования (таким образом, чтобы test(String) не считалось применимым к параметру int), некоторые разработчики JS чувствовали бы себя обремененными необходимостью прервать свою программу с вызовом метода String (например, t26 > для обеспечения x является строкой и т.д.

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

Наконец, когда вы вручную выбираете среди перегрузок, вы также можете придерживаться неквалифицированных имен типов, если нет никакой двусмысленности в методах потенциальных методов для имени пакета в позиции аргумента, то есть

API["test(Integer[])"](1);

тоже должен работать, не нужно префикс java.lang.. Это может немного облегчить синтаксический шум, если вы не можете переделать API.

НТН, Аттила.

Ответ 2

Это допустимые обходные пути:

Явное вызов метода test(Integer[]) с использованием аргумента массива:

var API = Java.type("nashorn.test.API");
API.test([1]);

Удаление перегрузки:

public class AlternativeAPI1 {
    public static void test(Integer... args) {
        System.out.println("OK");
    }
}

Удаление varargs:

public class AlternativeAPI3 {
    public static void test(String string) {
        throw new RuntimeException("Don't call this");
    }

    public static void test(Integer args) {
        System.out.println("OK");
    }
}

Замена String на CharSequence (или любой другой "подобный тип" ):

public class AlternativeAPI2 {
    public static void test(CharSequence string) {
        throw new RuntimeException("Don't call this");
    }

    public static void test(Integer args) {
        System.out.println("OK");
    }
}

Ответ 3

Это неоднозначная ситуация. Во втором случае он ищет либо массив целых чисел, либо несколько целых чисел, чтобы отличить его от первого случая. Вы можете использовать выбор метода, чтобы рассказать нам о том, что вы имеете в виду.

API["test(java.lang.Integer[])"](1);