2015-06-03 2 views
7

При создании лямбда вручную с использованием MethodHandles.Lookup, MethodHandle s, MethodType s, и т. Д., Как можно реализовать захват переменной?Lambda Metafactory Variable Capture

Например, без захвата:

public IntSupplier foo() { 
    return this::fortyTwo; 
} 
/** 
* Would not normally be virtual, but oh well. 
*/ 
public int fortyTwo() { 
    return 42; 
} 

и его clunkier форме, используя материал в java.lang.invoke:

public IntSupplier foo() { 
    MethodHandles.Lookup lookup = MethodHandles.lookup(); 
    MethodType methodType = MethodType.methodType(int.class), 
       lambdaType = MethodType.methodType(IntSupplier.class); 
    MethodHandle methodHandle = lookup.findVirtual(getClass(), "fortyTwo", methodType); 
    CallSite callSite = LambdaMetafactory.metafactory(lookup, "getAsInt", lambdaType, methodType, methodHandle, methodType); 
    return (IntSupplier) callSite.getTarget().invokeExact(); 
} 
/** 
* Would not normally be virtual, but oh well. 
*/ 
public int fortyTwo() { 
    return 42; 
} 

вернется простой, бессмысленно IntSupplier, которая возвращает 42 при вызове, но что если хочется что-то захватить?

+0

Я предполагаю, что это для понимания того, как lambdas работают внутри, потому что иначе вы бы вернули новый IntSupplier() {...}; 'без магии. – immibis

+1

Что я не понимаю, это комментарий «Не было бы виртуальным». – Holger

ответ

3

Третьим аргумент бутстраповского метод, который вы назвали lambdaType, является вызывается типа из ассоциированной invokedynamic (обычно заполняется JVM). Его семантика определяется методом начальной загрузки, а в случае LambdaMetaFactory - специфицирует функциональный интерфейс как тип возврата (тип объекта для построения) и значения для захвата как тип параметра (тип значений, которые нужно использовать, когда построение экземпляра лямбда).

Так что для того, чтобы захватить this, вы должны добавить тип this к вашему вызываемому типу и передать this в качестве аргумента invokeExact вызова:

public class Test { 
    public static void main(String... arg) throws Throwable { 
     System.out.println(new Test().foo().getAsInt()); 
    } 
    public IntSupplier foo() throws Throwable { 
     MethodHandles.Lookup lookup = MethodHandles.lookup(); 
     MethodType methodType = MethodType.methodType(int.class), 
        invokedType = MethodType.methodType(IntSupplier.class, Test.class); 
     MethodHandle methodHandle = lookup.findVirtual(getClass(), "fortyTwo", methodType); 
     CallSite callSite = LambdaMetafactory.metafactory(lookup, "getAsInt", 
      invokedType, methodType, methodHandle, methodType); 
     return (IntSupplier) callSite.getTarget().invokeExact(this); 
    } 
    public int fortyTwo() { 
     return 42; 
    } 
} 

Если вы хотите, чтобы захватить больше значения, вы должны добавить их в подпись в правильном порядке. НАПРИМЕР, чтобы захватить другую int значения:

public class Test { 
    public static void main(String... arg) throws Throwable { 
     System.out.println(new Test().foo(100).getAsInt()); 
    } 
    public IntSupplier foo(int capture) throws Throwable { 
     MethodHandles.Lookup lookup = MethodHandles.lookup(); 
     MethodType methodType = MethodType.methodType(int.class, int.class), 
      functionType = MethodType.methodType(int.class), 
      invokedType = MethodType.methodType(IntSupplier.class, Test.class, int.class); 
     MethodHandle methodHandle=lookup.findVirtual(getClass(),"addFortyTwo",methodType); 
     CallSite callSite = LambdaMetafactory.metafactory(lookup, "getAsInt", 
      invokedType, functionType, methodHandle, functionType); 
     return (IntSupplier) callSite.getTarget().invokeExact(this, capture); 
    } 
    public int addFortyTwo(int valueToAdd) { 
     return 42+valueToAdd; 
    } 
} 

Целевой метод будет иметь подпись, состоящую из this типа, если не static, а затем всех типов параметров. Значения захвата будут отображаться для типов этой подписи слева направо, а остальные типы параметров, если таковые имеются, вносят вклад в функциональную подпись, поэтому должны соответствовать типам параметров interface.

Это означает, что при отсутствии фиксированных значений и целевом методе нет static, тип приемника метода может ассоциироваться с первым типом функциональной сигнатуры, как в ToIntFunction<String> f=String::length;.

2

Ваш код не работает. Поскольку ваш метод fortyTwo не является статичным, вам необходимо будет записать this, используя MethodType.methodType(IntSupplier.class, getClass()) в качестве третьего аргумента в metafactory, а затем передайте this в качестве аргумента для invokeExact.

Вот пример захвата строки, используя статический метод, чтобы держать вещи проще:

public static int len(String s) { 
    return s.length(); 
} 

public IntSupplier supplyLength(String capture) throws Throwable { 
    MethodHandles.Lookup lookup = MethodHandles.lookup(); 

    CallSite callSite = LambdaMetafactory.metafactory(
      lookup, 
      "getAsInt", 
      methodType(IntSupplier.class, String.class), 
      methodType(int.class), 
      lookup.findStatic(getClass(), "len", methodType(int.class, String.class)), 
      methodType(int.class) 
    ); 

    return (IntSupplier) callSite.getTarget().invoke(capture); 
} 
+0

Я забыл, что «это» нужно было захватить ^^; Спасибо за напоминание –

Смежные вопросы