Java8でvoid(Voidではない)メソッドの関数型を指定する方法


143

私は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そのまま再利用する簡単な方法はありますか?

回答:


244

間違ったインターフェイスタイプを使用しようとしています。タイプFunctionは、パラメーターを受け取り、戻り値があるため、この場合は適切ではありません。代わりに、コンシューマー(旧称Block)を使用する必要があります

関数タイプは次のように宣言されます

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

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

ラムダ式ではなくメソッド参照を使用してこの型の消費を作成したい場合は、文字列を受け取ってvoidを返すメソッドが必要ですよね。

私は、メソッド参照の異なる種類を使用しますが、この場合には使用して、オブジェクトのメソッド参照ののテイク利点を聞かせてできたprintln方法をSystem.out、このように、オブジェクト:

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

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

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

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

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

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は、ラムダ式とメソッド参照から機能インターフェースの実装を作成する代替方法を提供するだけです。最終的にラムダ式とメソッド参照はオブジェクト参照にバインドされるため、私たちが持っているのは一流の市民としてのオブジェクトだけです。重要なことは、オブジェクトをパラメーターとして渡し、それらを変数参照にバインドし、他のメソッドからの値として返すことができるため、機能がそこにあることです。そうすれば、ほとんど同じような目的を果たします。


1
ちなみに、JDK 8 APIの最新リリースでBlock変更されConsumerました
Edwin Dalorzo

4
最後の段落では、いくつかの重要な事実について説明します。ラムダ式とメソッド参照は、構文の追加以上のものです。JVM自体は、匿名クラス(最も重要な場合MethodHandle)よりも効率的にメソッド参照を処理するための新しい型を提供します。これを使用すると、Javaコンパイラーはより効率的なコードを生成できます。たとえば、静的メソッドとしてクロージャーなしでラムダを実装することにより、追加のクラス。
Feuermurmel、2014

7
の正しいバージョンはFunction<Void, Void>ですRunnable
OrangeDog 2016

2
@OrangeDogこれは完全に真実ではありません。RunnableCallableインターフェイスへのコメントには、スレッドと組み合わせて使用​​することになっていると書かれています。その厄介な結果は、run()メソッドを直接呼び出すと、いくつかの静的分析ツール(Sonarなど)が文句を言うことです。
Denis

純粋にJavaの背景から来ているのですが、JavaがJava8以降の機能的なインターフェースとラムダを導入した後でも、関数をファーストクラスのシチズンとして処理できない場合は特にどうなりますか?
Vivek Sethi 2018

21

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

  public interface Thunk { void apply(); }

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

他の場合では、java.util.functionに必要なシグネチャと一致するものがある場合でも、インターフェイスの名前がコード内の関数の使用と一致しない場合でも、常に適切であるとは限りません。これは、ここで「Runnable」(Threadクラスに関連付けられている用語)に関して他の場所で行われたのと同様の点であるため、必要な署名があるかもしれませんが、それでも読者を混乱させる可能性があります。


特にRunnable、チェックされた例外を許可しないため、多くのアプリケーションで役に立たなくなります。Callable<Void>も役に立たないので、自分で定義する必要があるようです。醜い。
Jesse Glick 2017

チェックされた例外の問題を参照すると、Javaの新機能は一般にそれと闘っています。これは、ストリームAPIのようないくつかの巨大なブロッカーです。彼らの側の設計は非常に貧弱です。
user2223059

0

Void代わりに戻り値の型を設定しますvoidおよびのreturn null

// Modify existing method
public static Void displayInt(Integer i) {
    System.out.println(i);
    return null;
}

または

// Or use Lambda
myForEach(theList, i -> {System.out.println(i);return null;});

-2

代わりにコンシューマーインターフェイスを使用する必要があると思います Function<T, R>

コンシューマーは基本的に、値を受け入れて何も返さない(つまりvoid)ように設計された機能的なインターフェースです。

あなたのケースでは、次のようにコード内の他の場所にコンシューマを作成できます。

Consumer<Integer> myFunction = x -> {
    System.out.println("processing value: " + x);    
    .... do some more things with "x" which returns nothing...
}

次に、myForEachコードを以下のスニペットに置き換えることができます。

public static void myForEach(List<Integer> list, Consumer<Integer> myFunction) 
{
  list.forEach(x->myFunction.accept(x));
}

myFunctionをファーストクラスのオブジェクトとして扱います。


2
これは既存の答えを超えて何を追加しますか?
Matthewが
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.