パラメータとしてのJavaパスメソッド


277

参照によってメソッドを渡す方法を探しています。Javaはパラメーターとしてメソッドを渡さないことを理解していますが、別の方法を入手したいと考えています。

インターフェースはメソッドをパラメーターとして渡す代わりに使用できると言われましたが、インターフェースが参照としてメソッドとして機能する方法がわかりません。私が正しく理解していれば、インターフェースは、定義されていない抽象的なメソッドのセットにすぎません。複数の異なるメソッドが同じパラメーターで同じメソッドを呼び出す可能性があるため、毎回定義する必要があるインターフェースを送信したくありません。

私が達成したいのはこれに似たものです:

public void setAllComponents(Component[] myComponentArray, Method myMethod) {
    for (Component leaf : myComponentArray) {
        if (leaf instanceof Container) { //recursive call if Container
            Container node = (Container) leaf;
            setAllComponents(node.getComponents(), myMethod);
        } //end if node
        myMethod(leaf);
    } //end looping through components
}

次のように呼び出されます:

setAllComponents(this.getComponents(), changeColor());
setAllComponents(this.getComponents(), changeSize());

現在の私の解決策は、追加のパラメーターを渡し、内部のスイッチケースを使用して適切なメソッドを選択することです。ただし、このソリューションはコードの再利用には適していません。

同様の質問については、この回答stackoverflow.com/a/22933032/1010868も参照してください
Tomasz Gawel 14

回答:


233

編集:Java 8以降、他の回答が指摘しているように、ラムダ式は優れたソリューションです。以下の答えはJava 7以前のために書かれました...


コマンドパターンを見てみましょう。

// NOTE: code not tested, but I believe this is valid java...
public class CommandExample 
{
    public interface Command 
    {
        public void execute(Object data);
    }

    public class PrintCommand implements Command 
    {
        public void execute(Object data) 
        {
            System.out.println(data.toString());
        }    
    }

    public static void callCommand(Command command, Object data) 
    {
        command.execute(data);
    }

    public static void main(String... args) 
    {
        callCommand(new PrintCommand(), "hello world");
    }
}

編集:としてピートKirkhamが指摘する、使用してこれを行う別の方法がありますビジターが。ビジターアプローチはもう少し複雑です。ノードはすべて、acceptVisitor()メソッドでビジターを認識する必要があります。しかし、より複雑なオブジェクトグラフをトラバースする必要がある場合は、検討する価値があります。


2
@Mac-いいね!これは、それらをシミュレートする事実上の方法としてファーストクラスのメソッドを持たない言語で何度も何度も登場するので、覚えておく価値があります。
Dan Vinton、2010

7
これはビジターパターン(コレクションの各メンバーに適用される関数からコレクションを反復するアクションを分離する)であり、コマンドパターン(メソッド呼び出しの引数をオブジェクトにカプセル化する)ではありません。あなたは特に引数をカプセル化していません-それはビジターパターンの反復部分によって提供されます。
ピートカーカム

いいえ、訪問を二重ディスパッチと組み合わせる場合にのみ、acceptメソッドが必要です。モノモーフィックなビジターがいる場合、それはまさにあなたが上記に持っているコードです。
ピートカーカム

Java 8では、ex.operS(String :: toLowerCase、 "STRING")のようになります。素敵な記事を参照してください。studytrails.com/java/java8/...
ゾン

Pete Kirkhamは正解です。コードはCommandパターンではなくVisitorパターンを実装しています(これはOPが必要とするものであるため、これで十分です)。インターフェイスには、パラメーターを受け取る実行があります。ウィキペディアにはありません。これは、コマンドパターンの意図の基本です。最初の段落で「すべての情報をカプセル化ます...この情報には、メソッド名、メソッドを所有するオブジェクト、およびメソッドパラメータの値が含まれます。」
ToolmakerSteve

73

Java 8では、ラムダ式とメソッド参照を使用して、より簡単にメソッドを渡すことができます。まず、いくつかの背景:関数型インターフェースは、抽象メソッドを1つだけ持つインターフェースですが、デフォルトのメソッド(Java 8の新機能)と静的メソッドをいくつでも含めることができます。ラムダ式は、ラムダ式を使用しない場合に必要なすべての不要な構文なしで、抽象メソッドをすばやく実装できます。

ラムダ式なし:

obj.aMethod(new AFunctionalInterface() {
    @Override
    public boolean anotherMethod(int i)
    {
        return i == 982
    }
});

ラムダ式で:

obj.aMethod(i -> i == 982);

以下は、ラムダ式に関するJavaチュートリアルの抜粋です。

ラムダ式の構文

ラムダ式は次のもので構成されます。

  • 括弧で囲まれた仮パラメーターのコンマ区切りリスト。CheckPerson.testメソッドには、Personクラスのインスタンスを表す1つのパラメーターpが含まれています。

    :ラムダ式ではパラメーターのデータ型を省略できます。また、パラメーターが1つしかない場合は、括弧を省略できます。たとえば、次のラムダ式も有効です。

    p -> p.getGender() == Person.Sex.MALE 
        && p.getAge() >= 18
        && p.getAge() <= 25
  • アロートークン、 ->

  • 単一の式またはステートメントブロックで構成される本体。この例では、次の式を使用します。

    p.getGender() == Person.Sex.MALE 
        && p.getAge() >= 18
        && p.getAge() <= 25

    単一の式を指定すると、Javaランタイムは式を評価してからその値を返します。または、returnステートメントを使用できます。

    p -> {
        return p.getGender() == Person.Sex.MALE
            && p.getAge() >= 18
            && p.getAge() <= 25;
    }

    returnステートメントは式ではありません。ラムダ式では、ステートメントを中括弧({})で囲む必要があります。ただし、voidメソッドの呼び出しを中括弧で囲む必要はありません。たとえば、次は有効なラムダ式です。

    email -> System.out.println(email)

ラムダ式はメソッド宣言によく似ていることに注意してください。ラムダ式は匿名メソッド、つまり名前のないメソッドと見なすことができます。


ラムダ式を使用して「メソッドを渡す」方法は次のとおりです。

interface I {
    public void myMethod(Component component);
}

class A {
    public void changeColor(Component component) {
        // code here
    }

    public void changeSize(Component component) {
        // code here
    }
}
class B {
    public void setAllComponents(Component[] myComponentArray, I myMethodsInterface) {
        for(Component leaf : myComponentArray) {
            if(leaf instanceof Container) { // recursive call if Container
                Container node = (Container)leaf;
                setAllComponents(node.getComponents(), myMethodInterface);
            } // end if node
            myMethodsInterface.myMethod(leaf);
        } // end looping through components
    }
}
class C {
    A a = new A();
    B b = new B();

    public C() {
        b.setAllComponents(this.getComponents(), component -> a.changeColor(component));
        b.setAllComponents(this.getComponents(), component -> a.changeSize(component));
    }
}

次のCようにメソッド参照を使用することで、クラスをさらに短くすることができます。

class C {
    A a = new A();
    B b = new B();

    public C() {
        b.setAllComponents(this.getComponents(), a::changeColor);
        b.setAllComponents(this.getComponents(), a::changeSize);
    }
}

クラスAはインターフェースから継承する必要がありますか?
Serob_b 2017

1
@Serob_bいいえ。これをメソッド参照として渡したい場合(::演算子を参照)を除き、Aが何であるかは関係ありません。a.changeThing(component)voidを返す限り、任意のステートメントまたはコードブロックに変更できます。
帽子の男

29

java.lang.reflect.Methodオブジェクトを使用して呼び出すinvoke


12
なぜかわかりません。問題は、メソッドをパラメーターとして渡すことであり、これは非常に有効な方法です。これは、見栄えをよくするために、かなりきれいなパターンの数にラップすることもできます。そして、これは特別なインターフェースを必要とせずに得られるのと同じくらい一般的です。
Vinodh Ramasubramanian 2010

3
JavaScript fgにタイプセーフがありますか?型安全性は議論ではありません。
ダヌビアンセーラー

13
問題の言語がタイプセーフティを最も強力なコンポーネントの1つとして保持している場合、タイプセーフティはどのように引数ではないのでしょうか。Javaは強く型付けされた言語であり、その強い型付けは、別のコンパイル済み言語よりもJavaを選ぶ理由の1つです。
アダムパーキン2013

21
「コアリフレクション機能は、もともとコンポーネントベースのアプリケーションビルダーツール用に設計されました。[...]原則として、オブジェクトは実行時に通常のアプリケーションで反射的にアクセスされるべきではありません。」項目53:効果的なJava Second Editionから、リフレクションよりもインターフェースを優先する。-それがJavaの作成者の考え方です;-)
Wilhem Meignan

8
リフレクトの正当な使用ではありません。私はすべての賛成票を見るのが怖いです。Reflectは、一般的なプログラミングメカニズムとして使用することを意図したものではありません。他のクリーンなソリューションがない場合にのみ使用してください。
ToolmakerSteve

22

Java 8以降には、メソッドを持つFunction<T, R>インターフェース(docs)があります。

R apply(T t);

これを使用して、関数をパラメーターとして他の関数に渡すことができます。Tは関数の入力タイプ、Rは戻りタイプです。

あなたの例ではComponent、入力としてタイプを取り、何も返さない関数を渡す必要があります- Void。この場合Function<T, R>、Voidタイプのオートボクシングがないため、最良の選択ではありません。あなたが探しているインターフェイスはメソッドで呼び出されますConsumer<T>ドキュメント

void accept(T t);

次のようになります。

public void setAllComponents(Component[] myComponentArray, Consumer<Component> myMethod) {
    for (Component leaf : myComponentArray) {
        if (leaf instanceof Container) { 
            Container node = (Container) leaf;
            setAllComponents(node.getComponents(), myMethod);
        } 
        myMethod.accept(leaf);
    } 
}

そして、あなたはそれをメソッド参照を使って呼び出すでしょう:

setAllComponents(this.getComponents(), this::changeColor);
setAllComponents(this.getComponents(), this::changeSize); 

同じクラスでchangeColor()メソッドとchangeSize()メソッドを定義したと仮定します。


メソッドが偶然に複数のパラメーターを受け入れる場合は、BiFunction<T, U, R>TとUを入力パラメーターの型、Rを戻り型として使用できます。もありますBiConsumer<T, U>(二つの引数、ノーリターンタイプ)。残念ながら、3つ以上の入力パラメーターの場合、自分でインターフェイスを作成する必要があります。例えば:

public interface Function4<A, B, C, D, R> {

    R apply(A a, B b, C c, D d);
}

19

最初に、パラメーターとして渡したいメソッドでインターフェースを定義します

public interface Callable {
  public void call(int param);
}

メソッドでクラスを実装する

class Test implements Callable {
  public void call(int param) {
    System.out.println( param );
  }
}

//そのように呼び出す

Callable cmd = new Test();

これにより、cmdをパラメーターとして渡し、インターフェースで定義されたメソッド呼び出しを呼び出すことができます。

public invoke( Callable callable ) {
  callable.call( 5 );
}

1
あなたは、Javaがあなたのためにそれらの多くを定義しているように、独自のインターフェイスを作成する必要はありません。docs.oracle.com/javase/8/docs/api/java/util/function/...
スリム

@slim興味深い点、それらの定義はどれくらい安定しているか、それらは、あなたが提案するように慣習的に使用されることを意図しているか、または壊れる可能性が高いですか?
Manuel

