Почему ссылка метода создает java.lang.BootstrapMethodError [duplicate]

ECMAScript 6 имеет «генераторы», которые позволяют вам легко программировать в асинхронном стиле.

function* myGenerator() {
    const callback = yield;
    let [response] = yield $.ajax("https://stackoverflow.com", {complete: callback});
    console.log("response is:", response);

    // examples of other things you can do
    yield setTimeout(callback, 1000);
    console.log("it delayed for 1000ms");
    while (response.statusText === "error") {
        [response] = yield* anotherGenerator();
    }
}

Для запуска вышеуказанного кода вы делаете это:

const gen = myGenerator(); // Create generator
gen.next(); // Start it
gen.next((...args) => gen.next([...args])); // Set its callback function

Если вам нужно настроить таргетинг на браузеры, которые не поддерживают ES6, вы можете запустить код через Babel или short-compiler для генерации ECMAScript 5.

Обратный вызов ...args завернут в массив и разрушен, когда вы их читаете так что шаблон может справиться с обратными вызовами, которые имеют несколько аргументов. Например, с узлом fs :

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);

19
задан Steve McKay 20 November 2014 в 04:30
поделиться

4 ответа

Вот упрощенный пример, который воспроизводит проблему и использует только основные классы Java:

public static void main(String[] argv) {
    System.out.println(dummy("foo"));
}
static <T extends Serializable&CharSequence> int dummy(T value) {
    return Optional.ofNullable(value).map(CharSequence::length).orElse(0);
}

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

Как и в случае с множеством общих конструкций, на уровне байтового кода требуется тип, который не отображается в исходном коде. Поскольку LambdaMetafactory явно требует дескриптор метода direct , ссылка метода, которая инкапсулирует такой тип, не может быть передана как MethodHandle на фабрику.

Есть два возможных способа борьбы с ним.

Первым решением было бы изменить LambdaMetafactory, чтобы доверять MethodHandle, если тип приемника является interface и вставить требуемый тип бросать сам по себе в сгенерированном классе лямбда вместо того, чтобы отклонять его. В конце концов, он уже подходит для типов параметров и возвратов.

В качестве альтернативы, компилятор будет отвечать за создание синтетического вспомогательного метода, инкапсулирующего вызов типа и метода, как если бы вы написали лямбда выражение. Это не уникальная ситуация. Если вы используете ссылку метода на метод varargs или создание массива, например, например, String[]::new, они не могут быть выражены в виде ручных методов direct и заканчиваются синтетическими вспомогательными методами.

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

19
ответ дан Holger 1 September 2018 в 09:15
поделиться

Я только что исправил эту проблему в JDK9 и JDK8u45. См. эту ошибку . Для перехода к продвинутым сборкам потребуется немного времени. Дэн просто указал мне на этот вопрос StackOverflow, поэтому я добавляю эту заметку. Когда вы находите ошибки, пожалуйста, отправьте их.

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

14
ответ дан Don Branson 1 September 2018 в 09:15
поделиться

Я обнаружил, что обходной путь для этого заключался в замене порядка дженериков. Например, используйте class A<T extends B & C>, где вам нужно получить доступ к методу B или использовать class A<T extends C & B>, если вам нужен доступ к методу C. Конечно, если вам нужен доступ к методам обоих классов, это не сработает. Я нашел это полезным, когда один из интерфейсов был интерфейсом маркера, например Serializable.

Что касается исправления этого в JDK, единственной информацией, которую я мог найти, были некоторые ошибки в отладчике ошибок openjdk, которые отмечены как разрешенные в версии 9, что довольно бесполезно.

1
ответ дан Matt 1 September 2018 в 09:15
поделиться

Эта ошибка не полностью исправлена. Я просто столкнулся с LambdaConversionException в 1.8.0_72 и увидел, что в системе отслеживания ошибок Oracle есть отчеты об ошибках: link1 , link2 .

(Редактирование: Связанные ошибки, как сообщается, закрыты в JDK 9 b93)

В качестве простого обходного пути я избегаю обработки дескрипторов методов. Поэтому вместо

.map(entity::filename)

я делаю

.map(entity -> entity.filename())

Вот код для воспроизведения проблемы на Debian 3.11.8-1 x86_64.

import java.awt.Component;
import java.util.Collection;
import java.util.Collections;

public class MethodHandleTest {
    public static void main(String... args) {
        new MethodHandleTest().run();
    }

    private void run() {
        ComponentWithSomeMethod myComp = new ComponentWithSomeMethod();
        new Caller<ComponentWithSomeMethod>().callSomeMethod(Collections.singletonList(myComp));
    }

    private interface HasSomeMethod {
        void someMethod();
    }

    static class ComponentWithSomeMethod extends Component implements HasSomeMethod {
        @Override
        public void someMethod() {
            System.out.println("Some method");
        }
    }

    class Caller<T extends Component & HasSomeMethod> {
        public void callSomeMethod(Collection<T> components) {
            components.forEach(HasSomeMethod::someMethod); //  <-- crashes
//          components.forEach(comp -> comp.someMethod());     <-- works fine

        }
    }
}
10
ответ дан Matthias Braun 1 September 2018 в 09:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: