173

私はJava 8をいじって、ファーストクラスの市民としてどのように機能するかを調べています。次のスニペットがあります。

package test;

import java.util.*;
import java.util.function.*;

public class Test {

    public static void myForEach(List<Integer> list, Function<Integer, Void> myFunction) {
      list.forEach(functionToBlock(myFunction));
    }

    public static void displayInt(Integer i) {
      System.out.println(i);
    }


    public static void main(String[] args) {
      List<Integer> theList = new ArrayList<>();
      theList.add(1);
      theList.add(2);
      theList.add(3);
      theList.add(4);
      theList.add(5);
      theList.add(6);
      myForEach(theList, Test::displayInt);
    }
}

私がやろうとしているのは、メソッド参照を使用してdisplayIntメソッドをメソッドに渡すことです。myForEachコンパイラに次のエラーが生成されます。

src/test/Test.java:9: error: cannot find symbol
      list.forEach(functionToBlock(myFunction));
                   ^
  symbol:   method functionToBlock(Function<Integer,Void>)
  location: class Test
src/test/Test.java:25: error: method myForEach in class Test cannot be applied to given ty
pes;
      myForEach(theList, Test::displayInt);
      ^
  required: List<Integer>,Function<Integer,Void>
  found: List<Integer>,Test::displayInt
  reason: argument mismatch; bad return type in method reference
      void cannot be converted to Void

コンパイラはそれを不平を言いvoid cannot be converted to Voidます。myForEachコードがコンパイルされるような署名で関数インターフェイスの型を指定する方法がわかりません。displayInt戻り値の型をto に変更してからVoidreturnに変更できることはわかっていnullます。ただし、別の場所に渡したいメソッドを変更できない場合があります。そのまま再利用する簡単な方法displayIntはありますか?

4

4 に答える 4

301

間違ったインターフェイス タイプを使用しようとしています。Function型は、パラメーターを受け取り、戻り値を持つため、この場合は適切ではありません。代わりに、Consumer (以前は Block と呼ばれていた)を使用する必要があります。

Function 型は次のように宣言されます。

interface Function<T,R> {
  R apply(T t);
}

ただし、 Consumer タイプは、探しているものと互換性があります。

interface Consumer<T> {
   void accept(T t);
}

そのため、Consumer は、T を受け取って何も返さない (void) メソッドと互換性があります。そして、これはあなたが望むものです。

たとえば、リスト内のすべての要素を表示したい場合は、ラムダ式を使用してそのためのコンシューマーを簡単に作成できます。

List<String> allJedi = asList("Luke","Obiwan","Quigon");
allJedi.forEach( jedi -> System.out.println(jedi) );

この場合、ラムダ式はパラメーターを受け取り、戻り値がないことが上記でわかります。

ここで、ラムダ式の代わりにメソッド参照を使用してこの型の消費を作成したい場合は、String を受け取って void を返すメソッドが必要ですよね?

printlnさまざまな種類のメソッド参照を使用できますが、この場合は、次のように、オブジェクト内のメソッドを使用してオブジェクト メソッド参照を利用しましょうSystem.out

Consumer<String> block = System.out::println

または、私は単に行うことができます

allJedi.forEach(System.out::println);

Consumerprintlnのメソッドと同様に、値を受け取り、戻り値の型が void であるため、このメソッドは適切です。accept

したがって、コードでは、メソッド シグネチャを次のように変更する必要があります。

public static void myForEach(List<Integer> list, Consumer<Integer> myBlock) {
   list.forEach(myBlock);
}

そして、次のようにして、静的メソッド参照を使用してコンシューマーを作成できるはずです。

myForEach(theList, Test::displayInt);

myForEach最終的には、メソッドを完全に削除して、次のようにすることもできます。

theList.forEach(Test::displayInt);

第一種市民としての機能について

結局のところ、構造関数型が言語に追加されないため、Java 8 は第一級市民としての関数を持たないというのが真実です。Java は、ラムダ式とメソッド参照から関数型インターフェイスの実装を作成する別の方法を提供するだけです。最終的に、ラムダ式とメソッド参照はオブジェクト参照にバインドされるため、ファーストクラスの市民としてのオブジェクトしかありません。重要なことは、オブジェクトをパラメーターとして渡し、それらを変数参照にバインドし、他のメソッドから値として返すことができるため、機能が存在することです。その後、それらはほぼ同様の目的を果たします。

于 2013-01-15T13:01:03.883 に答える
27

引数を取らず、結果を返さない関数 (void) を引数として受け入れる必要がある場合、私の意見では、次のようなものを用意するのが最善です。

  public interface Thunk { void apply(); }

コードのどこかに。私の関数型プログラミングのコースでは、「サンク」という言葉がそのような関数を説明するために使用されていました。java.util.function にない理由は、私の理解を超えています。

他の場合では、java.util.function に必要な署名と一致するものがある場合でも、インターフェイスの命名がコード内の関数の使用と一致しない場合、常に正しく感じられるとは限りません。Thread クラスに関連する用語である「Runnable」に関して、ここの他の場所で行われているのと同様の点だと思います。そのため、必要な署名が含まれている可能性がありますが、それでも読者を混乱させる可能性があります。

于 2016-12-12T10:09:51.347 に答える