标签分类
技术文章
当前位置:主页 > 计算机编程 > java > 简单理解java泛型的本质

java泛型的本质(非类型擦除)知识点详解

  • 发布时间:
  • 作者:码农之家原创
  • 点击:170

简单理解java泛型的本质

这篇文章主要知识点是关于java,泛型,本质,简单理解java泛型的本质,java泛型类的定义与使用详解 的内容,如果大家想对相关知识点有系统深入的学习,可以参阅以下电子书

Scala与Clojure函数式编程模式:Java虚拟机高效编程
  • 类型:Java大小:34 MB格式:PDF出版:人民邮电出版社作者:贝维拉夸林
立即下载

背景

之前在网上发现这个问题

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类型失败了。

好了,基本思路就是这样。

泛型类呢?

我们上面举的例子都是泛型方法,那么泛型类呢?

同样地,我们来看个例子:

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

可以看到,跟泛型方法的表现一模一样。当然,这里运行时也会报java.lang.Integer cannot be cast to java.lang.String这个错误。

总结

java中的泛型只在编译期有效,在运行时只有调用者知道需要什么类型,且调用者调用泛型方法后自己做强制转换,被调用者是完全无感的。

所以,出现问题不要问被调用者,而是要问调用者,你丫是怎么调用的?!

解答开篇

为了方便我们还是把开篇的问题拿过来。

// GenericTest.java(源码)
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());
 }
}

这里似乎又不太一样,变成了<T extends Comparable<T>>,其实是一样的啦,如果单独写<T>是相当于<T extends Object>的。

那么,我们就延伸一下,被调用者是完全无感的,它只能尽力拿到它知道的类型,比如这里就只能尽力拿到Comparable,如果是<T>拿到的就是Object。

所以,方法二返回的就是实打实的Comparable[]类型,作为被调用者,它一点问题都没有。

但是,调用方是知道我需要的是Integer[]类型的,因为list里面是Integer类型,所以返回的应该是Integer[]类型,所以我就强转喽,然后就报错了。

到底是不是这样?我们来看看反编译后的代码:

