web-dev-qa-db-ja.com

Java ArrayListの拡張

ArrayListを拡張して、拡張されたArrayListによってインスタンスが保持される特定のクラスにいくつかのメソッドを追加したいと思います。簡略化したコード例を以下に示します。

これは私には理にかなっているように見えますが、Javaに慣れていないので、ArrayListの拡張を妨げる他の質問、たとえば ArrayListの拡張と新しいメソッドの作成 を見つけました。異論を理解するのに十分なJavaを知らない.

私の以前の試みでは、本質的にはArrayListへのパススルーであるThingContainerでいくつかのメソッドを作成することになり、拡張がより簡単に思えました。

私がやろうとしていることをするためのより良い方法はありますか?もしそうなら、それはどのように実装されるべきですか?

import Java.util.*;

class Thing {
    public String name;
    public int amt;

    public Thing(String name, int amt) {
        this.name = name;
        this.amt = amt;
    }

    public String toString() {
        return String.format("%s: %d", name, amt);
    }

    public int getAmt() {
        return amt;
    }
}

class ThingContainer extends ArrayList<Thing> {
    public void report() {
        for(int i=0; i < size(); i++) {
            System.out.println(get(i));
        }
    }

    public int total() {
        int tot = 0;
        for(int i=0; i < size(); i++) {
            tot += ((Thing)get(i)).getAmt();
        }
        return tot;
    }

}

public class Tester {
    public static void main(String[] args) {
        ThingContainer blue = new ThingContainer();

        Thing a = new Thing("A", 2);
        Thing b = new Thing("B", 4);

        blue.add(a);
        blue.add(b);

        blue.report();
        System.out.println(blue.total());

        for (Thing tc: blue) {
            System.out.println(tc);
        }
    }
}
16
foosion

その答えには、ArrayListの拡張を妨げるものはありません。構文の問題がありました。クラス拡張が存在するため、コードを再利用できます。

クラスの拡張に対する通常の異論は、 "継承よりも構成を優先する" の議論です。拡張機能は常に望ましいメカニズムとは限りませんが、実際に何をしているのかによって異なります。

要求に応じて構成例を編集します。

_public class ThingContainer implements List<Thing> { // Or Collection based on your needs.
    List<Thing> things;
    public boolean add(Thing thing) { things.add(thing); }
    public void clear() { things.clear(); }
    public Iterator<Thing> iterator() { things.iterator(); }
    // Etc., and create the list in the constructor
}
_

完全なリストインターフェイスを公開するために必ずしもneedする必要はなく、コレクションのみを公開するか、まったく公開しない必要があります。ただし、どの機能も公開しないと、一般的な有用性は大幅に低下します。

Groovyでは、_@Delegate_アノテーションを使用して、自動的にメソッドを構築できます。 Javaは Project Lombok の_@Delegate_アノテーションを使用して同じことを実行できます。Lombokがどのようにインターフェイスを公開するか、またはそれがします。

私はグローコーダーを使用していますが、この場合、拡張機能に根本的に問題があるとは思われません。実際には、どのソリューションが問題に適しているかが問題です。

継承がカプセル化にどのように違反するかについての詳細を編集します

詳細については、Blochの効果的なJava、項目16を参照してください。

サブクラスがスーパークラスの動作に依存していて、スーパークラスの動作が変化すると、サブクラスが壊れる可能性があります。スーパークラスを制御しない場合、これは悪いことです。

これは具体的な例であり、本から持ち上げられ(申し訳ありませんがJosh!)、疑似コードで、非常に言い換えられています(すべてのエラーは私のものです)。

_class CountingHashSet extends HashSet {
    private int count = 0;
    boolean add(Object o) {
        count++;
        return super.add(o);
    }
    boolean addAll(Collection c) {
        count += c.size();
        return super.addAll(c);
    }
    int getCount() { return count; }
}
_

次に、それを使用します。

_s = new CountingHashSet();
s.addAll(Arrays.asList("bar", "baz", "plugh");
_

そして、それは戻ってきます... 3?いいえ。六。どうして?

HashSet.addAll()HashSet.add()に実装されていますが、これは内部実装の詳細です。サブクラスaddAll()は3つを追加し、super.addAll()を呼び出すadd()を呼び出し、これもカウントを増分します。

サブクラスのaddAll()を削除することもできましたが、現在は変更可能なスーパークラスの実装の詳細に依存しています。 addAll()を変更して各要素でadd()を反復して呼び出すこともできますが、今度はスーパークラスの動作を再実装します。これにより目的が無効になり、スーパークラスの動作が常に可能ではない場合がありますプライベートメンバーへのアクセスに依存します。

または、スーパークラスが、サブクラスが実装しない新しいメソッドを実装する可能性があります。つまり、クラスのユーザーがスーパークラスメソッドを直接呼び出すことにより、意図しない動作を意図せずにバイパスできるため、スーパークラスAPIを追跡して、サブクラスをいつ、どのような場合に特定する必要があります。変更する必要があります。

8
Dave Newton

これが私の提案です:

_interface ThingStorage extends List<Thing> {
    public int total();
}

class ThingContainer implements ThingStorage {

    private List<Thing> things = new ArrayList<Thing>();

    public boolean add(Thing e) {
        return things.add(e);
    }

    ... remove/size/... etc     

    public int total() {
        int tot = 0;
        for(int i=0; i < size(); i++) {
            tot += ((Thing)get(i)).getAmt();
        }
        return tot;
    }

}
_

そしてreport()は実際には必要ありません。残りはtoString()で実行できます。

0
Kowser

ArrayListを拡張する必要はないと思います。

public class ThingContainer {

    private ArrayList<Thing> myThings;

    public ThingContainer(){
        myThings = new ArrayList<Thing>();
    }

    public void doSomething(){
         //code
    }

    public Iterator<Thing> getIter(){
        return myThings.iterator();
    }
}

ArrayListをThingContainerクラスでラップするだけです。 ThingContainerは、必要な処理メソッドを持つことができます。 ArrayListを拡張する必要はありません。ただプライベートメンバーを維持してください。お役に立てれば。

Thingクラスを表すインターフェースの作成を検討することもできます。これにより、拡張性の柔軟性が向上します。

public Interface ThingInterface {
   public void doThing();
}

...

public OneThing implements ThingInterface {
   public void doThing(){
        //code
   }
}

public TwoThing implements ThingInterface {
   private String name;
   public void doThing(){
        //code
   }
}
0
b3bop