Неверный тип в стеке операндов... при использовании 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 на сайте вызова... она видит один из многих созданных вами подтипов.

Другие вопросы по тегам