您的位置:首页 > 娱乐 > 八卦 > 【再探】Java—泛型

【再探】Java—泛型

2024/7/4 5:49:03 来源:https://blog.csdn.net/qq_25308331/article/details/139160858  浏览:    关键词:【再探】Java—泛型

 Java 泛型本质是参数化类型,可以用在类、接口和方法的创建中。

1 “擦除式”泛型

Java的“擦除式”的泛型实现一直受到开发者的诟病。

“擦除式”的实现几乎只需要在Javac编译器上做出改进即可,不要改动字节码、虚拟机,也保证了以前没有使用泛型的库可以之间运行在java 5.0 之上。但是这个带来了以下弊端:

  1. 例如对于List<String> 和 List<Integer> ,在运行时由于擦除了,所以这两个都变成了List,因此它们在运行中是同一类型。(而对于C#的泛型来说,无论在源码中、编译后及运行时它们始终是不同的类型)。这导致在运行时,获取不到类型信息。
  2. “擦除式”的实现,是在元素被赋值时编译器自动插入类型检查指令,访问元素时,自动插入类型强制转换指令。这样频繁的类型检查及转换,导致Java的泛型性能差于C#的泛型。
public class EraseGeneric {private static class Holder<T> {T t;public T getT() {return t;}public void setT(T t) {this.t = t;}}public static void main(String[] args) {Holder<String> holder = new Holder<>();holder.setT("hello");String str = holder.getT();}}

图 Holder类被编译后的字节码片段

图 main 方法中,对于Holder类型的赋值及访问操作字节码

1.1 擦除的补偿

如果要在运行时获取类型信息,那么可以通过引入类型标签来对擦除进行补偿。

public class TypeTagGeneric {private static class User {public User() {}}private static <T> void fun(Class<T> kind) throws InstantiationException, IllegalAccessException {T t = kind.newInstance();System.out.println(t);}public static void main(String[] args) throws InstantiationException, IllegalAccessException {fun(User.class);int[] array1 = new int[10];String[] array2 = new String[10];}}

2 协变与逆变

A 类型是B的父类型,对于某个构造器,构造出的复杂类型A`与B`。

协变

A`仍然是B`的父类型。比如Java中的数组,A[] 仍是B[]的父类型。

逆变

B`是A`的父类型。

抗变

A`与B`没有任何继承关系。例如List<A> 与List<B>没有任何继承关系。

表 协变、逆变与抗变

2.1 数组与泛型

T[] t = new T[10]; 这个代码是错误的,Java中规定不能创建泛型数组。

因为Java 在运行时,无法获取泛型的类型信息,因为在创建数组时,也就无法获取到泛型参数所表示的确切类型。

2.1.1 数组的类型

Java中数组的种类有两种:

  1. 基础类型的数组:[ + 开头大写字母。

int[] : [I

  1. 引用类型的数组:[ + L + 类型。

String[] array2 : [Ljava/lang/String

public class ArrayGeneric {private static class Fruit {}private static class Apple extends Fruit {}public static void main(String[] args) {Fruit[] fruits = new Fruit[10];Apple[] apples = new Apple[10];System.out.println(fruits instanceof Fruit[]); // trueSystem.out.println(fruits instanceof Apple[]); // falseSystem.out.println(apples instanceof Fruit[]); // trueSystem.out.println(apples instanceof Apple[]); // truefruits = apples;
//        apples = fruits; // 编译错误System.out.println(fruits.getClass().getSuperclass()); // class java.lang.ObjectSystem.out.println(apples.getClass().getSuperclass()); // class java.lang.Object
//        getSuperclass() 方法:如果此 Class 表示 Object 类、一个接口、一个基本类型或 void,则返回 null。
//        如果此对象表示一个数组类,则返回表示该 Object 类的 Class 对象。否则返回该类的超类。}}

2.2 通配符

泛型中的通配符用于在两个类型之间建立某种类型的向上转型关系。

协变

? extends T, 例如List<? extends Fruit> list。确定了元素类型的父类为Fruit,但不能确定其确切类型,因此不能往该容器添加新的元素(只能添加null)。但是可以从容器中提取元素,类型为Fruit。

逆变

? super T,例如List<? super Apple> list,确定了元素为Apple的父类,因为可以往容器中添加元素,但不能提取元素。

表 通配符的协变与逆变

public class CovarianceAndContravariance {private static class Fruit {}private static class Apple extends Fruit {}private static <T extends Apple> void setItem(List<? super Apple> list, T item) {list.add(item);}private static Fruit getItem(List<? extends Fruit> list,int pos) {return list.get(pos);}public static void main(String[] args) {List<? super Apple> list = new ArrayList<>();setItem(list,new Apple());List<? extends Fruit> list2 = Arrays.asList(new Fruit(),new Apple());Fruit item = getItem(list2, 0);}}

2.2.1 无界通配符

无界通配符,例如List<?>, 其相当于List<? extends Object>,但不等价于List(相当于List<Object>)。其有两个作用:

  1. 告诉编译器,我用了泛型,只是还没确定哪个类型;
  2. 用于捕获类型。
public class CaptureGeneric {private static class Holder<T> {}private static <T> void fun1(Holder<T> holder) {System.out.println(holder);}private static void fun2(Holder<?> holder) {fun1(holder);}public static void main(String[] args) {Holder holder = new Holder(); //  原生类型fun1(holder); // 警告,Unchecked assignment:fun2(holder); // 不会警告,无边界通配符将不会这个原生类型的类型参数(Object)}}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com