目录
1.泛型定义:
泛型简单来说就是适用于许多许多类型,它就像是C++中的模板一样,可以接收许多许多类型来使用。
泛型的主要目的:就是指定当前的容器,要持有什么类型的对象,然后让编译器去做检查。
2.泛型语法:
//class 泛型类名称<类型形参列表> {
// 在类中可以使用类型参数
//}
class Demo1<T> {
}
class Demo2<T1, T2, T3 ... Tn> {
}
class Demo3<T>extends 继承类 {
}
class Demo2<T1, T2, T3 ... Tn> extends 继承类<T1, T2>{
//只有相同的类型形参才能使用(T1, T2)
}
我们来模拟实现一个可以存放任意类型的数组吧:
class MyArrays<T> {
private T[] arr = (T[])new Object[10];
//这里需要强转!!
// T[] arr = new T[10];这样写是不对的,会报错!!
public void setArr(int pos, T value) {
arr[pos] = value;
}
public T getArr(int pos) {
return arr[pos];
}
}
public class Demo5 {
public static void main(String[] args) {
//这里的类型一定要是引用类型,不能是基本类型!!
MyArrays<Integer> arr = new MyArrays<>();
//在数组中存放元素
arr.setArr(0, 1);
arr.setArr(1, 2);
arr.setArr(2, 3);
arr.setArr(3, 4);
//打印下标为2的元素
System.out.println(arr.getArr(2));
}
}
3.泛型类的使用:
语法:
泛型类名称<类型参数> 变量名;
或者
泛型类名称<类型参数> 变量名 = new 泛型类名称<类型参数>();
或者
泛型类名称<类型参数> 变量名 = new 泛型类名称<>();
使用案例:
ArrayList<Integer> arrayList = new ArrayList<Integer>();
ArrayList<Integer> arrayList = new ArrayList<>();
//原因是编译器进行了类型推导,推导出了实例化的形参是Integer类
4.擦除机制:
在前文中,我们模拟写了一个泛型数组:
那么在运行的时候,编译器就会自动将代码里的“T”替换为Object类(确切来说是泛型参数用其最左边界(最顶级的父类型)类型替换,默认则是Object),这种机制我们称为擦除机制。
那这里提出一个问题:为什么不能直接初始化一个泛型数组呢(T[] arr = new T[10];)?
在编译器实行擦除机制后,会将(T[] arr = new T[10];)变为(Object[] arr = new Object[10];),因为返回的Object数组里存放的可以是如何类型的变量,比如String类的、Character类的等等,那么如果将这些类型强转为Integer类,编译器会认为是不安全的。
那么正确的写法应该是(了解即可):
class MyArrays<T> {
private T[] arr;
public MyArrays(Class<T> c, int capacity) {
arr = (T[]) Array.newInstance(c,capacity);
}
}
桥接方法:
public class Parent<T> {
public Number get(T key){//number几乎包含了所有数据类型
return 0;
}
}
class child1 extends Parent<String>{
public Number get(String key){
return 1;
}
}
class child2 extends Parent<String>{
public Integer get(String key){
return 2;
}
}
我们知道有擦除机制在,上述代码的泛型都会被擦除,并且类型参数会被擦成Object,那么子类的重写方法的参数应该是Object的,但我们写成上述也并没有报错,这是怎么回事呢?
原因就是父类的T被擦成了object,所以编译器为我们先重写了父类的参数类型为object的方法,再通过该桥接方法区调用自己的重写的方法。
擦除机制带来的影响:
1.不能用同一个泛型类的实例区分方法签名
示例:
public class Test {
public void test(List<String> a){
System.out.println("String");
}
public void test(List<Integer> b){
System.out.println("Integer");
}
}
这样是不行的,因为List< String >和List< Integeer >在类型擦除后都变成了List,那么两个方法的签名就一模一样了,编译器就会直接报错。
2.不能同时catch同一个泛型异常类的多个实例,原理同上一条相似。
4.泛型的边界:
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
4.1泛型的上界:
语法:
class 泛型类名称<类型形参 extends 类型边界> {
...
}
示例:
public class MyArray <T extends Number> {
...
}
在创建对象的时候:
MyArray<Integer> arr1;//正确,因为Integer是Number的子类
MyArray<String> arr1;//编译错误,因为String不是Number的子类
4.2泛型方法:
语法:
方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }
示例:
class Test<E> {
public <E>void method1(E value) {
}
public static <E>void method2(E value) {
//如果是static修饰的方法,就在static后加<参数类型>
}
}
使用示例:
Test test = new Test();
test.<Integer>method1(1);//使用类型推导
test.method2("String");//不使用类型推导
4.3通配符:
4.3.1通配符语法:
? 用于在泛型的使用,即为通配符
通配符的使用场景:
class Message<T> {
private T message ;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
}
public class TestDemo {
public static void main(String[] args) {
Message<String> message = new Message<>() ;
message.setMessage("abc");
fun(message);
}
public static void fun(Message<String> temp){
System.out.println(temp.getMessage());
}
}
以上代码如果使用这个fun方法那么就只能传入Message<String>,而不能传入Message<Integer>这种类型参数,那为例实现代码的复用,我们可将这个Message<String>参数类型使用通配符改成Message<?>,那么这个方法就能传入其他参数类型的变量啦~~
public static void fun(Message<?> temp){
//把String改成通配符'?'
//改成通配符后不能修改Message里的变量,原因是它传入的类型不确定
//temp.setMessage(100); 无法修改!
System.out.println(temp.getMessage());
}
注意:改成通配符后不能修改Message里的变量,因为它传入的类型不确定。
4.3.2通配符上界:
语法:<? extends 上界>
<? extends Number>//可以传入的实参类型是Number或者Number的子类
示例:
class Food {
}
class Fruit extends Food {
}
class Apple extends Fruit {
}
class Banana extends Fruit {
}
class Message<T> { // 设置泛型
private T message ;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
}
public class TestDemo {
public static void main(String[] args) {
Message<Apple> message = new Message<>() ;
message.setMessage(new Apple());
fun(message);
Message<Banana> message2 = new Message<>() ;
message2.setMessage(new Banana());
fun(message2);
}
// 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
public static void fun(Message<? extends Fruit> temp){
//temp.setMessage(new Banana()); //仍然无法修改!
//temp.setMessage(new Apple()); //仍然无法修改!
System.out.println(temp.getMessage());
}
}
通配符的上界,不能进行写入数据,只能进行读取数据。
4.3.3通配符下界:
语法:<? super 下界>
<? super Integer>//代表 可以传入的实参的类型是Integer或者Integer的父类类型
示例:
class Food {
}
class Fruit extends Food {
}
class Apple extends Fruit {
}
class Plate<T> {
private T plate ;
public T getPlate() {
return plate;
}
public void setPlate(T plate) {
this.plate = plate;
}
}
public class TestDemo {
public static void main(String[] args) {
Plate<Fruit> plate1 = new Plate<>();
plate1.setPlate(new Fruit());
fun(plate1);
Plate<Food> plate2 = new Plate<>();
plate2.setPlate(new Food());
fun(plate2);
}
public static void fun(Plate<? super Fruit> temp){
// 此时可以修改!!添加的是Fruit 或者Fruit的子类
temp.setPlate(new Apple());//这个是Fruit的子类
temp.setPlate(new Fruit());//这个是Fruit的本身
//Fruit fruit = temp.getPlate(); 不能接收,这里无法确定是哪个父类
System.out.println(temp.getPlate());//只能直接输出
}
}
通配符的下界,不能进行读取数据,只能写入数据。