基本概念
通常集合中可以存放不同类型的对象,是因为将所有对象都看做
Object
类型放入的,因此从集合中取出元素时也是Object
类型,为了表达该元素真实的数据类型,则需要强制类型转换,而强制类型转换可能会引发类型转换异常。
为了避免上述错误的发生,从Java5开始增加泛型机制,也就是在集合名称的右侧使用<数据类型>
的方式来明确要求该集合中可以存放的元素类型,若放入其它类型的元素则编译报错。
import java.util.ArrayList;
import java.util.List;
public class GenericDemo {
public static void main(String[] args) {
//1.准备支持泛型机制的list集合 明确集合中元素是String乐境
List<String> strlist = new ArrayList<String>();
//2.集合中添加元素打印
strlist.add("hello");
strlist.add("list");
//3.集合中添加其它类型元素编译标红
// strlist.add(1);
//打印
System.out.println(strlist);//[hello, list]
//获取元素
String s = strlist.get(1);
System.out.println("获取元素为:" + s);//获取元素为:list
}
}
- 泛型只在编译时期有效,在运行时期不区分是什么类型。
菱形特性
- java7后开始的新特性
- 对应的等号后面
<>
声明类型可以省略「反正写也是和前面保持一致」- 前后
<>
不能省略 - 前面
<>
内类型不能省略 - 后面
<>
内类型能省略
- 前后
底层原理
泛型的本质就是参数化类型,也就是让数据类型作为参数传递,其中E相当于形式参数负责占位,
而使用集合时<>
中的数据类型相当于实际参数,用于给形式参数E
进行初始化,让集合中所有的E
被实际参数替换,
由于实际参数可以传递各种各样广泛的数据类型,因此为泛型
可以看到对应的
List
及其实现类还有Collection
都有<E>
,对应的方法里面参数和返回值也是<E>
, 这个E
相当于形式参数负责占位
public static void show(int i) {
...
}
show(10);
- 实际参数为10
- 负责给形式参数初始化
i
为形式参数,负责占位
show(10)相当于调用show方法的时候把10传给了i;后面方法里用到i的时候对应的值就是10
int i = 10
String
叫做实际参数E
叫做形式参数,负责占位- 一旦被
String
替换后,后面的E都替换成了String
类型
- 一旦被
E = String;
public interface List {
...
}
List<String> lt1 = ...;
这里只是说了String类型,也可以传入Integer、Person等类型,这就是我们说的泛型
自定义泛型接口
泛型接口和普通接口的区别就是后面添加了类型参数列表,可以有多个类型参数,如:
自定义泛型类
泛型类和普通类的区别就是类名后面添加了类型参数列表,可以有多个类型参数,如:
实例化泛型类时应该指定具体的数据类型,并且是引用数据类型而不是基本数据类型。
/**泛型实体类
* 自定义泛型类,T相当于形式参数负责占位,具体数值由实参决定
* T 看做是一种名字为T的数据类型即可
*/
public class Animal<T> {
private String name;
private int age;
private T sex;
//get,set toString 构造方法
public T getSex() {
return sex;
}
public void setSex(T sex) {
this.sex = sex;
}
}
对应声明的时候,可以直接声明,就像Collection集合和List集合声明的时候一样。
对应的声明完成后,里面可以放的类型是Object,同样Animal如果没有<>
的声明,
对应的sex类型就是Object
//list集合声明的时候可以带参
Animal animal = new Animal("小黑",1,"女生");
System.out.println(animal);//Animal{name='小黑', age=1, sex=女生}
- 创建对象的同时指定数据类型,这样用于给T进行初始化
Animal<Integer> animal1 = new Animal();
animal1.setSex(1);
System.out.println(animal1);//Animal{name='null', age=0, sex=1}
和
linkList
一样使用,指定的时候就是具体的类型,不指定就是Object
类型
父子类泛型
- 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型。
- 子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型。
示例
不保留泛型并且没有指定类型
- 丢弃泛型
SubAnimal继承的时候没有把Animal的泛型保留下来,Animal里面的T就默认为是Object类型
- 没有指定类型时就会默认解析为Object
//此时Animal类的泛型默认为Object类型 也叫擦除
public class SubAnimal extends Animal{
}
//声明SubAnimal类型的引用指向SubAnimal并调用set方法进行测试
//SubAnimal<String> sa = new SubAnimal();//SubAnimal不支持泛型
SubAnimal sa = new SubAnimal();
//sa.setSex();//Object类型
sa.setSex("女生");
子类虽然继承父类,但是对应泛型没有继承过来,声明的时候不能
<>
指定对应泛型类型。
直接声明,对应的类,泛型T默认为Object。可以setSex看到对应参数提示为Object
不保留泛型但是指定了泛型类型
//这个时候Animal类的T被指定为String类型
public class SubAnimal extends Animal<String> {
}
//声明SubAnimal类型的引用指向SubAnimal并调用set方法进行测试
//SubAnimal<String> sa = new SubAnimal();//SubAnimal不支持泛型
SubAnimal sa = new SubAnimal();
//sa.setSex();//String类型
sa.setSex("女生");
直接声明,对应的类,泛型T默认为String类型。可以setSex看到对应参数提示为String
保留父类的泛型T
public class SubAnimal<T> extends Animal<T> {
}
SubAnimal<String> sa = new SubAnimal();
sa.setSex("女");//String
增加自己的泛型
public class SubAnimal<T, E> extends Animal<T> {
}
SubAnimal<Boolean,Integer> sa = new SubAnimal<>();
sa.setSex(true);
自定义泛型方法
泛型方法就是我们输入参数的时候,输入的是泛型参数,而不是具体的参数。
我们在调用这个泛型方法的时需要对泛型参数进行实例化。
泛型方法的格式
[访问权限] <泛型> 返回值类型 方法名([泛型标识 参数名称]) { 方法体; }
在静态方法中使用泛型参数的时候,需要我们把静态方法定义为泛型方法。
public static <T1> void show(T1[] a){
for (T1 t:a){
System.out.println("t:"+t);
}
}
- 参数类型是
T1
,自定义的类型。需要在方法返回值前面声明对应自定义的类型。
T1不是官方支持的类型,也不是我们自定义的类型。对应的不认识T1。这是一个泛型类型不是具体参数类型,所以要在返回值前面加上一个T1。这里就是表示是一个泛型。但是要注意,方法的T1和类上面的T是两种不同的概念
- 调用泛型方法
Integer[] arr = {11,22,44,55,99};
Animal.show(arr);
//这不是泛型方法
public T getSex() {
return sex;
}
- 在对象声明以后才能调用,泛型方法static修饰的就不需要实例化对象
- 不是泛型方法,该方法不能用static修饰,因为T需要在new对象时才能明确类型
泛型在继承上的体现
如果B是A的一个子类或子接口,而G
是具有泛型声明的类或接口,则G<B>
并不是G<A>
的子类型!
比如:String
是Object
的子类,但是List<String>
并不是List<Object>
的子类。
- 创建一个父类
public class Person {
}
- 创建一个子类继承父类
public class Man extends Person{
}
G<B>
并不是G<A>
的子类型
public class GenericDemo {
public static void main(String[] args) {
List<Person> lp = new ArrayList<>();
List<Man> lm = new ArrayList<>();
// lp = lm;
}
}
他们之间不具备公共关系,Person、Man对应的是否有公共的父类,为了找到对应的父类,就会有了一个公共的通配符。通配符有以下3种形式:
通配符的使用
有时候我们希望传入的类型在一个指定的范围内,此时就可以使用泛型通配符了。
如:之前传入的类型要求为Integer
类型,但是后来业务需要Integer
的父类Number
类也可以传入。
泛型中有三种通配符形式
<?>
- 无限制通配符
- 表示我们可以传入任意类型的参数。
?类似我们导包的时候的*,代表的是任意的。就是无限制的;指定任意的类型,不是任意类型的对象
- 不支持添加,支持获取
public class GenericDemo {
public static void main(String[] args) {
List<Person> lp = new ArrayList<>();
List<Man> lm = new ArrayList<>();
lm.add(new Man());
//试图将lm的数值赋值给lp,也就是发生List<Man>类型向List<Person>类型的转换
// lp = lm;Error: 类型之间不具备父子类关系
// 使用通配符作为泛型类型的公共父类
List<?> lt3 = new LinkedList<>();
lt3 = lp; // 可以发生List<Person>类型到List<?>类型的转换
lt3 = lm; // 可以发生List<Man>类型到List<?>类型的转换
System.out.println(lt3);
Object o = lt3.get(0); // ok,支持元素的获取操作,全部当做Object类型来处理
System.out.println("o:"+o);//打印man对象地址
}
}
<? extends E>
- 表示类型的上界是E,只能是E或者是E的子类。
- 不支持添加
//使用有限制的通配符
List<? extends Person> lp1 = new ArrayList<>();
//不支持元素添加
//lp1.add(new Person());
//lp1.add(new Man());
//lp1.add(new Object());
- 支持获取,获取到的是父类及其子类 类型
//可以获取元素 获取到的是Person 及其子类
Person person = lp1.get(0);
<? super E>
- 表示类型的下界是E,只能是E或者是E的父类。
- 支持添加,添加的是Animal及其父类,但是有可能只是Animal所以添加Object失败
- 可以获取,获取到的就是ObjecObject类型
List<? super Person> lp2 = new ArrayList<>();
lp2.add(new Person());
lp2.add(new Man());
//lp2.add(new Object());//超过Animal类型
Object object = lp2.get(0);