假设我有一个在内部存储数据列表的类:
import java.util.List;
public class Wrapper
{
private List<Integer> list;
public Wrapper(List<Integer> list)
{
this.list = list;
}
public Integer get(int index) { return list.get(index); }
}
为了这个例子,假装它是一个有用和必要的抽象。现在,这是我的关注:作为一个知道这个类的底层实现的程序员,我应该具体说明我在构造函数中要求哪种类型的List?为了证明,我做了这个测试:
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
public class Main
{
public static void main(String[] args)
{
long start;
List<Integer> list1 = new ArrayList<Integer>();
List<Integer> list2 = new LinkedList<Integer>();
Wrapper wrapper1, wrapper2;
for(int i = 0; i < 1000000; i++)
{
list1.add(i);
list2.add(i);
}
wrapper1 = new Wrapper(list1);
wrapper2 = new Wrapper(list2);
start = System.currentTimeMillis();
wrapper1.get(500000);
System.out.println(System.currentTimeMillis() - start);
start = System.currentTimeMillis();
wrapper2.get(500000);
System.out.println(System.currentTimeMillis() - start);
}
}
正如您最有可能知道的那样,随机访问元素需要花费更多时间使用链接列表而不是数组。那么,回到Wrapper构造函数,我是否应该是通用的并且允许任何类型的List,或者我应该指定用户传递ArrayList以确保最佳性能?虽然在这个例子中,用户可能很容易猜测 get 方法的底层实现是什么,但你可以想象这是更复杂的东西。提前谢谢!
答案 0 :(得分:3)
接口的全部意义在于允许对底层实现进行不可知论。与LinkedList或ArrayList相反使用List类型是为了允许一般操作而不必担心这种问题。只要您的代码可以编写而不必依赖List未公开的方法,您就不必担心。
这个类的用户可能正在编写对他们使用的列表类型有其他要求的代码,他们可能会将批量附加到列表的中间,例如LinkedList擅长的地方。因此,您应该接受最通用的类型,并假设用户有充分的理由使用该类型。
但是,如果在应用程序的任何其他用途中没有任何区别,那么这可能不会阻止您使用javadoc注释使用ArrayList可能会更好。
答案 1 :(得分:2)
那么,回到Wrapper构造函数,我是否应该是通用的并允许任何类型的List?
包装器的意图是支持任何类型的列表吗?或者只有ArrayList?
...或者我应该指定用户传递ArrayList以确保最佳性能吗?
如果你离开一般名单就可以了。您让该类的“客户端”决定他是否可以使用ArrayList。取决于客户。
您也可以使用RandomAccess界面来反映您的意图,但由于它只是一个标记界面,可能它没有多大意义。
再次,将其作为一般列表就足够了。
答案 2 :(得分:0)
这可能是人们可以争辩的事情。
我需要特定类型的 的论据是:
要求特定类型的 参数是:
答案 3 :(得分:0)
如果您要从此列表中随机访问,或者您认为将要添加到此列表中,请使用ArrayList。
如果要在大多数时间访问系列中的元素,请使用LinkedList。
答案 4 :(得分:0)
我认为这取决于您尝试使用Wrapper
类和/或您希望Wrapper
的客户使用它的目标。
如果您的目的是为List
提供一个包装器,客户端不应该期望get()
(或任何方法的名称)的某种性能水平,那么您的课程看起来如此对我来说很好,除了它只是对被复制的构造函数的list
参数的引用,而不是列表本身的内容(在下面的第3点更多内容)。
但是,如果您告诉您的客户希望get()
能够做出快速响应,我会想到一些替代方案(可能会有更多):
List
的那些实现,你知道这些实现对于get()
将在其上执行的任何操作具有高性能。例如:} //如果我知道如何避免这种支撑,我会...
public Wrapper {
Wrapper(ArrayList<Integer> list) { ... }
Wrapper(KnownListImplementationThatWillMakeMyGetMethodFast<Integer> list) { ... }
//...
}
这样做的一个缺点是,如果另外有一个有效的List
实现,你将不得不为它添加另一个构造函数。
保持您的Wrapper
课程不受影响,但通知您的客户(通过某种形式的文档,比如课程评论,自述文件等),无论List
实施什么操作他们传入的预期会有一定的表现(例如“get()
必须在恒定时间内返回”)。如果他们通过传递LinkedList
来“滥用”您的包装,那就是他们的错。
如果您想保证get()
实现快速,则值得将构建器中收到的列表复制到符合性能限制的成员数据结构中。这可以是ArrayList
,您知道的其他List
实现,也可以是完全不实现List
接口的容器(例如您编写的特殊用途)。关于我之前所说的关于复制List
的内容与复制对它的引用的内容,您对引用执行的任何“写入”操作都不会流入客户端的{{{ 1}}如果你复制其内容。这通常是一种避免客户想知道为什么在调用List
上的操作时触及列表副本的好方法,除非您明确告诉他们期望这种行为。