Java集合使用注意事项总结

《阿里巴巴 Java 开发手册》的描述如下: 使用工具类 Arrays.asList() 把数组转换成集合时,不能使用其修改集合相关的方法, 它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。 我在之前的一个项目中就遇到一个类似的坑。 Arrays.asList()在平时开发中还是比较常见的,我们可以使用它将一个数组转换为一个 List 集合。 String[] myArray = {"Apple", "Banana", "Orange"}; List myList = Arrays.asList(myArray); //上面两个语句等价于下面一条语句 List myList = Arrays.asList("Apple","Banana", "Orange"); JDK 源码对于这个方法的说明: /** *返回由指定数组支持的固定大小的列表。此方法作为基于数组和基于集合的API之间的桥梁, * 与 Collection.toArray()结合使用。返回的List是可序列化并实现RandomAccess接口。 */ public static List asList(T... a) { return new ArrayList<>(a); } 下面我们来总结一下使用注意事项。 1、Arrays.asList()是泛型方法,传递的数组必须是对象数组,而不是基本类型。 int[] myArray = {1, 2, 3}; List myList = Arrays.asList(myArray); System.out.println(myList.size());//1 System.out.println(myList.get(0));//数组地址值 System.out.println(myList.get(1));//报错:ArrayIndexOutOfBoundsException int[] array = (int[]) myList.get(0); System.out.println(array[0]);//1 当传入一个原生数据类型数组时,Arrays.asList() 的真正得到的参数就不是数组中的元素,而是数组对象本身!此时 List 的唯一元素就是这个数组,这也就解释了上面的代码。 我们使用包装类型数组就可以解决这个问题。 Integer[] myArray = {1, 2, 3}; 2、使用集合的修改方法: add()、remove()、clear()会抛出异常。 List myList = Arrays.asList(1, 2, 3); myList.add(4);//运行时报错:UnsupportedOperationException myList.remove(1);//运行时报错:UnsupportedOperationException myList.clear();//运行时报错:UnsupportedOperationException Arrays.asList() 方法返回的并不是 java.util.ArrayList ,而是 java.util.Arrays 的一个内部类,这个内部类并没有实现集合的修改方法或者说并没有重写这些方法。 List myList = Arrays.asList(1, 2, 3); System.out.println(myList.getClass());//class java.util.Arrays$ArrayList 下图是 java.util.Arrays$ArrayList 的简易源码,我们可以看到这个类重写的方法有哪些。 private static class ArrayList extends AbstractList implements RandomAccess, java.io.Serializable { ... @Override public E get(int index) { ... } @Override public E set(int index, E element) { ... } @Override public int indexOf(Object o) { ... } @Override public boolean contains(Object o) { ... } @Override public void forEach(Consumer action) { ... } @Override public void replaceAll(UnaryOperator operator) { ... } @Override public void sort(Comparator c) { ... } } 我们再看一下java.util.AbstractList的 add/remove/clear 方法就知道为什么会抛出 UnsupportedOperationException 了。 public E remove(int index) { throw new UnsupportedOperationException(); } public boolean add(E e) { add(size(), e); return true; } public void add(int index, E element) { throw new UnsupportedOperationException(); } public void clear() { removeRange(0, size()); } protected void removeRange(int fromIndex, int toIndex) { ListIterator it = listIterator(fromIndex); for (int i=0, n=toIndex-fromIndex; i List arrayToList(final T[] array) { final List l = new ArrayList(array.length); for (final T s : array) { l.add(s); } return l; } Integer [] myArray = { 1, 2, 3 }; System.out.println(arrayToList(myArray).getClass());//class java.util.ArrayList 2、最简便的方法 List list = new ArrayList<>(Arrays.asList("a", "b", "c")) 3、使用 Java8 的 Stream(推荐) Integer [] myArray = { 1, 2, 3 }; List myList = Arrays.stream(myArray).collect(Collectors.toList()); //基本类型也可以实现转换(依赖boxed的装箱操作) int [] myArray2 = { 1, 2, 3 }; List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList()); 4、使用 Guava 对于不可变集合,你可以使用ImmutableList类及其of()与copyOf()工厂方法:(参数不能

