Расширение java ArrayList

Я хотел бы расширить ArrayList, чтобы добавить несколько методов для определенного класса, экземпляры которого будут храниться расширенным ArrayList. Ниже приведен упрощенный пример кода примера.

Это кажется мне разумным, но я очень новичок в Java, и я вижу другие вопросы, которые препятствуют расширению ArrayList, например Расширение ArrayList и Создание новых методов. Я не знаю достаточно Java, чтобы понять возражения.

В моей предыдущей попытке я закончил создание ряда методов в ThingContainer, которые были по существу сквозными передачами в ArrayList, поэтому расширение оказалось проще.

Есть ли лучший способ сделать то, что я пытаюсь сделать? Если да, то как это должно быть реализовано?

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

Ответ 1

Ничто в этом ответе не позволяет расширить 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
}

Вам необязательно будет раскрывать полный интерфейс списка, просто коллекцию или вообще ничего. Однако использование любой из функций значительно снижает общую полезность.

В Groovy вы можете просто использовать аннотацию @Delegate для автоматического создания методов. Java может использовать Project Lombok @Delegate аннотация, чтобы сделать то же самое. Я не уверен, как Ломбок выведет интерфейс, или если он это сделает.

Я с glowcoder, я не вижу ничего принципиально неправильного с расширением в этом случае - это действительно вопрос, какое решение лучше подходит для проблемы.

Изменить для получения информации о том, как наследование может нарушать инкапсуляцию

Подробнее см. Bloch Effective Java, пункт 16.

Если подкласс полагается на поведение суперкласса, а поведение суперкласса изменяется, подкласс может сломаться. Если мы не будем контролировать суперкласс, это может быть плохо.

Вот конкретный пример, снятый из книги (извините Джош!), в псевдокоде и сильно перефразированный (все ошибки мои).

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");

И он возвращается... три? Неа. Шесть. Почему?

HashSet.addAll() реализуется на HashSet.add(), но это внутренняя деталь реализации. Наш подкласс addAll() добавляет три вызова super.addAll(), который вызывает add(), который также увеличивает счетчик.

Мы могли бы удалить подкласс addAll(), но теперь мы полагаемся на детали реализации суперкласса, которые могут измениться. Мы могли бы изменить наш addAll() для повторения и вызова add() для каждого элемента, но теперь мы переопределяем поведение суперкласса, которое побеждает цель, и может не всегда быть возможным, если поведение суперкласса зависит от доступа к закрытым членам.

Или суперкласс может реализовать новый метод, который наш подкласс не делает, то есть пользователь нашего класса может непреднамеренно обойти предполагаемое поведение, напрямую вызвав метод суперкласса, поэтому мы должны отслеживать API суперкласса, чтобы определить, когда и если, подкласс должен измениться.

Ответ 2

Я не думаю, что расширение 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
   }
}

Ответ 3

Вот мое предложение:

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() может сделать все остальное.