简单理解java泛型的本质(非类型擦除)

作者:彤哥读源码 时间:2023-10-13 03:54:34 

背景

之前在网上发现这个问题


public class GenericTest {
//方法一
public static <T extends Comparable<T>> List<T> sort(List<T> list) {
return Arrays.asList(list.toArray((T[]) new Comparable[list.size()]));
}
//方法二
public static <T extends Comparable<T>> T[] sort2(List<T> list) {
// 这里没报错
return list.toArray((T[]) new Comparable[list.size()]);
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
// 方法一调用正常
System.out.println(sort(list).getClass());
// 方法二调用报错了,这里报错了
System.out.println(sort2(list).getClass());
}
}

这个问题有以下四个现象:

(1)方法一调用完全正常;

(2)方法二调用报错了;

(3)方法二报错的地方是在System.out.println(sort2(list).getClass());这行,而不是return list.toArray((T[]) new Comparable[list.size()]);这行;

(4)报的错是[Ljava.lang.Comparable; cannot be cast to [Ljava.lang.Integer;;

怎么样?你心中有答案嘛?类型擦除?怎么擦?摩擦摩擦?

解决

刚拿到这道题,我也是一脸懵逼,这要报错也应该是在return list.toArray((T[]) new Comparable[list.size()]);这行啊,而且要报错应该两个方法都报错啊。

抱着不放弃不抛弃的心态,彤哥做了大量的实验,终于得出了泛型的本质,且听我娓娓道来。

小插曲

首先,我们要明白,java中的数组是不支持向下转型的,但是如果本身就是那个类型的是可以转过去的,请看下面的例子:


public static void main(String[] args) {
Object[] objs = new Object[]{1};
// 类型转换错误
// Integer[] ins = (Integer[]) objs;
Object[] objs2 = new Integer[]{1};
// 不报错
Integer[] ins2 = (Integer[]) objs2;

}

java里的泛型是假泛型,只在编译期有效,在运行时是没有泛型的概念的,举个简单的例子:


public static void main(String[] args) {
List<String> strList = Arrays.asList("1");
List<Integer> intList = Arrays.asList(1);
// 打印:true
System.out.println(strList.getClass() == intList.getClass());
}

可以看到两个list的类型是一样的,如果你觉得这个例子不够说服力,那我给你个过分点的例子:


public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
List<String> strList = new ArrayList<>();
Method addMethod = strList.getClass().getMethod("add", Object.class);
addMethod.invoke(strList, 1);
addMethod.invoke(strList, true);
addMethod.invoke(strList, new Long(1));
addMethod.invoke(strList, new Byte[]{1});

// 打印:[1, true, 1, 1]
System.out.println(strList);
}

瞧,我可以往一个String类型的List中扔任何我想扔的东西,服不服?!

所以说java里面的泛型是假的,运行时不存在滴。

回归正题

数组不能向下强转我懂了,类型擦除我也懂了,似乎还是过不好这一生,呃不是,是还是解决不了这道题啊?

呃,好像是~~

我们再来看一个简单的例子:


// GenericTest2.java(源码)
public class GenericTest2 {
public static void main(String[] args) {
System.out.println(raw("1"));
}
public static <T> T raw(T t) {
return t;
}
}
// GenericTest2.class(反编译)
public class GenericTest2 {
public GenericTest2() {
}
public static void main(String[] args) {
System.out.println((String)raw("1"));
}
public static <T> T raw(T t) {
return t;
}
}

嗯~似乎看出来点端倪,反编译后多了个构造方法。

呃,没错。还有呢?

仔细一看,System.out.println((String)raw("1"));这一句多加了个String强转。

这就是关键所在,结合类型擦除,运行时并没有所谓的泛型,所以raw()返回的其实是Object,但是调用者自己知道我要的是String类型啊,所以我就知道强转一下喽。

我们再来看个极端的例子:


// GenericTest2.java(源码)
public class GenericTest2 {
public static void main(String[] args) {
System.out.println(raw("1"));
}
public static <T> T raw(T t) {
return (T)new Integer(1);
}
}
// GenericTest2.class(反编译)
public class GenericTest2 {
public GenericTest2() {
}
public static void main(String[] args) {
System.out.println((String)raw("1"));
}
public static <T> T raw(T t) {
return new Integer(1);
}
}

仔细观察,可以发现,raw()方法里的强转(T)new Integer(1)变成了new Integer(1),强转被擦除了,实际上在运行时这里的T变成了Object,所有类型都是Object的子类,也就不需要强转了。

而(String)raw("1")的强转还是加上的,这是调用者知道类型是String,所以raw()返回后自己强转成String一下。

当然,这个代码运行是会报错的,java.lang.Integer cannot be cast to java.lang.String,因为raw()返回的是Integer类型,强转成String类型失败了。

来源:https://www.cnblogs.com/tong-yuan/p/generic.html

标签:java,泛型
0
投稿

猜你喜欢

  • Android硬件解码组件MediaCodec使用教程

    2023-03-14 01:35:36
  • Java 程序员掌握 Spring Boot非常有必要

    2021-06-27 19:06:52
  • Android中的广播和广播接收器代码实例

    2021-09-04 06:44:22
  • 深入聊一聊JDK中的Map和Set

    2023-10-21 15:54:20
  • 动态配置Spring Boot日志级别的全步骤

    2023-01-29 01:57:19
  • C#下使用XmlDocument操作XML详解

    2022-08-27 16:38:53
  • Android实现文字滚动播放效果的代码

    2021-07-15 22:35:15
  • Java实现答答租车系统

    2022-07-12 01:38:17
  • java中超过long范围的超大整数相加算法详解(面试高频)

    2022-09-15 11:22:05
  • 详解Java反射创建对象

    2022-12-10 03:52:23
  • 完美解决安卓jni项目会删除其他so文件的问题

    2023-11-07 16:45:47
  • C#净化版WebApi框架的实现

    2021-10-31 03:59:09
  • 详解Java实现缓存(LRU,FIFO)

    2022-04-24 13:35:26
  • Android调用外置摄像头的方法

    2021-10-19 01:25:13
  • SpringMVC路径匹配中使用通配符问题

    2023-07-18 20:44:03
  • 使用SpringBoot打jar包并部署到Tomcat详细步骤

    2023-12-06 07:15:16
  • springmvc请求转发和重定向问题(携带参数和不携带参数)

    2022-09-17 13:53:59
  • 百度人脸识别之人脸识别FaceIdentify(签到考勤)

    2022-08-24 18:25:03
  • 使用 Spring Boot 2.0 + WebFlux 实现 RESTful API功能

    2023-12-22 19:51:03
  • C# 实现绘制PDF嵌套表格案例详解

    2023-05-25 11:57:13
  • asp之家 软件编程 m.aspxhome.com