这篇文章我根据《阿里巴巴 Java 开发手册》总结了关于集合使用常见的注意事项以及其具体原理。

强烈建议小伙伴们多多阅读几遍,避免自己写代码的时候出现这些低级的问题。

集合判空

《阿里巴巴 Java 开发手册》的描述如下:

判断所有集合内部的元素是否为空,使用 isEmpty() 方法,而不是 size()==0 的方式。

这是因为 isEmpty() 方法的可读性更好,并且时间复杂度为 O(1)。

绝大部分我们使用的集合的 size() 方法的时间复杂度也是 O(1),不过,也有很多复杂度不是 O(1) 的,比如 java.util.concurrent 包下的某些集合(ConcurrentLinkedQueueConcurrentHashMap...)。

下面是 ConcurrentHashMapsize() 方法和 isEmpty() 方法的源码。

public int size() {long n = sumCount();return ((n < 0L) ? 0 :(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :(int)n);
}
final long sumCount() {CounterCell[] as = counterCells; CounterCell a;long sum = baseCount;if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)sum += a.value;}}return sum;
}
public boolean isEmpty() {return sumCount() <= 0L; // ignore transient negative values
}

集合转 Map

《阿里巴巴 Java 开发手册》的描述如下:

在使用 java.util.stream.Collectors 类的 toMap() 方法转为 Map 集合时,一定要注意当 value 为 null 时会抛 NPE 异常。

class Person {private String name;private String phoneNumber;// getters and setters
}List<Person> bookList = new ArrayList<>();
bookList.add(new Person("jack","18163138123"));
bookList.add(new Person("martin",null));
// 空指针异常
bookList.stream().collect(Collectors.toMap(Person::getName, Person::getPhoneNumber));

下面我们来解释一下原因。

首先,我们来看 java.util.stream.Collectors 类的 toMap() 方法 ,可以看到其内部调用了 Map 接口的 merge() 方法。

public static <T, K, U, M extends Map<K, U>>
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper,BinaryOperator<U> mergeFunction,Supplier<M> mapSupplier) {BiConsumer<M, T> accumulator= (map, element) -> map.merge(keyMapper.apply(element),valueMapper.apply(element), mergeFunction);return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
}

Map 接口的 merge() 方法如下,这个方法是接口中的默认实现。

如果你还不了解 Java 8 新特性的话,请看这篇文章:《Java8 新特性总结》 。

default V merge(K key, V value,BiFunction<? super V, ? super V, ? extends V> remappingFunction) {Objects.requireNonNull(remappingFunction);Objects.requireNonNull(value);V oldValue = get(key);V newValue = (oldValue == null) ? value :remappingFunction.apply(oldValue, value);if(newValue == null) {remove(key);} else {put(key, newValue);}return newValue;
}

merge() 方法会先调用 Objects.requireNonNull() 方法判断 value 是否为空。

public static <T> T requireNonNull(T obj) {if (obj == null)throw new NullPointerException();return obj;
}

集合遍历

《阿里巴巴 Java 开发手册》的描述如下:

不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。

通过反编译你会发现 foreach 语法底层其实还是依赖 Iterator 。不过, remove/add 操作直接调用的是集合自己的方法,而不是 Iteratorremove/add方法

这就导致 Iterator 莫名其妙地发现自己有元素被 remove/add ,然后,它就会抛出一个 ConcurrentModificationException 来提示用户发生了并发修改异常。这就是单线程状态下产生的 fail-fast 机制

fail-fast 机制:多个线程对 fail-fast 集合进行修改的时候,可能会抛出ConcurrentModificationException。 即使是单线程下也有可能会出现这种情况,上面已经提到过。

相关阅读:什么是 fail-fast 。

Java8 开始,可以使用 Collection#removeIf()方法删除满足特定条件的元素,如

