Можно ли получить лямбда-выражение во время выполнения

Вчера вечером я играл с Java8 Lambda, и мне было интересно, можно ли получить выражение Lambda во время выполнения. Короче говоря, насколько я понял, лямбда-выражения преобразуются в (статические) методы во время выполнения, а затем вызываются с помощью InvokeDynamics.

Возьмем такой пример:

people.filter(person -> person.getAge() >= minAge);

где filter будет настраиваемым методом, принимающим Predicate<T> в качестве параметра. Как в этом методе filter получить аргумент в форме, аналогичной (или идентичной) лямбда-выражению (person -> person.getAge() >= minAge) в этом случае?

Я попытался прочитать сгенерированный байт-код класса аргумента, используя ASM5_BETA, но не смог пойти дальше, чем использовать ClassVisitor и MethodVisitor, чтобы добраться до метода, связанного с лямбда-выражением.

public <T> List<T> filter(Filter<T> expression) {
    try {
        Class<? extends Filter> expressionClass = expression.getClass();
        byte[] content = getClassContent(expressionClass);
        ClassReader classReader = new ClassReader(content);
        classReader.accept(new PredicateClassVisitor(), 0);
    } catch (Throwable e) {
        e.printStackTrace();
    }
    return null;
}

private byte[] getClassContent(Class<? extends Filter> expressionClazz) throws  
               IOException {
    InputStream stream = Thread.currentThread().getContextClassLoader()
                           .getResourceAsStream(getClassName(expressionClazz.getName()));
    return IOUtils.toByteArray(stream);
}

private String getClassName(String expressionClazz) {
    return expressionClazz.substring(0, expressionClazz.indexOf('$'))
           .replace('.', '/') + ".class";
}

static class PredicateClassVisitor extends ClassVisitor {

    public PredicateClassVisitor() {
        super(Opcodes.ASM4);
    }

    @Override
    public MethodVisitor visitMethod(int i, String s, String s2, String s3, 
                                     String[] strings) {
        return new PredicateMethodVisitor();
    }
}

static class PredicateMethodVisitor extends MethodVisitor {

    public PredicateMethodVisitor() {
        super(Opcodes.ASM4);
    }

    @Override
    public void visitInvokeDynamicInsn(String name, String desc, Handle bsm,
                                       Object... bsmArgs) {
        for (Object object : bsmArgs) {
              System.out.println(" " + object.toString());
        }
    }
} 

Я не уверен, что это правильный путь, и мне было интересно, есть ли более подходящие инструменты в ASM или JDK8 для такой цели.

Спасибо за любой совет ;-) С уважением, Ксавьер


person Xavier Coulon    schedule 17.12.2013    source источник
comment
Чего вы на самом деле пытаетесь достичь здесь? Пока вы это не объясните, трудно вам советовать.   -  person Stephen C    schedule 17.12.2013
comment
Под получением лямбда-выражения я понимаю, что вы имеете в виду генерировать. Кстати, сам лямбда-вызов не над InvokeDynamic, он используется только в процессе создания объекта лямбда-вызывателя.   -  person Marko Topolnik    schedule 17.12.2013
comment
Я хотел бы зафиксировать лямбда-выражение, которое было предоставлено в вызывающем коде, например, для целей ведения журнала или, возможно, для других целей позже. Я не говорю о генерации байт-кода вместо JVM. Из примера, приведенного выше, в методе filter(Filter<T> expression) я хотел бы иметь возможность вернуть данный expressionargument к person -> person.getAge() >= minAge лямбда-выражению. Это выполнимо?   -  person Xavier Coulon    schedule 17.12.2013
comment
Вы можете попробовать использовать Procyon для декомпиляции лямбда-выражений.   -  person Antimony    schedule 17.12.2013
comment
Если у вас есть байт-код для лямбды, то да, его можно преобразовать в эквивалентный фрагмент исходного кода Java. Для этого подходят инструменты ASM (также asm-util и asm-tree).   -  person OrangeDog    schedule 19.05.2017


Ответы (2)


