在ArrayList上使用HashSet来传达意图?

时间:2009-06-17 08:33:10

标签: java collections arraylist set

想象一下,我需要创建一个元素集合,其中顺序可能或不重要。实际上,我计划做的就是使用迭代器。我注意到我的大多数同事使用ArrayList和LinkedHashSet / HashSet。我的问题是,如果我知道这些元素应该是唯一的,我应该使用Set还是List?实际上它并没有真正有所作为,但是没有更有效地表达元素是独一无二的吗?

我发现这对于大型企业应用程序来说是一个有趣的问题,原因如下: 1)如果你不能保证整体代码的质量,使用Set可能是危险的。为什么?因为equals()& hashcode可能被错误地覆盖,因此使用Set可能会导致一些非常讨厌的问题。 2)使用List对未来的更改更具弹性。如果出于某种原因重复,则无需担心。

基本上归结为:如果我知道我应该期待独特的元素,我是否应该支持在所有情况下使用Set over List?

编辑:我想我也问:Set是否应该用于确保没有添加重复项,或者它也可以用于说明为了便于理解,不存在重复的内容?

10 个答案:

答案 0 :(得分:7)

1)完全是虚假的。不要解决bug,修复它们。 因此,如果订单无关紧要,请使用任何Set实施,如果订单确实,请使用SortedSet。如果元素不必是唯一的(并且您现在应该确定它,并且通常不应该更改),请随意使用List

答案 1 :(得分:2)

如果您需要考虑独特元素,请使用Set。但是如果你不相信你的用户正确实现equals / hashCode,那么我建议你记录如果迭代有问题,检查你的equals / hashCode!但这实际上取决于数据模型的用例。

答案 2 :(得分:1)

考虑代码的可读性。

如果您期望并想要一个独特的集合,那么使用“SET”数据结构,从长远来看,事情会更加清晰。因此,这也将促进更好的编码。

答案 3 :(得分:1)

有人说HashSet在添加,删除,包含和大小方面提供恒定的时间性能。

JavaDocs中的实际语句是“此类为基本操作(添加,删除,包含和大小)提供恒定时间性能,假设散列函数在桶之间正确地分散元素。 “

这意味着如果添加了一些内容很糟糕的hashCode方法,那么在添加内容时可能会增加添加时间。

以下代码演示了根据您的hashCode实现可能发生的情况。

public void testHashSetAddition() {
    for(int mod=10; mod <= 100; mod=mod+10 ) {
        Set s = new HashSet();
        long start = new Date().getTime();
        for(int i=0; i<100000; i++) {
            s.add(new Foo(i % mod));
        }
        long end = new Date().getTime();
        System.out.println("Mod: " + mod + " - " + (end - start) + "ms");
    }
}

class Foo {
    private int hc;
    public Foo(int i) {
        this.hc = i;
    }
    public int hashCode() {
        return hc;
    }
}

时间结果如下:

Mod: 10 - 22683ms
Mod: 20 - 14200ms
Mod: 30 - 10486ms
Mod: 40 - 8562ms
Mod: 50 - 7761ms
Mod: 60 - 6740ms
Mod: 70 - 5778ms
Mod: 80 - 5268ms
Mod: 90 - 4716ms
Mod: 100 - 3966ms

然后,对ArrayList执行完全相同的测试:

public void testAddingToArrayList() {
    for(int mod=100; mod >= 10; mod=mod-10 ) {
        List l = new ArrayList();
        long start = new Date().getTime();
        for(int i=0; i<100000; i++) {
            l.add(new Foo(i % mod));
        }
        long end = new Date().getTime();
        System.out.println("Mod: " + mod + " - " + (end - start) + "ms");
    }
}

给出:

Mod: 100 - 50ms
Mod: 90 - 30ms
Mod: 80 - 40ms
Mod: 70 - 30ms
Mod: 60 - 30ms
Mod: 50 - 40ms
Mod: 40 - 20ms
Mod: 30 - 30ms
Mod: 20 - 30ms
Mod: 10 - 30ms