List<Integer> list = new ArrayList<>();
for (int i = 1; i <= 10; ++i) {list.add(i);
}
list.removeIf(filter -> filter % 2 == 0); /* 删除list中的所有偶数 */
System.out.println(list); /* [1, 3, 5, 7, 9] */

除了上面介绍的直接使用 Iterator 进行遍历操作之外,你还可以:

  • 使用普通的 for 循环
  • 使用 fail-safe 的集合类。java.util包下面的所有的集合类都是 fail-fast 的,而java.util.concurrent包下面的所有的类都是 fail-safe 的。
  • ……

集合去重

《阿里巴巴 Java 开发手册》的描述如下:

可以利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 Listcontains() 进行遍历去重或者判断包含操作。

这里我们以 HashSetArrayList 为例说明。

// Set 去重代码示例
public static <T> Set<T> removeDuplicateBySet(List<T> data) {if (CollectionUtils.isEmpty(data)) {return new HashSet<>();}return new HashSet<>(data);
}// List 去重代码示例
public static <T> List<T> removeDuplicateByList(List<T> data) {if (CollectionUtils.isEmpty(data)) {return new ArrayList<>();}List<T> result = new ArrayList<>(data.size());for (T current : data) {if (!result.contains(current)) {result.add(current);}}return result;
}

两者的核心差别在于 contains() 方法的实现。

HashSetcontains() 方法底部依赖的 HashMapcontainsKey() 方法,时间复杂度接近于 O(1)(没有出现哈希冲突的时候为 O(1))。

private transient HashMap<E,Object> map;
public boolean contains(Object o) {return map.containsKey(o);
}

我们有 N 个元素插入进 Set 中,那时间复杂度就接近是 O (n)。

ArrayListcontains() 方法是通过遍历所有元素的方法来做的,时间复杂度接近是 O(n)。

public boolean contains(Object o) {return indexOf(o) >= 0;
}
public int indexOf(Object o) {if (o == null) {for (int i = 0; i < size; i++)if (elementData[i]==null)return i;} else {for (int i = 0; i < size; i++)if (o.equals(elementData[i]))return i;}return -1;
}

集合转数组

《阿里巴巴 Java 开发手册》的描述如下:

使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组。

toArray(T[] array) 方法的参数是一个泛型数组,如果 toArray 方法中没有传递任何参数的话返回的是 Object类 型数组。

String [] s= new String[]{"dog", "lazy", "a", "over", "jumps", "fox", "brown", "quick", "A"
};
List<String> list = Arrays.asList(s);
Collections.reverse(list);
//没有指定类型的话会报错
s=list.toArray(new String[0]);

由于 JVM 优化,new String[0]作为Collection.toArray()方法的参数现在使用更好,new String[0]就是起一个模板的作用,指定了返回数组的类型,0 是为了节省空间,因为它只是为了说明返回的类型。详见:Arrays of Wisdom of the Ancients

数组转集合

《阿里巴巴 Java 开发手册》的描述如下:

使用工具类 Arrays.asList() 把数组转换成集合时,不能使用其修改集合相关的方法, 它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。

我在之前的一个项目中就遇到一个类似的坑。

Arrays.asList()在平时开发中还是比较常见的,我们可以使用它将一个数组转换为一个 List 集合。

String[] myArray = {"Apple", "Banana", "Orange"};
List<String> myList = Arrays.asList(myArray);
//上面两个语句等价于下面一条语句
List<String> myList = Arrays.asList("Apple","Banana", "Orange");

JDK 源码对于这个方法的说明:

/***返回由指定数组支持的固定大小的列表。此方法作为基于数组和基于集合的API之间的桥梁,* 与 Collection.toArray()结合使用。返回的List是可序列化并实现RandomAccess接口。*/
public static <T> List<T> asList(T... a) {return new ArrayList<>(a);
}

下面我们来总结一下使用注意事项。

1、Arrays.asList()是泛型方法,传递的数组必须是对象数组,而不是基本类型。

