大家好,我是向导!很多朋友在写代码的时候,没有考虑到一些基本的问题,导致项目在CodeReview的时候被diss了。上周五CodeReview的时候,团队里工作了一年多的小伙伴在使用Java集合的时候遇到了一个很基础的问题。在本文中,我根据《阿里巴巴 Java 开发手册》总结了使用集合的常见注意事项及其具体原则。强烈建议小伙伴多看几遍,避免自己写代码时出现这些低级问题。collectionempty《阿里巴巴 Java 开发手册》的说明如下:判断collection中的元素是否全部为空,使用isEmpty()方法代替size()==0方法。这是因为isEmpty()方法更具可读性并且具有O(1)时间复杂度。我们使用的大部分集合的size()方法的时间复杂度也是O(1),但是也有很多复杂度不是O(1)的,比如java.util.concurrent包下的一些集合(ConcurrentLinkedQueue、ConcurrentHashMap...)。下面是ConcurrentHashMap的size()方法和isEmpty()方法的源码。publicintsize(){longn=sumCount();return((n<0L)?0:(n>(long)Integer.MAX_VALUE)?Integer.MAX_VALUE:(int)n);}finallongsumCount(){CounterCell[]as=counterCells;CounterCella;longsum=baseCount;if(as!=null){for(inti=0;ibookList=newArrayList<>();bookList.add(newPerson("jack","18163138123"));bookList.add(newPerson("martin",null));//空指针异常bookList.stream().collect(Collectors.toMap(Person::getName,Person::getPhoneNumber));让我们解释一下为什么。首先我们看java.util.stream.Collectors类的toMap()方法,可以看到它内部调用了Map接口的merge()方法。publicstatic>CollectortoMap(FunctionkeyMapper,FunctionvalueMapper,BinaryOperatormergeFunction,SuppliermapSupplier){BiConsumeraccumulator=(map,element)->map.merge(keyMapper.apply(element),valueMapper.apply(element),mergeFunction);returnnewCollectorImpl<>(mapSupplier,accumulator,mapMerger(mergeFunction),CH_ID);}Map接口的merge()方法如下,该方法是接口中的默认实现。如果你不知道Java8的新特性,请阅读这篇文章:《Java8 新特性总结》。defaultVmerge(Kkey,Vvalue,BiFunctionremappingFunction){Objects.requireNonNull(remappingFunction);Objects.requireNonNull(value);VoldValue=get(key);VnewValue=(oldValue==null)?value:remappingFunction.apply(oldValue,value);if(newValue==null){remove(key);}else{put(key,newValue);}returnnewValue;}merge()方法会先调用Objects.requireNonNull()判断值是否为空的方法。publicstaticTrequireNonNull(Tobj){if(obj==null)thrownewNullPointerException();returnobj;}集合遍历的说明《阿里巴巴 Java 开发手册》如下:foreach循环中不要删除/添加元素。请使用Iterator方法移除元素。如果并发操作,则需要锁定Iterator对象。通过反编译,你会发现底层的foreach语法糖还是依赖于Iterator。但是remove/add操作直接调用了集合自身的方法,而不是Iterator的remove/add方法,导致Iterator不知何故发现自己有元素removed/added,然后抛出ConcurrentModificationException提示用户并发修改异常已经发生了。这是单线程状态下产生的fail-fast机制。fail-fast机制:当多个线程修改fail-fast集合时,可能会抛出ConcurrentModificationException。如上所述,即使在单线程下也可能发生这种情况。从Java8开始,可以使用Collection#removeIf()方法删除满足特定条件的元素,如Listlist=newArrayList<>();for(inti=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。......Set去重《阿里巴巴 Java 开发手册》介绍如下:可以利用Set元素的独特特性,快速对集合进行去重操作,避免使用List的contains()遍历去重或判断包含操作。这里我们以HashSet和ArrayList为例。//设置去重代码示例列表重复数据删除代码示例size());for(Tcurrent:data){if(!result.contains(current)){result.add(current);}}returnresult;}两者的核心区别在于contains()的实现方法。HashSet的contains()方法在底层依赖于HashMap的containsKey()方法,时间复杂度接近O(1)(没有hash冲突时为O(1))。privatetransientHashMap地图;publicbooleancontains(Objecto){returnmap.containsKey(o);}我们有N个元素插入到Set中,时间复杂度接近O(n)。ArrayList的contains()方法是通过遍历所有元素来完成的,时间复杂度接近O(n)。publicbooleancontains(Objecto){returnindexOf(o)>=0;}publicintindexOf(Objecto){if(o==null){for(inti=0;ilist=Arrays.asList(s);Collections.reverse(list);//不指定类型会报错s=list.toArray(newString[0]);由于JVM优化,现在使用newString[0]作为Collection.toArray()方法的参数比较好用,newString[0]作为模板,指定返回数组的类型,0是为了节省空间,因为它只是为了说明返回的类型。详见:https://shipilev.net/blog/2016/arrays-wisdom-ancients/Arraytocollection《阿里巴巴 Java 开发手册》描述如下:使用工具类Arrays.asList()将数组转为数组时collection,不能修改Collection相关的方法,它的add/remove/clear方法会抛出UnsupportedOperationException。在之前的项目中遇到过类似的坑。Arrays.asList()在平时的开发中比较常见,我们可以用它来将一个数组转换成一个List集合。String[]myArray={"Apple","Banana","Orange"};ListmyList=Arrays.asList(myArray);//上面两条语句等价于下面的语句ListmyList=Arrays.asList("苹果","香蕉","橘子");该方法的JDK源码说明:/***返回指定数组支持的固定大小列表。此方法充当基于数组和基于集合的API之间的桥梁,*与Collection.toArray()结合使用。返回的List是可序列化的,并实现了RandomAccess接口。*/publicstaticListasList(T...a){returnnewArrayList<>(a);}下面总结一下使用注意事项。1.Arrays.asList()是泛型方法,传入的数组必须是对象数组,不能是基本类型。int[]myArray={1,2,3};ListmyList=Arrays.asList(myArray);System.out.println(myList.size());//1System.out.println(myList.get(0));//数组地址值System.out.println(myList.get(1));//错误:ArrayIndexOutOfBoundsExceptionint[]array=(int[])myList.get(0);System.out.println(array[0]);//1当传入一个原生数据类型的数组时,Arrays.asList()真正的参数不是数组中的元素,而是数组对象本身!这时候List的元素就只有这个数组了,这就解释了上面的代码。我们可以通过使用包装类型数组来解决这个问题。整数[]我的数组={1,2,3};2、使用集合修改方法:add()、remove()、clear()会抛出异常。ListmyList=Arrays.asList(1,2,3);myList.add(4);//运行错误:UnsupportedOperationExceptionmyList.remove(1);//运行错误:UnsupportedOperationExceptionmyList.clear();//运行错误:WhattheUnsupportedOperationExceptionArrays.asList()方法返回的不是java.util.ArrayList,而是java.util.Arrays的一个内部类。该内部类没有实现集合修改方法或重写这些方法。ListmyList=Arrays.asList(1,2,3);System.out.println(myList.getClass());//classjava.util.Arrays$ArrayList下图是java.util.Arrays$的简单源码ArrayList,我们可以看到这个类重写了哪些方法。privatestaticclassArrayListextendsAbstractListimplementsRandomAccess,java.io.Serializable{...@OverridepublicEget(intindex){...}@OverridepublicEset(intindex,Eelement){...}@OverridepublicintindexOf(Objecto){...}@Overridepublicbooleancontains(Objecto){...}@OverridepublicvoidforEach(Consumeraction){...}@OverridepublicvoidreplaceAll(UnaryOperatoroperator){...}@Overridepublicvoidsort(Comparatorc){...}}我们来看看java.util.AbstractList的add/remove/clear方法就知道为什么会抛出UnsupportedOperationException。publicEremove(intindex){thrownewUnsupportedOperationException();}publicbooleanadd(Ee){add(size(),e);returntrue;}publicvoidadd(intindex,Eelement){thrownewUnsupportedOperationException();}publicvoidclear(){removeRange(0,size());}protectedvoidremoveRange(intfromIndex,inttoIndex){ListIteratorit=listIterator(fromIndex);for(inti=0,n=toIndex-fromIndex;iListarrayToList(finalT[]array){finalListl=newArrayList(array.length);for(finalTs:array){l.add(s);}returnl;}Integer[]myArray={1,2,3};System.out.println(arrayToList(myArray).getClass());//类java.util.ArrayList2。最简单的方法是Listlist=newArrayList<>(Arrays.asList("a","b","c"))3。使用Java8的Stream(推荐)Integer[]myArray={1,2,3};ListmyList=Arrays.stream(myArray).collect(Collectors.toList());//基本类型也可以转换(依赖装箱装箱操作)int[]myArray2={1,2,3};ListmyList=Arrays.stream(myArray2).boxed().collect(Collectors.toList());4.使用Guava对于不可变集合,可以使用ImmutableList类及其of()和copyOf()工厂方法:(参数不能为空)Listil=ImmutableList.of("string","elements");//fromvarargsListil=ImmutableList.copyOf(aStringArray);//fromarray对于可变集合,可以使用Lists类及其newArrayList()工厂方法:Listl1=Lists.newArrayList(anotherListOrCollection);//fromcollectionListl2=Lists.newArrayList(aStringArray);//fromarrayListl3=Lists.newArrayList("or","string","elements");//fromvarargs5.使用ApacheCommonsCollectionsListlist=newArrayList();CollectionUtils.addAll(list,str);6.使用Java9的List.of()方法Integer[]array={1,2,3};Listlist=List.of(array);