Java 中的泛型
引言
当我们不使用泛型的时候
当我们创建一个 Object 类型数组的时候,我们往里面放什么类型的元素都可以,但是,当我们拿出元素的时候,需要重新定义变量接收,此外,我们还需要在拿出数据的时候,进行强制类型转换。因为 Object 类是所有类型的父类,而当我们放进去可以兼容,而拿出来的时候,需要考虑类型,因为我们需要使用它。
程序清单1:
class MyArray{
public Object[] objects = new Object[10];
public void set(int pos, Object val){
objects[pos] = val;
}
public Object get(int pos){
return objects[pos];
}
}
public class Test1 {
public static void main(String[] args) {
MyArray myArray = new MyArray();
myArray.set(0,"abc");
myArray.set(1,123);
String str = (String) myArray.get(0);//需要强制转换
int x = (int)myArray.get(1);//需要强制转换
System.out.println(str);
System.out.println(x);
}
}
一、引入泛型
虽然在上述情况下,数组任何数据都可以存放,但是,更多情况下,我们还是希望他只能够持有一种数据类型,而不是同时持有这么多类型。所以,泛型的主要目的:就是指定当前的容器,要持有什么类型的对象,最后让编译器去做检查。
此时,就需要把类型作为参数传递,我们最后需要什么类型,就传入什么类型。
程序清单2:
/**
* <T> 此时代表当前类是一个泛型类,T: 代表一个占位符
*/
class MyArray1<T>{
//public T[] objects = new T[10]; //error, //1
public T[] objects = (T[])new Object[10];
public void set(int pos, T val){
objects[pos] = val;
}
public T get(int pos){
return objects[pos];
}
}
public class Test2 {
public static void main(String[] args) {
MyArray1<String> myArray1 = new MyArray1<>(); //2
myArray1.set(0,"abc");
//myArray1.set(1,123); //error //3
String str = myArray1.get(0); //4
System.out.println(str);
}
}
// 注释1: 不能实例化一个泛型的数组
// 注释2: 类 MyArray1 指定了当前类型为 String
// 注释3: val 需要 String 类型
// 注释4: 拿变量接收的时候,不再像程序清单1中需要强制类型转换
1. 擦除机制
在编译的过程当中,将所有的T替换为 Object 这种机制,我们称为:擦除机制。
Java 的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。
2. 泛型的意义
① 自动对类型进行检查
② 自动对类型进行强制类型的转换
二、泛型的上界
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
1. 语法格式
//语法格式
class 泛型类的名称<类型形参 extends 类型边界> {
}
//举例
public class MyArray<E extends Number> {
}
2. 例子
程序清单3:
class MyArray2<T extends Number>{
}
public class Test3 {
public static void main(String[] args) {
MyArray2<Integer> test1 = new MyArray2<>();
MyArray2<Number> test2 = new MyArray2<>();
//MyArray2<String> test3 = new MyArray2<>();//error
}
}
说明:上面的 T 只能是 Number 或 Number 的子类。
包装类 Integer 是 Number 的子类
包装类 String 不是 Number 的子类
三、泛型的比较
1. 语法格式
public class MyArray<E extends Comparable<E>> {
}
程序清单4:
class Alg<T extends Comparable<T>> {
//因为 T 是泛型,我们不知道比较的是引用类型还是整型、还是浮点型...
//所以我们可以确定类型边界 Comparable
//而 Integer 和 String 类这些都实现了 Comparable 接口
public T findMax(T[] array){
T max = array[0];
for (int i = 0; i < array.length; i++) {
if(max.compareTo(array[i]) < 0){
max = array[i];
}
}
return max;
}
}
public class Test4 {
public static void main(String[] args) {
Alg<Integer> alg1 = new Alg<>();
Integer[] array1 = {1,3,7,5};
System.out.println(alg1.findMax(array1));
Alg<String> alg2 = new Alg<>();
String[] array2 = {"he", "she", "nice"};
System.out.println(alg2.findMax(array2));
}
}
输出结果:
四、通配符
通配符就是一个问号的标识 " ? "
通配符的存在是用来解决泛型无法协变的问题的。
我们可以这样理解:泛型 T 就像一个变量,等着你来传一个具体的类型,而通配符更像是一种规定,规定你只能传哪些参数。
1. 通配符上界
语法:
<? extends 上界>
<? extends Number>//可以传入的实参类型是Number或者Number的子类
2. 通配符下界
语法:
<? super 下界>
<? super Integer>//代表 可以传入的实参的类型是Integer或者Integer的父类类型