0

假设我有一个类,它在内部存储一个数据列表:

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方法的底层实现是什么,但您可以想象这是更复杂的事情。提前致谢!

4

5 回答 5

3

接口的全部意义在于允许对底层实现不可知论。与 LinkedList 或 ArrayList 相比,List 类型的真正用途是允许进行一般操作而不必担心此类问题。只要您的代码可以编写而不必依赖 List 未公开的方法,您就不必担心。

这个类的用户很可能正在编写对他们使用的列表类型有其他要求的代码,他们可能会将很多附加到列表的中间,例如 LinkedList 擅长的地方。因此,您应该接受最通用的类​​型,并假设用户有充分的理由使用该类型。

然而,这并不是要阻止您可能包含一个 javadoc 注释,即如果应用程序的任何其他使用没有区别,则使用 ArrayList 可能会更好。

于 2009-06-10T00:06:33.087 回答
2

那么,回到 Wrapper 构造函数,我应该通用并允许任何类型的 List 吗?

包装器的意图是支持任何类型的列表吗?还是只有 ArrayList?

...或者我应该指定用户传递一个 ArrayList 以确保最佳性能?

如果您离开一般列表,它会很好。你让那个类的“客户”来决定他是否可以使用 ArrayList。由客户决定。

您也可以使用RandomAccess接口来反映您的意图,但由于它只是一个标记接口,因此可能没有多大意义。

因此,再次将其保留为一般列表就足够了。

于 2009-06-10T00:37:32.697 回答
0

这可能是一个可以争论的事情。

我要求特定类型的论点是:

  • 包装类确实知道哪种类型最适合使用包装类的所有情况。随机访问就是一个很好的例子——对于这种情况,不太可能有比支持的列表更好的列表实现数组。
  • 包装类实际上对实现进行了假设。如果做出此类假设,请确保通过要求适当的类型来满足这些假设。

反对要求特定类型的论点是:

  • 包装类用于不同的场景,并且有不同的 List 实现或多或少适合特定场景。即,应该由调用者找到最适合该场景的实现。也可能存在在编写包装类时尚未发明 List 的最佳 (TM) 实现的情况。
于 2009-06-10T00:07:04.473 回答
0

如果您要从该列表中随机访问,或者您认为您会在该列表中添加很多内容,请使用 ArrayList。

如果您要在大部分时间访问系列元素,请使用 LinkedList。

于 2009-06-10T00:12:38.893 回答
0

我认为这取决于您尝试通过该Wrapper课程实现的目标和/或您期望客户Wrapper使用它的目的。

如果您的意图只是为 a 提供一个包装器List,客户不应该期望从(或任何方法的名称)获得一定程度的性能get(),那么您的类对我来说看起来不错,除了它是只是对list被复制的构造函数参数的引用,而不是列表本身的内容(在下面的第 3 点中有更多内容)。

但是,如果您告诉您的客户期望get()响应速度非常快,那么您会想到一些替代方案(可能还有更多):

  1. 编写一组构造函数,它们只接受那些List你知道对于将在其上执行的任何操作具有高性能的实现get()。例如:

} // 如果我知道如何避免这个括号,我会...

 public Wrapper {
     Wrapper(ArrayList<Integer> list) { ... }
     Wrapper(KnownListImplementationThatWillMakeMyGetMethodFast<Integer> list) { ... }

     //...
 } 

这样做的一个缺点是,如果出现另一个有效的List实现,则必须为其添加另一个构造函数。

  1. 让您的Wrapper课程保持不变,但通知您的客户(通过某种形式的文档,例如,课程注释,自述文件等)List他们传入的任何实现上的某些操作预计将具有一定的性能(例如“get()必须返回恒定时间”)。如果他们随后通过传入 a 来“滥用”您的包装器LinkedList,那是他们的错。

  2. 如果您想保证您的get()实现快速,值得将您在构造函数中收到的列表复制到满足您的性能限制的成员数据结构中。这可以是一个ArrayList,你知道的其他一些List实现,或者一个完全不实现List接口的容器(例如你写的一些特殊用途的东西)。关于我之前所说的复制的内容与复制对它的引用,如果您复制其内容List,您对引用执行的任何“写入”操作都不会流向您的客户的副本。List这通常是避免客户想知道为什么他们的列表副本在调用您的操作时被触及的好方法Wrapper,除非您明确告诉他们期望这种行为。

于 2009-06-10T00:43:01.267 回答