目录
1. 什么是泛型
1.1 泛型的引入
我们在实现顺序表时,在声明阶段不能确定这个顺序表到底实际存的是什么类型的元素,所以我们将顺序表的元素类型定义成 Object 类型,这样就可以很自由的存储指向任意类型对象的引用到我们的顺序表了。
public class MyArrayList {
private Object[] array; // 保存顺序表的元素,即 Object 类型的引用
private int size; // 保存顺序表内数据个数
public void add(Object o) { 尾插 }
public Object get(int index) { 获取 index 位置的元素 }
...
}
但是有时候我们就会遇到一些问题,比如
MyArrayList books = new MyArrayList();
books.add(new Book);
// 将 Object 类型转换为 Person 类型,需要类型转换才能成功
// 这里编译正确,但运行时会抛出异常 ClassCastException
Person person = (Person)books.get(0);
上面的代码在编译时是完全没问题的,但由于 add 了一个 Book 类型的数据,但在 get 时却要求转换为 Person 类型,这将出现程序运行时的类型转换异常,但却逃离了编译器的检查。所以针对这些问题我们引入了泛型,它不仅可以增加编译期间的类型检查,还可以取消类型转换的使用。所以也可以说泛型是编译器检查类型是否正确的守门员。
1.2 泛型定义
泛型:就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个参数类型,将在使用者使用时才知道具体类型。
1.3 泛型的使用
语法:
泛型类<类型实参> 变量名 = new 泛型类<类型实参>(构造方法实参);
示例:
MyArrayList<String> list = new MyArrayList<String>();
// 当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写
MyArrayList<String> list = new MyArrayList<>();
定义一个泛型类顺序表
public class MyArrayList<E> {
private E[] array;
private int size;
public MyArrayList() {
array = (E[])new Object[16];
size = 0;
}
// 尾插
public void add(E e) {
array[size++] = e;
}
// 尾删
public E remove() {
E element = array[size - 1];
array[size - 1] = null; // 将容器置空,保证对象被正确释放
size--;
return element;
}
}
1.4 泛型的种类
1.4.1 泛型类
public class MyClass<T,E> {
private T[] array;
private E[] array2;
}
1. 泛型类可以一次有多个类型变量,用逗号分割。
2. <> 尖括号时泛型的标志。
3. <>中的T , E 在定义时是形参,代表的意思是 MyArrayList 最终传入的类型,但现在还不知道。
4. E 是类型变量,变量名一般要大写。
1.4.2 泛型方法
public class Point <T>{
private T x;
private T y;
public Point(T x, T y) {
this.x = x;
this.y = y;
}
// 在方法声明上仍然可以使用泛型
public <T> T fun (T t) {
return t;
}
public <T> void func(T t) {
}
public static void main(String[] args) {
Point<Integer> point = new Point<>(10,20);
point.fun("zs");
}
}
1.4.3 泛型接口
public interface IMessgae<T> {
void setMesg(T t);
}
子类实现泛型接口的两种方法:
1. 子类继续保留泛型。
// 子类保留泛型,仍然时泛型类
class IMessageImpl<T> implements IMessage<T> {
@Override
public void setMesg(T t) {
}
}
2. 子类实现接口时给出具体类型。
class IMessageImpl2 implements IMessage<String> {
@Override
public void setMesg(String s) {
}
}
1.5 通配符
- ? 用于在泛型的使用,用在方法的声明上,即为通配符。
public class MyArrayList<E> {...}
// 可以传入任意类型的 MyArrayList
public static void printAll(MyArrayList<?> list) {
...
}
// 以下调用都是正确的
printAll(new MyArrayList<String>());
printAll(new MyArrayList<Integer>());
printAll(new MyArrayList<Double>());
printAll(new MyArrayList<Number>());
printAll(new MyArrayList<Object>());
- 通配符-上界
语法:<? extends 上界>
// 可以传入类型实参是 Number 子类的任意类型的 MyArrayList
public static void printAll(MyArrayList<? extends Number> list) {
...
}
// 以下调用都是正确的
printAll(new MyArrayList<Integer>());
printAll(new MyArrayList<Double>());
printAll(new MyArrayList<Number>());
// 以下调用是编译错误的
printAll(new MyArrayList<String>());
printAll(new MyArrayList<Object>());
MyArrayList 可以传入的参数类型可以是 Number 类和它的子类,所以当传入的参数类型是 String 类和 Object 类时编译出错。
- 通配符-下界
语法:<? super 下界>
// 可以传入类型实参是 Integer 父类的任意类型的 MyArrayList
public static void printAll(MyArrayList<? super Integer> list) {
...
}
// 以下调用都是正确的
printAll(new MyArrayList<Integer>());
printAll(new MyArrayList<Number>());
printAll(new MyArrayList<Object>());
// 以下调用是编译错误的
printAll(new MyArrayList<String>());
printAll(new MyArrayList<Double>());
MyArrayList 可以传入的参数类型可以是 Integer 类和它的父类,所以当传入的参数类型是 String 类和 Double 类时编译出错。
三种类型的通配符中,只有下界通配符可以使用对象调用修改方法修改内容,而上界通配符可以用在方法或者接口的泛型的声明上。
1.6 类型擦除
泛型只存在于编译阶段,在进入 JVM 之前,所有与泛型相关的内容都会被擦除。普通泛型都被擦除为 Object 类,若泛型规定了泛型上界,擦除为相应的泛型上界。总的来说就是类型擦除主要看其类型边界而定。
⭐总结:
1. 泛型是为了解决某些容器、算法等代码的通用性而引入,并且能在编译期间做类型检查。
2. 泛型利用的是 Object 是所有类的祖先类,并且父类的引用可以指向子类对象的特定而工作。
3. 泛型是一种编译期间的机制,即 MyArrayList<Person> 和 MyArrayList<Book> 在运行期间是一个类型。
4. 泛型是 java 中的一种合法语法,标志就是尖括号<>
2. 什么是包装类
2.1 概念
Object 引用可以指向任意类型的对象,但是八大基本数据类型不是对象,所以为了解决这个问题,java 引入了一类特殊的类,将这八大基本数据类型封装到类中,即包装类。
2.1 包装类种类
- 对象型包装类:Object 类的直接子类
Character、Boolean
- 数值型包装类:Number 类子类
Byte、Short、Integer、Long、Float、Double
2.2 基本数据类型和包装类的对应关系
基本数据类型 | 包装类 |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
2.3 装箱与拆箱
装箱:将基本类型封装到类中。使用 valueOf() 方法。
拆箱:将包装类对象还原为基本数据类型。使用 intValue() 方法。
示例:
int i = 10;
// 装箱操作,新建一个 Integer 类型对象,将 i 的值放入对象的某个属性中
Integer num1 = Integer.valueOf(i);
Integer num2 = new Integer(i);
// 拆箱操作,将 Integer 对象中的值取出,放到一个基本数据类型中
int j = num1.intValue();
自动拆装箱:使用包装类过程中,装箱和拆箱看起来很麻烦,java 提供了自动拆装箱机制。
// 自动装箱,不需要自己动手使用 valueOf()方法
Integer i = 10;
// 自动拆箱,不需要自己动手使用 intValue()方法
int a = i + 1;