【问题标题】:Extending a java ArrayList扩展一个 java ArrayList
【发布时间】:2011-12-27 06:29:04
【问题描述】:

我想扩展 ArrayList 为特定类添加一些方法,该类的实例将由扩展的 ArrayList 保存。下面是一个简化的说明性代码示例。

这对我来说似乎很明智,但我对 Java 还是很陌生,而且我看到了其他不鼓励扩展 ArrayList 的问题,例如 Extending ArrayList and Creating new methods。我对 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);
        }
    }
}

【问题讨论】:

  • Jon Skeet 的回答包括“我不会扩展 ArrayList 除非我真的不得不这样做,我更喜欢组合而不是继承”或“正如许多其他人所说,是的,你可以扩展类 ArrayList,但这不是你通常应该做的事情;这在 Java 中被认为不是好的做法。”来自stackoverflow.com/questions/4779173/…。但是,从您和其他答案来看,我似乎误解了。
  • 就我个人而言,我倾向于避免子类化 JDK 类,因为在实现中可能会发生我无法控制的更改,或者会破坏我不知道的扩展中的某些内容——那里在依赖超类行为时偶尔会出现微妙的“陷阱”,而我们并不总是考虑,或者如果我们没有实现细节,则无法考虑。
  • @DaveNewton,这个问题不是同样适用于使用 JDK 类吗?
  • 你的意思是只使用它们而不子类化它们?
  • 那是不同的,因为类方法的所有使用都由类控制。我会在几分钟内在我的答案中添加一个小插曲。

标签: java arraylist extend


【解决方案1】:

该答案中没有任何内容不鼓励扩展 ArrayList;存在语法问题。类扩展存在,因此我们可以重用代码。

对扩展类的一般反对意见是"favor composition over inheritance" 讨论。扩展并不总是首选机制,但这取决于您实际在做什么。

根据要求编辑合成示例。

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 注释来做同样的事情。我不确定 Lombok 将如何公开界面,或者是否公开。

我在使用 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 以确定何时以及是否,子类应该改变。

【讨论】:

  • 我喜欢你的最后一行,“扩展并不总是首选机制”。这说明了一切。当涉及到这样的事情时,你说 java.util.List 来编写,如果你仍然希望你的类扩展它,你必须创建一个成员 List 然后创建,还有哪些其他 20 样板方法?所以是的,通常我同意组合比继承更好。但我认为在这种情况下继承是要走的路。
  • 我不明白如何在这里实现组合。请详细说明。或者你是说正常的反对意见在这里不适用?
  • @foosion 要进行组合,您需要创建应用程序特定的类,给它一个List 实例变量,并将类似列表的功能直接委托给列表。我会在答案中举一个小例子。
  • @DaveNewton,啊哈。我实际上就是这样开始的。然后我认为扩展会更好,因为我不需要实现 add、clear 等方法。扩展会免费给我。
【解决方案2】:

这是我的建议:

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() 可以完成剩下的工作。

【讨论】:

    【解决方案3】:

    我不认为扩展 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;只保留一个私人成员。 希望这会有所帮助。

    您可能还想考虑创建一个代表您的事物类的接口。这为您提供了更大的可扩展性灵活性。

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

    【讨论】:

    • 在这种情况下,如果我想从 ArrayList 中添加、添加、清除、获取、删除等,似乎我需要创建相应的方法,而扩展让我可以从 ArrayList 中使用这些方法而无需进一步行动。我错过了什么吗?
    • 如何在ThingContainer 中添加Thing? :P
    猜你喜欢
    • 2017-10-08
    • 2012-12-19
    • 2012-06-25
    • 2018-05-16
    • 2015-04-29
    • 2015-12-05
    • 2015-10-14
    • 2013-09-10
    • 2011-05-07
    相关资源
    最近更新 更多