最近在网上看到一个问题,状况相似以下(记为问题1):java
public class Demo { public static void main(String[] args) { System.out.println(testInteger(1)); System.out.println(testInt(1)); } public static boolean testInteger (Integer num) { Integer[] nums = new Integer[]{1, 2, 3, 4, 5, 6}; boolean flag = Arrays.asList(nums).contains(num); return flag; } public static boolean testInt (int num) { int[] nums = new int[]{1, 2, 3, 4, 5, 6}; boolean flag = Arrays.asList(nums).contains(num); return flag; } }
结果第一个输出为true,提问者以为很正常,第二个输出却为false了,很奇怪。若是没有深刻使用过该集合,或是理解泛型,在个人第一眼看来,也是有疑惑。按理来讲,Java中自己针对基本类型与对应包装类型,就有自动装箱拆箱功能,你Integer能行,我int按理来讲应该也能行。因而我大体在网上搜寻了相似的问题,发现除了上面的问题,还有相似以下的状况(记为问题2)数组
public class Demo2 { public static void main(String[] args) { Integer[] nums1 = new Integer[]{1, 2, 3, 4, 5, 6}; List list1 = Arrays.asList(nums1); list1.set(0, 888); System.out.println(list1); int[] nums2 = new int[]{1, 2, 3, 4, 5, 6}; List list2 = Arrays.asList(nums2); list2.set(0, 888); System.out.println(list2); } }
第一个输出正常,集合list1中第一个元素修改成888,可是第二输出还没到就已经报错,完整运行结果以下:dom
java.lang.ArrayStoreException:数组存储异常。下面具体就集合方法与泛型探究上面的问题。ide
Integer[] nums = new Integer[]{1, 2, 3, 4, 5, 6};
Arrays.asList(nums);进入这个方法,源代码以下:工具
public static <T> List<T> asList(T... a) { return new ArrayList<>(a); }
短短的两行代码,内容却并不简单
1.方法的参数比较特殊:参数是泛型类的,而且是可变参数。一个泛型,一个可变参数,说实话,初学者或者开发中不敢说都没用过,但要是说有多经常使用,显然也不符合。因此其实上面的内容在这一步就已经出问题了,下面会具体分析。
2.方法的返回值是一个ArrayList,这里又是一个大坑,这个ArrayList并非咱们之前学习或者平时经常使用到的有序集合ArrayList,而是数组工具类Arrays类的一个静态内部类,继承了AbstractList,以下所示:学习
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) { //上面调用的构造方法内容即为这里,调用requireNonNull方法,对象array为空的话会报空指针异常,不为空则将其赋值给成员a。 a = Objects.requireNonNull(array); } //赋值后此时a就表明了array数组的内容,因此后面的方法基本上都围绕a展开。 @Override public int size() { return a.length; } @Override public Object[] toArray() { return a.clone(); } ...... }
requireNonNull方法内容:优化
public static <T> T requireNonNull(T obj) { if (obj == null) throw new NullPointerException(); return obj; }
完整的过完了这一遍流程后,咱们要思考的是,经过Arrays.asList(nums)方法,咱们获得的究竟是一个什么。从上面的内容,咱们首先能够肯定是一个集合。在问题1中,包装类Integer状况下,调用Arrays.asList(nums),基于可变参数的定义,这里咱们至关于传入可变参数的长度为5。
在public static <T> List<T> asList(T... a)方法中,此时至关于泛型T指定为Integer类型。而后private final E[] a; 此时的数组a的泛型E也相应为Integer。此时a的内容至关于 new Integer[]{1,2,3,4,5,6}; 即一个Integer类型的一维数组,集合也随之为List<Integer>ui
最后获取的是一个Arrays的一个静态内部类ArrayList对象,在内部类中重写了contains方法:spa
@Override public boolean contains(Object o) { return indexOf(o) != -1; }
因此在问题1中的第一种状况传入1时会自动转为对象Object类型,即上面的o,此时显然成立,因此返回true。
那么第二种状况输出false,问题出在哪里?
在基本类型int状况时,一样基于可变参数的定义,同时基于java的自动转换类型,跟上面同样传入可变参数的长度仍是至关于5吗?其实不是,根本缘由在于这里除了可变参数的定义,还有泛型的定义,可是别忘了泛型也有一些限制,首先第一点就是:
泛型的类型参数只能是类类型(包括自定义类),不能是简单类型!
因此这里其实就已经有分歧了,因此这里咱们至关于传入了一个对象,对象类型为数组类型,可变参数的长度是1,而不是5。此时a至关于一个二维数组,在上面的状况中,即a数组的元素类型为数组,元素个数为1,而不是上面的Integer,此时集合为List<int[]>。
最后获得的集合是这样子:List<int[]>形式,集合长度为1,包含了一个数组对象,而不是误觉得的List<Intger>。
接着调用contains方法,集合中就一个数组类型对象,显然不包含1。因此问题1中int状况下输出false
一样的,在问题2的int状况下,list2.set(0,888);即至关于将集合索引为0的元素(即第1个,这里集合中就一个数组元素)赋值为一个888自动转型的Integer类,给1个数组Array类型对象赋值Intger对象,因此才报错了上面的数组存储异常。指针
为了更好的理清上面的内容,作以下扩展。
首先是Integer对象类型。
Integer[] nums = new Integer[]{1, 2, 3, 4, 5, 888}; List<Integer> list2 = Arrays.asList(nums);
System.out.println(list2.get(5));//输出888
这里咱们顺利的经过一次索引拿到在这个888。
接着是int基本类型,前面提到了既然是可变参数,咱们传了一个数组,一个数组也是一个对象,那咱们能够传入多个数组看看,以下所示。
int[] nums1 = new int[]{1, 2, 3, 4, 5, 666}; int[] nums2 = new int[]{1, 2, 3, 4, 5, 777}; int[] nums3 = new int[]{1, 2, 3, 4, 5, 888}; List<int[]> list1 = Arrays.asList(nums1,nums2,nums3); System.out.println(list1.get(2)[5]);//输出888
这里的get方法咱们点进去查看源代码:
@Override public E get(int index) { return a[index]; }
因此这里咱们输出其实就是a[2][5],拿到888,这也印证了前面为何说本质上就是一个二维数组的缘由,同时加深了咱们对集合与数组关系的理解。
前面重点提到这里咱们经过Arrays.asList()方法获得的"ArrayList",并非咱们平时经常使用的那个ArrayList,既然强调了,固然是为了要区分,那么不区分会有什么问题呢,下面以简单的Integer状况为例:
Integer[] nums = new Integer[]{1, 2, 3, 4, 5}; List<Integer> list = Arrays.asList(nums); list.add(888); System.out.println(list);
集合后面加个888,以为会打印出来什么?【1, 2, 3, 4, 5,888】?
而后事实是还没到打印就已经抛出异常了,以下所示:
java.lang.UnsupportedOperationException:不支持的操做异常。为何会不支持,咱们之前一直add,remove等等都没问题。深究到底查看源代码。
首先java.util包下的ArrayList即咱们熟知的,它的add方法实现以下:
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
这也是咱们一直以来操做没毛病的缘由。
再来看这个"ArrayLitst",它在继承抽象类AbstractList的时候,并未实现(或者准确来讲叫作重写)add方法,因此这里在调用add方法的时候,其实是调用的抽象类AbstractList中已经实现的add方法,咱们来看其方法内容:
public boolean add(E e) { add(size(), e); return true; }
经过add(size(), e);这个传入2个参数的方法咱们继续查看内容:
public void add(int index, E element) { throw new UnsupportedOperationException(); }
真相大白!throw new UnsupportedOperationException();这个异常从哪来的一目了然。这个"ArrayLitst"根本没有实现add方法,因此才会报错。回到初始,还能想起集合与数组的种种关联,数组自己长度就是不可变的,而这里本质上咱们就是在操做数组,因此没有add方法不是很正常吗。仔细查看其类的源码,会发现例如remove删除等方法,也是没有实现的,因此一样也会报错。
继续探讨,那么这里增也不行,删也不行,那我改总行吧,就数组而言,按理来讲应该是支持的,而实际状况也的确如此。在"ArrayLitst"内部类中,其重写了set方法,方法能将指定索引处的元素修改成指定值,同时将旧元素的值做为返回值返回。
@Override public E set(int index, E element) { E oldValue = a[index]; a[index] = element; return oldValue; }
可是这里还要注意一点,若是咱们在这里针对集合修改了某处元素值,那么原来数组的内容也会相应改变!即经过Arrays.asList()方法,获得的集合与原数组就已经关联起来,反之,若是咱们修改了数组内容,那么集合获取到的内容也会随之改变。实践检验一下:
Integer[] nums = new Integer[]{1, 2, 3, 4, 5}; List<Integer> list = Arrays.asList(nums); list.set(0, 888);//修改集合内容 nums[1] = 999;//修改数组内容 for(Integer i:nums) { System.out.println(i); } System.out.println(list);
运行后,控制台输出以下:
咱们发现,不管是修改数组,仍是修改集合,另外一方都会相应改变。
一开始觉得是一个小问题,渐渐的发现,其中内容很多,集合是咱们开发中算是很经常使用类库了,良好的熟悉程度能对咱们的开发优化很多。而泛型关联到反射等等核心内容,若是想深刻学习,也须要认真下功夫,在问题的探究中每每能有更深入的印象。