1. ホーム
  2. java

[解決済み] Java 8では、なぜArraysにはIterableのforEachメソッドが与えられなかったのですか?

2022-03-04 22:08:34

疑問点

何か見落としているような気がするのですが。

Java 5 では for-eachループ文(enhanced for loopとも呼ばれる)。 が導入されました。これは、主に コレクション . コレクション(またはコンテナ)クラスで Iterable インターフェイスは、"for-eachループ"を使用した反復処理の対象となります。おそらく歴史的な理由から、Javaの配列は Iterable インターフェイスを使用します。しかし、配列はどこにでもあった/あるものなので javac は、配列に対する for-each ループの使用 (従来の for ループと同等のバイトコードを生成) を許可します。

Java 8 では forEach メソッド が追加されました。 Iterable インターフェースに デフォルト というメソッドがあります。これにより、ラムダ式をコレクションに(反復しながら)渡すことが可能になった(例えば list.forEach(System.out::println) ). しかし、やはり配列はこの扱いを受けられない。(回避策があることは理解しています)。

技術的な理由はありますか? javac での配列を受け入れるように拡張することはできませんでした。 forEach 拡張されたforループでそれらを受け入れるのと同じように?配列の実装を必要とせず、コード生成が可能なようです。 Iterable . 私が甘いのでしょうか?

このことは、構文的に簡単だからということで、むしろ自然に配列を使っている言語の初心者にとっては、特に重要なことです。リストに切り替えて、その中で Arrays.asList(1, 2, 3) .

解決方法は?

Java言語とJVMには、配列に関する特殊なケースがたくさんあります。配列にはAPIがありますが、それはほとんど見えません。それは、配列が持っていると宣言されているようなものです。

  • implements Cloneable, Serializable
  • public final int length
  • public T[] clone() ここで T は配列のコンポーネント型です。

しかし、これらの宣言は、どこのソースコードにも表示されていません。参照 JLS 4.10.3 JLS 10.7 を解説しています。 CloneableSerializable の呼び出しによって返されます。

Object[].class.getInterfaces()

おそらく驚くことに、この length フィールドと clone() メソッドは反射的に見えない。そのため length フィールドはフィールドではなく、これを使うと特別な arraylength のバイトコードです。の呼び出しは clone() は実際の仮想メソッド呼び出しになりますが、 レシーバーが配列型の場合、これはJVMによって特別に処理されます。

しかし、注目すべきは、配列クラスは Iterable インターフェイスを使用します。

Java SE 5 で拡張 for ループ ("for-each") が追加されたとき、右辺の式に 2 つの異なるケースがサポートされました。 Iterable または配列型( JLS 14.14.2 ). その理由は Iterable インスタンスと配列では、enhanced-for文での扱いが全く異なります。JLSのそのセクションに詳しい扱いがありますが、もっと簡単に言うと、以下のような状況です。

に対して Iterable<T> iterable は、コード

for (T t : iterable) {
    <loop body>
}

の構文解析です。

for (Iterator<T> iterator = iterable.iterator(); iterator.hasNext(); ) {
    t = iterator.next();
    <loop body>
}

配列の場合 T[] の場合、コードは

for (T t : array) {
    <loop body>
}

の構文解析です。

int len = array.length;
for (int i = 0; i < len; i++) {
    t = array[i];
    <loop body>
}

さて、なぜこのような形になったのでしょうか。確かに、配列に Iterable というのも、他のインターフェースはすでに実装されているからです。また、コンパイラが Iterator の実装は、配列にバックされています。(前例がある。コンパイラはすでに静的な values()valueOf() メソッドが自動的に追加され、すべての enum クラスで説明されているように JLS 8.9.3 .)

しかし、配列は非常に低レベルな構成であり、配列にアクセスするのは int の値は非常に安価な操作であることが期待されます。からのループインデックスを実行するのは非常にイディオムです。 0 を配列の長さ分、毎回1ずつ増やしていきます。配列に対するenhanced-forループはまさにこれを行う。もし、配列に対するenhanced-forループが Iterable プロトコルを使用する場合、配列をループする際に最初のメソッド呼び出しとメモリ割り当て (配列の上に Iterator その後、ループを1回繰り返すごとに2回のメソッド呼び出しが発生します。

そのため、デフォルトのメソッドが Iterable は、Java 8では配列に全く影響を与えませんでした。

他の方が指摘されているように、もし配列が int , long , double または参照型の場合、これをストリームに変換することができます。 Arrays.stream() の呼び出しがあります。これによって map() , filter() , forEach() など。

しかし、Java言語とJVMにおける配列の特殊なケースは、次のように置き換えることができればいいと思います。 リアル という構文があります(2次元以上の配列の貧弱な処理、2^31の長さの制限など、他の多くの配列関連の問題の修正と一緒に)。これはJohn Roseが主導しているquot;Arrays 2.0"の調査の主題である。JVMLS 2012でのJohnの講演を参照してください( ビデオ , スライド ). この議論に関連するアイデアとしては、配列に対する実際のインタフェースの導入、ライブラリによる要素アクセスの補間、スライスやコピーといった追加操作のサポート、などがあります。

なお、これらはすべて調査であり、今後の課題です。この記事を書いている時点(2016-02-23)では、これらの配列の強化から、どのリリースのJavaロードマップにもコミットされているものはない。