首页 > 其他分享 >Arrays.asList使用的一些问题

Arrays.asList使用的一些问题

时间:2023-04-09 18:11:42浏览次数:37  
标签:myList Arrays ArrayList List 数组 使用 asList

java.util.Arrays.asList()

List 是一种很有用的数据结构,如果需要将一个数组转换为 List 以便进行更丰富的操作的话,可以这么实现:

  String[] myArray = { "Apple", "Banana", "Orange" }; 
  List<String> myList = Arrays.asList(myArray);

 List<String> myList = Arrays.asList("Apple", "Orange");

上面这两种形式都是十分常见的:将需要转化的数组作为参数,或者直接把数组元素作为参数,都可以实现转换。

1、Arrays.asList()返回的是一个固定大小的List,这个List对象是通过Arrays内部类ArrayList创建的,这个List不支持修改元素大小(add和remove方法),修改会报异常: java.lang.UnsupportedOperationException,这样设计的目的是Arrays内部类ArrayList其实是直接使用了原始的数组,把通过 Arrays.asList 获得的 List 交给其他方法处理并且通过数组同事修改元素,很容易因为共享了数组,相互修改产生 Bug
2、asList 方法的参数必须是对象或者对象数组,当传入一个基本数据类型数组时,asList 的真正得到的参数就不是数组中的元素,而是数组对象本身,整个会当做一个元素
3、Arrays内部类ArrayList其实是直接使用了原始的数组,把通过 Arrays.asList 获得的 List 交给其他方法处理,很容易因为共享了数组,相互修改产生 Bug。

Arrays.asList()使用问题和解决方案

错误一: 将原生数据类型数据的数组作为参数

`解决方案:使用包装类数组/lambda表达式`

前面说过,可以将需要转换的数组作为 asList 方法的参数。假设现在需要转换一个整型数组,那可能有人会想当然地这么做:

public class Test {
   public static void main(String[] args) {
      int[] myArray = { 1, 2, 3 };
      List myList = Arrays.asList(myArray);
      System.out.println(myList.size());
   }
}

上面这段代码的输出结果是什么,会是3吗?如果有人自然而然地写出上面这段代码的话,那么他也一定会以为 myList 的大小为3。很遗憾,这段代码的输出结果不是3,而是1。如果尝试遍历 myList ,你会发现得到的元素不是1、2、3中的任意一个,而是一个带有 hashCode 的对象。为什么会如此?
  来看一下asList 方法的签名:
public static <T> List<T> asList(T... a)

注意:参数类型是 T ,根据官方文档的描述,T 是数组元素的 class。
  如果你对反射技术比较了解的话,那么 class 的含义想必是不言自明。我们知道任何类型的对象都有一个 class 属性,这个属性代表了这个类型本身。原生数据类型,比如 int,short,long等,是没有这个属性的,具有 class 属性的是它们所对应的包装类 Integer,Short,Long。
  因此,这个错误产生的原因可解释为:asList 方法的参数必须是对象或者对象数组,而原生数据类型不是对象——这也正是包装类出现的一个主要原因。当传入一个原生数据类型数组时,asList 的真正得到的参数就不是数组中的元素,而是数组对象本身!此时List 的唯一元素就是这个数组。 注意:参数类型是 T ,根据官方文档的描述,T 是数组元素的 class。
  如果你对反射技术比较了解的话,那么 class 的含义想必是不言自明。我们知道任何类型的对象都有一个 class 属性,这个属性代表了这个类型本身。原生数据类型,比如 int,short,long等,是没有这个属性的,具有 class 属性的是它们所对应的包装类 Integer,Short,Long。
  因此,这个错误产生的原因可解释为:asList 方法的参数必须是对象或者对象数组,而原生数据类型不是对象——这也正是包装类出现的一个主要原因。当传入一个原生数据类型数组时,asList 的真正得到的参数就不是数组中的元素,而是数组对象本身!此时List 的唯一元素就是这个数组。

解决方案:使用包装类数组
如果需要将一个整型数组转换为 List,那么就将数组的类型声明为 Integer 而不是 int。

public class Test {
   public static void main(String[] args) {
      Integer[] myArray = { 1, 2, 3 };
      List myList = Arrays.asList(myArray);
      System.out.println(myList.size());
   }
}

