目录
1、初识泛型
1.1、泛型类的使用
1.2、泛型如何编译的
2、泛型的上界
3、通配符
4、通配符上界
5、通配符下界
1、初识泛型
泛型:就是将类型进行了传递。从代码上讲,就是对类型实现了参数化。
泛型的主要目的:就是指定当前的容器,要持有什么类型的对象。让编译器去做检查
语法:
class 泛型类名称<类型形参列表> {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> {
}class 泛型类名称<类型形参列表> extends 继承类 /* 这里可以使用类型参数 */ {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
// 可以只使用部分类型参数
}
例:创建一个泛型数组
class MyArray <T> {
//public T[] array = new T[10]; // 1
//public T[] array = (T[]) new Object[10];
public Object[] array = new Object[10];
// 不考虑pos非法情况,默认合法
public void setValue(int pos,T val) {
array[pos] = val;
}
public T getValue(int pos) {
return (T)array[pos];//把返回的类型 强转为指定类型
}
public Object[] getArray() {
return array;
}
}
public class Test {
public static void main(String[] args) {
public static void main(String[] args) {
MyArray<Integer> myArray = new MyArray<>();//2
myArray.setValue(0,10);
myArray.setValue(1,12);
int ret = myArray.getValue(1);//3
System.out.println(ret);
myArray.setValue(2,"abc");//4
MyArray<String> myArray2 = new MyArray<>();
myArray2.setValue(0,"abcd");
myArray2.setValue(1,"efg");
String ret = myArray2.getValue(1);
System.out.println(ret);
}
}
代码解释:
1. 类名后的<T>代表占位符,表示当前类是一个泛型类
类型形参一般使用一个大写字母表示,常用的名称有:
- E 表示 Element
- K 表示 Key V 表示 Value
- N 表示 Number
- T 表示 Type
- S, U, V 等等 - 第二、第三、第四个类型
2. 注释1处,Java中不允许new一个泛型类型数组
- 意味着 T[] ts = new T[5]; 是不对的
- T[] array = (T[])new Object[10]; 这个方式也不太好
- 推荐这种方式 Object[] array = new Object[10];
3. 注释2处,类型后加入<Integer>指定当前类型
4. 注释3处,不需要进行强制类型转换
5. 注释4处,代码编译报错,此时因为在注释2处指定类当前的类型,此时在注释4处,编译器会在存放元素的时 候帮助我们进行类型检查。
1.1、泛型类的使用
语法:
泛型类<类型实参>变量名;// 定义一个泛型类引用
new泛型类<类型实参>(构造方法实参);// 实例化一个泛型类对象
示例:
MyArray<Integer> list = new MyArray<Integer>();
注意:泛型只能接受类,所有的基本数据类型必须使用包装类!
类型推导:
当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写
MyArray list = new MyArray<>(); // 可以推导出实例化需要的类型实参为 Integer
1.2、泛型如何编译的
泛型是编译时期存在的,当程序运行起来到JVM之后,就没有泛型的概念了,编译器生成的字节码在运行期间并不包含泛型的类型信息。
泛型在编译的时候如何编译的呢?
答:在编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制
注:Java的泛型机制是在编译时期实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息
总结:
- 泛型是将数据类型参数化,进行传递
- 使用<T>表示当前类是一个泛型类
- 泛型目前为止的优点:数据类型参数化,编译时自动进行类型检查和转换
2、泛型的上界
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束
语法:
class 泛型类名称 {
...
}
示例:
//T 只接受 Number或Number的子类
class TestGeneric<T extends Number> {
}
//T 一定是 Number或者Number的子类
class TestGeneric<T extends Number> {
}
public class Test2 {
public static void main1(String[] args) {
// Integer、Double等都是Number的子类
TestGeneric<Number> testGeneric1 = new TestGeneric<>();
TestGeneric<Integer> testGeneric2 = new TestGeneric<>();
TestGeneric<Double> testGeneric3 = new TestGeneric<>();
//TestGeneric<String> testGeneric4 = new TestGeneric<>(); // 编译错误
}
}
1. 写一个泛型类 求一个数组中的最大值
发现报错了,因为 T 一定是引用数据类型,不能直接通过大于号小于号进行比较;T 最终被擦除为了Object 类型,而Object 类型没有实现Comparable接口,也不能通过compareTo进行比较。总结出 T 类型一定要是可以比较的
^
问题:怎么能够约束 这个T 一定是可以比较大小的?
答:T extends Comparable<T>,此时 T 一定是实现了 Comparable接口的
2. 写一个泛型方法
发现在调用的过程中没有指定参数类型。此时是通过类型推导来确定 T 的类型的
类型推导:根据实参传值 来推导 此时的类型
^
不使用类型推导的写法:
3. 静态泛型方法
不使用类型推导:
3、通配符
? 用于在泛型的使用,即为通配符
通配符解决什么问题?
上述程序会带来问题,如果现在泛型的类型设置的不是String,而是Integer
需要的解决方案:可以接收所有的泛型类型,但是又不能够让用户随意修改。这种情况就需要使用通配符 "?" 来处理
4、通配符上界
语法:
<? extends 上界>
<? extends Number>//可以传入的实参类型是Number或者Number的子类
示例:
此时无法在fun函数中对temp进行添加元素,因为temp接收的是Fruit和他的子类,此时存储的元素应该是哪个子 类无法确定。所以添加会报错!但是可以获取元素
通配符的上界,不能进行写入数据,只能进行读取数据。
5、通配符下界
语法:
<? super 下界>
<? super Integer> // 代表可以传入的实参的类型是Integer或者Integer的父类类型
示例:
通配符的下界,不能进行读取数据,只能写入数据