- 什么是泛型
泛型是jdk1.5引入的新语法,泛型就是适用于许多许多类型,就是对类型实现了参数化。 - 实现一个类,类中包含一个数据成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值
class MyArray {
public Object[] obj;
//此时存储数据的时候,什么类型都可以存储
//此时获取数据的时候,必须强转
//思考:能不能指定放啥类型,这样就不要强转了——在类的后面加<>,并指定类型
public MyArray() {
obj = new Object[10];
}
public void setValue(int pos, Object value) {
obj[pos] = value;
}
public Object getValue(int pos) {
return obj[pos];
}
}
public class Test {
public static void main(String[] args) {
MyArray myArray = new MyArray();
myArray.setValue(1,12);
myArray.setValue(0,"123");
int a = (int)myArray.getValue(1);
}
}
这样写:
①此时存储数据的时候,什么数据都可以存储【比如int类型和String类型都可以存进去】
myArray.setValue(1,12);
myArray.setValue(0,"123");
②此时获取数据的时候,必须强制类型转换
int a = (int)myArray.getValue(1);
思考:能不能指定放啥类型,然后就不需要强制类型转换了呢?——泛型!!!
(虽然此时当前数组可以存放任何数据,但是我们还是希望数组只能持有一种数据类型,而不是同时持有这么多类型)所以,泛型的主要目的是:指定当前容器要持有什么类型的对象,让编译器去做检查。把类型作为参数传递,需要什么类型,就传入什么类型。
- 将上述程序修改成泛型类
class MyArray<E> {
public E[] obj = (E[]) new Object[10];
public void setValue(int pos, E value) {
obj[pos] = value;
}
public E getValue(int pos) {
return obj[pos];
}
}
public class Test {
public static void main(String[] args) {
MyArray<Integer> myArray = new MyArray<>();
myArray.setValue(0,1);
int a = myArray.getValue(0);
System.out.println(a);
}
}
①此时只有指定的数据类型才可以存储
MyArray<Integer> myArray = new MyArray<>();
myArray.setValue(0,1);
myArray.setValue(1,"abc0");//报错
因为此时在实例化对象的同时,指定了当前泛型类的指定参数类型是Integer【并且中鼎的参数类型必须是引用类型,放int就错了,int是基本数据类型】
②此时获取0下标存放的值没有发生强制类型转换
int a = myArray.getValue(0);
System.out.println(a);
但是这里发生了自动装箱和拆箱操作。
因此,泛型的好处/意义:
1.存储数据的时候,可以帮我们进行自动的类型检查
2.获取元素的时候,可以帮我们进行类型转换
【注意以上2个好处/意义都是发生在编译的时候,泛型是编译时期的一种机制,在运行的时候没有泛型的概念。】
- 泛型的语法
①定义泛型类
class 泛型类名称<类型形参列表>{
}
注意:
1、类名后的<>,比如代表占位符,表示当前类是一个泛型类,只相当于一个形参,实参是在实例化泛型类的时候给的。
2、不能new泛型类型的数组
规定:泛型当中不能去实例化一个泛型类型数组
T[] t = new T[5]; //不能实例化泛型数组
T[] t = (T[])new Object[5]; //对的,实例化Object类型数组,并且强制类型转换成泛型,但是不是足够好
//这种方法是最好的
Object[] obj = new Object[5]; //还是new Object对象
public T getPos(int pos) {
return (T)obj[pos]; //在getset中再强转成T类型
②实例化泛型类对象
MyArray<Integer> list = new MyArray<Integer>();
泛型类<类型实参> 变量名 泛型类<类型实参>(构造方法实参)
注意:泛型只能接受类,类型实参所有的基本数据类型必须使用包装类。
关于省略类型实参:当编译器可以根据上下文推导的时候可以省略new后面的那个泛型实参
MyArray<Integer> list = new MyArray();//可以推导出实例化需要的类型实参为Integer
③裸类型(左右两边的实参都省略了)
裸类型是一个泛型类但是没有带类型实参。
MyArray list = new MyArray();
理论上应该要报错,但是这里牵扯到Java的历史原因,裸类型是为了兼容老版本的API保留的机制。
-
泛型再Java中是怎么编译的?——擦除机制
查看泛型程序的字节码文件
可以发现,在编译的过程中,将所有的E都替换成了Object,这种机制也叫擦除机制。
擦除机制:运行的时候没有泛型,编译完成之后,泛型类型被擦除为Object类型,也就是说编译好之后E其实是一个Object。
Java的泛型机制是在编译级别实现的,编译器生成的字节码在运行期间不包含泛型的类型信息。
那么泛型只存在在编译的时候,并且在这个时候干了2件伟大而具有意义的事:【这也是和直接写Object的区别,泛型的好处】
①检查:检查是不是要的类型
②转换:去除了强制类型转换 -
泛型的上界
泛型如果没有边界的话,都变成了Objetct。因此有时需要对传入的类型变量做一定的约束,通过类型边界来约束。(用到extends)
① 泛型的上界是类
<E extends Number>
代表E是Number的子类,或者E是Number本身
class A<T extends Number> {
public T[] obj = (T[])new Object[10];
}
public class Test {
A<Integer> a1 = new A<>();
A<Double> a2 = new A<>();
A<String> a3 = new A<>(); //报错
}
如代码A<String> a3 = new A<>();
会编译错误,因为String不是Number的子类类型。
②泛型的上界是接口
<E extends Comparable<E>>
代表将来指定的参数E类型一定要实现了Comparable这个接口
class AA<E extends Comparable<E>> {
}
class BB {
}
public class demo {
AA<Integer> a1 = new AA<>();//不报错,因为Integer里面实现了Comparable接口
AA<BB> a2 = new AA<>();//报错,因为BB类没有实现Comparable接口
}
代码AA<BB> a2 = new AA<>();
会编译错误,因为BB类没有实现Comparable接口,但是Integer不会,因为Integer包装类实现了Comparable接口。
7. 泛型也可以用在方法中
在返回值类型前加<>类型参数列表
public <E>void swap(E[] array, int i, int j) {
E tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
静态方法的泛型,需要在static后面<>声明泛型类型参数
public static <E> void swap(E[] array, int i, int j) {
E tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
关于调用泛型方法:
使用类型推导和不使用类型推导
public static void main(String[] args) {
Test test = new Test();
Integer[] array = {1,2,3};
test.swap(array,1,2); //使用类型推导
test.<Integer>swap(array,0,1);//使用类型推导
}