8

ローカル変数のインターフェイスへのプログラミングは役に立たず、パフォーマンスを損なうだけで何のメリットもないため、行うべきではないと言われました。

public void foo() {
    ArrayList<Integer> numbers = new ArrayList<Integer>();
    // do list-y stuff with numbers
}

それ以外の

public void foo() {
    List<Integer> numbers = new ArrayList<Integer>();
    // do list-y stuff with numbers
}

パフォーマンスへの影響はごくわずかだと思いますが、ArrayList のリスト セマンティクスを使用しても得られることはあまりありません。いずれかの方法に進む強い理由はありますか?

4

7 に答える 7

14

たとえば、次のクラスを見てください。

public class Tmp {
    public static void main(String[] args) {
        ArrayList<Integer> numbers = new ArrayList<Integer>();
        numbers.add(1);
    }
}

コンパイルすると次のようになります。

Compiled from "Tmp.java"
public class Tmp extends java.lang.Object{
public Tmp();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   new #2; //class java/util/ArrayList
   3:   dup
   4:   invokespecial   #3; //Method java/util/ArrayList."<init>":()V
   7:   astore_1
   8:   aload_1
   9:   iconst_1
   10:  invokestatic    #4; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   13:  invokevirtual   #5; //Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
   16:  pop
   17:  return

}

このクラスの間:

public class Tmp {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<Integer>();
        numbers.add(1);
    }
}

これにコンパイルされます:

Compiled from "Tmp.java"
public class Tmp extends java.lang.Object{
public Tmp();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   new #2; //class java/util/ArrayList
   3:   dup
   4:   invokespecial   #3; //Method java/util/ArrayList."<init>":()V
   7:   astore_1
   8:   aload_1
   9:   iconst_1
   10:  invokestatic    #4; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   13:  invokeinterface #5,  2; //InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
   18:  pop
   19:  return

}

唯一の違いは、最初のもの ( を使用ArrayList) が呼び出しをinvokevirtual行い、もう 1 つは (使用をList使用)であることがわかりますinvokeinterface実際には(この男によると〜38% )invokeinterfaceよりも髪の毛が遅いです。これは明らかに、具象クラスの仮想メソッド テーブルとインターフェイスのメソッド テーブルを検索するときに JVM が実行できる最適化によるものです。だからあなたの言ってることは本当です。インターフェイスの呼び出し、具体的なクラスの呼び出しよりも遅くなります。invokevirtual

ただし、私たちが話している速度の種類を考慮する必要があります。1 億回の呼び出しでは、実際の差は 0.03 秒でした。したがって、実際に速度を大幅に低下させるには、大量の呼び出しが必要です

一方、@ChinBoon が指摘しているように、特にコードが何らかの sort を返す場合は、インターフェイスにコーディングすると、コードを使用する人にとって非常に簡単になりますList。そのため、ほとんどの場合、コーディングの容易さは相対的なパフォーマンスの犠牲をはるかに上回ります。


@MattQuigley のコメントを読み、家に帰るドライブで考えた後に追加

これが意味することは、これはあまり心配する必要がないということです。パフォーマンスの向上またはペナルティは、非常に小さい可能性があります。

戻り値の型とメソッドのパラメーターにインターフェイスを使用することは、非常に良いアイデアです。これにより、あなたとあなたのコードを使用するすべての人がList、ニーズに最適な の実装を使用できます。List私の例では、99% の確率で を返すメソッドを使用した場合、パフォーマンスを向上させるためだけにそれを具象クラスにキャストするのは得策ではないことも示しています。キャストにかかる時間は、パフォーマンスの向上を上回る可能性があります。

そうは言っても、この例は、ローカル変数の場合、インターフェースよりも具象クラスを使用する方が実際に優れていることも示しています。使用するメソッドが onListのメソッドだけである場合、副作用なしで実装クラスを切り替えることができます。さらに、必要に応じて実装固有のメソッドにアクセスできます。さらに、マイナーなパフォーマンスの向上があります。

tl;dr

メソッドの戻り値の型とパラメーターには常にインターフェイスを使用します。ローカル変数に具象クラスを使用することをお勧めします。わずかなパフォーマンスの向上が得られ、使用するメソッドがインターフェイス上にある場合のみ実装を切り替えるコストはありません。結局、あまり気にしなくていいです。(戻り値の型とパラメーターのことを除いて。それは重要です。)

