Arrays.asList(array)和new ArrayList<Integer>(Arrays.asList(array))的区别

137

这两者有什么区别:

  • List<Integer> list1 = new ArrayList<Integer>(Arrays.asList(ia)); // Copy

  • List<Integer> list2 = Arrays.asList(ia);

这里的 ia 是一个整数数组。

我了解到一些操作在 list2 中不被允许。为什么会这样?它在内存中是如何存储的(引用/复制)?

当我对这些列表进行洗牌时,list1 不会影响原始数组,但是 list2 会。但是对于 list2 我还是有点困惑。

ArrayList 向上转型成 List 和创建新的 ArrayList 之间有什么区别?

list1 differs from (1)
ArrayList<Integer> list1 = new ArrayList<Integer>(Arrays.asList(ia));

2
我建议你关注Google Guava的选项。Lists.newArrayList(ia) 可以创建一个独立的副本,就像第一个选项一样。它只是更通用和更好看。 - qben
13个回答

257
  1. 首先,让我们看看这个做了什么:

Arrays.asList(ia)
它接受一个数组ia并创建一个包装器,实现List<Integer>,使原始数组可用作列表。没有复制任何内容,只创建了一个包装器对象。对列表包装器的操作会传播到原始数组。这意味着如果您打乱列表包装器,原始数组也会被打乱,如果您覆盖一个元素,它将在原始数组中被覆盖,等等。当然,一些List操作不允许在包装器上执行,例如往列表中添加或删除元素,您只能读取或覆盖元素。