int[] myArray = {1, 2, 3};
List myList = Arrays.asList(myArray);
System.out.println(myList.size());//1
System.out.println(myList.get(0));//数组地址值
System.out.println(myList.get(1));//报错:ArrayIndexOutOfBoundsException
int[] array = (int[]) myList.get(0);
System.out.println(array[0]);//1

当传入一个原生数据类型数组时,Arrays.asList() 的真正得到的参数就不是数组中的元素,而是数组对象本身!此时 List 的唯一元素就是这个数组,这也就解释了上面的代码。

我们使用包装类型数组就可以解决这个问题。

Integer[] myArray = {1, 2, 3};

2、使用集合的修改方法: add()remove()clear()会抛出异常。

List myList = Arrays.asList(1, 2, 3);
myList.add(4);//运行时报错:UnsupportedOperationException
myList.remove(1);//运行时报错:UnsupportedOperationException
myList.clear();//运行时报错:UnsupportedOperationException

Arrays.asList() 方法返回的并不是 java.util.ArrayList ,而是 java.util.Arrays 的一个内部类,这个内部类并没有实现集合的修改方法或者说并没有重写这些方法。

List myList = Arrays.asList(1, 2, 3);
System.out.println(myList.getClass());//class java.util.Arrays$ArrayList

下图是 java.util.Arrays$ArrayList 的简易源码,我们可以看到这个类重写的方法有哪些。

private static class ArrayList<E> extends AbstractList<E>implements RandomAccess, java.io.Serializable{...@Overridepublic E get(int index) {...}@Overridepublic E set(int index, E element) {...}@Overridepublic int indexOf(Object o) {...}@Overridepublic boolean contains(Object o) {...}@Overridepublic void forEach(Consumer<? super E> action) {...}@Overridepublic void replaceAll(UnaryOperator<E> operator) {...}@Overridepublic void sort(Comparator<? super E> c) {...}}

我们再看一下java.util.AbstractListadd/remove/clear 方法就知道为什么会抛出 UnsupportedOperationException 了。

public E remove(int index) {throw new UnsupportedOperationException();
}
public boolean add(E e) {add(size(), e);return true;
}
public void add(int index, E element) {throw new UnsupportedOperationException();
}public void clear() {removeRange(0, size());
}
protected void removeRange(int fromIndex, int toIndex) {ListIterator<E> it = listIterator(fromIndex);for (int i=0, n=toIndex-fromIndex; i<n; i++) {it.next();it.remove();}
}

那我们如何正确的将数组转换为 ArrayList ?

1、手动实现工具类

//JDK1.5+
static <T> List<T> arrayToList(final T[] array) {final List<T> l = new ArrayList<T>(array.length);for (final T s : array) {l.add(s);}return l;
}Integer [] myArray = { 1, 2, 3 };
System.out.println(arrayToList(myArray).getClass());//class java.util.ArrayList

2、最简便的方法

List list = new ArrayList<>(Arrays.asList("a", "b", "c"))

3、使用 Java8 的 Stream(推荐)

Integer [] myArray = { 1, 2, 3 };
List myList = Arrays.stream(myArray).collect(Collectors.toList());
//基本类型也可以实现转换(依赖boxed的装箱操作)
int [] myArray2 = { 1, 2, 3 };
List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());

4、使用 Guava

对于不可变集合,你可以使用ImmutableList类及其of()与copyOf()工厂方法:(参数不能为空)

List<String> il = ImmutableList.of("string", "elements");  // from varargs
List<String> il = ImmutableList.copyOf(aStringArray);      // from array

对于可变集合,你可以使用Lists类及其newArrayList()工厂方法:

List<String> l1 = Lists.newArrayList(anotherListOrCollection);    // from collection
List<String> l2 = Lists.newArrayList(aStringArray);               // from array
List<String> l3 = Lists.newArrayList("or", "string", "elements"); // from varargs

5、使用 Apache Commons Collections

List<String> list = new ArrayList<String>();
CollectionUtils.addAll(list, str);

6、 使用 Java9 的 List.of()方法

Integer[] array = {1, 2, 3};
List<Integer> list = List.of(array);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/460782.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

