Плохой тип в стеке операнда... используя jdk 8, lambdas с анонимными внутренними классами не удается, почему?
Выполнение кода ниже приводит к сообщению об ошибке Bad type on operand stack
.
public static void main(String args[]) {
TransformService transformService = (inputs) -> {
return new ArrayList<String>(3) {{
add("one");
add("two");
add("three");
}};
};
Collection<Integer> inputs = new HashSet<Integer>(2) {{
add(5);
add(7);
}};
Collection<String> results = transformService.transform(inputs);
System.out.println(results.size());
}
public interface TransformService {
Collection<String> transform(Collection<Integer> inputs);
}
Однако удаление инициализации двойной привязки (анонимные внутренние классы) внутри lamda позволяет коду работать как ожидалось, почему? Нижеследующие работы:
public class SecondLambda {
public static void main(String args[]) {
TransformService transformService = (inputs) -> {
Collection<String> results = new ArrayList<String>(3);
results.add("one");
results.add("two");
results.add("three");
return results;
};
Collection<Integer> inputs = new HashSet<Integer>(2) {{
add(5);
add(7);
}};
Collection<String> results = transformService.transform(inputs);
System.out.println(results.size());
}
public interface TransformService {
Collection<String> transform(Collection<Integer> inputs);
}
}
Ошибка компилятора? Это ранняя версия доступа...
(Это не будет компилироваться, если у вас нет последней jdk 8 lambda download.)
Ответы
Ответ 1
Кажется, эта проблема возникает не только в том случае, когда lambda
возвращает тип anonymous
, но даже если какой-либо анонимный класс построен внутри lambda
. То есть:.
public class TestLambda {
public static void main(String[] args) {
xxx();
}
static void xxx() {
Functional1 f = () -> {
Object o = new Object() { };
return new A();
};
}
static class A { }
static interface Functional1 { A func(); }
}
Это фактически приводит к Exception in thread "main" java.lang.VerifyError: Bad local variable type
(...) Reason: Type top (current frame, locals[0]) is not assignable to reference type
.
Дальнейшее исследование показывает, что если мы вводим параметр в метод xxx
, причина исключения будет содержать его тип. Например:.
Type 'java/lang/Integer' (current frame, stack[0]) is not assignable to 'lambda/TestLambda'
И это уже очень интересно. Пусть тип изменения параметра xxx
(который фактически не используется) для типа класса top, т.е. TestLambda
:
...
xxx(new TestLambda());
}
private static void xxx(TestLambda x) {
...
И что ты думаешь? Это устраняет проблему! Все начинает хорошо работать. Даже если мы изменим return A();
на return new A() {};
. Проверьте это!
Я пришел к выводу, что это реальная ошибка JVM. Кажется, проблема связана со стеком загруженных классов. Это происходит в сочетании с методом, который Java
использует для перевода выражений lambda
(http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html) - он создает синтетические методы внутри высшего класса. Кажется, что когда анонимные классы вводятся в lambda
, стек становится сломанным. Он может быть исправлен с использованием упомянутого обходного пути.
Ответ 2
Ошибка компилятора? Это ранняя версия доступа...
Я бы сказал, что любое сообщение об ошибке, которое упоминает стек операнда, скорее всего, связано с ошибкой компилятора или ошибкой в JVM. Особенно, если вы можете получить его с использованием чистого Java-примера.
(Похоже, JVM сообщает о проблеме типа безопасности, которая должна была быть обнаружена компилятором, и/или верификаторе байт-кода во время загрузки класса.)
Сообщить об этом через рекомендуемый канал для ошибок Java 8.
Ответ 3
Не имеет прямого отношения к вашей проблеме, но я настоятельно рекомендую не использовать анонимные классы таким образом. Вы создаете полностью новый подтип HashSet исключительно для того, чтобы добавить к нему два значения. Мало того, что это раздувает систему (она остается в памяти навсегда), она также может помешать JVM JIT, поскольку она никогда не видит HashSet на сайте вызова... он видит один из многих подтипов, которые вы создали.