良好的编码风格和完善统一的规约是最高效的方式。
前言
本篇汲取了本书中较为精华的知识要点和实践经验加上读者整理,作为本系列里的第四篇章第二节:数据结构与集合的数组和泛型篇。
本系列目录:
- 《码出高效》系列笔记(一):面向对象中的类
- 《码出高效》系列笔记(一):面向对象中的方法
- 《码出高效》系列笔记(一):面向对象中的其他知识点
- 《码出高效》系列笔记(二):代码风格
- 《码出高效》系列笔记(三):异常与日志
- 《码出高效》系列笔记(四):数据结构与集合的框架
- 《码出高效》系列笔记(四):数据结构与集合的数组和泛型
- 《码出高效》系列笔记(四):元素的比较
- 走进JVM之内部布局
- 走进JVM之字节码与类加载
- 走进JVM之GC
数组与集合
数组是一种顺序表,可以使用索引下标进行快速定位并获取指定位置的元素。
为什么下标从0开始?
因为这样需要计算偏移量需要从当前下标减1的操作,加减法运算对CPU是一种双数运算,在数组下标使用频率很高的场景下,该运算方式十分耗时。在Java的体系中,数组一旦分配内存后无法扩容。
1 | String[] args1 = {"a", "b"}; |
以上代码一般是数组的两种初始化方式,第一种是静态初始化,第二种是动态初始化。数组的容量大小随着数组对象的创建就固定了。
数组的遍历优先推荐JDK5引进的foreach方式,即for(e : obj);
JDK8以上可以使用stream操作
1 | Arrays.stream(args1).forEach(str -> System.out.println(str)); |
数组转集合
将数组转集合后,不能使用集合的某些方法,以Arrays.asList()
为例,不能使用其修改集合add、remove、clear方法,但是可以使用set方法。
1 | String[] args1 = {"a", "b"}; |
后面会输出UnsupportedOperationException
异常。
Arrays.asList()
体现的是适配器模式,其实是Arrays的一个名为ArrayList的内部类(阉割版),继承自AbstractList
类,实现了set和get方法。但是其他部分方法未实现所以会抛出该父类AbstractList
的异常。
1 | String[] args1 = {"a", "b"}; |
实际控制台打印情况:
java.util.Arrays$ArrayList
java.util.ArrayList
数组转集合在需要添加元素的情况下,利用java.util.ArrayList
创建一个新集合。
1 | String[] args = {"a", "b"}; |
集合转数组
集合转数组更加的可控。
1 | List<String> e1 = new ArrayList<>(2); |
实际控制台打印情况:
[null]
[c, d]
不同的区别在于即将复制进去的数组容量是否足够,如果容量不等,则弃用该数组,另起炉灶。
集合与泛型
泛型与集合的联合使用,可以把泛型的功能发挥到极致。
1 | List list1 = new ArrayList(3); |
List<?>
是一个泛型,在没有赋值之前,表示它可以接收任何类型的集合赋值,赋值之后就不能随便往里面添加元素了,但可以remove和clear。
而List<T>
最大的问题就是只能放置一种类型,如果要实现多种受泛型约束的类型,可以使用<? extends T>
与<? super T>
两种语法,但是两者的区别非常微妙。
<? extends T>
是Get First,适用于生产集合元素为主的场景;<? super T>
是Put First,适用于消费集合元素为主的场景。
<? extends T>
可以赋值给任何T以及T子类的集合,上界为T。取出来的类型带有泛型限制,向上转型为T。null可以表示任何类型,所以除了null外,任何元素都不得添加进<? extends T>
集合内。
<? super T>
可以赋值给任何T以及T的父类集合,下界为T。在生活中,投票选举类似<? super T>
的操作。选举代表时,你只能往里投票,取数据时,根本不知道是谁的票,相遇泛型丢失。
extends
的场景是put功能受限,而super的场景是get功能受限。
extends与super的差异
假设有一个斗鱼TV平台,拥有一个DOTA2板块,其下有一个恶心人的D能儿主播:谢彬DD。
那我们从代码里可以这样写:
1 |
|
三个类的继承关系说明DD < DotA2 < DouYu < Object
。
第一处编译出错,因为只能赋值给T以及T的子类,上界是DotA2类。DouYu类明显不符合extends
DotA2类的情况。不能把douYu对象赋值给<? extends DotA2>
,因为List<DouYu>
不只只有DotA2板块,还有吃♂鸡、颜♂值区、舞♂蹈区这些板块。
第二处编译出错,因为只能赋值给T以及T的父类,DD类属于DotA2的子类,下界只能DotA2类的对象。
1 | // 以下<? extends DotA2>类型的对象无法进行add操作,编译出错 |
除了null以外,任何元素都不能添加进<? extends T>
集合内。<? super T>
可以放,但是只能放进去自身以及子类。
1 | Object obj1 = extendsDotA2FromDotA2.get(0); |
首先<? super T>
可以进行Get操作返回元素,但是类型会丢失。<? extends T>
可以返回带类型的元素,仅限自身及父类,子类会被擦除。
小总结
对于一个笼子,只取不放,属于Get First,应采用<? extends T>
;只放不取,属于Put First,应采用<? super T>
。
2021.03.31 新阶段的新总结
以前对泛型的上下限老是会忘记,现在总结了一个例子:
在 Java 中Integer --继承--> Number --实现--> Serializable
例如这样一个方法:
1 | private static void func(List<? extends Number> producer, List<? super Number> consumer) { |
想成功调用这个方法的参数的类型可以是:
1 | private static void ex(List<Number> numbers) { |
但是对集合的操作是相反的:
1 | private static void lowerBoundedWildcardsDemo(List<? extends Number> producer, List<? super Number> consumer) { |