Вы уже знаете, что лямбда-выражения обычно компилируются в синтетический метод, поэтому вы уже знаете, какой код нужно декомпилировать, чтобы получить исходный код лямбды, или, ну, что-то похожее на исходный код или даже что-то, выглядящее совершенно иначе, в зависимости от конкретного кода. .

Нет причин, по которым декомпиляция лямбда-выражения должна быть проще, чем декомпиляция любого другого выражения Java. Простые выражения может быть легко восстановить, особенно когда в коде есть отладочная информация, сложные выражения, скорее всего, будут выглядеть по-другому при декомпиляции, особенно когда компилятор применяет к коду оптимизацию.

person Holger    schedule 17.12.2013
comment
Как узнать какой это код? Имя лямбда-класса (например, $$Lambda$58/918965208) не соответствует именам лямбда-методов, которые пронумерованы отдельно. - person OrangeDog; 19.05.2017
comment
@OrangeDog: если вы декомпилируете этот класс, вы узнаете, какой метод он будет вызывать. В случае этого вопроса ОП уже отследил метод через сайт создания экземпляров. Однако смысл этого ответа в том, что это нелегко легко или даже невозможно. - person Holger; 19.05.2017
comment
Как они нашли место создания экземпляра? Судя по моему прочтению вопроса, у них есть только одна лямбда в охватывающем классе, поэтому expressionClass должен быть этим. - person OrangeDog; 19.05.2017
comment
На чем вы это основываете? Код в вопросе начинается с объекта Filter, который является лямбдой, и перебирает байт-код окружающего его класса. Нигде нет намека на то, что есть какое-то соображение для случая, когда в этом классе есть несколько лямбда-выражений. - person OrangeDog; 19.05.2017
comment
Если вы не знаете, что такое Filter и является ли это лямбдой, то вы явно невнимательно прочитали вопрос. Это целевой метод сгенерированного класса, в котором есть код - вот о чем вопрос. И класс, переданный ClassReader, не является синтетическим, так что нет причин не ожидать найти где-нибудь в нем invokedynamic. - person OrangeDog; 19.05.2017
comment
Я утверждал, что объект Filter был лямбдой, потому что это так. Читай код! - person OrangeDog; 19.05.2017
comment
Обычно ответы комментируют, чтобы объяснить, что в них не так или чего не хватает. - person OrangeDog; 19.05.2017
comment
Так что же не так с моим ответом, кроме того, что он не касается вашего совершенно другого вопроса? Почти все, что вы написали, относится к коду вопроса. Если вы считаете, что код спрашивающего должен служить вашей цели, обсудите его с его. - person Holger; 19.05.2017
comment
Да, вы не поняли комментарии так же, как не поняли вопроса. Утверждение, что вы уже знаете, какой код нужно декомпилировать, чтобы получить исходный код лямбды, либо неверно, либо двусмысленно. Если вы знаете, как они узнали (предыдущие ответы показывают, что вы этого не знаете), то вы должны сделать это явным образом. - person OrangeDog; 19.05.2017
comment
@OrangeDog: весь смысл ответа в том, что вы не можете получить исходный код. Вот и все. Как найти целевой метод сгенерированного класса никогда не было темой. Введение только предлагает OP, что «если вы знаете метод, вы знаете, что это обычный скомпилированный код, а не что-то особенное, зная исходный исходный код». Рассказывать, как найти целевой метод сгенерированного класса, не имело смысла, так как он все еще не дает исходный код, который просил ОП. - person Holger; 19.05.2017
comment
Поэтому следует убрать первый абзац как вводящий в заблуждение и двусмысленный, а собственно суть ответа поставить в ответ. - person OrangeDog; 19.05.2017
comment
@OrangeDog: вы первый за последние 3,5 года, кто считает это вводящим в заблуждение и двусмысленным. - person Holger; 19.05.2017
comment
Это не относится к делу, не доказуемо и не является статистически значимым. - person OrangeDog; 19.05.2017

В некоторых случаях вы можете сделать это с Groovy, если это поможет вам: содержимое закрытия в groovy. Geb фактически использует эту функцию, чтобы выделить ошибку утверждения внутри оцениваемого выражения.

person Andrey Chaschev    schedule 17.12.2013