@slim実際、ドキュメントは次のように答えています。「このパッケージのインターフェースは、JDKが使用する汎用の機能インターフェースであり、ユーザーコードでも使用できます。」
Manuel

14

これはJava 7以下ではまだ有効ではありませんが、将来を見据え、少なくとも変更を認識する必要があると思います。 Java 8などの新しいバージョンでを。

つまり、この新しいバージョンでは、ラムダとメソッド参照がJavaに新しいAPIます。これは、この問題の別の有効な解決策です。これらのインターフェースは引き続き必要ですが、新しいオブジェクトは作成されず、追加のクラスファイルは、 JVMによる処理。

両方のフレーバー(ラムダとメソッド参照)には、シグニチャーが使用される単一のメソッドで使用可能なインターフェースが必要です。

public interface NewVersionTest{
    String returnAString(Object oIn, String str);
}

これ以降、メソッドの名前は関係ありません。ラムダが受け入れられる場合、メソッド参照も同様です。たとえば、ここで署名を使用するには:

public static void printOutput(NewVersionTest t, Object o, String s){
    System.out.println(t.returnAString(o, s));
}

これは、ラムダ1が渡されるまでの単純なインターフェース呼び出しです。

public static void main(String[] args){
    printOutput( (Object oIn, String sIn) -> {
        System.out.println("Lambda reached!");
        return "lambda return";
    }
    );
}

これは出力します:

Lambda reached!
lambda return

メソッド参照も同様です。与えられた:

public class HelperClass{
    public static String testOtherSig(Object o, String s){
        return "real static method";
    }
}

そしてメイン:

public static void main(String[] args){
    printOutput(HelperClass::testOtherSig);
}

出力はになりますreal static methodメソッド参照は、静的、インスタンス、任意のインスタンスを持つ非静的、さらにはコンストラクターにすることができます。コンストラクタに似たものClassName::newが使用されます。

1これには副作用があるため、ラムダとは見なされません。ただし、1つをより簡単に視覚化して使用する方法を示しています。


12

前回チェックしたところ、Javaは本来の目的を実行できません。このような制限を回避するには、「回避策」を使用する必要があります。私が見る限り、インターフェースは代替手段ですが、良い代替手段ではありません。おそらく、それが次のような意味であるとあなたが言った人:

public interface ComponentMethod {
  public abstract void PerfromMethod(Container c);
}

public class ChangeColor implements ComponentMethod {
  @Override
  public void PerfromMethod(Container c) {
    // do color change stuff
  }
}

public class ChangeSize implements ComponentMethod {
  @Override
  public void PerfromMethod(Container c) {
    // do color change stuff
  }
}

public void setAllComponents(Component[] myComponentArray, ComponentMethod myMethod) {
    for (Component leaf : myComponentArray) {
        if (leaf instanceof Container) { //recursive call if Container
            Container node = (Container) leaf;
            setAllComponents(node.getComponents(), myMethod);
        } //end if node
        myMethod.PerfromMethod(leaf);
    } //end looping through components
}

あなたはそれからそれを呼び出すでしょう:

setAllComponents(this.getComponents(), new ChangeColor());
setAllComponents(this.getComponents(), new ChangeSize());

6

これらのメソッドが何かを返す必要がない場合は、Runnableオブジェクトを返すようにすることができます。

private Runnable methodName (final int arg) {
    return (new Runnable() {
        public void run() {
          // do stuff with arg
        }
    });
}

次に、次のように使用します。

private void otherMethodName (Runnable arg){
    arg.run();
}

2

java.util.function.Function単純なメソッドをパラメーター関数として使用する方法について、十分に明確な例は見つかりませんでした。以下に簡単な例を示します。

import java.util.function.Function;

public class Foo {

  private Foo(String parameter) {
    System.out.println("I'm a Foo " + parameter);
  }

  public static Foo method(final String parameter) {
    return new Foo(parameter);
  }