请注意,列表包装器不扩展ArrayList——它是一种不同类型的对象。ArrayList具有自己的内部数组,其中存储它们的元素,并能够调整内部数组的大小等。该包装器没有自己的内部数组,它只将操作传播到给定的数组。

  • 另一方面,如果随后创建一个新数组:

    new ArrayList<Integer>(Arrays.asList(ia))
    

    然后你创建一个新的ArrayList,它是原始ArrayList的完整独立副本。虽然在这里你也使用Arrays.asList创建了包装器,但它仅在构造新的ArrayList时使用,并在之后被垃圾回收掉。这个新ArrayList的结构与原始数组完全独立。它包含相同的元素(原始数组和这个新的ArrayList引用相同的整数内存),但它创建了一个新的内部数组来保存这些引用。因此当你洗牌、添加、删除元素等操作时,原始数组不会改变。


  • 9
    包装器是一种设计模式,它将一个类的接口转换为另一个接口。请参阅包装器模式文章。建议在哪些地方进行向上转型?我建议您使用 List<Integer> 作为您的变量类型(或方法参数等)。这可以使您的代码更加通用,您可以根据需要轻松切换到另一个 List 实现,而不必重写大量代码。 - Petr
    这是一个很好的解释。针对这个问题的延伸,Arrays.asList()方法也接受可变参数。如果我传递特定的值,比如说我执行:Arrays.asList(1,3,5) 两次,它会返回相同的List吗? - sbsatter

    31
    这是因为由Arrays.asList()创建的ArrayList不属于类型java.util.ArrayListArrays.asList()创建了一个ArrayList,类型为java.util.Arrays$ArrayList,它并没有继承java.util.ArrayList,而只继承了java.util.AbstractList

    1
    我认为这是许多问题的根源。 - Daniel Pop

    9
    List<Integer> list1 = new ArrayList<Integer>(Arrays.asList(ia));  //copy
    

    在这种情况下,list1 的类型是 ArrayList
    List<Integer> list2 = Arrays.asList(ia);
    

    在这里,列表作为List视图返回,这意味着它只有与该接口相关的方法。 因此,一些方法在list2上不被允许。
    ArrayList<Integer> list1 = new ArrayList<Integer>(Arrays.asList(ia));
    

    这里,你正在创建一个新的ArrayList。你只需在构造函数中传递一个值即可。这不是一个类型转换的示例。在类型转换中,它可能看起来更像这样:

    ArrayList list1 = (ArrayList)Arrays.asList(ia);
    

    5
    首先,Arrays类是一个实用程序类,其中包含许多操作数组的实用方法(感谢Arrays类。否则,我们需要创建自己的方法来操作数组对象)。

    asList() 方法:

    1. asList 方法是Array类的实用程序方法之一,它是一个静态方法,因此我们可以通过其类名调用此方法(例如Arrays.asList(T...a))。
    2. 现在有个问题,请注意,此方法不会创建新的ArrayList对象。它只返回对现有Array对象的List引用(因此,在使用asList方法后,将创建两个对现有Array对象的引用)。
    3. 这就是原因。所有操作List对象的方法可能无法使用List引用在该Array对象上工作。例如,Array的大小是固定的,因此您显然无法使用此List引用向Array对象添加或删除元素(如list.add(10)list.remove(10);。否则它会抛出UnsupportedOperationException)。
    4. 您使用列表引用进行的任何更改都将反映在现有Array对象中(因为您正在使用列表引用操作现有Array对象)。

    在第一种情况下,您正在创建一个新的ArrayList对象(在第二种情况下,仅创建对现有Array对象的引用,但不是新的ArrayList对象),因此现在有两个不同的对象。一个是Array对象,另一个是ArrayList对象,并且它们之间没有任何连接(因此,在一个对象中进行的更改不会反映/影响另一个对象(也就是说,在情况2中,Array和ArrayList是两个不同的对象)。

    情况1:

    Integer [] ia = {1,2,3,4};
    System.out.println("Array : "+Arrays.toString(ia));
    List<Integer> list1 = new ArrayList<Integer>(Arrays.asList(ia));  // new ArrayList object is created , no connection between existing Array Object
    list1.add(5);
    list1.add(6);
    list1.remove(0);
    list1.remove(0);
    System.out.println("list1: " + list1);
    System.out.println("Array: " + Arrays.toString(ia));
    

    Case 2:

    Integer [] ia = {1,2,3,4};
    System.out.println("Array: " + Arrays.toString(ia));
    List<Integer> list2 = Arrays.asList(ia); // Creates only a (new) List reference to the existing Array object (and NOT a new ArrayList Object)
    //  list2.add(5); // It will throw java.lang.UnsupportedOperationException - invalid operation (as Array size is fixed)
    list2.set(0,10);  // Making changes in the existing Array object using the List reference - valid 
    list2.set(1,11); 
    ia[2]=12;     // Making changes in the existing Array object using the Array reference - valid
    System.out.println("list2: " + list2);
    System.out.println("Array: " + Arrays.toString(ia));
    

    4
    String names[] = new String[]{"Avinash","Amol","John","Peter"};
    java.util.List<String> namesList = Arrays.asList(names);
    

    或者

    String names[] = new String[]{"Avinash","Amol","John","Peter"};
    java.util.List<String> temp = Arrays.asList(names);
    

    以上语句在输入数组上添加了包装器。因此,像addremove这样的方法将无法应用于列表引用对象“namesList”。

    如果您尝试向现有的数组/列表中添加元素,则会收到“Exception in thread "main" java.lang.UnsupportedOperationException”的错误。

    以上操作是只读或视图操作。
    我们不能在列表对象中执行添加或删除操作。

    但是,

    String names[] = new String[]{"Avinash","Amol","John","Peter"};
    java.util.ArrayList<String> list1 = new ArrayList<>(Arrays.asList(names));
    

    或者

    String names[] = new String[]{"Avinash","Amol","John","Peter"};
    java.util.List<String> listObject = Arrays.asList(names);
    java.util.ArrayList<String> list1 = new ArrayList<>(listObject);
    

    在上述语句中,您创建了一个ArrayList类的具体实例并将列表作为参数传递。
    在这种情况下,add和remove方法将正常工作,因为两个方法都来自ArrayList类,因此我们不会遇到任何UnSupportedOperationException异常。更改Arraylist对象中的内容(向数组列表中添加或删除元素)将不会反映到原始java.util.List对象中。
    String names[] = new String[] {
        "Avinash",
        "Amol",
        "John",
        "Peter"
    };
    
    java.util.List < String > listObject = Arrays.asList(names);
    java.util.ArrayList < String > list1 = new ArrayList < > (listObject);
    for (String string: list1) {
        System.out.print("   " + string);
    }
    list1.add("Alex"); // Added without any exception
    list1.remove("Avinash"); // Added without any exception will not make any changes in original list in this case temp object.
    
    
    for (String string: list1) {
        System.out.print("   " + string);
    }
    String existingNames[] = new String[] {
        "Avinash",
        "Amol",
        "John",
        "Peter"
    };
    java.util.List < String > namesList = Arrays.asList(names);
    namesList.add("Bob"); // UnsupportedOperationException occur
    namesList.remove("Avinash"); // UnsupportedOperationException
    

    4

    对于寻找答案的人来说,一份附有文档引用的解释会更好。

    1. java.util.Arrays

    • 这是一个实用类,具有许多静态方法用于操作给定的数组。
    • asList是其中之一,它接受输入数组并返回一个java.util.Arrays.ArrayList对象,它是一个静态嵌套类,扩展了AbstractList<E>,后者又实现了List接口。
    • 因此,Arrays.asList(inarray)返回一个围绕输入数组的List包装器,但这个包装器是java.util.Arrays.ArrayList而不是java.util.ArrayList,它引用同一个数组,因此向List包装的数组添加更多元素将影响原始数组,而且我们不能改变长度。

    2. java.util.ArrayList

    • ArrayList has a bunch of overloaded constructors

       public ArrayList() - // Returns arraylist with default capacity 10
      
       public ArrayList(Collection<? extends E> c)
      
       public ArrayList(int initialCapacity)
      
    • So when we pass the Arrays.asList returned object, i.e., List(AbstractList) to the second constructor above, it will create a new dynamic array (this array size increases as we add more elements than its capacity and also the new elements will not affect the original array) shallow copying the original array (shallow copy means it copies over the references only and does not create a new set of same objects as in original array)


    2
    请注意,在Java 8中,“ia”必须是Integer[]而不是int[]。对int数组使用Arrays.asList()会返回一个只有一个元素的列表。当使用OP的代码片段时,编译器将捕获此问题,但某些方法(例如Collections.shuffle())将悄无声息地未能按预期执行。

    1
    当我执行ArrayList<Integer> al = new ArrayList<Integer>(Arrays.asList(a));时,我遇到了编译器问题,其中a是int[]。我的al只有一个元素,而且打印出来的样子很糟糕。那个元素是什么?它是怎么出现的?我在Java 7中遇到了这个问题。 - Jyotsana Nandwani
    @JyotsanaNandwani 请查看我的答案:https://dev59.com/qGQn5IYBdhLWcg3wiXqd#54105519 - AllTooSir

    2
    许多人已经回答了机械细节,但值得注意的是: 这是Java的一个糟糕的设计选择。
    Java的asList方法被记录为“返回一个固定大小的列表...”。如果你取它的结果并调用(比如)add方法,它会抛出UnsupportedOperationException异常。这是不直观的行为!如果一个方法说它返回一个List,标准的期望是它返回一个支持List接口方法的对象。开发者不应该必须记住哪些util.List方法创建的List实际上不支持所有List方法。
    如果他们将这个方法命名为asImmutableList,那么这样做就有意义了。或者,如果他们只是让这个方法返回一个实际的List(并复制备份数组),那么这样做也有意义。他们决定在运行时性能和短名称之间取舍,以违反最小惊奇原则和避免UnsupportedOperationException等不良面向对象实践为代价。
    (此外,设计师们可能会创建一个interface ImmutableList,以避免大量的UnsupportedOperationException。)

    1
    package com.copy;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Iterator;
    import java.util.List;
    
    public class CopyArray {
    
        public static void main(String[] args) {
            List<Integer> list1, list2 = null;
            Integer[] intarr = { 3, 4, 2, 1 };
            list1 = new ArrayList<Integer>(Arrays.asList(intarr));
            list1.add(30);
            list2 = Arrays.asList(intarr);
            // list2.add(40); Here, we can't modify the existing list,because it's a wrapper
            System.out.println("List1");
            Iterator<Integer> itr1 = list1.iterator();
            while (itr1.hasNext()) {
                System.out.println(itr1.next());
            }
            System.out.println("List2");
            Iterator<Integer> itr2 = list2.iterator();
            while (itr2.hasNext()) {
                System.out.println(itr2.next());
            }
        }
    }
    

    1

    Arrays.asList()

    该方法返回自己的列表实现。它以一个数组作为参数,并在其上构建方法和属性,因为它没有从数组中复制任何数据,而是使用原始数组,当您修改Arrays.asList()方法返回的列表时,会导致对原始数组的更改。
    另一方面,ArrayList(Arrays.asList());ArrayList类的一个构造函数,它以列表作为参数,并返回与列表无关的ArrayList,即在这种情况下传递为参数的Arrays.asList()
    这就是你看到这些结果的原因。

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接