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);
Вот упрощенный пример, который воспроизводит проблему и использует только основные классы 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 должны договориться о том, каким образом он должен быть обработан, прежде чем мы сможем сказать, на какой стороне находится ошибка.
Я только что исправил эту проблему в JDK9 и JDK8u45. См. эту ошибку . Для перехода к продвинутым сборкам потребуется немного времени. Дэн просто указал мне на этот вопрос StackOverflow, поэтому я добавляю эту заметку. Когда вы находите ошибки, пожалуйста, отправьте их.
Я обратился к этому с помощью того, что компилятор создает мост, а также подход ко многим случаям ссылок на сложные методы. Мы также изучаем технические последствия.
Я обнаружил, что обходной путь для этого заключался в замене порядка дженериков. Например, используйте class A<T extends B & C>
, где вам нужно получить доступ к методу B
или использовать class A<T extends C & B>
, если вам нужен доступ к методу C
. Конечно, если вам нужен доступ к методам обоих классов, это не сработает. Я нашел это полезным, когда один из интерфейсов был интерфейсом маркера, например Serializable
.
Что касается исправления этого в JDK, единственной информацией, которую я мог найти, были некоторые ошибки в отладчике ошибок openjdk, которые отмечены как разрешенные в версии 9, что довольно бесполезно.
Эта ошибка не полностью исправлена. Я просто столкнулся с 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
}
}
}