2013-12-17 11 views
5

Ich spielte letzte Nacht mit Java8 Lambda und ich fragte mich, ob es möglich ist, den Lambda-Ausdruck zur Laufzeit abrufen. Kurz und soweit ich verstanden habe, werden Lambda-Ausdrücke zur Laufzeit in (statische) Methoden umgewandelt und dann mit InvokeDynamics aufgerufen.Ist es möglich, Lambda-Ausdruck zur Laufzeit abrufen

Nehmen wir ein Beispiel, wie diese nehmen:

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

wo filter eine benutzerdefinierte Methode eine Predicate<T> als Parameter nehmen würde. Innerhalb dieser filter Methode, wie konnte ich das Argument in einer Form ähnlich (oder identisch) mit dem Lambda-Ausdruck (person -> person.getAge() >= minAge) in diesem Fall abrufen?

Ich versuchte, den generierten Bytecode der Klasse des Arguments mit ASM5_BETA zu lesen, aber ich konnte nicht weiter gehen, als mit einem ClassVisitor und einem MethodVisitor die Methode zu erreichen, die dem Lambda-Ausdruck zugeordnet ist.

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()); 
     } 
    } 
} 

Ich bin nicht sicher, ob dies der richtige Weg ist, zu folgen, und ich frage mich, ob es in ASM oder in JDK8 für einen solchen Zweck geeignetere Werkzeuge waren.

Vielen Dank für jede Beratung ;-) Mit freundlichen Grüßen, Xavier

+2

Was versuchen Sie eigentlich hier zu erreichen? Bis Sie das erklären, ist es schwierig, Sie zu beraten. –

+0

Mit "Lambda-Ausdruck abrufen" nehme ich an, dass Sie "erzeugen" meinen. BTW der Lambda-Aufruf selbst ist nicht über InvokeDynamic, dies wird nur bei der Erstellung des Lambda Invocator-Objekts verwendet. –

+1

Ich möchte den Lambda-Ausdruck erfassen, der im aufrufenden Code zur Verfügung gestellt wurde, zum Beispiel für den Zweck der Protokollierung oder andere Nutzungen später. Ich spreche nicht über die Generierung des Bytecodes anstelle der JVM. Aus dem obigen Beispiel möchte ich in der Methode filter (Filter ) das gegebene Ausdruckargument in die Person -> person.getAge()> = minAge 'Lambda umwandeln können Ausdruck. Ist das machbar? –

Antwort

4

Sie bereits, dass Lambda-Ausdrücke kennen, sind in der Regel in ein synthetisches Verfahren zusammengestellt, so dass Sie bereits wissen, welcher Code der Lambda-Quellcode zu erhalten zu dekompilieren oder , naja, etwas, das dem ursprünglichen Code ähnelt oder sogar etwas ganz anderes, je nach Code.

Es gibt keinen Grund, warum das Dekompilieren eines Lambda-Ausdrucks einfacher sein sollte als das Dekompilieren eines anderen Java-Ausdrucks. Einfache Ausdrücke können leicht wiederhergestellt werden, besonders wenn der Code Debugging-Informationen enthält, werden komplexe Ausdrücke beim Dekompilieren höchstwahrscheinlich anders aussehen, insbesondere wenn der Compiler Optimierungen am Code anwendet.

+0

Woher wissen Sie, welcher Code es ist? Der Name der Lambda-Klasse (z. B. "$$ Lambda $ 58/918965208") entspricht nicht den Namen der Lambda-Methoden, die separat nummeriert sind. – OrangeDog

+0

@OrangeDog: Wenn Sie diese Klasse dekompilieren, werden Sie herausfinden, welche Methode sie aufrufen wird. Bei dieser Frage hat das OP die Methode bereits über die Instanziierungsstelle verfolgt. Der Sinn dieser Antwort ist jedoch, dass es * nicht * einfach oder sogar unmöglich ist. – Holger

+0

Wie haben sie die Instanziierungsstelle gefunden? Aus meiner Lektüre der Frage haben sie zufällig nur ein Lambda in der einschließenden Klasse, also muss "expressionClass" das sein. – OrangeDog

0

Sie können dies in einigen Fällen mit Groovy tun, wenn Ihnen das helfen würde: Getting the contents of closure, in groovy. Geb verwendet diese Funktion tatsächlich, um einen Assertionsfehler innerhalb eines ausgewerteten Ausdrucks hervorzuheben.