Java——擦除
直接代码分析一波:
import java.util.*; public class Ex12 {
public static void main(String[] args) {
Class c1 = new Double(0).getClass();
Class c2 = new Integer(0).getClass();
Class c3 = new ArrayList<String>().getClass();
Class c4 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2);
System.out.println(c3 == c4);
}
}
这里的c1 c2 c3 c4将分别得到Double、Integer、ArrayList<String>、ArrayList<Integer>的类,输出是:
false
true
c1 == c2很明显是不一样的,这里的ArrayList<String>和ArrayList<Integer>很容易认为是不同的类型,但是其实他两是相同类型的,
import java.util.*; class test1<T>{}
class test2<T,Q>{}
public class Ex12 {
public static void main(String[] args) {
test1<Double> t1 = new test1<Double>();
test2<Double, Double> t2 = new test2<Double, Double>();
System.out.println(Arrays.toString(t1.getClass().getTypeParameters()));
System.out.println(Arrays.toString(t2.getClass().getTypeParameters()));
}
}
Class.getTypeParameters()将返回一个泛型声明所声明的类型参数数组,但是只能看到T,Q,得不到Double,这是因为在java泛型是使用擦除来实现的,这就意味这当你在使用泛型的时候,任何具体点的类型信息都被擦除了,唯一知道的就是你在使用一个对象,因此List<String>和List<Integer>是相同的类型。
1、边界
class HasF{
public void f() {System.out.println("f()");}
}
class Ma<T>{
T obj;
Ma(T t){ obj = t;}
public void maf(){obj.f();}//这里是错的
}
假如你有一个HasF类,这个类中有一个f()方法,然后你想创建一个泛型类,并且想将obj的f()方法,这是java编译器无法实现的,因为擦除。为了调用f(),我们必须协助泛型类,给定泛型的边界,以此告知编译器只能接受遵循这个边界的类型。
用extends关键字,就能设置边界。
import java.util.*; class HasF{
public void f() {System.out.println("f()");}
}
class Ma<T extends HasF >{
T obj;
Ma(T t){ obj = t;}
public void maf(){obj.f();}//这里是错的
}
public class Ex12 {
public static void main(String[] args) {
HasF hasf = new HasF();
Ma<HasF> ma = new Ma<HasF>(hasf);
ma.maf();
}
}
边界<T extends HasF >声明T必须具有类型HasF或者从HasF导出的类型。
泛型类型参数将擦除到它的第一个边界,实际上编译器会把类型参数替换为它的擦除,上面的示例就是T擦除到HasF。
2、擦除的问题
泛型不能显式的引用运行时类型的操作中,例如转型、instanceof操作和new表达式,因为所有关于参数的类型信息都丢失了。例如:
class Foo<T>{
T var;
}
class Cat{}
定义这样两个类,Foo是一个泛型类,Cat是一个普通类
Foo<Cat> foo = new Foo<Cat>();
这里看起来class Foo中的所有操作都像是使用Cat来操作,泛型好像也在告诉我们所有的类型T都将会被替换,但事实上我们必须提醒自己,被擦除了,它只是一个Object,也就是说这里没有边界,应该说边界就是Object。
import java.lang.reflect.Array;
import java.util.Arrays; class ArrayMaker<T>{
private Class<T> kind;
ArrayMaker(Class<T> kind){ this.kind = kind;}
T[] create(int size) {
return (T[])Array.newInstance(kind, size);//这里会有警告
}
} public class Ex13 {
public static void main(String[] args) throws Exception{
ArrayMaker<String> stringMaker =
new ArrayMaker<String>(String.class);
String[] stringArray = stringMaker.create(9);
System.out.println(Arrays.toString(stringArray));
}
}
输出:
[null, null, null, null, null, null, null, null, null]
即使这里的泛型被指定为Class<T>擦除就意味这kind实际被储存的是Class,由此在使用的时候,Array.newInstance(kind, size)并为拥有kind的类型信息,就是不知道kind是String.class,所以需要转型。
但是在创建一个容器情况稍有不同
public class Ex13<T> {
List<T> create(){return new ArrayList<T>();}
public static void main(String[] args) throws Exception{
Ex13<String> ex = new Ex13<String>();
List<String> stringList = ex.create();
}
}
这里的编译器 不会给出任何的警告,尽管我们知道create中的<T>被移除了。这样就能这样操作:
public class Ex13<T> {
List<T> create(T t , int n){
List<T> result = new ArrayList<T> ();
for(int i = 0; i < n; i++)
result.add(t);
return result;
}
public static void main(String[] args) throws Exception{
Ex13<String> ex = new Ex13<String>();
List<String> stringList = ex.create("hello", 4);
System.out.println(stringList);
}
}
输出:
[hello, hello, hello, hello]
但是数组就不能这样做:
public class Ex13<T> {
T[] create(T t , int n){
T[] result = new T[n];//这里编译器无法编译
for(int i = 0; i < n; i++)
result[i] = t;
return result;
} public static void main(String[] args) throws Exception{
Ex13<String> ex = new Ex13<String>();
List<String> stringList = ex.create("hello", 4);
System.out.println(stringList);
}
}
因为T的类型被擦除了。一般都使用Array.newInstance()方法或者使用List。
3、擦除的补偿
public class Ex13<T> {
public void f(Object o) {
if(o instanceof T) {}//编译出错
T var = new T();//编译出错
T[] ts = new T[10];//编译出错
T[] tss = (T)new Object[10];//编译出错
}
}
因为擦除使泛型代码失去某些执行某些操作的能力,想要绕过这些问题进行编程需要引入类型标签对擦除进行补偿。
- instanceof的使用失败 可以使用动态的ISinstance)
public class Ex13<T> {
Class<T> kind;
Ex13(Class<T> kind){
this.kind = kind;
}
public boolean f(Object o) {
return kind.isInstance(o);
}
public static void main(String[] args) {
Ex13<String> ex = new Ex13<String>(String.class);
System.out.println(ex.f("233"));
System.out.println(ex.f(233));
}
}
输出:
true
false
- T var = new T();创建类型实例无法实现,一部分原因是因为擦除了,另一部分原因是因为编译器不能验证T具有默认(无参)的构造器,解决方法还是同样的使用Class对象。
public class Ex13<T> {
Class<T> kind;
Ex13(Class<T> kind){
this.kind = kind;
}
public T f() {
try {
return kind.newInstance();
} catch(Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
String a = new Ex13<String>(String.class).f();
}
}
调用用Class的newInstance()方法即可,但是如果在main中这样使用
public static void main(String[] args) {
Integer b = new Ex13<Integer>(Integer.class).f();
}
就会失败,因为Integer是没有任何的默认的构造器。
可以使用显式的工厂,并限制其类型,使得只能接受实现这个类。
interface Factoryi<T>{
T create();
}
先创建一个泛型接口,create()方法将返回一个T类对象。
可以
class IntegerFactory implements Factoryi<Integer>{ public Integer create() {
return new Integer(0);
} }
可以直接实现这个接口。
class Widest{
public static class Factory implements Factoryi<Widest>{
public Widest create() {
return new Widest();
}
}
}
可以在这个类中创建一个内部类去实现这个接口,
class Foo2<T>{
private T x;
public <F extends Factoryi<T>> Foo2(F factory){
x = factory.create();
}
}
这就是实际上的泛型类了,因为Integer和Widest都没有任何默认的构造器,所以就创建一个类实现Factoryi<T>接口,实现其中的create()方法然后在泛型类从使用这个Factoryi<T>作为边界,使用其中的create()方法,从而实现在泛型类从创建泛型类型实例。
- 泛型数组
不能创建泛型数组,一般的解决方案是使用Araayist:
public class Ex13<T> {
public List<T> array = new ArrayList<T>();
public void add(T item) {
array.add(item);
}
public T get(int index) {
return array.get(index);
}
}
这样和数组其实也能实现和数组差不多的功能。
还能通过传入Class类型对象,然后使用Array.newInstance()方法创建一个数组
class ArrayMaker<T>{
private Class<T> kind;
ArrayMaker(Class<T> kind){ this.kind = kind;}
T[] create(int size) {
return (T[])Array.newInstance(kind, size);//这里会有警告
}
} public class Ex13 {
public static void main(String[] args) {
String[] s = new ArrayMaker<String>(String.class).create(10);
}
}
5、通配符
先看一个数组的特殊行为:
class Fruit{}
class Apple extends Fruit{}
class Orange extends Fruit{}
class Jon extends Apple{}
public class Ex14 {
public static void main(String[] args) {
Fruit[] fruit = new Apple[10];
fruit[0] = new Apple();
fruit[1] = new Jon();
try {
fruit[2] = new Fruit();
}catch(Exception e) {
System.out.println(e.toString());
}
try {
fruit[3] = new Orange();
}catch(Exception e) {
System.out.println(e.toString());
}
}
}
这里的输出是:
java.lang.ArrayStoreException: Fruit
java.lang.ArrayStoreException: Orange
这里创建了一个Apple数组,并将其赋值给一个Fruit数组。你可以将一个Apple或者Apple的子类对象放入数组,并不会有警告,和错误。
但是当你想要放入一个Fruit或是Orange对象的时候,编译器不会有警告但是运行的时候却能捕获到java.lang.ArrayStoreException异常,编译器不会给出警告是因为数组有一个Fruit[]引用,但是运行的时候数组机制知道这是一个Apple[]数组,所以就会抛出异常 。
类似的当我们试图使用泛型容器来代替数组的时候:
public class Ex14 {
public static void main(String[] args) {
List<Fruit> flist = new ArrayList<Apple>();
}
}
这时编译器会直接给出警告,讲道理Apple是Fruit的子类,这应该属于向上转型,然而实际上这里并不是根本不是向上转型这里是两个List,Apple可以算是Fruit类型的,但是Apple的List并不是Fruit的List,这时如果想要建立某种类型的向上转型关系,就需要使用通配符了。
public class Ex14 {
public static void main(String[] args) {
List<? extends Fruit> flist = new ArrayList<Apple>();
flist.add(new Orange());
flist.add(new Apple());
flist.add(new Jon());
}
}
然后创建List就不会报错了,但是后面的调用add()方法还是会给出警告。
flist的类型现在是List<? extends Fruit>,可以将其读为“具有任何从Fruit继承的类型的列表”,但是并不就意味着这个List就可以持有任何类型的Fruit。通配符指定的是没有指定具体类型,其实就是只能接受不能修改。
public class Ex14 {
public static void main(String[] args) {
List<Apple> list = new ArrayList<Apple>();
list.add(new Apple());
list.add(new Jon());
List<? extends Fruit> flist = list;
}
}
这样的使用就是没错的。
Java——擦除的更多相关文章
- Java擦除
概述: Java泛型在使用过程有诸多的问题,如不存在List<String>.class, List<Integer>不能赋值给List<Number>(不可协变) ...
- 关于Java擦除特性
package thinkingInJava; /* * 模拟擦除 */ public class SimpleHolder { private Object obj ; public void se ...
- Spark案例分析
一.需求:计算网页访问量前三名 import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} /* ...
- 【Java心得总结四】Java泛型下——万恶的擦除
一.万恶的擦除 我在自己总结的[Java心得总结三]Java泛型上——初识泛型这篇博文中提到了Java中对泛型擦除的问题,考虑下面代码: import java.util.*; public clas ...
- 记一次由于Java泛型类型擦除而导致的问题,及解决办法
中所周知,Java中的泛型并不像C++.C#一样是真正的泛型,其泛型是通过类型擦除来实现的.具体什么是类型擦除,可以参看这篇博文:http://icyfenix.iteye.com/blog/1021 ...
- java泛型编译时被擦除引起多态的破坏,用 桥方法解决此类问题。(java 桥方法)
在JVM虚拟机中泛型编译的时候,会出现类型擦除.但是,在多态场景中,编译时,擦除方式会出现多态被破坏的可能. 举个栗子: A.java public class A<T> { void g ...
- Java泛型-内部原理: 类型擦除以及类型擦除带来的问题
一:Java泛型的实现方法:类型擦除 大家都知道,Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除.Java的泛型基本上都是在编译 ...
- Java泛型-类型擦除
一.概述 Java泛型在使用过程有诸多的问题,如不存在List<String>.class, List<Integer>不能赋值给List<Number>(不可协变 ...
- Java类型擦除机制
Java泛型是JDK 5引入的一个特性,它允许我们定义类和接口的时候使用参数类型,泛型在集合框架中被广泛使用.类型擦除是泛型中最让人困惑的部分,本篇文章将阐明什么是类型擦除,以及如何使用它. 一个常见 ...
随机推荐
- AWS S3 上传文件
一.获取签名的URL 通过后端给的接口拿到已经签名好的文件上传的URL地址 二.读取文件(注:AWS 接受的二进制,不能使用form-data) // 获取文件二进制 getFileMd5 = (ke ...
- NOIP最后阶段每日小记
前言:最后几天了,记录一下每天的经验.教训.启示.大牛路过请补充一点内容…… 2017-10-31 21:32 今天考试打完第一题就知道是AK场,然后就死命搞题,结果第一题手贱多打了一个%,本机无显示 ...
- Python解题技巧
Python解题技巧 一直都是用C++和C解题,某题简单解完后便心血来潮想用Python解一次,发现一些问题,特写此篇随笔来记录. 一. 输入格式: 例:输入第1行给出正整数n和整数m:第2行给出n个 ...
- STM32F072从零配置工程-建立工程文件
快速建立工程有两种方法: 第一种是通过官方提供的外设库来搭建,好处是使用库函数,而不需要深入研究寄存器配置: 第二种是通过STM32CubeMX,好处是直观快速,可以直接帮你配置好功能和时钟,不过使用 ...
- 教你发布vue+.netCore项目到服务器
最近一直在做项目,发布部署的事情都是同事或者老大做的,无奈什么事都要自己尝试经历后才能记住,所以发布的事情轮到我了,由于是第一次发布部署项目到一个新的服务器环境,难免会遇到各种各样的问题,总结下来,希 ...
- 详叙BeanWrapper和PropertyDescriptor
每篇一句 千古以来要饭的没有要早饭的,知道为什么吗? 相关阅读 [小家Spring]聊聊Spring中的数据转换:Converter.ConversionService.TypeConverter.P ...
- Postgresql_fdw
Postgresql_fdw 测试环境 Ubuntu 16.04 LTS云主机2台,主机名为pg1(192.168.0.34)和pg2(192.168.0.39). 安装postgresql 下面这个 ...
- 开源框架Autofac使用入门
目录导航 1.Autofac是什么 1.1什么是DI和IOC 1.2DI和IOC在项目中起到什么作用 2.Autofac如何使用 2.1下载 2.2代码Demo 2.3Demo分析 3总结 1.Aut ...
- 五分钟了解Zabbix
Zabbix-简介 Zabbix概念 Zabbix组成 Server Zabbix server 是 Zabbix agent 向其报告可用性.系统完整性信息和统计信息的核心组件.是存储所有配置信息. ...
- [JavaWeb] Ubuntu下载eclipse for ee
进入网站进行下载 https://www.eclipse.org/downloads/download.php?file=/technology/epp/downloads/release/2019- ...