想象一下,我需要创建一个元素集合,其中顺序可能或不重要。实际上,我计划做的就是使用迭代器。我注意到我的大多数同事使用ArrayList和LinkedHashSet / HashSet。我的问题是,如果我知道这些元素应该是唯一的,我应该使用Set还是List?实际上它并没有真正有所作为,但是没有更有效地表达元素是独一无二的吗?
我发现这对于大型企业应用程序来说是一个有趣的问题,原因如下: 1)如果你不能保证整体代码的质量,使用Set可能是危险的。为什么?因为equals()& hashcode可能被错误地覆盖,因此使用Set可能会导致一些非常讨厌的问题。 2)使用List对未来的更改更具弹性。如果出于某种原因重复,则无需担心。
基本上归结为:如果我知道我应该期待独特的元素,我是否应该支持在所有情况下使用Set over List?
编辑:我想我也问:Set是否应该用于确保没有添加重复项,或者它也可以用于说明为了便于理解,不存在重复的内容?
答案 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接口公开它,特别是如果你的类的调用者只需要遍历集合。