Rozszerzanie java ArrayList

Chciałbym rozszerzyć ArrayList, aby dodać kilka metod dla konkretnej klasy, której instancje będą przechowywane przez rozszerzoną ArrayList. Uproszczony przykładowy kod ilustracyjny znajduje się poniżej.

Wydaje mi się to sensowne, ale jestem zupełnie nowy w Javie i widzę inne pytania, które zniechęcają do rozszerzania ArrayList, na przykład Rozszerzanie ArrayList i tworzenie nowych metod. Nie znam wystarczająco Javy, żeby zrozumieć obiekcje.

W mojej wcześniejszej próbie, skończyłem tworząc numer metod w ThingContainer, które były zasadniczo przejściami do ArrayList, więc Rozszerzanie wydawało się łatwiejsze.

Czy jest lepszy sposób, aby zrobić to, co próbuję zrobić? Jeśli tak, to w jaki sposób należy je wdrożyć?
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);
        }
    }
}
Author: Community, 2011-11-11

3 answers

Nic w tej odpowiedzi nie zniechęca do rozszerzania ArrayList; wystąpił problem ze składnią. Rozszerzenie klasy istnieje więc możemy ponownie użyć kodu.

Normalnym sprzeciwem wobec rozszerzenia klasy jest dyskusja "favor composition over inheritance". Rozszerzenie nie zawsze jest preferowanym mechanizmem, ale zależy od tego, co faktycznie robisz.

edycja dla przykładu kompozycji zgodnie z żądaniem.

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
}

Niekoniecznie musisz ujawnić pełny interfejs listy, tylko kolekcja, lub w ogóle brak. Eksponowanie żadnej z funkcjonalności znacznie zmniejsza jednak ogólną użyteczność.

W Groovy możesz po prostu użyć adnotacji @Delegate do automatycznego budowania metod. Java może używać adnotacji projektu Lombok w celu zrobienia tego samego. Nie jestem pewien, jak Lombok ujawniłby interfejs, ani czy tak jest.

Jestem z glowcoderem, nie widzę w tym przypadku nic zasadniczo złego w extension--to naprawdę kwestia, które rozwiązanie lepiej pasuje do problemu.

Edytuj, aby uzyskać szczegółowe informacje na temat tego, jak dziedziczenie może naruszać enkapsulację

Zobacz skuteczne Java Blocha, poz. 16, aby uzyskać więcej szczegółów.

Jeśli podklasa opiera się na zachowaniu superklasy, a zachowanie superklasy się zmienia, podklasa może się zepsuć. Jeśli nie będziemy kontrolować klasy, to może być źle.

Oto konkretny przykład, wyciągnięty z książki (sorry Josh!), w pseudokodach i mocno parafrazowane (wszystkie błędy są moje).

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; }
}

Następnie używamy:

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

I powraca... trzy? Nie. Sześć. Dlaczego?

HashSet.addAll() jest zaimplementowana na HashSet.add(), ale jest to wewnętrzny szczegół implementacji. Nasza podklasa addAll() dodaje trzy wywołania super.addAll(), które wywołują add(), które również zwiększają liczbę.

Moglibyśmy usunąć podklasę addAll(), ale teraz opieramy się na szczegółach implementacji superklasy, które mogą się zmienić. Możemy zmodyfikować nasze addAll() do iteracji i wywołania add() na każdym elemencie, ale teraz ponownie wdrażamy zachowanie klasy superclass, które pokonuje cel i może nie zawsze być możliwe, jeśli zachowanie klasy superclass zależy od dostępu do prywatnych członków.

Lub klasa superclass może zaimplementować nową metodę, której nie ma nasza podklasa, co oznacza, że użytkownik naszej klasy może nieumyślnie ominąć zamierzone zachowanie przez bezpośrednie wywołanie metody superclass, więc musimy śledzić API klasy superclass, aby określić, kiedy i czy podklasa powinna się zmienić.

 8
Author: Dave Newton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-12 16:06:21

Nie sądzę, aby Rozszerzanie arrayList było konieczne.

public class ThingContainer {

    private ArrayList<Thing> myThings;

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

    public void doSomething(){
         //code
    }

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

Powinieneś zawinąć ArrayList do swojej klasy ThingContainer. ThingContainer może wtedy mieć dowolne metody przetwarzania, których potrzebujesz. Nie trzeba rozszerzać ArrayList; wystarczy zachować prywatny członek. Mam nadzieję, że to pomoże.

Możesz również rozważyć stworzenie interfejsu, który reprezentuje Twoją klasę rzeczy. Daje to większą elastyczność w zakresie rozciągliwości.

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
Author: b3bop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-11 19:13:04

Oto moja propozycja:

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;
    }

}

I {[1] } nie są potrzebne. toString () może zrobić resztę.

 0
Author: Kowser,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-11 19:44:13