一、泛型概述
- 泛型指的是“参数化类型”
- 即把某些参数的数据类型使用一个泛型代替,在使用或调用的时候传入具体的数据类型赋值给泛型
- 使用方式
- 泛型定义在尖括号中,可以同时定义多种泛型,使用逗号隔开
- 如<T>、<T,V>
- 泛型定义在尖括号中,可以同时定义多种泛型,使用逗号隔开
二、泛型的类别
1、泛型类
- 在定义类的时候,在类名后定义泛型
- 用的比较多
- jdk1.7之后,new对象时后面的尖括号内的泛型可以省略,只需要在定义变量数据类型后定义泛型即可
- 举例
//定义一个类,使用泛型
public class Test<T>{
//数据的参数类型由泛型指定
public T data;
public T getData(){
return this.data;
}
}
class Demo{
public static void main(String[] args){
Test<String> t = new Test<>();
//或者
Test<String> t = new Test<String>();
}
}
2、泛型接口
- 在定义接口时,把泛型写在接口名后面
- 实现类在实现接口时有两种定义方式
- 指定泛型的类型
- 创建实现类实例的时候无序再定义泛型
- 保留泛型
- 定义实现类时,类名后也需要加上泛型
- 创建实现类实例时需要指定泛型的具体类型
- 指定泛型的类型
- 举例
public class Demo1 {
public static void main(String[] args) {
//t1无序指定数据类型,数据类型是String
Test1 t1 = new Test1();
t1.data = "hello";
System.out.println(t1.say());
//t2需要指定具体的数据类型
Test2<String> t2 = new Test2();
t2.data = "hello";
System.out.println(t2.say());
}
}
//定义泛型接口
interface Test<T>{
T say();
}
//实现泛型接口方式一:指定泛型的类型
class Test1 implements Test<String>{
public String data;
@Override
public String say() {
return data;
}
}
//实现泛型接口方式二:保留泛型
//类名后也需要定义泛型
class Test2<T> implements Test<T>{
public T data;
@Override
public T say() {
return data;
}
}
3、泛型方法
- 语法
权限修饰符 (static)泛型 返回值类型 方法名 (参数列表){
}
- 泛型方法的泛型只在方法的区域内有效
- 举例
public class Demo2 {
public static void main(String[] args) {
A.say("hello");
A.say(123);
}
}
class A{
public static <T> void say(T a){
System.out.println(a);
}
}
三、泛型限制类型
- 在使用泛型时,可以指定泛型的限定区域
- 语法
< T extends 类或接口1 & 接口2 >
- 举例
public class Demo3 {
public static void main(String[] args) {
// Plate<String> p1 = new Plate<String>(); //报错
Plate<Apple> p2 = new Plate<>(); //不报错
}
}
//定义水果接口
interface Fruit{}
//苹果类实现水果接口
class Apple implements Fruit{}
//盘子类,泛型T只能是Fruit或其子类
class Plate<T extends Fruit>{}
四、泛型的通配符
- 泛型的通配符指的是“?”,用于代替方法具体的类型实参
< ? extends Parent> 指定泛型的上界,即?只能是Parent或其子类
在创建对象的时候,new后面的泛型也需要定义具体的数据类型
< ? super Child> 指定泛型的下界,即?只能是Child或其父类
在创建对象的时候,new后面的泛型也需要定义具体的数据类型
< ? > 指定了没有限制的泛型类型,在调用的时候指定具体的参数类型
- 举例
package fanxing;
public class Demo3 {
public static void main(String[] args) {
// Plate<Fruit> p = new Plate<Apple>(); //报错,除非改为下面的写法
//泛型上界,后面的泛型必须是Fruit或其子类
Plate<? extends Fruit> p1 = new Plate<Apple>();
//泛型下界,后面的泛型必须是Apple或其父类
Plate<? super Apple> p2 = new Plate<Fruit>();
}
}
//水果接口
interface Fruit{}
//苹果类实现水果接口
class Apple implements Fruit{}
//盘子类
class Plate<T>{}
五、泛型的作用
- 提高代码的复用率
- 比如在方法重载中,可能需要重载大量的方法,这些重载方法仅仅是参数类型不同。这个时候可以使用泛型替代重载,只需要将入参的数据类型变成泛型即可。因此可以提高代码的复用率,避免写过多重复的代码
- 泛型中的类型在使用时指定,不需要额外的强制类型转换操作
- 类型安全,编译器会检查类型是否有误
六、注意事项
- 在编译的过程中,正确检验泛型结果后,会将泛型的相关信息擦除,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法
- 也就是说泛型只在编译期间有效,不会进入到运行时阶段