对话瀚荃:为何欧美拟统一采用USB-C充电接口?

【哔哥哔特导读】英国、美国等地都准备统一电气设备充电标准&#xff0c;USB-C接口为何成为业界首选?选择USB-C连接器&#xff0c;又有什么注意事项? 近期&#xff0c;有消息指出英国、美国等地均启动了关于电气设备充电标准的咨询活动&#xff0c;希望听取制造商、进口商、…

如何解决RabbitMQ消息的重复消费问题

什么情况下会导致消息的重复消费——在消费者还没成功发送自动确认机制时发生&#xff1a; 网络抖动消费者挂了 解决方案 每条消息设置一个唯一的标识id幂等方案&#xff1a;【Redis分布式锁、数据库锁&#xff08;悲观锁、乐观锁&#xff09;】 面试官&#xff1a;如何解决…

vue中el-table显示文本过长提示

1.el-table设置轻提示:show-overflow-tooltip“true“&#xff0c;改变轻提示宽度

Couldn‘t apply path mapping to the remote file.

Couldn’t apply path mapping to the remote file. /s6home2/zjw524/projects/seq2seq/code/deepnmtpycharm/deepNmt/code/deepNmtPycharm/deepNmt/model/Deep_NMT_Model.py can’t be found in project. You can continue debugging, but without the source. To fix that yo…

Tech Talk: 浅谈AI浪潮下的计算型存储SSD

引言 近年来&#xff0c;AI应用态势迅猛增加&#xff0c;对计算侧的算力和内存提出了更高的要求。GPU、HBM这些高性能高密计算部件和内存部件&#xff0c;在AI计算场景中作为必需品&#xff0c;成为市场热点。业界也在讨论能否把计算侧的业务卸载到存储侧&#xff0c;称为计算…

华为配置 之 STP

目录 简介&#xff1a; STP&#xff1a; RSTP: 如何改变根网桥&#xff1a; &#xff08;1&#xff09;改变优先级&#xff1a; &#xff08;2&#xff09;改变root: 各端口的状态&#xff1a; 总结&#xff1a; 简介&#xff1a; STP&#xff08;Spanning Tree Protoco…

大数据挖掘和数据挖掘有什么不一样?

一、数据挖掘&#xff1a; 数据挖掘&#xff08;Data Mining&#xff09;是指从大量的、不完全的、有噪声的、模糊的、随机的数据中&#xff0c;提取隐含在其中的、人们事先不知道的、但又是潜在有用的信息和知识的过程。 数据挖掘的概念起源于 20 世纪 80 年代后期&#xff0c…

活动|2024 CodeFuse 「编码挑战季」活动已开启!欢迎报名参加

Hi~开发者们&#xff0c;1024 程序员节快乐&#xff0c;向你们致敬&#xff01; CodeFuse 开源一年多以来&#xff0c;受到众多开发者的欢迎。在 1024 程序员节之际&#xff0c;CodeFuse 发起「编码挑战季」活动&#xff0c;诚邀广大开发者们参与 muAgent、MFTCoder、ModelCach…

Linux上本地部署KubeSphere与cpolar实现远程管理和监控集群

文章目录 前言1. 部署KubeSphere2. 本地测试访问3. Linux 安装Cpolar4. 配置KubeSphere公网访问地址5. 公网远程访问KubeSphere6. 固定KubeSphere公网地址 前言 本文主要介绍如何在Linux CentOS搭建KubeSphere并结合Cpolar内网穿透工具&#xff0c;实现远程访问&#xff0c;根…

Chrome浏览器音/视频无法自动播放

背景&#xff1a;由于google的一些制度&#xff0c;我们在写html项目时会发现刷新页面时无法自动播放audio和video&#xff0c;即使你添加了autoplay属性也无济于事&#xff0c; 但是IE和Edge浏览器是可以自动播放的。 解决方案&#xff1a; 本人在网上搜寻了很多方法&#xf…

vue的路由的两种模式 hash与history 详细讲解

