首先来介绍一下什么是泛型类?
泛型类就是上一节中介绍的泛型的第一个应用,即在类名的后面加上类型参数。
**问题1:当使用类型没有确定之前的泛型类和类型一旦确定之后的泛型类当作形参的区别?**比如ArrayList<T>和ArrayList<String>。
没有答案,内心依然模糊(2024.7.25)。
(2024.7.26)首先ArrayList<String>已经是一个具体的类了,根据多态只能传递本类及其子类对象,但是ArrayList<T>还是一个泛型类,依然可以为类型参数传递任意的类型值。
为类型参数指定具体类型的泛型类是原始类型的一个子类。比如,ArrayList<String>和ArrayList<Integer>都是原始类型ArrayList的一个子类型,是子类有继承关系就可以使用多态。
关于原始类型,比如ArrayList,其实就是将类型参数设置为Object。
问题2:ArrayList<String> list = new ArrayList();这行代码为什么不报错?
(2024.7.26)可能与类型擦除有关。
泛型的使用
注:为了方便,下面中的"泛型"均指代"泛型类和泛型接口"。
(1)泛型在定义的时候使用形参,但是使用泛型的时候可以传入实参(实参就是具体的类型,比如 String、Integer 等类或者自定义类型),也可以不指定具体类型。泛型的引入就是为了指定类型,不指定都失去泛型的意义,但是也不会报错,为了和以前的代码进行兼容。
(2)什么时候使用泛型呢?
大概分为两种情况:
- 定义引用变量、创建对象;
- 继承泛型类、实现泛型接口。
下面来介绍一下指定类型的情况:
1、定义引用变量和创建对象的时候指定类型
java
ArrayList<String> list = new ArrayList<String>;
// 指定具体的类型,像这里就是String来代替形参,即实参
2、实现一个泛型接口或者继承一个泛型类
(1)父类中为类型参数指定具体的类型
①派生出来的类即子类就是一个正常的类。
在继承泛型类或者实现泛型接口时,父类或者接口为类型参数指定具体的类型,那么父类或者接口中所有的型参数都被替换成具体的类型。
例如下面这个例子,Son类中的这三个方法都是从父类中继承来的,只不过类型形参T被实例化成了String。
java
class Father<T> {
T info;
public Father(T info) {
this.info = info;
}
public T get() {
return info;
}
public T set(T info) {
T oldInfo = this.info;
this.info = info;
return this.info;
}
}
class Son extends Father<String> {
// 所有从父类继承来的方法的类型参数全部都确定为String了
// 因此在覆盖的时候都要使用具体的类型实参了!
public Son(String info) {
super(info);
}
@Override
public String get() {
return "haha";
}
@Override
public String set(String info) {
return "lala";
}
}
(2)类型参数的传递
①从泛型类继续派生出泛型类,即子类还是泛型类。
将类型参数进行传递,即父类的T传递给子类继续使用,因此子类也是一个跟父类的类型参数一样的泛型类,语法如下:
java
class Father<T> { ... }
class Son<T> extends Father<T> { ... }
细节:在继承时子类后面的类型参数的和extends 父类的类型参数必须和父类定义时的类型参数一样,这是Java语法的特殊规定。
a. 以下三种情况全部错误(全部发生编译报错):
java
class Father<T> { }
class Son<E> extends Father<T> { }
class Father<T> { }
class Son<T> extends Father<E> { }
class Father<T> { }
class Son<E> extends Father<E> { }
这个时候子类和父类中的类型参数是同一个,创建子类的时候需要为子类指定具体的类型,同时会把父类中的也指定了。
Java容器中很多类/接口都是通过类型参数传递来定义的,比如:
java
public interface List<T> extends Collection<T> { ... }
当传递实参,为类型参数指定具体的类型时,上述代码就变成了:
java
public interface List<String> extends Collection<String> { ... }
其中List<String>是一个具体的类型,Collection<String>也是,继承关系也随之确定。
因此继承泛型类或者实现泛型接口的类叫做原来的泛型类的子类型,就可以使用多态了。比如,ArrayList<T>实现了List<T>接口,那么ArrayList<Manager>就是List<Manager>的子类。
先介绍一下这个图中的两条线:
- 虚线+空心三角:接口的实现
- 实线+空心三角形:类或接口之间的继承
假如在使用泛型的时候可以不使用菱形语法指定实参,直接裸用类型名。
① 定义引用(对象)时裸用类名:
java
ArrayList list = new ArrayList();
// 当然也可以写成ArrayList list = new ArrayList<>();
②实现/继承:
java
public class MyType extends MyGeneric { ... }
上面使用的类型或者接口在定义的时候都是泛型,但是在使用它们的时候忽略类型参数(都不用加菱形)。
其实底层依然是一个泛型,那么它的类型实参会是什么呢?
如果使用泛型的时候没有显式不指定类型实参,那么Java会隐式地用该泛型的"原始类型"来作为类型实参传入!
那么"原始类型"会是什么呢?
Java集合的原始类型基本都是Object,因此像上面的ArrayList list = new ArrayList();写法其实传入的是Object类型实参,即ArrayList<Object>!
参考博客:Java泛型:泛型的定义(类、接口、对象)、使用、继承 - IT张先生 - 博客园 (cnblogs.com)
**如何在两个泛型类之间建立更多的子类型的关系?**就需要使用通配符了。