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