  private static Function parametrisedMethod(Function<String, Foo> function) {
    return function;
  }

  public static void main(String[] args) {
    parametrisedMethod(Foo::method).apply("from a method");
  }
}

基本的にFoo、デフォルトのコンストラクターを持つオブジェクトがあります。A methodからのパラメータとして呼び出されるparametrisedMethodタイプのものですFunction<String, Foo>

  • Function<String, Foo>は、関数がStringパラメータとしてを受け取り、を返すことを意味しますFoo
  • Foo::Methodようなラムダに対応x -> Foo.method(x);
  • parametrisedMethod(Foo::method) と見ることができました x -> parametrisedMethod(Foo.method(x))
  • .apply("from a method")行うには基本的にparametrisedMethod(Foo.method("from a method"))

その後、出力に戻ります。

>> I'm a Foo from a method

この例はそのまま実行する必要があります。その後、上記の回答からより複雑なものをさまざまなクラスとインターフェースで試すことができます。


Androidで適用呼び出しを使用するには、最低限のAPI 24が必要です
Ines Belhouchet

1

Javaには、名前を渡して呼び出すメカニズムがあります。これは反射メカニズムの一部です。関数は、クラスメソッドの追加パラメーターを取る必要があります。

public void YouMethod(..... Method methodToCall, Object objWithAllMethodsToBeCalled)
{
...
Object retobj = methodToCall.invoke(objWithAllMethodsToBeCalled, arglist);
...
}

1

私はJavaのエキスパートではありませんが、問題を次のように解決します。

@FunctionalInterface
public interface AutoCompleteCallable<T> {
  String call(T model) throws Exception;
}

特別なインターフェースでパラメーターを定義します

public <T> void initialize(List<T> entries, AutoCompleteCallable getSearchText) {.......
//call here
String value = getSearchText.call(item);
...
}

最後に、initializeメソッドを呼び出すときにgetSearchTextメソッドを実装します

initialize(getMessageContactModelList(), new AutoCompleteCallable() {
          @Override
          public String call(Object model) throws Exception {
            return "custom string" + ((xxxModel)model.getTitle());
          }
        })

実際には、それが最良の答えであり、それを行う適切な方法です。+1に
値する

0

オブザーバーパターン(リスナーパターンとも呼ばれます)を使用します。

interface ComponentDelegate {
    void doSomething(Component component);
}

public void setAllComponents(Component[] myComponentArray, ComponentDelegate delegate) {
    // ...
    delegate.doSomething(leaf);
}

setAllComponents(this.getComponents(), new ComponentDelegate() {
                                            void doSomething(Component component) {
                                                changeColor(component); // or do directly what you want
                                            }
                                       });

new ComponentDelegate()... インターフェイスを実装する匿名型を宣言します。


8
これはあなたが探しているパターンではありません。
ピートカーカム

1
オブザーバーパターンは、変更に対応する機能を抽象化することです。OPは、コレクションを反復処理するコード(ビジターパターン)から離れて、コレクション内の各項目で実行されるアクションを抽象化したいと考えています。
ピートカーカム、2010

Observer / Listenerパターンは、実際にはそのCommandパターンと同じです。彼らは意図だけが異なります。オブザーバーは通知についてですが、コマンドはファーストクラスの関数/ラムダの代わりです。一方、ビジターはまったく違うものです。数文では説明できないと思いますので、en.wikipedia.org
wiki / Visitor_pattern

0

基本的な例を次に示します。

public class TestMethodPassing
{
    private static void println()
    {
        System.out.println("Do println");
    }

    private static void print()
    {
        System.out.print("Do print");
    }

    private static void performTask(BasicFunctionalInterface functionalInterface)
    {
        functionalInterface.performTask();
    }

    @FunctionalInterface
    interface BasicFunctionalInterface
    {
        void performTask();
    }

