Неверный тип в стеке операндов... при использовании jdk 8, лямбда-выражения с анонимными внутренними классами не работают, почему?
Выполнение кода ниже приводит к сообщению об ошибке 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.)
3 ответа
Кажется, эта проблема возникает не только в том случае, когда 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
параметр (который на самом деле не используется) для типа высшего класса, т.е. 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
стек становится сломанным. Это можно исправить с помощью упомянутого обходного пути.
Ошибка компилятора? В конце концов, это ранняя версия доступа...
Я бы сказал, что любое сообщение об ошибке, в котором упоминается стек операндов, может быть связано с ошибкой компилятора или с ошибкой в JVM. Особенно, если вы можете получить его, используя чистый пример Java.
(Похоже, JVM сообщает о проблеме безопасности типов, которая должна была быть обнаружена компилятором и / или верификатором байт-кода во время загрузки класса.)
Сообщите об этом через рекомендуемый канал для ошибок Java 8.
Не имеет прямого отношения к вашей проблеме, но я настоятельно рекомендую не использовать анонимные классы таким образом. Вы создаете совершенно новый подтип HashSet исключительно с целью добавления к нему двух значений. Это не только раздувает систему (она остается в памяти навсегда), но также может сбить с толку JIT JVM, поскольку она просто не видит HashSet на сайте вызова... она видит один из многих созданных вами подтипов.