答案 4 :(得分:1)

    import java.util.*;

    public class Test {
        public void testHashSetAddition() {
            for(int mod=10; mod <= 100; mod=mod+10 ) {
                Set s = new HashSet();
                long start = new Date().getTime();
                for(int i=0; i<100000; i++) {
                    s.add(new Foo(i % mod));
                }
                System.out.println(s.size());
                long end = new Date().getTime();
                System.out.println("Mod: " + mod + " - " + (end - start) + "ms");
            }
        }
        public void testAddingToArrayList() {
            for(int mod=100; mod >= 10; mod=mod-10 ) {
                List l = new ArrayList();
                long start = new Date().getTime();
                for(int i=0; i<100000; i++) {
                    l.add(new Foo(i % mod));
                }
                System.out.println(l.size());
                long end = new Date().getTime();
                System.out.println("Mod: " + mod + " - " + (end - start) + "ms");
            }
        }

        public static void main(String...a){
            new Test().testHashSetAddition();
            new Test().testAddingToArrayList();
        }
        class Foo {
            private int hc;
            public Foo(int i) {
                this.hc = i;
            }
            public int hashCode() {
                return hc;
            }
            public int getHc(){
                return hc;
            }
            public boolean equals(Object o){
                if(!(o instanceof Foo)) return false;
                Foo fo = (Foo)o;
                return fo.getHc() == this.hc;
            }
        }

    }
/*
10
Mod: 10 - 31ms
20
Mod: 20 - 16ms
30
Mod: 30 - 15ms
40
Mod: 40 - 16ms
50
Mod: 50 - 0ms
60
Mod: 60 - 16ms
70
Mod: 70 - 0ms
80
Mod: 80 - 15ms
90
Mod: 90 - 0ms
100
Mod: 100 - 0ms
100000
Mod: 100 - 32ms
100000
Mod: 90 - 31ms
100000
Mod: 80 - 31ms
100000
Mod: 70 - 31ms
100000
Mod: 60 - 32ms
100000
Mod: 50 - 15ms
100000
Mod: 40 - 31ms
100000
Mod: 30 - 32ms
100000
Mod: 20 - 15ms
100000
Mod: 10 - 32ms
*/

答案 5 :(得分:0)

设置是否可取,因为它会强制执行唯一性并向您显示您的错误。

当方法被错误地覆盖时,您可能会遇到一些问题,但正确的选择不是祈祷并避免调用它们。检测错误并修复它们!

编辑:是的,当你看到Set,uniques值,甚至更好时,它更清晰:强制执行唯一值。永远不要猜测/信任您的代码的使用;)

答案 6 :(得分:0)

我认为不应该考虑任何一种选择来传达意图 - 你的方法应该被宣布为只返回一个具有适当通用参数的Collection,既灵活又因为正如你所说,消费者它应该能够迭代它而不用担心它是什么类型。这提供了额外的优势,如果需求稍后更改,或者由于任何原因您的初始选择错误,您需要在一个地方更改代码(初始构造函数调用)。

应该在方法的文档中指定意图,该文档应详细说明集合的迭代器是否将以任何特定顺序返回元素,以及是否会出现重复元素。

我也同意上述帖子,说明你在第1点的推理是关闭的 - 如果你想要放入equals和/或hashcode的不正确的实施课程设置,你修复它们,然后使用Set!

答案 7 :(得分:0)

@Andrzej Doyle - 我不认为当你在一个集合中添加一个elemnt时,重复的比较就完成了。一个Set内部使用hashMap,所以任何重复的键都会被覆盖而且没有特定的检查

答案 8 :(得分:0)

@Andrzej Doyle - 我不认为当你在一个集合中添加一个elemnt时,重复的比较就完成了。一个Set内部使用hashMap,所以任何重复的键都会被覆盖而且没有特定的检查

答案 9 :(得分:-1)

在List实现上使用Set实现可能会降低性能。在Set中插入元素时,需要检查它是否重复。如果您打算只使用迭代器,请尽可能使用最简单的实现(ArrayList)。

我不认为使用Set来传达信息是个好主意。如果您自己添加项目并且可以保证不会添加重复项,则使用Set是没有意义的。使用适当的名称来传达有关该集合的信息。此外,最好通过Collection接口公开它,特别是如果你的类的调用者只需要遍历集合。