配列がIterableに割り当てられないのはなぜですか?


186

Java5では、次のように記述できます。

Foo[] foos = ...
for (Foo foo : foos) 

またはforループでIterableを使用するだけです。これはとても便利です。

ただし、次のようにイテラブルのジェネリックメソッドを作成することはできません。

public void bar(Iterable<Foo> foos) { .. }

Iterableではないため、配列で呼び出します。

Foo[] foos = { .. };
bar(foos);  // compile time error 

このデザイン決定の背後にある理由について疑問に思っています。


8
Arrays.asListは十分だと思います
dfa

17
それは哲学的な質問です
dfa

2
Java 5以降で配列を処理するのに適した理由は、varargsメソッドです。
ジェフウォーカー

2
@Torsten:trueですが、Iterableを受け入れるメソッドに渡した場合は、おそらく何も変更を加えていません。
マイケルマイヤーズ

5
実際には、は、Arrays.asListはありません、それはプリミティブ型の配列では動作しませんので、十分によいです。プリミティブ型の要素を一般的に反復する唯一の組み込みの方法は、を使用したリフレクションですjava.lang.reflect.Arrayが、そのパフォーマンスは弱いです。ただし、必要に応じて、独自のイテレータ(またはList実装!)を記述して、プリミティブ型の配列をラップすることができます。
Boann

回答:


78

配列はインターフェースを実装できます(Cloneableおよびjava.io.Serializable)。では、なぜIterableでしょうか。Iterable強制的にiteratorメソッドを追加すると思いますが、配列はメソッドを実装していません。char[]オーバーライドすらしませんtoString。とにかく、参照の配列は理想的ではないと考えるべきです-を使用してくださいList。DFAコメントとしてArrays.asList、明示的に変換を行います。

(そうは言っても、clone配列を呼び出すことができます。)


23
>「...配列はメソッドを実装していません。」これは別の哲学的問題だと思います。配列は決してプリミティブ型ではありませんでした。Javaの哲学では、「すべてがオブジェクトです(プリミティブ型を除く)」。では、最初から配列を使用したいと思うような膨大な数の操作があるにもかかわらず、配列がメソッドを実装しないのはなぜですか。ああ、そうです、ジェネリックが後悔してしまう前に、配列は強く型付けされた唯一のコレクションでした。
fatuhoku

2
配列にデータがある場合は、ストリームからのbyte []の読み取りを処理するなど、低レベルでパフォーマンスが重要な作業を行っている可能性があります。配列を反復できないのは、@ Garethが以下に述べるように、Javaジェネリックスが型引数としてプリミティブをサポートしていないためと考えられます。
Drew Noakes

2
@FatuHokuジェネリックスの提案は後悔しましたが間違っていました。ジェネリックの望ましさは常に高く評価されていました。配列はプリミティブではありません(そうだとは言いませんでした)が、低レベルです。配列でやりたいことの1つは、配列をベクトルのような構造の実装の詳細として使用することです。
トム・ホーティン-タックライン

1
Iterator<T>remove(T)スローできますが、も必要UnsupportedOperationExceptionです。
wchargin

配列はメソッドを実装します。それらはのすべてのメソッドを実装しますjava.lang.Object
mhsmith 2017

59

配列はオブジェクトですが、その項目はそうでない可能性があります。配列は、Iterableが処理できないintのようなプリミティブ型を保持する場合があります。少なくともそれは私が考えていることです。


3
つまり、Iterableインターフェイスをサポートするには、ラッパークラスを使用するためにプリミティブ配列を特殊化する必要があります。とにかく型パラメータはすべて偽物なので、これは実際には大したことではありません。
thejoshwolfe

8
これは、オブジェクト配列がIterableを実装することを妨げません。また、プリミティブ配列がラップされた型のIterableを実装することを妨げません。
Boann

1
オートボクシングはこれを処理できます
TimBüthe2013

これが正しい理由だと思います。Genericsがプリミティブ型をサポートするようになるまで(たとえばのList<int>代わりに)List<Integer>、プリミティブ型の配列に対しては十分に機能しません。ハッパーはラッパーで実行できますが、パフォーマンスが低下します-さらに重要なことですが、このハッキングが行われた場合、将来的にJavaで適切に実装することint[].iterator()ができIterator<Integer>なくなります(たとえば、ではなく永久にロックされて戻りIterator<int>ます)。おそらく、Java(プロジェクトヴァルハラ)の今後の値タイプ+汎用的な特殊化により、配列が実装されるようになりますIterable
Bjarke

16

配列はIterable、.NET配列が位置による読み取り専用のランダムアクセスを許可するインターフェイスをサポートしていないのと同じ理由で、をサポートする必要がありますが、サポートしません(そのようなインターフェイスは標準として定義されていません)。基本的に、フレームワークには多くの場合煩わしい小さなギャップがあり、それを修正する価値はありません。何らかの方法で自分たちで修正できるかどうかは重要ではありませんが、できない場合がよくあります。