    public static void main(String[] arguments)
    {
        performTask(TestMethodPassing::println);
        performTask(TestMethodPassing::print);
    }
}

出力:

Do println
Do print

0

メソッドのパラメーターとしてバインドされたパラメーターを持つメソッドを渡す方法を示す解決策はここでは見つかりませんでした。以下は、パラメーター値が既にバインドされているメソッドを渡す方法の例です。

  1. ステップ1:2つのインターフェースを作成します。1つは戻り値型、もう1つは戻り値タイプです。Javaにも同様のインターフェースがありますが、例外のスローをサポートしていないため、実用性はほとんどありません。


    public interface Do {
    void run() throws Exception;
    }


    public interface Return {
        R run() throws Exception;
    }
  1. トランザクションでメソッド呼び出しをラップするために両方のインターフェースを使用する方法の例。実際のパラメーターでメソッドを渡すことに注意してください。


    //example - when passed method does not return any value
    public void tx(final Do func) throws Exception {
        connectionScope.beginTransaction();
        try {
            func.run();
            connectionScope.commit();
        } catch (Exception e) {
            connectionScope.rollback();
            throw e;
        } finally {
            connectionScope.close();
        }
    }

    //Invoke code above by 
    tx(() -> api.delete(6));

別の例は、実際に何かを返すメソッドを渡す方法を示しています



        public  R tx(final Return func) throws Exception {
    R r=null;
    connectionScope.beginTransaction();
    try {
                r=func.run();
                connectionScope.commit();
            } catch (Exception e) {
                connectionScope.rollback();
                throw e;
            } finally {
                connectionScope.close();
            }
        return r;
        }
        //Invoke code above by 
        Object x= tx(() -> api.get(id));

0

リフレクションを使用したソリューションの例、渡されたメソッドはパブリックでなければならない

import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

public class Program {
    int i;

    public static void main(String[] args) {
        Program   obj = new Program();    //some object

        try {
            Method method = obj.getClass().getMethod("target");
            repeatMethod( 5, obj, method );
        } 
        catch ( NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            System.out.println( e ); 
        }
    }

    static void repeatMethod (int times, Object object, Method method)
        throws IllegalAccessException, InvocationTargetException {

        for (int i=0; i<times; i++)
            method.invoke(object);
    }
    public void target() {                 //public is necessary
        System.out.println("target(): "+ ++i);
    }
}

0

上記の回答に感謝しますが、以下の方法を使用して同じ動作を実現できました。Javascriptコールバックから借りたアイデア。これまでのところ、(プロダクションで)修正は可能です。

アイデアはシグネチャで関数の戻り値の型を使用することです。つまり、yieldは静的でなければなりません。

以下は、タイムアウトでプロセスを実行する関数です。

public static void timeoutFunction(String fnReturnVal) {

    Object p = null; // whatever object you need here

    String threadSleeptime = null;

    Config config;

    try {
        config = ConfigReader.getConfigProperties();
        threadSleeptime = config.getThreadSleepTime();

    } catch (Exception e) {
        log.error(e);
        log.error("");
        log.error("Defaulting thread sleep time to 105000 miliseconds.");
        log.error("");
        threadSleeptime = "100000";
    }

    ExecutorService executor = Executors.newCachedThreadPool();
    Callable<Object> task = new Callable<Object>() {
        public Object call() {
            // Do job here using --- fnReturnVal --- and return appropriate value
            return null;
        }
    };
    Future<Object> future = executor.submit(task);

    try {
        p = future.get(Integer.parseInt(threadSleeptime), TimeUnit.MILLISECONDS);
    } catch (Exception e) {
        log.error(e + ". The function timed out after [" + threadSleeptime
                + "] miliseconds before a response was received.");
    } finally {
        // if task has started then don't stop it
        future.cancel(false);
    }
}

private static String returnString() {
    return "hello";
}

public static void main(String[] args) {
    timeoutFunction(returnString());
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.