文章目录 1. Hash 模式工作原理优点缺点使用示例 2. History 模式工作原理优点缺点服务器配置示例使用示例 总结 Vue Router 是 Vue.js 的官方路由管理器&#xff0c;它支持多种路由模式&#xff0c;其中最常用的两种是 hash 模式和 history 模式。下面我们详细讲解这两种模式的…

什么是目标检测?

首先计算机视觉能够解决哪些问题&#xff1f;&#xff1f; 分类、检测、分割 首先以下面这幅图为例&#xff1a; 分类就是输入一张图像&#xff0c;算法能够告诉我们图像中有什么类别&#xff0c;比如说猫或者狗&#xff0c;而并不知道这个类别在图像中的位置&#xff0c;如…

转移概率矩阵的计算

目录 T1T2 T1 写出图示信道的转移概率矩阵&#xff0c;并指出其是否为对称信道。 解&#xff1a; 信道的转移概率矩阵 P ( Y ∣ X ) [ 0.99 0.01 0 0.005 0.99 0.005 0 0.01 0.99 ] P(Y|X)\begin{bmatrix}0.99&0.01&0\\0.005&0.99&0.005\\0&0.01&0.9…

Linux中Samba服务配置和管理

文章目录 一、Samba介绍1.1、Samba是什么1.2、Samba的核心功能1.3、Samba的主要组件1.4、Samba的工作流程1.5、Samba主要配置文件smb.conf 二、Samba安装2.1、更新yum源2.2、安装Samba客户端和服务器软件包2.3、启动Samba 三、Samba的使用3.1、设置Samba服务的全局选项3.2、tes…

MS01SF1 精准测距UWB模组助力露天采矿中的人车定位安全和作业效率提升

在当今矿业行业&#xff0c;随着全球对资源需求的不断增加和开采难度的逐步提升&#xff0c;传统的作业方式面临着越来越多的挑战。露天矿山开采&#xff0c;因其大规模的作业环境和复杂的地形特点&#xff0c;面临着作业人员的安全风险、设备调度的高难度以及资源利用率低下等…

Spring Security 门神中的战斗机

Spring Security 是 Spring 家族中的一个安全管理框架。相比与另外一个安全框架Shiro&#xff0c;它提供了更丰富的功能&#xff0c;社区资源也比Shiro丰富。 一般来说中大型的项目都是使用SpringSecurity 来做安全框架。 小项目有Shiro的比较多&#xff0c;因为相比与SpringS…

CentOS 7 下升级 OpenSSL

升级openssh,下载&#xff1a;https://download.csdn.net/download/weimeilayer/89935114 上传到服务器&#xff0c;然后执行命令 rpm -Uvh *.rpm --nodeps --force安装依赖 yum -y install gcc perl make zlib-devel perl-CPAN下载安装包&#xff1a;https://github.com/ope…

unordered_map、unordered_set 底层原理及其相关面试题

目录 unordered_map、unordered_set的底层原理 哈希表的实现 unordered_map 与map的区别&#xff1f;使用场景&#xff1f; unordered_map、unordered_set的常用函数 unordered_map map区别和联系 unordered_map、unordered_set的底层原理 unordered_map的底层是一个防冗余…

若依框架部署到服务器后头像资源访问404

排错过程 第一开始以为是代理出问题了 官网给出的解决方案 第一种是用代理后端接口&#xff0c;第二种是重写路径直接访问静态文件 接口通过捕获profile开头的路径/profile/avatar…&#xff0c;转为/home…/avatar找到我们在该路径下的文件 但是我想了一下&#xff0c;我ngin…

Linux——五种IO模型

目录 一IO基本理解 二五种IO模型 1五种IO模型示意图 2同步IO和异步IO 二非阻塞IO 1fcntl 2实现非阻塞IO 三多路复用 1select 1.1定位和作用 1.2介绍参数 1.3编写多路复用代码 1.4优缺点 2poll 2.1作用和定位 2.2介绍参数 2.3修改select代码 3epoll 3.1介绍…