红岩第四次课课件

Posted by roger on November 3, 2019

泛型

层次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循环可以遍历集合和数组

比较器

什么时候需要自定义比较器

异常

先来一张图

img

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接口使用的”错误码”

如何自定义异常