摘要:本文学习了什么是泛型,以及如何使用泛型。
环境
Windows 10 企业版 LTSC 21H2
Java 1.8
1 简介
1.1 定义
在提到参数时,最熟悉的就是定义方法时使用形参指定参数类型,然后调用此方法时传递该类型的实参。
泛型,即参数化类型,就是将参数类型由原来的具体的类型参数化,定义成参数形式(可以称之为类型形参),在使用时传入具体的类型(称之为类型实参)。
在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
1.2 体验
示例:
1 | List list = new ArrayList(); |
运行时会报异常:
1 | abc |
在实例化List的时候没有指定集合里的元素类型,使用默认的Object类型,能放下String类型和Integer类型的数据。在使用List的时候,如果不知道存放的元素类型,会将所有的数据都转换成String类型的数据,就会抛出类型转换失败的异常。
解决办法是在实例化时指定元素的类型,比如指定类型为String,那么如果存入了Integer类型的数据,在编译期间进行检查发现不是String类型的数据,就会进行错误提示。
示例:
1 | List<String> list = new ArrayList<String>(); |
1.3 特性
打印不同泛型的List的类型:
1 | List<String> strList = new ArrayList<String>(); |
说明泛型只在编译阶段有效,虽然指定了不同泛型,但他们的类型都是ArrayList。
2 使用
泛型的使用有三种形式:泛型类,泛型接口,泛型方法。
2.1 泛型类
泛型类是在实例化类的时候指明泛型的具体类型。
在使用和定义泛型类时,需要使用泛型标识来代替普通类中的类型。
语法:
1 | public class 类名<泛型标识> { |
其中类名和成员变量名都可以任意取值,泛型标识可以使用诸如T、R、E、K、V等字母作为参数。
定义泛型类:
1 | public class Generic<T> { |
使用泛型类:
1 | public static void main(String[] args) { |
2.2 泛型接口
泛型接口与泛型类的定义及使用基本相同,泛型接口常被用在各种类的生产器中。
定义泛型接口:
1 | public interface Generator<T> { |
实现泛型接口:
- 如果实现类没有指定泛型类型:
- 实现类需要按照泛型类的方式定义,实现普通方法和泛型方法。
- 普通方法不需要处理,泛型方法不需要处理。
- 示例:
java 1
2
3
4
5
6public class Generic<T> implements Generator<T> {
public T showGeneric() {
return null;
}
} - 如果实现类指定了泛型类型:
- 实现类需要按照普通类的方式定义,实现普通方法和泛型方法。
- 普通方法的泛型改为指定的泛型类型,泛型方法不需要处理。
- 示例:
java 1
2
3
4
5
6public class Generic implements Generator<String> {
public String showGeneric() {
return null;
}
}
2.3 泛型方法
泛型方法是在调用方法的时候指明泛型的具体类型。
泛型方法和普通方法的不同之处,就在于泛型方法在访问修饰符和返回类型中间有一个用于声明泛型的泛型标识,然后在参数列表中就可以使用这个泛型类型的参数了。
语法:
1 | public <泛型标识> void show(泛型标识 generic) { |
与泛型类的定义一样,此处泛型标识可以使用诸如T、E、K、V等字母作为参数,如果有多个的话使用逗号分隔。
定义泛型方法:
1 | public <T, R> void show(T t, R r) { |
使用泛型方法:
1 | public static void main(String[] args) { |
结果:
1 | class java.lang.Integer |
2.3.1 泛型类与泛型方法
泛型类中的方法不一定是泛型方法,是不是泛型方法要根据方法的访问修饰符与返回类型中间有没有泛型标识来判断。
2.3.1.1 定义普通方法
定义泛型类中的普通方法:
1 | public class Generic<T> { |
因为在实例化泛型时就明确了泛型的类型是Integer类型,所以在调用方法的时候就只能传入Integer类型的数据,否则会报错。
使用泛型类中的普通方法:
1 | public static void main(String[] args) { |
2.3.1.2 定义泛型方法
如果要在泛型类中定义泛型方法,需要在方法的访问修饰符和返回类型中间加入对泛型类型的声明。
定义泛型类中的泛型方法:
1 | public class Generic<T> { |
使用泛型类中的泛型方法:
1 | public static void main(String[] args) { |
2.3.2 可变参数泛型方法
定义可变参数的泛型方法:
1 | public class Generic<T> { |
使用可变参数的泛型方法:
1 | public static void main(String[] args) { |
结果:
1 | class [Ljava.io.Serializable; |
2.3.3 静态泛型方法
泛型类中静态方法的参数不能使用泛型,如果要使用的话,需要将静态方法定义为泛型方法。
在静态方法中使用泛型会在编译期间报错:
1 | public class Generic<T> { |
可以将静态方法声明为泛型方法:
1 | public class Generic<T> { |
3 通配符
3.1 定义
泛型类的类型和泛型类型无关,对于Generic
定义泛型类和方法,并指定方法的入参为Number类型的泛型类对象:
1 | public class Generic<T> { |
如果要想在调用方法的时候,传入任何泛型类型的参数都能使用,就需要使用通配符,使用?
代替具体的参数类型。
将方法的泛型类型换成通配符:
1 | public class Generic<T> { |
3.2 使用
3.2.2 上界
如果要限制只能是Number类及其子类,就需要设置通配符上界,将?
替换为? extends Number
。
示例:
1 | public class Generic<T> { |
3.2.3 下界
如果要限制只能是Number类及其父类,就需要设置通配符下限,将?
替换为? super Number
。
示例:
1 | public class Generic<T> { |
4 泛型数组
不能使用泛型类型创建数组:
1 | List<String>[] listArr = new List[10]; |
需要使用通配符的方式创建:
1 | List<?>[] listArr = new List<?>[10]; |
条