// GenericTest.class(反编译)
public class GenericTest {
 public GenericTest() {
 }
 public static <T extends Comparable<T>> List<T> sort(List<T> list) {
 return Arrays.asList(list.toArray((Comparable[])(new Comparable[list.size()])));
 }
 public static <T extends Comparable<T>> T[] sort2(List<T> list) {
 // 这里使用的是Comparable[]强转,所以返回的也是实打实的Comparable[]类型
 return (Comparable[])list.toArray((Comparable[])(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(((Integer[])sort2(list)).getClass());
 }
}

可以看到,跟我们的分析完全一致。

java中的泛型只在编译期有效,在运行时只有调用者知道它自己需要什么类型,且调用者调用泛型方法后自己做强制转换,被调用者是完全无感的,被调用者只能尽力拿到它所知道的类型。

此时,我的脑海中不经响起那熟悉的旋律,“一句话,一辈子……”,今天的这句话你记住了吗?

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持码农之家。

java泛型类的定义与使用详解

本文为大家分享了java泛型类的定义与使用的具体代码,供大家参考,具体内容如下

当类中要操作的引用数据类型不确定时,可以定义泛型类完成扩展。下面是程序演示。

package packB;

class Student { //定义学生类
 public String st = "student";
}

class Worker { //定义工人类
 public String wo = "worker";
}

//定义泛型类
class Operate<type> { // <type> 是给不确定的类型自定义的名字

 private type elem;

 public void setElem( type t ) { //设置元素值方法
 elem = t;
 }

 public type getElem() { //返回元素值方法
 return elem;
 }
}


public class GenericDemo {
 public static void main(String[] args) {

 Operate<Student> obj = new Operate<Student>(); //表明操作的类型是Student类
 
 obj.setElem( new Student() );
 Student stu = obj.getElem(); //上面的代码已经使用泛型说明了操作的对象,这里不需要强转
 
 sop(stu.st);
 }

 public static void sop(Object obj) {
 System.out.println(obj);
 System.out.println();
 }
}

注:希望与各位读者相互交流,共同学习进步。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持码农之家。

以上就是本次给大家分享的全部知识点内容总结,大家还可以在下方相关文章里找到儿童python编程入门书籍推、 vue项目中使用md5加密以及、 解决axios.interceptors.respon、 等java文章进一步学习,感谢大家的阅读和支持。

上一篇:Java Lombok开发用法指南

下一篇:SpringBoot中启动Tomcat的步骤流程

展开 +

收起 -

学习笔记
网友NO.946606

Java泛型之上界下界通配符详解

泛型,继承和子类 如你所知,只要类型兼容,就可以将一种类型的对象分配给另一种类型的对象。例如,你可以指定一个整数一个对象,因为对象是一个整数的超类型: Object someObject = new Object();Integer someInteger = new Integer(10);someObject = someInteger; // 好 在面向对象的术语中,这被称为“是一种”关系。由于Integer 是一种Object,因此允许赋值。但是Integer也是一种Number,所以下面的代码也是有效的: public void someMethod(Number n){/ * ... *……

网友NO.662445

Java中泛型总结(推荐)

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。 泛型类 范例:泛型类的基本语法 class MyClassT {T value1;} 尖括号 中的 T 被称作是类型参数,用于指代任何类型。实际上这个T你可以任意写,但出于规范的目的,Java还是建议我们用单个大写字母来代表类型参数。常见的如: T 代表一般的任何类。 E 代表 Element 的意思,或者 Exception 异常的意思。 K 代表 Key 的意思 V 代表 Value 的意思,通常与 K 一起配合使用。 S 代表 Subtype 的意思,文章后面部分会讲解示意。 范例:泛型类引入多个类型参数以及使用 class MyClassT,E {T value1;E value2;}public class Test {public static void main(String[] args) {MyClassString,Integer myClass1 = new MyClassString,Integer();}} 当开发的程序可以避免向下转型,也就意味着安全隐患被消除了。尽量不要去使用向下转型。 泛型的出现彻底改变了向下转型的需求。引入泛型后,如果明确设置了类型,则为设置类型;如果没有设置类型, 则默认为Object类型。 泛型方法 泛型不仅可以用于定义类,还可以单独来定义方法。如下所示: 范例:泛型方法定义 class MyClass{public T void testMethod(T t)……

网友NO.637753

通过代码理解java泛型

泛型数据java基础,但真正理解需要悉心品尝。毕竟在工作中用到的是在是太多了。 不要以为new ArrayList这就是泛型,这只能属于会使用。 在工作中,相对于现有的项目源码的数据库操作层,无论是mybatis,hibernate或者是自己封装的baseModel层,都会使用到泛型。 以及? extends T 和 ? super T这个屌东西。 泛型使用情况分为三类 1. 泛型类。 2. 泛型方法。 3. 泛型接口。 出于规范的目的,Java 还是建议我们用单个大写字母来代表类型参数。常见的如: 1. T 代表一般的任何类。 2. E 代表 Element 的意思,或者 Exception 异常的意思。 3. K 代表 Key 的意思。 4. V 代表 Value 的意思,通常与 K 一起配合使用。 5. S 代表 Subtype 的意思,文章后面部分会讲解示意。 最直接的一段代码。 ListString l1 = new ArrayListString(); ListInteger l2 = new ArrayListInteger(); System.out.println(l1.getClass() == l2.getClass()); 打印的判断为TRUE,因为泛型信息被擦除了。 泛型擦除实例。 ListString listErasure = new ArrayListString() { // 直接初始化,这也是一种方式。直接传入一个collection。 {add("aaa");add("bbb");} }; listErasure.add("ccc"); Class? extends List class1 = listErasure.getClass(); Method method = class1.getMethod("add",Object.class); method.invoke(listErasure, 123); System.out.println(listErasure) 输出结果 [aaa, bbb, ccc, 123] 明明是接收String类型,……

网友NO.479660

详解Java常用工具类—泛型

一、泛型概述 1、背景 在Java中增加泛型之前,泛型程序设计使用继承来实现的。 坏处: 需要进行强制类型转换 可向集合中添加任意类型的对象,存在风险 2、泛型的使用 ListString list=new ArrayListString(); 3、多态与泛型 class Animal{}class Cat extends Animal{}ListAnimal list=new ArrayListCat(); //这是不允许的,变量声明的类型必须匹配传递给实际对象的类型。 其它错误的例子: ListObject list=new ArrayListString();ListNumber number=new ArrayListInteger(); 4、泛型内容 泛型作为方法参数 自定义泛型类 自定义泛型方法 二、泛型作为方法参数 泛型作为参数时,如果参数为多个子类,可以使用(List? extends 父类 xxx)。这种情况下,在调用方法时,就可以传递父类及其子类作为参数了。 还有一个:(List? super 类 xxx)。这种情况下是指类及其超类(父类)。 三、自定义泛型 public class NumGenericT {private T num;public NumGeneric() {}public NumGeneric(T num) {this.setNum(num);}public T getNum() {return num;}public void setNum(T num) {this.num = num;}//测试public static void main(String[] args) {NumGenericInteger intNum = new NumGeneric();intNum.setNum(10);System.out.println("Integer:" + intNum.getNum());NumGenericFloat floatNum = new NumGeneric();floatNum.setNum(5.0f);System.out.println("Float:" + floatNum.getNum());}} 泛型类的定义和使用,可以传进不同类的对象作为参……

<
1
>

Copyright 2018-2019 xz577.com 码农之家

版权责任说明