2016-07-20 7 views
8

Java 8でラムダ関数を作成し、そのクラス名を取得してから、そのクラス名から関数を再度インスタンス化したいと考えています。名前でJavaラムダ関数をインスタンス化する

これは私がしようとするものである:

import java.util.function.Consumer; 

public class SimpleLambda 
{ 
    public static void call(String aLambdaClassName, String aArg) throws Exception 
    { 
     Class<Consumer<String>> lClass = (Class<Consumer<String>>) Class.forName(aLambdaClassName); 
     Consumer<String> newlamba = lClass.newInstance(); 
     newlamba.accept(aArg); 
    } 

    public static void main(String[] args) throws Exception 
    { 
     { 
      // Attempt with a static method as lambda 
      Consumer<String> lambda = Host::action; 
      String classname = lambda.getClass().getName(); 
      call(classname, "Hello world"); 
     } 

     { 
      // Attempt with a locally defined lambda 
      Consumer<String> lambda = (s) -> { System.out.println(s); }; 
      String classname = lambda.getClass().getName(); 
      call(classname, "Hello world"); 
     } 
    } 
} 

class Host { 
    public static void action(String aMessage) { 
     System.out.println(aMessage); 
    } 
} 

しかし、(静的メソッド参照を使用して、両方のバリアントで、ローカルでラムダ宣言を使用して)このコードで、私は例外を取得:

Exception in thread "main" java.lang.ClassNotFoundException: mypackage.SimpleLambda$$Lambda$1/471910020 
    at java.lang.Class.forName0(Native Method) 
    at java.lang.Class.forName(Class.java:264) 
    at mypackage.SimpleLambda.main(SimpleLambda.java:12) 

私は静的メソッド参照を少なくとも再インスタンス化することができると期待していたでしょう...いいえ、明らかにそうではありません。

Groovy Closuresで同様のアプローチを使用していて、うまく機能しています。だから私はちょうどJava 8のラムダに間違って何かをしていますか、または名前でラムダをインスタンス化することはできませんか?私はラムダが(デ)シリアライズできるネット上のいくつかのヒントを発見したので、名前でインスタンス化することもできるはずです。

+2

あなたはそれらと何をしようとしていますか?ラムダはインスタンス化されておらず、ただ*です。 – 4castle

+0

本質的には、ラムダを単一の関数で静的な内部クラスのように扱いたいと思います。文字列パラメータだけを受け入れるAPIを渡す必要があるので、クラス名ごとに渡したいと思います。だから私はAPIの中で名前を取得すると、私はそれを呼び出すことができるようにクラス名からラムダを再作成したい。奇妙に聞こえる?はい、それは...ですが、私が言ったように:それはGroovyで行い、うまく動作します。私はJavaで動作しないだろうという気持ちがありますが、誰かがそれをどうやって行うのか手がかりがあれば、本当にクールです。 – rec

+0

私はこの例をもっと使いやすくするために更新しました。 – rec

答えて

5

名前でアクセスできない「匿名のクラス」を使用するのは、OracleのJRE/OpenJDKの特別なプロパティです。しかし、これさえせずに、これが機能するはずない理由はありません。

  • Class.forName(String)は、発信者のClassLoaderを経由して、クラスを解決しようとします。たとえラムダ式が普通のクラスを使って実装されたとしても、ClassLoader
  • Class.newInstance()は、引数なしのコンストラクタpublicがある場合にのみ動作します。引数なしのコンストラクタがあると仮定することもできません。public
  • 関数のロジック全体が単一のクラスに存在しなければならないという仮定は間違っています。カウンタの例は、に委譲するinterfaceの実装を生成するjava.lang.reflect.Proxyです。実際のInvocationHandlerインスタンスをプロキシのコンストラクタに渡す必要があるため、そのクラス名でそのようなプロキシを再インスタンス化しようとすると失敗します。原則として、JRE固有のラムダ式の実装でも同様のパターンを使用できます。

上記の点を考慮すると、一般的に内部クラスで動作するとは言えません。それを実現するためには多くの制約があります。


シリアライズに関しては永続フォームが完全にdescribed in this answerとして、ランタイム実装クラスから切り離されているので、それは、シリアライズ可能なラムダ式のために動作します。したがって、生成されたクラスの名前はシリアル化された形式には含まれておらず、デシリアライズの終わりは完全に異なる実行時の実装を持つ可能性があります。

0

ラムダインスタンスをマップに格納し、インスタンス名をキーにして格納します。マップをシングルトンラッパークラスを介してグローバルに利用できるようにすることができます(同期の問題については注意してください)。

class LambdaMap { 

    private HashMap<String, Consumer<String>> theMap; 

    private LambdaMap() { 
     theMap = new HashMap<>(); 
    } 

    private static class INSTANCE_HOLDER { 
     private static LambdaMap INSTANCE = new LambdaMap(); 
    } 

    public static LambdaMap getInstance() { 
     return INSTANCE_HOLDER.INSTANCE; 
    } 

    public Consumer<String> put(String key, Consumer<String> value) { 
     return theMap.put(key, value); 
    } 

    public static void Call(String aLambdaClassName, String aArg) { 
     Consumer<String> func = getInstance().theMap.get(aLambdaClassName); 
     if (func != null) { 
      func.accept(aArg); 
     } 
    } 

} 

class Host { 
    public static void action(String aMessage) { 
     System.out.println("Goodbye, " + aMessage); 
    } 
} 

public class GlobalLambdas { 

    public static void main(String[] args) { 
     LambdaMap.getInstance().put("print greeting", s -> { 
      System.out.println("Hello, " + s); 
     }); 
     LambdaMap.getInstance().put("print goodbye", Host::action); 

     LambdaMap.Call("print greeting", "John"); 
     LambdaMap.Call("print goodbye", "John"); 
    } 
} 

run: 
Hello, John 
Goodbye, John 
関連する問題