1. ホーム
  2. java

[解決済み] Java8でvoid(Voidではない)メソッドに関数型を指定する方法は?

2022-04-29 02:42:34

質問

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 このような場合、コードはコンパイルされます。の戻り値の型を単純に変更することができるのは知っています。 displayIntVoid を返し、さらに null . しかし、渡したいメソッドをどこかに変更することができない状況もあるかもしれません。を簡単に再利用する方法はないでしょうか? displayInt をそのまま使うのでしょうか?

解決方法は?

間違ったインターフェースの種類を使おうとしています。そのタイプは 機能 は、パラメータを受け取り、戻り値を持つので、この場合適切ではありません。代わりに 消費者 (旧名ブロック)

関数型は次のように宣言されています。

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) );

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

さて、このタイプのconsumeを作るのにラムダ式の代わりにメソッド参照を使いたかったら、Stringを受け取ってvoidを返すメソッドが必要ですよね?

別の種類のメソッド参照を使うこともできますが、今回はオブジェクトのメソッド参照を利用することにしましょう。 println メソッドを System.out オブジェクトをこのように使用します。

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

あるいは、単純に

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

println メソッドは、値を受け取り、戻り値の型が 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は、ラムダ式とメソッド参照から関数型インターフェースの実装を作成するための代替方法を提供するだけです。最終的には、ラムダ式とメソッド参照はオブジェクト参照に束縛されるでしょうから、私たちが手にするのは一級市民としてのオブジェクトだけです。重要なのは、オブジェクトをパラメータとして渡したり、変数参照に束縛したり、他のメソッドから値として返したりできるため、機能的にはほとんど同じであることだ。