一.为什么需要泛型?
- 使用编译时提示来解决运行时类型转换异常!
- 使得程序更加灵活复用。
二.泛型限定和通配符
1.通配符
泛型只是在编译时存在,运行时擦除的,所以泛型并没有继承,本质上是一个class,如List和 List作为参数时其实是一种类型,如下面的代码,编译器就会提示我们报错。
public void testGeneric(List<Number> numberList){
}
public void testGeneric(List<Integer> list){
}
但是需要注意的是,List中可以添加Integer类型,因为他们是Number和Integer父子关系,但是List和List却不是。
我们使用通配符?来表示任意不确定类型,List<?>可以传入任意类型,
@Test
public void showList(){
List<Integer> list=new ArrayList<>();
list.add(1);
list.add(2);
testShow(list);
List<String > listStr=new ArrayList<>();
listStr.add("one");
listStr.add("two");
testShow(listStr);
}
public void testShow(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
// list.add("1");//不可以添加,因为不确定它的类型
}
List<?>这个和List有什么区别?
通配符?标识的是任意类型,比如你传入的是Integer,它就是Integer,如果你传入的是String,他就是String,而Object就比较刚性,我们来看下下面的代码: 这段代码就会报错,提示参数的泛型是Object,无法传递。 泛型的extends和super可以约束泛型的上界和下界,extends可以使用在方法上、类上、也可以在参数上,super一般放在参数和方法限定泛型上。 前面我们学习到了参数类型可以加通配符?来让参数类型更加灵活,我们还可以搭配extends和super来确定上下界,这个就是PECS,我们来看代码: 上面这段就是上界搭配通配符限定泛型类型参数只可以是Number的子类,并且我们只能从里面获取,所以它是Produce Extends,下面我们再来看看通配符和super搭配使用作为形参时的特性: 如代码展示,下界和通配符搭配使用时,可以添加其类型及其子类,因为不涉及到向上转型,这种只能存入的,我们称之为Consumer Super,和Produce Extends合称为PECS规则。 1.泛型类中约束上界 2.泛型方法中约束泛型参数上界 父类是泛型,子类可以是泛型,可以不是泛型,如果不是需要指定父类泛型的类型。 同泛型类 在返回值前使用 public void show(T t){} 无边界的它会以Object形式在字节码中,右边界会以边界的类型存在在字节码中。 1.无法在运行时获取泛型类型信息 泛型擦除,会让我们在需要反射创建对象时无法获取到泛型的真实类型,我们可以通过在构造方法中传入泛型类型或者利用java中当一个类继承了一个带有泛型的父类时,这个泛型的具体类型信息会被保留在子类的字节码的常量池中 。 2.泛型数组的创建 Java 数组是协变的,即如果 泛型数组的创建受到限制:不能直接创建泛型数组,因为泛型擦除会导致数组在运行时无法确定其元素的具体类型,可能会引发类型安全问题。 可能有点不好理解,我们反过来来看,如果允许创建泛型数组,编译器无法在编译时阻止上述类型错误的赋值操作,因为类型擦除后, @Test
public void showList2() {
List<Integer> list=new ArrayList<>();
list.add(1);
list.add(2);
testShow2(list);
List<String > listStr=new ArrayList<>();
listStr.add("one");
listStr.add("two");
testShow2(listStr);
}
public void testShow2(List<Object> list) {
for (Object obj : list) {
System.out.println(obj);
}
// list.add("1");//不可以添加,因为不确定它的类型
}
2.泛型限定
1.PECS
@Test
public void showList() {
List<Integer> list=new ArrayList<>();
list.add(1);
list.add(2);
testShow(list);
}
//上界 只可以从里面读取,因为是父类接受,不会是上转型(kt中的out)
public void testShow(List<? extends Number> list) {
for (Number obj : list) {
System.out.println(obj);
}
//list.add(1);//不可以添加,因为不确定它的类型
}
@Test
public void showList2() {
List<P> list=new ArrayList<>();
// List<S> list=new ArrayList<>(); 不行 show2参数不匹配
list.add(new S());//这个可以添加
list.add(new S());
testShow2(list);
}
//下界,泛型参数至少是P及其父类
// 可以添加下界的子类包括它,因为它的参数类型至少是它自己或者其父类
//当然可以接受其子类,不涉及到向上转型,安全
public void testShow2(List<? super P> list) {
list.add(new P());//不可以添加,因为不确定它的类型
list.add(new S());//不可以添加,因为不确定它的类型
}
2.通配符的其他应用
static class P {
}
static class S extends P {
}
//上届搭配泛型类,约束泛型范围
static class Gen<T extends P>{
T t;
public Gen(){
}
public void attach(T t){
this.t=t;
}
}
@Test
public void test(){
Gen<P> pGen = new Gen<>();
}
public <T extends Number> void show(T t){
System.out.println(t);
}
三.泛型类、接口与方法。
1.泛型类
static abstract class P<T,V>{
abstract V app(T t);
}
//1.子类不是泛型类 需要指明父类泛型类型
static class S extends P<String,Integer>{
@Override
Integer app(String s) {
return Integer.parseInt(s);
}
}
//2.子类是泛型类,参数带到父类中去
static class S2<K,V> extends P<K,V>{
@Override
V app(K k) {
return null;
}
}
@Test
public void testGenClz(){
System.out.println(new S().app("1"));
}
2.泛型接口
3.泛型方法
<T> 声明的方法才是泛型方法,即使该方法属于泛型类。如:
四.泛型擦除
1.泛型擦除
2.擦除的弊端
Sub 是 Super 的子类,那么 Sub[] 也是 Super[] 的子类。这意味着可以将一个 Sub[] 类型的数组赋值给一个 Super[] 类型的变量。class Fruit {}
class Apple extends Fruit {}
Apple[] apples = new Apple[5];
Fruit[] fruits = apples; // 合法,数组的协变特性
// 运行时会抛出 ArrayStoreException 异常
fruits[0] = new Orange();
}
// 错误示例,不能直接创建泛型数组
List<String>[] stringListArray = new List<String>[10];
// 正确示例,可以使用通配符创建数组 通配符标识任意类型,但是你要确定好
// List<?>[] listArray = new List[10];
List<String>[] 和 List<Integer>[] 在运行时都是 List[] ,这会破坏泛型的类型安全保证。import java.util.ArrayList;
import java.util.List;
// 假设这是合法的
// List<String>[] stringLists = new List<String>[5];
List<Integer> intList = new ArrayList<>();
intList.add(10);
// 由于数组协变,如果泛型数组合法,下面的赋值可能会通过编译
// stringLists[0] = intList;
// 后续从 stringLists[0] 中获取元素时,会导致类型错误
// String str = stringLists[0].get(0