Статьи

Как Nashorn влияет на развитие API на новом уровне

После нашей предыдущей статьи о том, как использовать jOOQ с Java 8 и Nashorn , один из наших пользователей обнаружил недостаток в использовании jOOQ API, как обсуждалось здесь в группе пользователей . В сущности, недостаток можно резюмировать так:

Java-код

package org.jooq.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");
    }
}

Код JavaScript

var API = Java.type("org.jooq.nashorn.test.API");
API.test(1); // This will fail with RuntimeException

После некоторого расследования и любезной помощи Аттилы Сегеди , а также Джима Ласки (обоих разработчиков Nashorn из Oracle) стало ясно, что Nashorn устраняет неоднозначность перегруженных методов и переменных по-разному, чем того мог ожидать старый Java-разработчик. Цитирую Аттилу:

Разрешение метода перегрузки Nashorn имитирует спецификацию языка Java (JLS) в максимально возможной степени, но допускает также специфичные для JavaScript преобразования. JLS говорит, что при выборе метода для вызова для перегруженного имени методы переменной арности могут рассматриваться для вызова только в том случае, если нет применимого метода фиксированной арности.

Я согласен с тем, что методы переменной арности можно рассматривать только тогда, когда нет применимого метода фиксированной арности. Но само понятие «применимы» себя полностью изменено , как продвижение типа (или принуждение / преобразование) с помощью ToString , ToNumber , ToBoolean является предпочтительным над тем, что интуитивно кажется, «точным» матчи с переменной длиной методы!

Пусть это утонет!

Учитывая, что теперь мы знаем, как Nashorn разрешает перегрузку, мы можем видеть, что любое из следующего является допустимым обходным путем:

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

Это самый простой подход, когда вы игнорируете тот факт, что varargs существуют, и просто создаете явный массив.

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

Явный вызов метода test (Integer []) следующим образом:

Это, безусловно, самый безопасный подход, поскольку вы убираете всю неопределенность из вызова метода.

var API = Java.type("org.jooq.nashorn.test.API");
API["test(Integer[])"](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");
    }
}

Предоставление точного варианта:

public class AlternativeAPI4 {
    public static void test(String string) {
        throw new RuntimeException("Don't call this");
    }
 
    public static void test(Integer args) {
        test(new Integer[] { args });
    }
 
    public static void test(Integer... args) {
        System.out.println("OK");
    }
}

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

Теперь это интересно:

public class AlternativeAPI5 {
    public static void test(CharSequence string) {
        throw new RuntimeException("Don't call this");
    }
 
    public static void test(Integer args) {
        System.out.println("OK");
    }
}

В частности, на мой взгляд , различие между типами CharSequenceи Stringтипами кажется очень случайным с точки зрения Java.

Согласитесь, реализовать преобразование перегруженного метода в динамически типизированном языке очень сложно, если вообще возможно. Любое решение — это компромисс, который может привести к ошибкам с некоторых сторон. Или, как сказал Аттила:

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

Правда! Но не только выбор метода перегрузки очень чувствителен даже к небольшим изменениям. Использование Nashorn с совместимостью с Java тоже! Как разработчик API, с годами я привык к семантическому версионированию и множеству тонких правил, которым нужно следовать, чтобы обеспечить совместимость с исходным кодом API, совместимость с поведением — и, если возможно, — в значительной степени с двоичной совместимостью .

Забудьте об этом, когда ваши клиенты используют Nashorn. Они сами по себе. Вновь введенная перегрузка в вашем Java API может очень сильно сломать ваших клиентов Nashorn. Но опять же, это JavaScript, язык, который говорит вам во время выполнения, что:

['10','10','10','10'].map(parseInt)

… дает

[10, NaN, 2, 3]

… и где

++[[]][+[]]+[+[]] === "10"

дает истину! ( источники здесь )

Для получения дополнительной информации о JavaScript, пожалуйста, посетите этот вводный урок .