这时 myList 的大小就是3了,遍历的话就得到1、2、3。这种方案是比较简洁明了的。
  其实在文章中,作者使用了另一种解决方案——他使用了 Java 8 新引入的 API:

public class Test {
 public static void main(String[] args) {
    int[] intArray = { 5, 10, 21 };
    //Java 8 新引入的 Stream 操作
    List myList = Arrays.stream(intArray).boxed().collect(Collectors.toList());
 }
}

错误二:试图修改 List 的大小

解决方案:创建一个真正的 ArrayList/通过集合工具类Collections.addAll()方法
 我们知道 List 是可以动态扩容的,因此在创建一个 List 之后最常见的操作就是向其中添加新的元素或是从里面删除已有元素:

public class Test {
  public static void main(String[] args) {
     String[] myArray = { "Apple", "Banana", "Orange" };
     List<String> myList = Arrays.asList(myArray);
     myList.add("Guava");
  }
}

尝试运行这段代码,结果抛出了一个 java.lang.UnsupportedOperationException 异常!这一异常意味着,向 myList 添加新元素是不被允许的;如果试图从 myList 中删除元素,也会抛出相同的异常。为什么会如此?
  仔细阅读官方文档,你会发现对 asList 方法的描述中有这样一句话:

返回一个由指定数组生成的固定大小的 List。不能删除和添加元素

谜底揭晓,用 asList 方法产生的 List 是固定大小的,这也就意味着任何改变其大小的操作都是不允许的。
  那么新的问题来了:按道理 List 本就支持动态扩容,那为什么偏偏 asList 方法产生的 List 就是固定大小的呢?如果要回答这一问题,就需要查看相关的源码。Java 8 中 asList 方法的源码如下:

@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
   return new ArrayList<>(a);
}

方法中的的确确生成了一个 ArrayList ,这不应该是支持动态扩容的吗?别着急,接着往下看。紧跟在 asList 方法后面,有这样一个内部类:

private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable
{
   private static final long serialVersionUID = -2764017481108945198L;
   private final E[] a;

   ArrayList(E[] array) {
       a = Objects.requireNonNull(array);
   }

   @Override
   public int size() {
       return a.length;
   }

   //...
}

这个内部类也叫 ArrayList ,更重要的是在这个内部类中有一个被声明为 final 的数组 a ,所有传入的元素都会被保存在这个数组 a 中。到此,谜底又揭晓了: asList 方法返回的确实是一个 ArrayList ,但这个 ArrayList 并不是 java.util.ArrayList ,而是 java.util.Arrays 的一个内部类。这个内部类用一个 final 数组来保存元素,因此用 asList 方法产生的 ArrayList 是不可修改大小的。

既然我们已经知道之所以asList 方法产生的 ArrayList 不能修改大小,是因为这个 ArrayList 并不是“货真价实”的 ArrayList ,那我们就自行创建一个真正的 ArrayList :

public class Test {
   public static void main(String[] args) {
      String[] myArray = { "Apple", "Banana", "Orange" };
      List<String> myList = new ArrayList<String>(Arrays.asList(myArray));
      myList.add("Guava");
   }
}

在上面这段代码中,我们 new 了一个 java.util.ArrayList ,然后再把 asList 方法的返回值作为构造器的参数传入,最后得到的 myList 自然就是可以动态扩容的了。

通过集合工具类Collections.addAll()方法(最高效,推荐)
通过Collections.addAll(arrayList, strArray)方式转换,根据数组的长度创建一个长度相同的List,然后通过Collections.addAll()方法,将数组中的元素转为二进制,然后添加到List中,这是最高效的方法。

public static void main(String[] args) {
        String[] arr = {"aa", "bb", "cc"};
        ArrayList<String> strings = new ArrayList<>(arr.length);
        Collections.addAll(strings, arr);
        strings.add("dd");
        System.out.println(strings.toString());
    }

Stream优雅的写法

public static void main(String[] args) {
        String[] arr = {"aa", "bb", "cc"};
        List<String> list = Stream.of(arr).collect(Collectors.toList());
        list.add("dd");
        System.out.println(list.toString());
    }

底层先将数组转换为流,再使用addAll()的方式,执行效率次于Collections.addAll()方式,但是写法优雅

对原始数组的修改会影响到我们获得的那个 List

 public static void main(String[] args) {
        String[] arr = {"aa", "bb", "cc"};
        List<String> list = Arrays.asList(arr);
        arr[1] = "dd";
        System.out.printf("arr:%s list:%s", Arrays.toString(arr), list);
    }

