首页 文章

在Java中键入List vs type ArrayList

提问于
浏览
477
(1) List<?> myList = new ArrayList<?>();

(2) ArrayList<?> myList = new ArrayList<?>();

我理解,使用(1),可以交换 List 接口的实现 . 似乎(1)通常在应用程序中使用而不管需要(我自己总是使用它) .

我想知道是否有人使用(2)?

此外,有多少次(我可以得到一个例子)情况实际上需要使用(1)超过(2)(即,(2)不足以......旁边 coding to interfacesbest practices 等)

15 回答

  • 7

    List 接口有几个不同的类 - ArrayListLinkedList . LinkedList 用于创建索引集合, ArrayList 用于创建排序列表 . 所以,你可以在你的参数中使用它,但你可以允许其他使用你的代码,库等的开发人员使用不同类型的列表,而不仅仅是你使用的列表,因此,在这个方法中

    ArrayList<Object> myMethod (ArrayList<Object> input) {
       // body
    }
    

    你只能使用 ArrayList ,而不是 LinkedList ,但你可以允许在方法使用的其他地方使用任何 List 类,这只是你的选择,所以使用接口可以允许它:

    List<Object> myMethod (List<Object> input) {
       // body
    }
    

    在此方法参数中,您可以使用要使用的任何 List 类:

    List<Object> list = new ArrayList<Object> ();
    
    list.add ("string");
    
    myMethod (list);
    

    CONCLUSION:

    尽可能在任何地方使用接口,不要限制您或其他人使用他们想要使用的不同方法 .

  • 384

    例如,您可能认为 LinkedList 是您的应用程序的最佳选择,但后来决定 ArrayList 可能是出于性能原因的更好选择 .

    使用:

    List list = new ArrayList(100); // will be better also to set the initial capacity of a collection
    

    代替:

    ArrayList list = new ArrayList();
    

    For reference:

    enter image description here

    (主要用于收集图)

  • 9

    我会说1是首选,除非

    • 你依赖于ArrayList中可选行为*的实现,在这种情况下显式使用ArrayList更清楚

    • 您将在需要ArrayList的方法调用中使用ArrayList,可能用于可选行为或性能特征

    我的猜测是,在99%的情况下,你可以使用List,这是首选 .

    • 例如 removeAll ,或 add(null)
  • 100

    List is an interface.It doesn't have methods. When you call method on a List reference. It in fact calls the method of ArrayList in both cases.

    并且将来你可以将 List obj = new ArrayList<> 更改为 List obj = new LinkList<> 或其他实现 List interface. 的类型

  • 23

    当您编写 List 时,您实际上告诉您,您的对象仅实现了 List 接口,但您没有指定您的对象所属的类 .

    编写 ArrayList 时,指定对象类是可调整大小的数组 .

    因此,第一个版本将使您的代码在未来更加灵活 .

    看看Java文档:

    Class ArrayList - List 接口的可调整大小的数组实现 .

    Interface List - 有序集合(也称为序列) . 该接口的用户可以精确控制列表中每个元素的插入位置 .

    Array - 包含固定数量的单个类型值的容器对象 .

  • 35

    如果代码是列表的"owner",我使用(2) . 例如,对于仅本地变量,这是真的 . 没有理由使用抽象类型 List 而不是 ArrayList . 另一个证明所有权的例子:

    public class Test {
    
        // This object is the owner of strings, so use the concrete type.
        private final ArrayList<String> strings = new ArrayList<>();
    
        // This object uses the argument but doesn't own it, so use abstract type.
        public void addStrings(List<String> add) {
            strings.addAll(add);
        }
    
        // Here we return the list but we do not give ownership away, so use abstract type. This also allows to create optionally an unmodifiable list.
        public List<String> getStrings() {
            return Collections.unmodifiableList(strings);
        }
    
        // Here we create a new list and give ownership to the caller. Use concrete type.
        public ArrayList<String> getStringsCopy() {
            return new ArrayList<>(strings);
        }
    }
    
  • 1

    我想知道是否有人使用(2)?

    是 . 但很少有正当理由(IMO) .

    人们因为使用 ArrayList 而使用 List 而被烧毁:

    • Collections.singletonList(...)Arrays.asList(...) 等实用程序方法不返回 ArrayList .

    • List API中的方法不保证返回相同类型的列表 .

    例如某人被烧伤,在https://stackoverflow.com/a/1481123/139985中,海报出现问题"slicing"因为 ArrayList.sublist(...) 没有返回 ArrayList ...而且他设计了他的代码以使用 ArrayList 作为所有列表变量的类型 . 他通过将子列表复制到新的 ArrayList 来解决问题 .

    通过使用 RandomAccess 标记接口,您需要了解 List 行为的方式 . 是的,它有点笨重,但替代方案更糟糕 .

    此外,情况多久经常需要使用(1)超过(2)(即,(2)不足以“对接口进行编码”和最佳实践等)

    问题的“多久”部分是客观上无法回答的 .

    (我可以举一个例子)

    有时,应用程序可能要求您使用 ArrayList API中不在 List API中的方法 . 例如, ensureCapacity(int)trimToSize()removeRange(int, int) . (并且只有在您创建了一个声明方法为 public 的ArrayList子类型时才会出现最后一个 . )

    这是编写到类而不是接口IMO的唯一合理原因 .

    (从理论上讲,在某些情况下......在某些平台上......你的性能会略有改善......但除非你真的需要最后的0.05%,否则不值得这样做 . 这不是一个合理的理由,国际海事组织 . )


    如果您不知道随机访问是否有效,则无法编写有效的代码 .

    这是一个有效的观点 . 但是,Java提供了更好的方法来处理它;例如

    public <T extends List & RandomAccess> void test(T list) {
        // do stuff
    }
    

    如果使用未实现 RandomAccess 的列表调用它,则会出现编译错误 .

    您还可以动态测试...使用 instanceof ...如果静态输入太笨拙 . 您甚至可以编写代码以使用不同的算法(动态),具体取决于列表是否支持随机访问 .

    请注意, ArrayList 不是唯一实现 RandomAccess 的列表类 . 其他包括 CopyOnWriteListStackVector .

    我见过人们对 Serializable 做出同样的论证(因为 List 没有实现它)......但上面的方法也解决了这个问题 . (在某种程度上它可以使用运行时类型解决 . 如果任何元素不可序列化, ArrayList 将失败序列化 . )

  • 2

    我知道哪个(2)可能更好的唯一情况是使用GWT,因为它减少了应用程序占用空间(不是我的想法,但谷歌网络工具包团队这样说) . 但是对于在JVM(1)内部运行的常规Java可能总是更好 .

  • 1

    我认为使用(2)的人不知道Liskov substitution principleDependency inversion principle . 或者他们真的必须使用 ArrayList .

  • 8

    (3)Collection myCollection = new ArrayList();

    我通常使用这个 . 并且 only 如果我需要List方法,我将使用List . 与ArrayList相同 . 你总是可以切换到更多"narrow"界面,但你不能切换到更多"wide" .

  • 2

    HashSetTreeSet 的引用存储在Set类型的变量中是 considered good style .

    Set<String> names = new HashSet<String>();

    这样,如果您决定使用 TreeSet ,则只需更改一行 .

    此外,对集合进行操作的方法应指定Set类型的参数:

    public static void print(Set<String> s)

    然后 the method can be used for all set implementations .

    理论上,我们应该对链表做出相同的建议,即在List类型的变量中保存LinkedList引用 . 但是,在Java库中,List接口对于 ArrayListLinkedList 类都是通用的 . 特别是,它具有获取和设置随机访问的方法,即使这些方法对链表非常低效 .

    如果您不知道随机访问是否有效,请 can’t write efficient code .

    这显然是标准库中的一个严重的设计错误,因此我不建议使用List接口 .

    要查看错误是多么令人尴尬,请查看 Collections 类的 binarySearch 方法的源代码 . 该方法采用List参数,但二进制搜索对链表没有意义 . 然后代码笨拙地试图发现列表是否是链表,然后切换到线性搜索!

    Set 接口和 Map 接口设计得很好,您应该使用它们 .

  • 9

    有人再问这个问题(重复),这让我对这个问题有了更深入的了解 .

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("a");
        list.add("b");
    
        ArrayList<String> aList = new ArrayList<String>();
        aList.add("a");
        aList.add("b");
    
    }
    

    如果我们使用字节码查看器(我使用http://asm.ow2.org/eclipse/index.html),我们将看到 list 片段的以下内容(仅列表初始化和分配):

    L0
        LINENUMBER 9 L0
        NEW ArrayList
        DUP
        INVOKESPECIAL ArrayList.<init> () : void
        ASTORE 1
       L1
        LINENUMBER 10 L1
        ALOAD 1: list
        LDC "a"
        INVOKEINTERFACE List.add (Object) : boolean
        POP
       L2
        LINENUMBER 11 L2
        ALOAD 1: list
        LDC "b"
        INVOKEINTERFACE List.add (Object) : boolean
        POP
    

    并为 alist

    L3
        LINENUMBER 13 L3
        NEW java/util/ArrayList
        DUP
        INVOKESPECIAL java/util/ArrayList.<init> ()V
        ASTORE 2
       L4
        LINENUMBER 14 L4
        ALOAD 2
        LDC "a"
        INVOKEVIRTUAL java/util/ArrayList.add (Ljava/lang/Object;)Z
        POP
       L5
        LINENUMBER 15 L5
        ALOAD 2
        LDC "b"
        INVOKEVIRTUAL java/util/ArrayList.add (Ljava/lang/Object;)Z
        POP
    

    差异是 list 最终调用 INVOKEINTERFACEaList 调用 INVOKEVIRTUAL . 根据Bycode Outline插件参考,

    invokeinterface用于调用Java接口中声明的方法

    而invokevirtual

    调用除接口方法(使用invokeinterface),静态方法(使用invokestatic)以及invokespecial处理的少数特殊情况之外的所有方法 .

    总之,对于invokeinterface,invokevirtual从堆栈中弹出 objectref

    解释器弹出操作数堆栈中的'n'项,其中'n'是取自字节码的8位无符号整数参数 . 这些项中的第一项是objectref,它是对正在调用其方法的对象的引用 .

    如果我理解正确,差异基本上是每种方式检索 objectref .

  • 11

    在以下两个中:

    (1) List<?> myList = new ArrayList<?>();
    (2) ArrayList<?> myList = new ArrayList<?>();
    

    首先通常是优选的 . 由于您将仅使用 List 接口中的方法,因此它为您提供了使用 List 的其他一些实现的自由,例如 LinkedList 将来 . 因此它将您与特定实现分离 . 现在有两点值得一提:

    • 我们应该始终编程接口 . 更多here .

    • 你几乎总是最终使用 ArrayList 而不是 LinkedList . 更多here .

    我想知道是否有人使用(2)

    有时是(很少读) . 当我们需要的方法是 ArrayList 的实现的一部分但不是接口 List 的一部分 . 例如 ensureCapacity .

    此外,多久(我可以得到一个例子)情况实际上需要使用(1)over(2)

    几乎总是你更喜欢选项(1) . 这是OOP中的经典设计模式,您总是尝试将代码从特定的实现和程序与接口分离 .

  • 3

    实际上有些情况下(2)不仅是首选,而且是强制性的,我很惊讶,没有人在这里提到这一点 .

    序列化!

    如果你有序列化类,你希望它包含一个列表,然后你必须声明该字段是一个具体和可序列化的类型,如 ArrayList ,因为 List 接口不扩展 java.io.Serializable

    显然大多数人不需要序列化而忘记了这一点 .

    一个例子:

    public class ExampleData implements java.io.Serializable {
    
    // The following also guarantees that strings is always an ArrayList.
    private final ArrayList<String> strings = new ArrayList<>();
    
  • 9

    几乎总是第一个优先于第二个 . 第一个优点是 List 的实现可以改变(例如LinkedList),而不会影响其余的代码 . 这对于 ArrayList 来说将是一项艰巨的任务,不仅因为您需要在任何地方将 ArrayList 更改为 LinkedList ,还因为您可能使用了 ArrayList 特定方法 .

    你可以阅读 List implementation here . 您可以从 ArrayList 开始,但不久之后发现另一个实现更合适 .

相关问题