泛型

1、泛型

把明确类型的工作推迟到创建对象或调用方法的时候,可以把运行时的问题提前到编译时期。提供了编译期的类型安全,确保把正确类型的对象放入集合中,避免了在运行时出现 ClassCastException 异常

  • 可以明确集合中的数据类型,提高安全性、可读性,可以使用增强 for 循环遍历
  • 代码更加简洁,不需要强制转换
  • 程序更加健壮,只要编译时期没有警告,那么运行时期就不会出现 ClassCastException 异常

1.1 通配符

本质上这些都是通配符,没有区别,只有字面意思,只是一个约定的规范,Test<T>Test<A> 在执行效果上并没有什么区别

  • T:代表一个具体的 Java 类型
  • E:代表 Element 的意思,或者 Exception 异常的意思
  • K:代表 Key 的意思,通常与 V 一起配合使用
  • V:代表 Value 的意思,通常与 K 一起配合使用
  • S:代表 Subtype 的意思
  • ?:无限定的通配符,代表不确定的 Java 类型
    • 主要针对泛型类的限制, 无法像 T 类型参数一样单独存在
  • ? extends E设定通配符上限,接收 E 类型或者 E 的子类型
  • ? super E设定通配符下限,接收 E 类型或者 E 的父类型

1.2 泛型的使用

1.2.1 泛型类

用于类的定义中,用户使用该类的时候,才把类型明确下来。通过泛型可以完成对一组类的操作对外开放相同的接口,如 List、Set、Map 等容器类。泛型类的子类,如果有明确的类型参数,需将用泛型的地方全部替换成传入的实参类型,否则需将泛型的声明也一起加到类中。类上声明的泛型只对非静态成员有效,如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法

public class Test<T> {
    private T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

    public static void main(String[] args) {
        Test<Integer> test = new Test<>();
        test.setT(1);
        System.out.println(test.getT());
    }
}
  • 继承泛型类的子类,未传入泛型实参时,在声明类的时候,需将泛型的声明也一起加到类中。如果不声明泛型,编译器会报错
class One<T> extends Test<T> {}

class Two<String> extends Test<T> {}	// 报错

class Three extends Test<T> {}			// 报错

class Four<T> extends Test<String> {}

class Five<String> extends Test<String> {}

class Six extends Test<String> {}

class Seven extends Test {}

1.2.2 泛型接口

与泛型类相似

public interface Test<T> {
    T doAnything();
}

public class One<T> implements Test<T> {
    T doAnything();
}

// 当实现泛型接口的类,传入泛型实参时,需将用泛型的地方全部替换成传入的实参类型
public class One<String> implements Test<String> {
    String doAnything();
}

1.2.3 泛型方法

在调用方法的时候指明泛型的具体类型,类型参数写在返回值前面,声明的类型参数,还可以当作返回值的类型

public class Test {
    // 只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法
	public <T> T method(T t){
        return t;
    }
    // 静态方法使用泛型,必须将静态方法也定义成泛型方法
    public static <T> void staticMethod(T t) {
        System.out.println("static方法");
    }
    
    public static void main(String[] args) {
        Test test = new Test();
        System.out.println(test.method("fuck"));
        System.out.println(test.method(12));
        staticMethod("fuck");
        staticMethod(12);
    }
}

1.3 泛型擦除

泛型是通过类型擦除来实现的,泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除

List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();

System.out.println(l1.getClass() == l2.getClass());	// true

无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型就是擦除了泛型信息,最后在字节码中的类型变量的真正类型。擦除(erased)类型变量,并替换为限定类型(无限定的变量用 Object)

1.3.1 局限性

泛型擦除,是泛型能够与之前的 Java 版本代码兼容共存的原因。但也因为类型擦除,它会抹掉很多继承相关的特性,这是它带来的局限性

List<Integer> list = new ArrayList<>();
list.add(1);
// 因为泛型的限制,以下代码会报错,基于对类型擦除的了解,利用反射,就可以绕过这个限制
// list.add("String");list.add(27.12);会报错
Method method = list.getClass().getDeclaredMethod("add", Object.class);
method.invoke(list, "String");
method.invoke(list, 27.12);

for (Object o : list) {
    System.out.println(o);	// 成功打印
}

1.4 可以把 List<String> 传递给一个接受 List<Object> 参数的方法吗

会导致编译错误,因为 List<Object> 可以存储任何类型的对象包括 String、Integer 等,而 List<String> 却只能用来存储 String

public class Test {

    public static void main(String[] args) {
        Test test = new Test();
        List<String> list = new ArrayList<>();
        // 编译错误
        test.demo(list);
    }

    public void demo(List<Object> list) {
        System.out.println("success");
    }
}
  • 有两种解决办法
    • 将 Test 设为泛型类
    • 将 demo 方法中的泛型 <Object> 改为 <?>

1.5 Class<T>Class<?> 的区别

Class<T> 在实例化的时候,T 要替换成具体类,Class<?> 是个通配泛型,? 可以代表任何类型,主要用于声明时的限制情况

1.6 PECS 原则

  • 如果要从集合中读取类型 T 的数据,并且不能写入,可以使用 ? extends 通配符(Producer Extends)
  • 如果要从集合中写入类型 T 的数据,并且不需要读取,可以使用 ? super 通配符(Consumer Super)
  • 如果既要存又要取,那么就不要使用任何通配符

1.7 泛型数组

Java 不能创建具体类型的泛型数组

// 这两行代码是无法在编译器中编译通过的。原因是类型擦除带来的影响
List<Integer>[] list = new ArrayList<Integer>[];
List<Boolean> list = new ArrayList<Boolean>[];

List<Integer>List<Boolean> 在 JVM 中等同于 List<Object>,所有的类型信息都被擦除,程序也无法分辨一个数组中的元素类型具体是 List<Integer> 类型还是 List<Boolean> 类型

List<?>[] list = new ArrayList<?>[10];
list[1] = new ArrayList<String>();
List<?> v = list[1];

借助于无限定通配符却可以,?代表未知类型,所以它涉及的操作都基本上与类型无关,因此 JVM 不需要针对它对类型作判断,因此它能编译通过

更多:泛型就这么简单java 泛型详解Java 泛型,你了解类型擦除吗JAVA泛型通配符T,E,K,V区别面试常问的PECS原则,到底是什么鬼?Java泛型解惑之上下通配符

posted @ 2020-09-15 00:04  holyholic704  阅读(175)  评论(0)    收藏  举报