web-dev-qa-db-ja.com

Javaにおける型リストと型ArrayListの比較

(1) List<?> myList = new ArrayList<?>();

(2) ArrayList<?> myList = new ArrayList<?>();

(1)では、Listインターフェースの実装を入れ替えることができます。 (1)は通常、必要性に関係なくアプリケーションで使用されているようです(私自身は常にこれを使用しています)。

誰かが(2)を使っているのかどうか疑問に思いますか?

また、実際には(2)に対して(1)を使用することが実際にどのくらいの頻度で(そして例を挙げてください)(つまり、(2)では足りない場合があります。)_をインタフェースにコーディングおよびbest practiceなど)

508
kji

ほとんどの場合、最初のものが2番目のものよりも優先されます。 1つ目は、残りのコードに影響を与えることなく、Listの実装を(たとえば LinkedList に)変更できるという利点があります。どこでもArrayListArrayListに変更する必要があるだけでなく、LinkedList特有のメソッドを使用したかもしれないので、これはArrayListを処理するのが難しいタスクになります。

Listの実装について読むことができます ここ 。あなたはArrayListで始めるかもしれませんが、その直後に別の実装がより適切であることを発見します。

404
kgiannakakis

誰かが(2)を使っているのかどうか疑問に思いますか?

はい。しかし、稀な理由(IMO)のためにめったにありません。

そしてArrayListを使うべきだったときにListを使っていたので人々はやけどをします:

  • Collections.singletonList(...)Arrays.asList(...)のようなユーティリティメソッドはArrayListを返しません。

  • List APIのメソッドは、同じ型のリストを返すことを保証しません。

たとえば、誰かがやけどをした、 https://stackoverflow.com/a/1481123/139985ArrayList.sublist(...)ArrayListを返さないため、ポスターに「スライス」の問題がありました。彼のすべてのリスト変数の型としてArrayListを使うこと。彼はサブリストを新しいArrayListにコピーすることで問題を「解決」しました。

Listがどのように振る舞うのかを知るために必要な引数は、主にRandomAccessマーカーインタフェースを使用して対処します。はい、それは少し不格好ですが、代替案はもっと悪いです。

また、実際にどのくらいの頻度で実際に(2)より(1)を使用する必要があるか(つまり、(2)で十分ではない場合。「インターフェイスへのコーディング」およびベストプラクティスなど)。

質問の「どのくらいの頻度で」の部分は客観的に解決できないものです。

(そして例を挙げてください)

場合によっては、アプリケーションによっては、ArrayList APIのnotであるList APIのメソッドを使用する必要があります。たとえば、ensureCapacity(int)trimToSize()removeRange(int, int)などです。 (そして最後のものは、メソッドがpublicであると宣言するArrayListのサブタイプを作成した場合にのみ発生します。)

それが、IMOというインターフェースではなくクラスにコーディングする唯一の正当な理由です。

(特定の状況下では...一部のプラットフォームではパフォーマンスがわずかに向上する可能性があることは理論的には可能です...ただし、最後の0.05%が本当に必要でない限り、これを実行する価値はありません。健全な理由、IMO)


ランダムアクセスが効率的かどうかわからない場合は、効率的なコードを書くことはできません。

それが妥当な点です。しかし、Javaはそれに対処するためのより良い方法を提供します。例えば.

public <T extends List & RandomAccess> void test(T list) {
    // do stuff
}

RandomAccessを実装していないリストでそれを呼び出すと、コンパイルエラーになります。

静的型付けが面倒な場合は、instanceof ...を使用して動的にテストすることもできます。また、リストがランダムアクセスをサポートしているかどうかに応じて、(動的に)異なるアルゴリズムを使用するようにコードを書くこともできます。

ArrayListRandomAccessを実装する唯一のリストクラスではないことに注意してください。その他にはCopyOnWriteListStackVectorがあります。

