一文带你深入了解Java擦除机制
Posted 海绵宝宝养的的小窝
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文带你深入了解Java擦除机制相关的知识,希望对你有一定的参考价值。
前言
⭐️大家好,我是小窝,今天来为大家介绍擦除机制在java中的应用。
说到擦除机制,我们不得不提Java中的泛型,因为擦除机制可以说就是为Java泛型而构造出来的。在这篇博客里我们先简单了解下泛型,关于泛型的详细知识,博主之后将会再写一篇博客带你领略泛型的妙处所在。🔔因为完全理解泛型得首先了解擦除机制,而初步认识现在就够理解了。
📖博客主页:海绵宝宝养的小窝
👏欢迎关注🔥点赞💓收藏✨评论💬
⭐️看前先三连🙉养成好习惯🙈
📅首发时间:🌴 2022.01.08 🌴
⌛️生活不止眼前的苟且,还有诗和远方💫
🔑参考书籍:📚《Effective Java》、📚《深入理解Java虚拟机》
💨博主在校大学生,水平有限,如有错误请及时评论或私信告知我哟✌️
博主的码云地址,日常代码及博客代码会在上面
本文收录专栏📓《深入理解Java虚拟机》,大家可以多多订阅呀,毕竟是免费的 😉此时不白嫖何时白嫖 🎁
📌什么是擦除机制
🍊Java在编译后的字节码(.class)文件中是不包含泛型中的类型信息的,使用泛型的时候加上的类型参数,会在编译的时候被擦除,这个过程就叫做类型擦除机制。
🍺泛型
关于泛型,我们先来简单了解下。
⭐️泛型可以理解为对类型的抽象。以前我们定义一个属性或者方法的时候,我们都会明确具体的类型,比如int、String、void等等,但泛型不同,泛型是一个参数化类型,即不明确类型,只有在具体调用对象的时候,才传递实际类型实参。指定了泛型参数的类型就是一个具体化了的类型。
🌰举个栗子:
List arrayList = new ArrayList(); arrayList.add("aaa"); arrayList.add(100); for (int i = 0; i < arrayList.size(); i++) String item = (String) arrayList.get(i); System.out.println("泛型测试,item = "+item);
毫无疑问,程序的运行结果会以崩溃结束
ArrayList可以存放任意类型,例子中添加了一个String类型,添加了一个Integer类型,在使用时都以String的方式使用,因此程序崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型就应运而生了。
我们将ArrayList的初始化改一下:
List<String> arrayList = new ArrayList();
那么编译器将会直接在编译阶段就报错提醒我们只能存放String类型的数据。
这个<>里括起来的就是一种参数化的类型(也就是泛型),例如String、Integer、Float等。记住要用包装类,不能用int、char等基础数据类型。
正如我们前面所说泛型的类型参数会在编译的时候擦除,怎么证明呢?很简单,但我们写了如下代码后:
👇
> public class Test
public static void main(String[] args)
Map<String,String> map = new HashMap<String,String>();
map.put("三国演义","罗贯中");
map.put("西游记","吴承恩");
System.out.println(map.get("三国演义"));
System.out.println(map.get("西游记"));
因为泛型只在编译期就会被擦除,我们可以用反编译查看代码内的泛型是否被擦除。
我们使用命令反编译后:
👇
发现泛型果然被擦除了,并且还将类型参数全都擦成了Object这个具体的类型。
为什么会被擦成object呢❓
⭐️原来,在JDK5.0之前,容器存储的对象都只有具有Java的通用类型:Object。单根继承结构意味着所有的东西都是Object类型,所以该容器可以存储任何东西(就像我们最开始写的代码一样,可以存,但取会报错),但是由于容器只存储Object,所以当将对象引入容器时,他必须被向上转型成Object。
📌擦除机制的过程
⭐️擦除机制过程可以理解为将泛型类变成普通Java代码的过程。一般包括两个步骤:
🍊1:将所有的泛型参数用其最左边界(最顶级的父类型)类型替换,默认则是Object。
🍊2:擦除泛型
🌰举个栗子:
public class MyClass < T extends TestB > private T object; public void setObject (T object) this.object = object; public T getObject() return object; public static void main(String[] args) MyClass<TestC> testCMyClass = new MyClass<TestC>(); testCMyClass.setObject(new TestC()); TestC testC = testCMyClass.getObject(); System.out.println(testC);
在擦除之后,你可以把它理解为
👇public class MyClass private TestB object; public void setObject (TestB object) this.object = object; public TestB getObject() return object; public static void main(String[] args) MyClass testCMyClass = new MyClass(); testCMyClass.setObject(new TestC()); TestC testC = testCMyClass.getObject(); System.out.println(testC);
擦除机制过程真有这么简单❓
我们再来看一个栗子🌰
public class Parent<T> public Number get(T key)//number几乎包含了所有数据类型 return 0; class child1 extends Parent<String> public Number get(String key) return 1; class child2 extends Parent<String> public Integer get(String key) return 2;
我们知道有擦除机制在,上述代码的泛型都会被擦除,并且类型参数会被擦成Object,那么子类的重写方法的参数应该是Object的,但我们写成上述也并没有报错,这是怎么回事呢?
🍺桥接方法
看到标题你们应该猜出来了吧,没错,这是因为编译器为我们自动生成了桥接方法。
那什么是桥接方法呢❓
栗子🌰
public class Parent public Number get()//number几乎包含了所有数据类型 return 0; class child1 extends Parent public Number get() return 1; class child2 extends Parent public Integer get() return 2;
我们先将代码转换成非泛型来瞧瞧,通过反编译:
🍊child1
🍊child2
原来如此,编译器为我们自动生成了桥接方法,为父类和子类之间架起了一座桥梁。
🔑那么同理,原来的我们也通过反编译查看:
🍊parent
🍊child1
🍊child2
⭐️因为父类的T被擦成了object,所以编译器为我们先重写了父类的参数类型为object的方法,再通过该桥接方法区调用自己的重写的方法。
📌擦除机制带来的影响
上面也说了类型擦除机制的实现原理-类型擦除指的是通过类型参数合并,将泛型实例关联到同一份字节码上,在运行期间类型参数丢失。就单单只有一份字节码这个事上就会出现许多匪夷所思的问题。
🍊不能用同一个泛型类的实例区分方法签名
栗子🌰
public class Test public void test(List<String> a) System.out.println("String"); public void test(List<Integer> b) System.out.println("Integer");
⭐️这样是不行的,因为List< String >和List< Integeer >在类型擦除后都变成了List,那么两个方法的签名就一模一样了,编译器就会直接报错。
🍊不能同时catch同一个泛型异常类的多个实例
⭐️原理同上一条
💡还有许多都是因为擦除机制带来的问题,博主在此就不一一举例了,只需明白在使用泛型时要考虑到擦除机制带来的影响。
📌结语
这篇博客的分享就到此结束了。下一篇博主将就泛型来详细介绍。
如果觉得文章写的不错的话,可以给个三连不🙈谢谢支持了
以上是关于一文带你深入了解Java擦除机制的主要内容,如果未能解决你的问题,请参考以下文章