更新:片手で説明するために、位置によるランダムアクセスをサポートするインターフェイスをサポートしていない.NET配列についても触れました(コメントも参照)。しかし.NET 4.5では、その正確なインターフェイスが定義されており、配列とList<T>クラスでサポートされています。

IReadOnlyList<int> a = new[] {1, 2, 3, 4};
IReadOnlyList<int> b = new List<int> { 1, 2, 3, 4 };

可変リストのインターフェースIList<T>は継承しないため、すべてがまだ完全ではありませんIReadOnlyList<T>

IList<int> c = new List<int> { 1, 2, 3, 4 };
IReadOnlyList<int> d = c; // error

たぶん、そのような変更と後方互換性の問題が発生する可能性があります。

新しいバージョンのJavaで同様のことが進展している場合は、コメントで知りたいです。:)


8
.NET配列はIListインターフェイスを実装しています
Tom Gillen

2
@Aphid-私は読み取り専用のランダムアクセスと言った。IList<T>変更のための操作を公開します。場合、それは素晴らしいことだIList<T>何か継承されていたIReadonlyList<T>だけだったはずのインターフェース、CountおよびT this[int]継承されたIEnumerable<T>(すでに読み取り専用の列挙をサポートしています)。もう一つの素晴らしいところは、逆の順序の列挙子を取得するためのインタフェース、だろうReverse拡張メソッドは、(同じようにするために照会することができCountための拡張メソッドクエリICollection。自分自身を最適化する)
ダニエルエリカー

はい、物事がそのように設計されていれば、はるかに良いでしょう。IListインターフェイスは、IsReadOnlyプロパティとIsFixedSizeプロパティを定義します。これらは、配列によって適切に実装されます。しかし、与えられたリストが実際に読み取り専用であることをコンパイル時にチェックすることができず、これらのプロパティをチェックするコードはほとんどありません。
トム・ギレン

1
.NETの配列は実装IListICollection.NET 1.1以降、およびIList<T>およびICollection<T>.NET 2.0以降。これは、Javaが競争に大きく遅れているもう1つのケースです。
アミールアビリ2014

@TomGillen:私の最大の問題IListは、クエリ可能な属性が提供されないことです。最初に、適切なセットにはIsUpdateable、IsResizable、IsReadOnly、IsFixedSize、およびExistingElementsAreImmutableを含める必要があると思います。型キャストなしで参照をコードがリストを変更できるかどうかの質問は、変更を想定していないリストへの参照を保持するコードがその参照を外部のコードと安全に直接共有できるかどうか、またはリストの一部の側面は決して変更されないと想定しても安全です。
スーパーキャット2014

14

残念ながら、配列は「class十分」ではありません。それらはIterableインターフェースを実装していません。

配列は現在、ClonableおよびSerializableを実装するオブジェクトですが、配列は通常の意味でのオブジェクトではなく、インターフェースを実装していません。

これらをfor-eachループで使用できるのは、Sunが配列のシンタティックシュガーを追加したためです(特殊なケースです)。

配列はJava 1では「ほとんどのオブジェクト」として始まったので、Javaで実際のオブジェクトにするための変更はあまりにも劇的です。


14
それでも、for-eachループには砂糖があるので、Iterableに砂糖がないのはなぜですか?
マイケルマイヤーズ

8
@mmyers:for-eachで使用される砂糖はコンパイル時の砂糖です。VMシュガーよりもはるかに簡単です。そうは言っても、.NET配列はこの領域で大幅に優れています...
Jon Skeet、

12
配列インターフェース実装できます。それらは実装しCloneableSerializableインターフェースします。
notnoop 2009

34
java配列はあらゆる意味でオブジェクトです。その誤った情報を削除してください。Iterableを実装しないだけです。
ykaganovich 2009

5
配列はオブジェクトです。これは、サポート便利:待ちなどP法()、ウェイト(N)、待機(n、m)は、通知()、のnotifyAll()、ファイナライズ()のtoString(の無意味な実装)のみ有用な方法ではgetClass(IS) 。
Peter Lawrey、2010

1

コンパイラは実際にfor each配列のをforカウンタ変数を使用した単純なループに変換します。

以下をコンパイルする

public void doArrayForEach() {
    int[] ints = new int[5];

    for(int i : ints) {
        System.out.println(i);
    }
}

そして.classファイルを逆コンパイルすると

public void doArrayForEach() {
    int[] ints = new int[5];
    int[] var2 = ints;
    int var3 = ints.length;

    for(int var4 = 0; var4 < var3; ++var4) {
        int i = var2[var4];
        System.out.println(i);
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.