私は人々がSerializableについて同じ議論をしているのを見ました(Listはそれを実装していないので)...しかし、上記のアプローチはこの問題も解決します。 (ランタイム型を使用している場合は、まったく解決可能です。ArrayListは、要素が直列化可能でない場合、直列化に失敗します。

103
Stephen C

例えば、LinkedListがあなたのアプリケーションに最適な選択であると決めるかもしれません、しかし、後でArrayListがパフォーマンス上の理由からより良い選択であるかもしれないと決めます。

つかいます:

List list = new ArrayList(100); // will be better also to set the initial capacity of a collection 

の代わりに:

ArrayList list = new ArrayList();

参考のため:

enter image description here

(主にコレクション図に掲載)

36
Maxim Shoustin

Set型の変数にHashSetまたはTreeSetへの参照を格納することは、 正しいスタイルと見なされます です。

Set<String> names = new HashSet<String>();

こうすれば、代わりにTreeSetを使用することにした場合、1行だけ変更する必要があります。

また、集合を操作するメソッドはSet型のパラメータを指定する必要があります。

public static void print(Set<String> s)

それから このメソッドはすべての集合の実装に使用できる

理論的には、リンクリストについても同じことを推奨する必要があります。つまり、LinkedList参照をList型の変数に保存することです。ただし、Javaライブラリでは、ListインタフェースはArrayListクラスとLinkedListクラスの両方に共通です。特に、リンクリストには非常に非効率的ですが、ランダムアクセス用のgetおよびsetメソッドがあります。

ランダムアクセスが効率的かどうかわからない場合は、 効率的なコードを記述できません

これは明らかに標準ライブラリの重大な設計エラーであり、そのためListインタフェースを使用することはお勧めできません。

そのエラーがどれほど厄介であるかを見るために、 Collections クラスのbinarySearchメソッドのソースコードを見てください。このメソッドはListパラメータを取りますが、バイナリ検索ではリンクリストには意味がありません。それからコードは不器用にそのリストがリンクされたリストであるかどうかを発見しようと試みてそれから線形検索に切り替わります!

SetインターフェースとMapインターフェースはうまく設計されていますので、それらを使用してください。

23
nazar_art

Codeがリストの「所有者」の場合は、(2)を使用します。これは、例えばローカルのみの変数に当てはまります。 Listではなく抽象型ArrayListを使用する理由はありません。所有権を証明するもう1つの例:

public class Test {

    // This object is the owner of strings, so use the concrete type.
    private final ArrayList<String> strings = new ArrayList<>();

    // This object uses the argument but doesn't own it, so use abstract type.
    public void addStrings(List<String> add) {
        strings.addAll(add);
    }

    // Here we return the list but we do not give ownership away, so use abstract type. This also allows to create optionally an unmodifiable list.
    public List<String> getStrings() {
        return Collections.unmodifiableList(strings);
    }

    // Here we create a new list and give ownership to the caller. Use concrete type.
    public ArrayList<String> getStringsCopy() {
        return new ArrayList<>(strings);
    }
}
12
mazatwork

あなたがListを書くとき、あなたはあなたのオブジェクトがListインターフェースだけを実装することを実際に言う、しかしあなたはあなたのオブジェクトがどのクラスに属するかを指定しない。

ArrayListを書くとき、あなたはあなたのオブジェクトクラスがサイズ変更可能な配列であることを指定します。

そのため、最初のバージョンでは将来的にコードがより柔軟になります。

Javaのドキュメントを見てください。

クラスArrayList - Listインタフェースのサイズ変更可能な配列の実装。

インタフェースList - 順序付きコレクション(シーケンスとも呼ばれる)。このインタフェースのユーザは、リストの各要素が挿入される場所を正確に制御できます。

Array - 単一型の固定数の値を保持するコンテナオブジェクト。

11
wzbozon

(3)コレクションmyCollection = new ArrayList();

私はこれを普通に使っています。そして only Listメソッドが必要な場合は、Listを使います。 ArrayListと同じです。あなたはいつももっと "狭い"インターフェースに切り替えることができますが、もっと "広い"に切り替えることはできません。

9
dart

私は(2)を使う人々は Liskov置換の原則 または 依存性の逆転の原理 を知らないと思う。あるいは彼らは本当にArrayListを使わなければなりません。

9
True Soft

実際には(2)が望ましいだけでなく必須である場合もあります、そして私は非常に驚いています。

連載!

直列化可能クラスがあり、それにリストを含める場合は、ArrayListインターフェースはJava.io.Serializableを拡張しないため、フィールドをListのような具象で直列化可能な型にする必要があります。

明らかに、ほとんどの人は直列化を必要とせず、これを忘れています。

例:

public class ExampleData implements Java.io.Serializable {

// The following also guarantees that strings is always an ArrayList.
private final ArrayList<String> strings = new ArrayList<>();
9
raudi

次の2つのうち

(1) List<?> myList = new ArrayList<?>();
(2) ArrayList<?> myList = new ArrayList<?>();

第一が一般的に好ましい。 Listインターフェースからのメソッドのみを使用するので、他のListの実装を自由に使用できます。将来的にはLinkedList。だからそれはあなたを特定の実装から切り離します。今言及する価値がある2つのポイントがあります:

  1. 私たちは常にインターフェイスするようにプログラムするべきです。もっと ここ
  2. あなたはほとんどの場合ArrayListよりLinkedListを使うことになります。もっと ここ

誰かが使用するかどうか私は思っています

たまにしか読みません。 ArrayListの実装の一部であるがインタフェースListの一部ではないメソッドが必要なとき。例えばensureCapacityです。

また、実際にどのくらいの頻度で(そして例を挙げてください)実際に(1)を(2)の使用が必要になるのでしょうか。

ほとんど常にあなたはオプション(1)を好む。これはOOPの古典的なデザインパターンで、コードを特定の実装やプログラムからインタフェースに分離することを常に試みています。

7
i_am_zero

誰かがこれをもう一度(二重に)尋ねたので、私はこの問題についてもう少し詳しく説明しました。

public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    list.add("a");
    list.add("b");

    ArrayList<String> aList = new ArrayList<String>();
    aList.add("a");
    aList.add("b");

}

バイトコードビューアを使用する場合(私は http://asm.ow2.org/Eclipse/index.html を使用しました)、 list snippetについては以下を参照してください(リストの初期化と割り当てのみ)。 :

   L0
    LINENUMBER 9 L0
    NEW ArrayList
    DUP
    INVOKESPECIAL ArrayList.<init> () : void
    ASTORE 1
   L1
    LINENUMBER 10 L1
    ALOAD 1: list
    LDC "a"
    INVOKEINTERFACE List.add (Object) : boolean
    POP
   L2
    LINENUMBER 11 L2
    ALOAD 1: list
    LDC "b"
    INVOKEINTERFACE List.add (Object) : boolean
    POP

そして alist の場合:

   L3
    LINENUMBER 13 L3
    NEW Java/util/ArrayList
    DUP
    INVOKESPECIAL Java/util/ArrayList.<init> ()V
    ASTORE 2
   L4
    LINENUMBER 14 L4
    ALOAD 2
    LDC "a"
    INVOKEVIRTUAL Java/util/ArrayList.add (Ljava/lang/Object;)Z
    POP
   L5
    LINENUMBER 15 L5
    ALOAD 2
    LDC "b"
    INVOKEVIRTUAL Java/util/ArrayList.add (Ljava/lang/Object;)Z
    POP

違いは、 list _ invokeinterface _ を呼び出すことになり、 aList calls _ invokevirtual _ となることです。 Bycode Outline Pluginへの参照

invokeinterfaceは、Javaインタフェース内で宣言されたメソッドを呼び出すために使用されます。

仮想呼び出し中

インタフェースメソッド(invokeinterfaceを使用)、静的メソッド(invokestaticを使用)、およびinvokespecialで処理されるいくつかの特別な場合を除く、すべてのメソッドを呼び出します。

要約すると、invokeinterfaceの間はinvokevirtual pops objectref がスタックをオフになります。

インタプリタはオペランドスタックから 'n'項目をポップします。 'n'はバイトコードから取られた8ビットの符号なし整数パラメータです。これらの項目の最初のものはobjectrefで、メソッドが呼び出されているオブジェクトへの参照です。

私がこれを正しく理解していれば、違いは基本的にそれぞれの方法で objectref を取得する方法です。

3
toxicafunk

Listはインタフェースです。メソッドはありません。 List参照でmethodを呼び出すときどちらの場合も、実際にはArrayListのメソッドを呼び出します。

そして将来的にはList obj = new ArrayList<>List obj = new LinkList<>または Listインターフェースを実装する他の型に変更することができます。

3

私が知っているのは、(2) - になる可能性があることを知っている唯一のケースです。GWTを使用するときだけです。しかし、JVM(1)の内部で実行されている通常のJavaでは、おそらく常に優れています。

2
Hans Westerbeek

1が優先されない限り、私は言うと思います

  • arrayListでのオプションの動作*の実装に依存しているので、その場合は明示的にArrayListを使用する方が明確です。
  • おそらくオプションの動作やパフォーマンス特性のために、ArrayListを必要とするメソッド呼び出しでArrayListを使用します。

私の推測では、99%のケースではListを使用することができますが、これが推奨されます。

  • 例えばremoveAll、またはadd(null)
1
extraneon

Listインターフェースには、いくつかの異なるクラスがあります - ArrayListLinkedListLinkedListはインデックス付きコレクションを作成するために使用され、ArrayList - ソートされたリストを作成するために使用されます。それで、あなたはそれを引数に使うことができますが、あなたが使うものだけではなく、あなたのコードやライブラリなどを使う他の開発者が異なる種類のリストを使うことを許可することができます。

ArrayList<Object> myMethod (ArrayList<Object> input) {
   // body
}

ArrayListではなくLinkedListでのみ使用できますが、メソッドが使用されている他の場所ではListクラスのいずれかを使用することができます。これは単なる選択であるため、インターフェイスを使用すると許可できます。

List<Object> myMethod (List<Object> input) {
   // body
}

このメソッドの引数では、使用したいListクラスをどれでも使用できます。

List<Object> list = new ArrayList<Object> ();

list.add ("string");

myMethod (list);

結論:

可能であればどこでもインターフェースを使用してください。あなたや他の人が使用したい異なる方法を使用するように制限しないでください。

1
Acuna