14

私は現在Java 8、ラムダやメソッド参照などの機能について深く掘り下げています。少し遊んでみると、次の例にたどり着きました。

public class ConsumerTest {

  private static final String[] NAMES = {"Tony", "Bruce", "Steve", "Thor"};

   public static void main(String[] args) {
      Arrays.asList(NAMES).forEach(Objects::requireNonNull);
   }
}

私の質問は:

main メソッド内の行がコンパイルされるのはなぜですか?

私の理解が正しければ、参照されたメソッドの署名は、機能インターフェースの SAM 署名に対応している必要があります。この場合、コンシューマーは次の署名を必要とします。

void accept(T t);

ただし、requireNonNullメソッドはTvoid の代わりに戻ります。

public static <T> T requireNonNull(T obj)
4

2 に答える 2

13

Java 言語仕様バージョン 8 は、15.13.2で次のように述べています。

メソッド参照式は、代入コンテキスト、呼び出しコンテキスト、またはキャスト コンテキストにおいて、T が関数型インターフェイス型 ( §9.8 ) であり、式が T から派生したグラウンド ターゲット型の関数型と合同である場合、ターゲット型 T と互換性があります。 .

[..]

次の両方が真の場合、メソッド参照式は関数型と合同です。

  • 関数型は、参照に対応する単一のコンパイル時宣言を識別します。
  • 次のいずれかが当てはまります。
    • 関数型の結果は void です。
    • 関数型の結果は R であり、選択されたコンパイル時宣言の呼び出し型 ( §15.12.2.6 ) の戻り値の型にキャプチャ変換 ( §5.1.10 ) を適用した結果は R' (R はR') を推論するために使用できるターゲット型であり、R も R' も void ではなく、R' は代入コンテキストで R と互換性があります。

(私のものを強調)

したがって、関数型の結果が void であるという事実は、一致させるのに十分です。

JLS 15.12.2.5では、メソッドを照合する際の void の使用についても具体的に言及しています。ラムダ式には、15.12.2.1で参照されるvoid 互換ブロック ( 15.27.2 )の概念がありますが、メソッド参照の同等の定義はありません。

より具体的な説明を見つけることができませんでした(ただし、JLSはクラックするのが難しいため、関連するセクションがいくつか欠けている可能性があります)が、これは、非呼び出しも許可されているという事実に関係していると思います-void メソッドを単独のステートメントとして (代入returnなどなしで))。

メソッド参照のJLS 15.13.3実行時評価は、次のようにも述べています。

コンパイル時の結果を決定する目的で、メソッド呼び出し式は、呼び出しメソッドの結果が void の場合は式ステートメントであり、呼び出しメソッドの結果が非 void の場合は return ステートメントの Expression です。

メソッド参照のコンパイル時の宣言がシグネチャ ポリモーフィックである場合のこの決定の影響は、次のとおりです。

  • メソッド呼び出しのパラメーターの型は、対応する引数の型です。
  • メソッド呼び出しは、メソッド呼び出しを囲む呼び出しメソッドが void であるか戻り値の型を持つかに応じて、void であるか、Object の戻り値の型を持ちます。

そのため、生成されたメソッド呼び出しは、関数型と一致するように無効になります。

于 2015-09-12T13:09:22.797 に答える
4

@Mark Rotteveel の正確な回答で述べられていることとは別に、次の例のように、Java ではメソッド呼び出しの結果を無視できるため、コンパイルされます。

Map<String, String> map = new HashMap<>();

map.put("1", "a");

map.put("1", "A"); // Who cares about the returned value "a"?

コンシューマーのブロックで何も返さないためforEach()、仕様に従って有効です。

于 2015-09-12T13:47:11.513 に答える