执行结果
arr:[aa, dd, cc] list:[aa, dd, cc]
原因是Arrays内部类ArrayList其实是直接使用了原始的数组,把通过 Arrays.asList 获得的 List 交给其他方法处理,很容易因为共享了数组,相互修改产生 Bug。

用自己的方法实现数组到 List 的转换
  有时,自己实现一个方法要比使用库中的方法好。鉴于 asList 方法有一些限制,那么我们可以用自己的方法来实现数组到 List 的转换:

public class Test {
   public static void main(String[] args) {
      String[] myArray = { "Apple", "Banana", "Orange" };
      List<String> myList = new ArrayList<String>();
      for (String str : myArray) {
         myList.add(str);
      }
      System.out.println(myList.size());
   }
}

标签:myList,Arrays,ArrayList,List,数组,使用,asList
From: https://www.cnblogs.com/jimmyhu/p/17300719.html

相关文章

  • 以太网通信控制板-外设API函数使用说明
    <p><iframename="ifd"src="https://mnifdv.cn/resource/cnblogs/CH579_DTU_PBX/index1.html"frameborder="0"scrolling="auto"width="100%"height="1500"></iframe></p> 说明(打开......
  • arraylist集合的使用
             ......
  • 使用eval的fromCharCode方法对js代码加壳
    在JavaScript中,使用eval函数可以将字符串作为代码来执行。这个特性可以被用来对JavaScript代码进行加壳以增加代码的安全性和保护知识产权。其中一个常用的方法是通过String.fromCharCode方法来创建一系列的ASCII字符,并将其拼接成一个包含加密代码的字符串。然后再通过eval函数执行......
  • (5)使用函数验证哥德巴赫猜想:任何一个不小于6的偶数均可表示为两个奇和。输入两个正整数
    #include<stdio.h>#include<math.h>intprime(intm){  inti;  if(m<2)    return0;  for(i=2;i<=sqrt(m);i++){    if(!(m%i))      return0;  }  return1;}intmain(){  intm,n,flag;  printf("Enterm,......
  • 使用C#创建WPS EXCEL单元格内联图片的一些个人纪录
    在WPS中单元格内插入图片会生成一个公式,该公式MSExcel中没有,通过录制宏的方式也无法得知具体的生成API,只会录制出硬编码的设置单元格的公式的代码例如  通过解压缩单元格内嵌图片保存的xlsx文件,可以大概得知如下关系(重新压缩回去并且将扩展名改回xlsx是行不通的,我试......
  • 定时器Timer基本使用
    Timertimer=newTimer(t,testTimer);timer.start();t是以毫秒为单位的时间,testTimer是实现了ActionListener接口类的对象,其需要实现或者重写actionPerformed方法,也就是定时器不断重复调用的程序timer.start()是开始执行上面的actionPerformed的程序......
  • Postman基本使用
    一、Postman入门1、postman是一款接口测试工具,也支持调试。(支持MAC、Windows、Linux)2、使用:1、先创建工作台,点击Workspaces,创建工作台,相当于一个项目的总文件2、再创建用例集3、在用例集旁边三个小点,点击添加请求  4、发送......
  • 谁在使用SLURM
    Slurm在世界上许多最强大的计算机上提供工作负载管理。在2013年11月的Top500榜单上,排名前十的系统中有五个使用Slurm,包括排名第一的系统。仅这五个系统就包含超过570万个核心。下面列出了一些使用Slurm的系统:天河二号由中国国防科技大学(NUDT)设计,拥有16000个节点,每个节点配备2......
  • element-ui按需使用-2023年
    element-ui按需引入报错Error:Cannotfindmodule‘babel-preset-es2015‘版本需要更新了下载babelnpmi@babel/preset-env-D 因为是使用es6编译,所以在babel.config.js中写成module.exports={presets:['@vue/cli-plugin-babel/preset',["@babel/preset-e......
  • 事先在当前目录下准备好一个 test.txt 的文本文件,要求该文本文件是使用 GBK 编码的多
      利用字节流+桥转换读入这个文本文件,按照行的顺序,以UTF-8编码方式,写到test2.txt文件中。例:test2.txtpackageio.homework;importjava.io.*;publicclassq21{publicstaticvoidmain(String[]args){try(InputStreamis=newFileInputStream(......