文章目录
泛型
一、 什么是泛型
1.能用于多种类型,把类型当做参数
JDK1.5后引入的
1.1 作用
在编译的时候
- 存储数据的时候,进行自动的类型检查
- 获取元素的时候,帮助进行类型转换
-
泛型是编译时期的一种机制,在运行的时候没有泛型的概念
1.2 语法
class MyArray <T>{//当前类是一个泛型类
//public Object[] obj = new Object[5];//实现一个类,类中包含一个数组成员,\
public T[] obj = (T[]) new Object[5];//
// 使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个
//下标的值?
public T getPos(int pos) {
return this.obj[pos];//回数组中某个
//下标的值?
}
public void setObj(int pos,T val){
this.obj[pos] = val;
}
}
public class Test2 {
public static void main(String[] args) {
MyArray<Integer> myArray = new MyArray<>();//后面的<>可以省略
//实例化对象的同时,指定当前的泛型类 的 指定参数类型为Integer
//把类型进行了传递,只能存指定的数据类型
//MyArray<Integer> myArray = new MyArray<Integer>();
//指定的参数类型,必须是引用类型
myArray.setObj(0,3);
myArray.setObj(1,4);//这时就不能传String类型
myArray.setObj(2,5);
Object pos = myArray.getPos(1);
// double d = myArray.getPos(2);//不知道是什么类型的,取出的时候很麻烦
//什么数据都能存储
//传进去后,都向上转型常量object,获取数据的时候要强转
//需要指定类型,才能不进行强转
System.out.println(pos);
System.out.println("-------------");
MyArray<String> myArray1 = new MyArray<>();
myArray1.setObj(1,"hello");
//获取数据的时候不用强转,里面都是指定的数据类型,直接拿指定的类型接收
myArray1.setObj(0,"Hello");
String pos1 = myArray1.getPos(0);
String pos2 = myArray1.getPos(1);
//在编译的时候
//存储数据的时候,进行自动的类型检查
//获取元素的时候,帮助进行类型转换
}
}
- 类名后的 代表占位符,表示当前类是一个泛型类
- 前面<>指定类型,后面的<>可以省略
- 把类型进行了传递,只能存指定的数据类型
- 指定的参数类型,必须是引用类型
- 不能实例化一个泛型类型的数组
- 裸类型:不加<>,兼容老版本的机制
E 表示 Element K 表示 Key V 表示 Value N 表示 Number
二、擦除机制
1. 为什么采用擦除机制实现泛型?
为什么Sun公司选择采用擦除机制实现泛型?
向后兼容性
- 这种设计是为向后兼容性提供源代码和目标代码两方面的支持,希望现有的代码和类文件在新版本的Java中继续使用,而不发生冲突,在不破坏语言的同时,更改语言
移植兼容性
- 移植兼容性要求API的泛型版本要和老的版本兼容,就是说用户能继续编译(源文件)和运行(编译后的程序)。这种要求在很大程度上限制了Java泛型设计的空间
2. 为什么不能使用“newT()”?
是因为,并不是每个类型都有一个空的构造器
- Java是静态类型语言,不能简单的调用某一类型的空构造器,因为这个类型自身并不静态地知道有这样的构造器存在。由于擦除机制,所以没有办法来生成这种代码。
3. 创建类型T的数组
3.1 不安全的写法
T[] a = (T[]) new Object[N];
不安全,但可以在多数情况下运行,在编译时会警告
因为Object数组不是具体T类型的数组
如果方法返回T[],并且指定的类型是,将返回值存入String[]类型的变量中,在运行时将得到ClssCastException错误。
public T[] copyArr(T[] obj){
return obj;
}
public static void main(String[] args) {
Student<String> student = new Student<>();
student.setObj(0,"小明");
student.setObj(1,"小王");
student.setObj(2,"小红");
String[] copyArr = student.copyArr(student.obj);
System.out.println(Arrays.toString(copyArr));
}
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
at learn.Test3.main(Test3.java:35)
返回的Object数组里面,可能存放的是任何的数据类型,可能是String,可能是Person,可能是Integer.运行的时候,直接转给String类型的数组,编译器认为是不安全的。
3.2 官方的写法
public Object[] obj = new Object[5];//正确写法
return (E) elementData[index];
在Java源码中,使用的是Object类型的数组,在方法中,返回时进行泛型的转换
3. 3 正确的写法
通过反射创建,实现类型的数组
/**
* 通过反射创建,实现类型的数组
* @param clazz
* @param capacity
*/
public Student(Class<T>clazz, int capacity) {
this.obj = (T[])Array.newInstance(clazz,capacity);
}
Student<String> student = new Student<>(String.class,10);
4. 反编译后,对比方法的参数
- 在运行的时候,没有泛型的概念
- 泛型只是编译时期的一种机制
- 编译完成后,会将泛类型擦除成了Object类型,叫做擦除机制
- Java的泛型机制是在编译级别实现的。
- 编译器生成的字节码在运行期间并不包含泛型的类型信息。
三、泛型的上界
- 在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
- 泛型是没有下界的
public class MyArray<E extends Number>
E是number的子类 或者 E是Number本身
class Alg<E extends Comparable<E>> {}
代表将来指定的数据类型,一定实现了这个接口
四、泛型方法
-
静态的泛型方法,要在static后面声明泛型的参数
-
public static void main(String[] args) { List<Integer>list = new ArrayList<>(); Integer [] arr = {1,2,3,4,5}; Test2.<Integer>swap(arr,0,2); for (int x:arr) { System.out.println(x); } } public static<E> void swap(E[]array, int i, int j){ E t = array[i]; array[i] = array[j]; array[j] = t; } }
五、通配符
- 通配符: “ ?”
class Message<T> {//泛型类
private T message ;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
}
public static void main(String[] args) {
Message<String> message = new Message<>() ;
message.setMessage("Hello world");
fun(message);//Hello world
Message<Integer> message2 = new Message<>() ;
message2.setMessage(888);
// fun(message2);//fun方法,只能接受<String>类型的对象
fun(message2);
}
/* public static void fun(Message<String> temp){
System.out.println(temp.getMessage());
}*/
public static void fun(Message<?> temp){
//用通配符来规范传进的参数
System.out.println(temp.getMessage());
}
- 通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
通配符的上界和下界
? extends 类:设置通配符上限
- 实例化的只能是参数类型本身或者它的子类
- 不能进行写入数据(无法确定是哪个子类),只能进行读取数据(向上转型)
? super 类:设置通配符下限
-
实例化的只能是参数类型本身、或者它的父类
-
不能进行读取数据(无法确定是哪个父类),只能写入数据(传进去子类对象)