1 定义:申明中存在类型参数(type parameter)的“类”、“接口”、“方法”,称为泛型。
1.1泛型类
1.1.1 申明
public class Apple<T> {
private T t;
public Apple(T t) {
this.t = t;
}
public T getInfo() {
return t;
}
}
当泛型定义的类型参数T后,参数T可以在类申明中使用(包括属性、方法中)
1.1.2 实例化泛型必须要指定类型参数的具体类型(这也是类型参数不能在静态域的原因)
Apple<String> apple = new Apple<>("red");
1.1.3 泛型继承(继承分为两种:指定类型参数,不指定类型参数)
public class AppleSon1 extends Apple<String>{
public AppleSon1(String t) {
super(t);
}
@Override
public String getInfo() {
return super.getInfo();
}
}
从重写父类的方法可以看出,继承指定参数类型后,实际已经将类型参数的具体类型String告知了编译器
public class AppleSon2 extends Apple{
public AppleSon2(Object t) {
super(t);
}
@Override
public Object getInfo() {
return super.getInfo();
}
}
AppleSon2 son2 = new AppleSon2(new Some())
son2.getInfo()在编译时,编译器只知道是一个Object(但是实际运行时还是Some),需要强制转换为一个Some,才能使用Some的方法。
参数类型T不能在静态域中,原因显而易见:泛型参数是实例化才能使用的!
运行时有:
List<String> list1 = new ArraryList<String>(), List<Integer> list2 = new ArraryList<Integer>()
总有list1.getClass() == list2.getClass()成立,因为list1和list2的类型都是java.util.ArrayList,和类型参数半点关系没有
1.2 泛型接口
1.3 泛型方法
1.3.1 泛型方法定义
<T,S,R> 返回类型 方法名(含有T、S、R的参数列表)
public static <T,R> R fun(T t, R r) {
List<T> list = new ArrayList<>();
list.add(t);// 指定类型参数T,编译器认为集合类型是已知,可以存入T
return r;
}
调用:
String s = "ssss";
Integer i = 1;
Integer result = fun(s,1);
本质是由编译器根据s类型,i类型 以及类型参数列表参数位置,自动推断出类型
1.3.2 编译器推断
public static <T> T fun(T t1, T t2){}
Integer i
String s
fun(i,s)
尽管t1和t2的实际类型不同,参数类型都是T,故而编译器自动推断T为Object
如果有:
String s1, s2
fun(s1,s2)
编译器自动推断T为String类型
List<String> list1
List<Integer> list2
fun(list1, list2)
编译器自动推断为List<? extends Object>
如果
<T> void fun(List<T> list1, List<T> list2)
List<String> list1
List<Integer> list2
fun(list1,list2)
出现编译错误,把编译器搞懵逼了,因为list1和list2是两个完全不同类型,泛型方法的类型参数是两个相同类型。
注意:即使是两个list中的元素具有继承关系,编译器也无法推断:
List<Object> list1
List<String> list2
fun(list1,list2)
编译器推断错误,需要使用通配符?改造:
<T> void fun(list<T> t1, list<? extend T> t2)
编译器自动推断 List<Object> 和 List<? extends Object>
泛型方法和泛型类、接口最大区别是,调用时候前者需要编译器自动推断参数类型是否匹配,后者需要程序员指定类型
如果将List<E>认为是泛型,在定义另一个泛型类A,将List作为方法参数时,下面等价的
class A<T>{
void fun(List<? extends T> t)
}
VS.
class A<T>{
<E extends T> void fun(List<E> t)
}
几乎效果相同,但是建议如果集合的参数类型之间没有依赖关系,参数与返回没有依赖关系,使用通配符
1.4 类型参数上限
public class Apple<T extends Number> {
private T t;
public Apple(T t) {
this.t = t;
}
public T getInfo() {
return t;
}
}
在使用或者继承Apple时,指定的泛型参数不是Number的子类,会报编译错误,提高代码安全性
2 通配符?
2.1在使用泛型时,如果不指定参数类型,编译器会发出警告(注意不是报错,主要提示用户防止误使用运行时类型犯错)
void test(List list); 会存在编译警告
2.2但是注意编译器进行类型检查时,List<Object>和List<String> 以及 ArraryList<Object>和ArraryList<String>是完全不同两种类型(与运行类型区别:.getClass()拿到运行时类型)
void test(List<Object> list);
List<String> list = new ArraryList<>();
test(list);会有编译错误,编译器任务List<Object>和List<String>是完全不同的类型
2.3 类型通配符号应运而生
void test(List<?> list);
List<String> list = new ArraryList<>();
test(list);完全正确,List<?> 是所有List<String>,List<Integer>的父类
2.4 通配符上限
只想要通配符表示一部分泛型类型的父类
定义:void test(List<? extends Number> list);
那么 List<Integer> list1 和 List<Long> list2 可以作为参数,List<String>作为参数非法
注意:通配符?任然是不能确定泛型类型的
void test(List<?> list){
list.add(new Object())// 报错,这时的泛型任然是未知类型,不能存入,只能通过参数类型化泛型化解决
}
如:
void test(List<T> list, T t){
list.add(t)
}
2.5 通配符下限
需求从src把数据拷贝到des中,为了通用性,des中元素保证是src中元素的父类,有以下两种方法
T copy1(List<? extends T> src, List<T> des)
T copy2(List<T> src, List<? super T> des)
List<Integer> res
List<Number> des
Number result1 = copy1(res, des);
Integer result2 = copy2(res, des);
好处:copy2返回值能拿到更准确的子类
2.6 使用通配符?定义方法参数List<?> list,不能在方法内部操作?,只能将list作为一个整体操作
void fun(List<?> list){
? a = list.get(0); // 错误
int size = list.size() // 正确
}