于 2012-05-16T22:41:17.093 に答える
12

「インターフェースへのプログラミング」のベストプラクティスはローカル変数に適用されますか?

このような変数のスコープはメンバー変数のスコープよりも小さいですが、メンバー変数に関してインターフェイスに対してコーディングする 利点のほとんどは、ローカル変数にも当てはまります。

ArrayList 固有のメソッドが必要でない限り、List を使用してください。

パフォーマンスへの影響はごくわずかだと思いますが、ArrayList の List セマンティクスを使用して得られるものはあまりありません。

インターフェイス メソッドの呼び出しが、クラス型のメソッドの呼び出しよりも遅いとは聞いたことがありません。

ArrayListas static 型の代わりに使用することはList、時期尚早の最適化のように思えます。いつものように、プログラム全体のプロファイルを作成するまでは、最適化よりも可読性と保守性を優先してください。

于 2012-05-16T22:40:08.490 に答える
5

TLDR:メソッドの呼び出し、インターフェイス/抽象クラスでは遅く、後の時点でローカル変数の実装を変更することを妨げるものはほとんどありません。これにより、ほとんどの引数が重要なポイントになります。

理由を理解せずにローカル変数をインターフェースとして宣言することを盲目的に言っている多くの答えがここにあります。まず第一に、メソッドの呼び出し遅くなります-テストで思い出したように、約3倍です。最初のコードブロックのaddメソッドは、2番目のコードブロックの3倍遅いです。これは、forループの経過時間を測定する簡単なテストで自分でテストできます。

List<Integer> numbers = new ArrayList<Integer>();
numbers.add(1); // slower

ArrayList<Integer> numbers = new ArrayList<Integer>();
numbers.add(1); // faster

第二に、問題はローカル変数に関するものであり、一般的なインターフェイスのオブジェクト指向設計の原則ではありません。パブリッククラスメソッドが実装(ArrayList)ではなくインターフェイスまたは抽象クラス(List)を返すというオブジェクト指向の理由があります。そのような理由については他の場所で読むことができますが、この質問はそれについてではなく、ローカル変数についてです。99.999%の場合、そのローカル変数の実装をからに変更することはありませArrayListVectorただし、実際に行う.001%の回数では、そうすることを妨げるものはありません。Vectorという単語をArrayListという単語にコピーして貼り付けるだけで、完了です。そんな変化は難しいと誰もが思っているようです。そうではありません。

第三に、aLinkedListは。と同じものではありませんArrayList。理由(アクセス/追加/挿入/削除にかかる時間)でどちらかを選択します。それをとして宣言することArrayListは、このローカル変数が迅速なランダムアクセスを目的としていることをあなたに知らせる肯定です。時期尚早の最適化であるという議論はばかげています-インスタンス化されるとすぐに、いずれかの方法で最適化されます。

于 2012-05-16T23:44:58.893 に答える
3

仕事を成し遂げることができる最も抽象的な型を使用してください。のみが必要な場合はList、それを使用してください。タイプをより具体的なものに制限する必要がある場合は、代わりにそれを使用してください。たとえば、場合によってListは、using が具体的すぎて、usingCollectionが好まれることがあります。ConcurrentMap別の例として、状況によっては通常の代わりに aを必要とする場合がありMapます。

于 2012-05-16T22:41:03.123 に答える
0

特にメソッドがリストを返す場合、ある日実装をリンク リストに変更しなければならなくなった場合、インターフェースへのコーディングが役に立ちます。これは利点の 1 つであり、メソッドを変更してリンク リストまたは配列リストを返す必要はありません。

于 2012-05-16T22:43:00.323 に答える
0

ここに提案があります: List を final として宣言しますが、インターフェイスを使用します。

final List<Integer> numbers = new ArrayList<Integer>();

私が推測しなければならなかった場合(そして私はJavaコンパイラ自体の専門家ではありません)、変数をfinalにすると、コンパイラはinvokevirtualを優先してinvokeinterfaceを削除できるはずです。変数が宣言されると変更されます。

于 2012-05-17T02:39:15.007 に答える
-1

はい@Matthew、使用するベストプラクティスの方がはるかに優れています

List<Integer> numbers = new ArrayList<Integer>

ArrayList クラスと List クラスのメソッドを使用できるためです。お役に立てれば幸いです。

于 2012-05-16T22:49:49.913 に答える