2016-01-21 52 views
6

(這是很難搜索,因爲結果都是關於「方法參考」)的Java 8:轉換拉姆達與clousure方法實例包括

我希望得到一個Method實例與一個使用lambda表達式傳統的基於反射的API。應該包含clousure,所以調用thatMethod.invoke(null, ...)應該與調用lambda具有相同的效果。

我已經看過MethodHandles.Lookup,但它似乎只與逆向變換有關。但我認爲bind方法可能有助於包括clousure?

編輯:

說我有上午的λ表達研究:

Function<String, String> sayHello = name -> "Hello, " + name; 

,我有一箇舊的框架(SpEL),有一個API類似

registerFunction(String name, Method method) 

,它將調用給定Method沒有this參數(即方法假定爲靜態)。所以我需要獲得一個包含lambda邏輯+ clousure數據的特殊Method實例。

+0

沒有什麼像lambda表達式的'方法'實例。 Lambda是匿名函數的語法糖。他們爲你提供了一個實例,但是你不能從中得到一個方法 – Jatin

+0

@Jatin顯然,合成的函數接口驅動仍然只是一個正常的方法對象,你可以使用正常的反射來訪問。我只是想知道如何弄好包裹。 –

回答

7

如果你沒有找到一個優雅的方式,這裏是醜陋的方式(Ideone)。當反射參與通常的警告:可能會在將來的版本等

public static void main(String[] args) throws Exception { 
    Function<String, String> sayHello = name -> "Hello, " + name; 
    Method m = getMethodFromLambda(sayHello); 
    registerFunction("World", m); 
} 

static void registerFunction(String name, Method method) throws Exception { 
    String result = (String) method.invoke(null, name); 
    System.out.println("result = " + result); 
} 

private static Method getMethodFromLambda(Function<String, String> lambda) throws Exception { 
    Constructor<?> c = Method.class.getDeclaredConstructors()[0]; 
    c.setAccessible(true); 
    Method m = (Method) c.newInstance(null, null, null, null, null, 0, 0, null, null, null, null); 
    m.setAccessible(true); //sets override field to true 

    //m.methodAccessor = new LambdaAccessor(...) 
    Field ma = Method.class.getDeclaredField("methodAccessor"); 
    ma.setAccessible(true); 
    ma.set(m, new LambdaAccessor(array -> lambda.apply((String) array[0]))); 

    return m; 
} 

static class LambdaAccessor implements MethodAccessor { 
    private final Function<Object[], Object> lambda; 
    public LambdaAccessor(Function<Object[], Object> lambda) { 
    this.lambda = lambda; 
    } 

    @Override public Object invoke(Object o, Object[] os) { 
    return lambda.apply(os); 
    } 
} 
+3

我向你致敬! –

+0

我在'MethodHandle'屬性中查找了可能性,看起來它能做的最好的事情是生成實例方法,因此由於動態需求而成爲死衚衕。 –

+1

最有趣的部分是你讓'm'可訪問的方式;不是調用'setAccessible(true)',而是獲取後端字段'override'來調用'Field'上的'setAccessible(true)'以後使用它將其設置爲'true'。我想,如果你經常使用'setAccessible(true)',那會發生什麼...... – Holger

4

好,lambda表達式被脫到編譯過程中的方法和,只要他們不佔領this(不訪問非static會員突破),這些方法將是static。棘手的部分是到達這些方法,因爲功能接口實例和其目標方法之間沒有可檢查的連接。

爲了說明這一點,在這裏最簡單的情況:

public class LambdaToMethod { 
    public static void legacyCaller(Object arg, Method m) { 
     System.out.println("calling Method \""+m.getName()+"\" reflectively"); 
     try { 
      m.invoke(null, arg); 
     } catch(ReflectiveOperationException ex) { 
      ex.printStackTrace(); 
     } 
    } 
    public static void main(String[] args) throws URISyntaxException 
    { 
     Consumer<String> consumer=s -> System.out.println("lambda called with "+s); 
     for(Method m: LambdaToMethod.class.getDeclaredMethods()) 
      if(m.isSynthetic() && m.getName().contains("lambda")) { 
       legacyCaller("a string", m); 
       break; 
      } 
    } 
} 

這工作順利,因爲這裏僅有一個lambda表達式,因此,一個候選方法。該方法的名稱是編譯器特定的,可能含有一些序列號或哈希碼等

在組裝機是使lambda表達式序列化和檢查其序列化形式:

static Method lambdaToMethod(Serializable lambda) { 
    for(Class<?> cl=lambda.getClass(); cl!=null; cl=cl.getSuperclass()) try { 
     Method m=cl.getDeclaredMethod("writeReplace"); 
     m.setAccessible(true); 
     try { 
      SerializedLambda sl=(SerializedLambda)m.invoke(lambda); 
      return LambdaToMethod.class.getDeclaredMethod(sl.getImplMethodName(), 
       MethodType.fromMethodDescriptorString(sl.getImplMethodSignature(), 
        LambdaToMethod.class.getClassLoader()).parameterArray()); 
     } catch(ReflectiveOperationException ex) { 
      throw new RuntimeException(ex); 
     } 
    } catch(NoSuchMethodException ex){} 
    throw new AssertionError(); 
} 
public static void main(String[] args) 
{ 
    legacyCaller("a string", lambdaToMethod((Consumer<String>&Serializable) 
     s -> System.out.println("first lambda called with "+s))); 
    legacyCaller("a string", lambdaToMethod((Consumer<String>&Serializable) 
     s -> System.out.println("second lambda called with "+s))); 
} 

這工作,但,可串行化的lambda表達式價格很高。


最簡單的解決辦法是將註釋添加到lambda表達式的參數遍歷方法時被發現,但是,目前,javac不存儲標註正確,也看到this question這個話題。


但你也可以考慮只創建普通static方法持代碼,而不是一個lambda表達式。爲一個方法獲得一個Method對象是直接的,你仍然可以使用方法引用創建一個功能接口實例...