泛型
层次1:用Integer来写一个集合类,返回中间元素
层次2:用Object来实现复用
层次3:用泛型来实现复用 (注:集合内部还是维护着一个Object[]数组,因为泛型对象无法实例化)
层次4:对泛型变量限定 (定义一个getLargest()方法,阐述Comaparable, 选讲自定义比较器)
层次5:通配符,实现只读和只写
层次5:throws exception 当index 界限时
为什么要使用泛型:典型例子容器类
jdk1.5之前用Object来实现伪泛型:不安全,插入对象没有安全检查
0.泛型类和泛型方法
如何定义一个泛型类
如何定义一个泛型方法
1.类型擦除
有类型限定擦除为类型限定,否则擦除为Object
局限
- 参数化类型的数组不合法
- 无法实例化类型变量
原因:因为在运行时,所有参数的类型信息都消失了
利用jclasslib插件查看字节码,验证泛型擦除
2.类型限定
为什么要使用类型限定
如何使用类型限定
3.通配符
泛型类型的继承规则:比如Manager继承Employee,而Pair<Employee>与Pair<Manager>并不具备继承关系,这样做的原因,假设可以的后果(代码),为了解决这个局限,引入了通配符类型
为了解决继承的限定,Pair<Manager>是Pair<? extends Employee>的子类型
3.1 PECS原则
Joshua Bloch(Effective java 作者) 称那些你只能从中读取的对象为生产者,并称那些你只能写入的对象为消费者。他建议:“为了灵活性最大化,在表示生产者或消费者的输入参数上使用通配符类型”,并提出了以下助记符:
PECS 代表生产者-Extends,消费者-Super(Producer-Extends, Consumer-Super)
计算机术语:协变,逆变
3.2 协变
数组Manager[] 是Employee的子类型,因此数组是协变的,
但Pair<Manager>不是Pair<Employee>的子类型,因此,泛型是不可协变的
使用场景:只读不可写
虽然java不支持直接协变,但借助通配符?
和extends
关键字可以实现间接协变
3.3 逆变
使用场景:只写不可读
借助通配符?
和super
关键字可以实现间接逆变
容器
一.几种数据结构
什么是数据结构,数据结构不依赖特定语言,java有各种容器可以实现特定的数据结构,其他语言也有
1.线性表
数据元素有序的数据结构,有两种实现,一种使用数组,一种使用链表
- 顺序表
- 线性链表
主要是在计算机内的存储结构不同
2.队列
一种FIFO(先进先出)的数据结构,是操作受限的线性表,只能在表的一端插入,另一端删除,可以想象为排队
队列方法 | 等效方法 |
---|---|
offer(e) | 向队尾插入元素e |
poll(e) | 删除并返回队头的元素 |
peek() | 返回队头的元素(未删除) |
3.栈
FILO,先进后出的数据结构,也是操作受限的线性表
使用场景:逆波兰表达式,浏览器/app的回退栈
常见API:
栈方法 | 意思 |
---|---|
push(e) | 将元素e压入栈顶 |
pop() | 删除并返回栈顶的元素 |
peek() | 返回栈顶元素(未删除) |
二.Java的各种容器
可以将java中的各种容器理解为数据结构在java中的实现
1. List
1.1 ArrayList
内部通过维护一个Object[]类型的数组来实现
1.2 LinkedList
可以被当作栈,队列或双端队列等进行操作
包含一个非常中要的内部类:Node
当作为队列时:remove()/poll(),element()/peekFirst()的区别,有效判断与异常抛出
1.3 其他List
- PriorityQueue:
- 优先队列内部使用了heap堆,使用场景:任务调度,查找1000个数中最大的5个数
- 和TreeSet做对比,一个允许重复元素,一个不允许,同时有API差异
- ArrayDeque:双端队列,可以当作栈,也可以当作队列
2. Map
2.1 HashMap
HashMap的内部有两个参数影响其性能:“初始容量”和”加载因子”
拉链法解决散列冲突
2.2 TreeMap
key有序,基于红黑树实现
2.3 其他Map
HashTable(已过时)
LinkedHashMap
WeakHashMap
3. Set
HashSet
内部通过HashMap实现,value始终是一个Object
TreeSet
基于TreeMap实现
4. 迭代器和比较器
迭代器
通常的容器类都会有一个iterator()
方法,返回一个Iterator对象
包含3个方法:
hasNext()
配合next()查看集合中的所有元素-
next()
可以使返回下一个元素,并使指针前移 remove()
方法对next()方法有依赖
利用for each循环可以遍历集合和数组
比较器
什么时候需要自定义比较器
异常
先来一张图
1.分类
- Error,
- 什么时候发生:系统崩溃,内存不足,堆栈溢出
- Error不是异常,划重点!!!
- Exception:
- 一种是RuntimeException,也叫未受检异常
- 一种是其他异常,也叫受检异常
运行时异常(也叫未受检异常)和受检异常的区别:
-
受检异常:受检异常在编译时被检测,如果一个代码会抛出受检异常,则必须处理或者继续抛出。通常是应用环境中的错误,即外部错误,而非应用程序本身的错误。
-
未受检异常:不应捕获,通常是程序设计出了错误。
-
编译器的区别处理:
编译器不会对未受检异常进行检测
编译器会对受查异常进行检测,如果没有try-catch,方法签名没有throws关键字的话无法通过编译
2.异常的抛出与捕获
通常, 应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下去,注:这里的异常都是受检异常
try-catch-finally
如果catch代码块中包含return语句,finally的代码还会执行吗
注:不要再finally块中使用return,因为finally块中的return返回后方法结束执行,不会再执行try块中的return语句
try-with-resource
finally的close方法也可能抛出IOException,Java 7提供了更优雅的方式实现了资源的自动释放
3.自定义异常
什么时候需要自定义异常:当不满足Java自带的异常类,我们想知道抛出的异常的一些额外的信息时
常见的api接口使用的”错误码”
如何自定义异常