`
IcyFenix
  • 浏览: 362240 次
  • 性别: Icon_minigender_1
  • 来自: 珠海
文章分类
社区版块
存档分类
最新评论

Java语法糖的味道:泛型与类型擦除

阅读更多
趁着编辑许可,尽量多发一些独立性比较强的内容出来分享一下。

原创文章,转载请注明以下信息:
作者:icyfenix@gmail.com
来源:《深入理解Java虚拟机:JVM高级特性与最佳实践》

Java语法糖的味道:泛型与类型擦除

  泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。
  泛型思想早在C++语言的模板(Templates)中就开始生根发芽,在Java语言处于还没有出现泛型的版本时,只能通过Object是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化。例如在哈希表的存取中,JDK 1.5之前使用HashMap的get()方法,返回值就是一个Object对象,由于Java语言里面所有的类型都继承于java.lang.Object,那Object转型成任何对象都是有可能的。但是也因为有无限的可能性,就只有程序员和运行期的虚拟机才知道这个Object到底是个什么类型的对象。在编译期间,编译器无法检查这个Object的强制转型是否成功,如果仅仅依赖程序员去保障这项操作的正确性,许多ClassCastException的风险就会被转嫁到程序运行期之中。
  泛型技术在C#和Java之中的使用方式看似相同,但实现上却有着根本性的分歧,C#里面泛型无论在程序源码中、编译后的IL中(Intermediate Language,中间语言,这时候泛型是一个占位符)或是运行期的CLR中都是切实存在的,List<int>与List<String>就是两个不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型被称为真实泛型。
  Java语言中的泛型则不一样,它只在程序源码中存在,在编译后的字节码文件中,就已经被替换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此对于运行期的Java语言来说,ArrayList<int>与ArrayList<String>就是同一个类。所以说泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型被称为伪泛型。
  代码清单10-2是一段简单的Java泛型例子,我们可以看一下它编译后的结果是怎样的?

  代码清单 10-2 泛型擦除前的例子
public static void main(String[] args) {
	Map<String, String> map = new HashMap<String, String>();
	map.put("hello", "你好");
	map.put("how are you?", "吃了没?");
	System.out.println(map.get("hello"));
	System.out.println(map.get("how are you?"));
}
  把这段Java代码编译成Class文件,然后再用字节码反编译工具进行反编译后,将会发现泛型都不见了,程序又变回了Java泛型出现之前的写法,泛型类型都变回了原生类型,如代码清单10-3所示。

  代码清单 10-3 泛型擦除后的例子
public static void main(String[] args) {
	Map map = new HashMap();
	map.put("hello", "你好");
	map.put("how are you?", "吃了没?");
	System.out.println((String) map.get("hello"));
	System.out.println((String) map.get("how are you?"));
}
  当初JDK设计团队为什么选择类型擦除的方式来实现Java语言的泛型支持呢?是因为实现简单、兼容性考虑还是别的原因?我们已不得而知,但确实有不少人对Java语言提供的伪泛型颇有微词,当时甚至连《Thinking In Java》一书的作者Bruce Eckel也发表了一篇文章《这不是泛型!》 来批评JDK 1.5中的泛型实现。
注1:原文:http://www.anyang-window.com.cn/quotthis-is-not-a-genericquot-bruce-eckel-eyes-of-the-generic-java/
  当时众多的批评之中,有一些是比较表面的,还有一些从性能上说泛型会由于强制转型操作和运行期缺少针对类型的优化等从而导致比C#的泛型慢一些,则是完全偏离了方向,姑且不论Java泛型是不是真的会比C#泛型慢,选择从性能的角度上评价用于提升语义准确性的泛型思想,就犹如在讨论刘翔打斯诺克的水平与丁俊晖有多大的差距一般。但笔者也并非在为Java的泛型辩护,它在某些场景下确实存在不足,笔者认为通过擦除法来实现泛型丧失了一些泛型思想应有的优雅,例如下面代码清单10-4的例子:

  代码清单 10-4 当泛型遇见重载 1
public class GenericTypes {

    public static void method(List<String> list) {
        System.out.println("invoke method(List<String> list)");
    }

    public static void method(List<Integer> list) {
        System.out.println("invoke method(List<Integer> list)");
    }
}
  请想一想,上面这段代码是否正确,能否编译执行?也许您已经有了答案,这段代码是不能被编译的,是因为参数List<Integer>和List<String>编译之后都被擦除了,变成了一样的原生类型List<E>,擦除动作导致这两个方法的特征签名变得一模一样。初步看来,无法重载的原因已经找到了,但是真的就是如此吗?只能说,泛型擦除成相同的原生类型只是无法重载的其中一部分原因,请再接着看一看代码清单10-5中的内容。

  代码清单 10-5 当泛型遇见重载 2
public class GenericTypes {

    public static String method(List<String> list) {
        System.out.println("invoke method(List<String> list)");
        return "";
    }

    public static int method(List<Integer> list) {
        System.out.println("invoke method(List<Integer> list)");
        return 1;
    }

    public static void main(String[] args) {
        method(new ArrayList<String>());
        method(new ArrayList<Integer>());
    }
}
  执行结果:
invoke method(List<String> list)
invoke method(List<Integer> list)
  代码清单10-5与代码清单10-4的差别,是两个method方法添加了不同的返回值,由于这两个返回值的加入,方法重载居然成功了,即这段代码可以被编译和执行 了。这是我们对Java语言中返回值不参与重载选择的基本认知的挑战吗?
注2:测试的时候请使用Sun JDK的Javac编译器进行编译,其他编译器,如Eclipse JDT的ECJ编译器,仍然可能会拒绝编译这段代码,ECJ编译时会提示“Method method(List<String>) has the same erasure method(List<E>) as another method in type GenericTypes”。
  代码清单10-5中的重载当然不是根据返回值来确定的,之所以这次能编译和执行成功,是因为两个mehtod()方法加入了不同的返回值后才能共存在一个Class文件之中。第6章介绍Class文件方法表(method_info)的数据结构时曾经提到过,方法重载要求方法具备不同的特征签名,返回值并不包含在方法的特征签名之中,所以返回值不参与重载选择,但是在Class文件格式之中,只要描述符不是完全一致的两个方法就可以共存。也就是说两个方法如果有相同的名称和特征签名,但返回值不同,那它们也是可以合法地共存于一个Class文件中的。
  由于Java泛型的引入,各种场景(虚拟机解析、反射等)下的方法调用都可能对原有的基础产生影响和新的需求,如在泛型类中如何获取传入的参数化类型等。所以JCP组织对虚拟机规范做出了相应的修改,引入了诸如Signature、LocalVariableTypeTable等新的属性用于解决伴随泛型而来的参数类型的识别问题,Signature是其中最重要的一项属性,它的作用就是存储一个方法在字节码层面的特征签名 ,这个属性中保存的参数类型并不是原生类型,而是包括了参数化类型的信息。修改后的虚拟机规范 要求所有能识别49.0以上版本的Class文件的虚拟机都要能正确地识别Signature参数。
注3:在《Java虚拟机规范第二版》(JDK 1.5修改后的版本)的“§4.4.4 Signatures”章节及《Java语言规范第三版》的“§8.4.2 Method Signature”章节中分别都定义了字节码层面的方法特征签名,以及Java代码层面的方法特征签名,特征签名最重要的任务就是作为方法独一无二不可重复的ID,在Java代码中的方法特征签名只包括了方法名称、参数顺序及参数类型,而在字节码中的特征签名还包括方法返回值及受查异常表,本书中如果指的是字节码层面的方法签名,笔者会加入限定语进行说明,也请读者根据上下文语境注意区分。
  从上面的例子可以看到擦除法对实际编码带来的影响,由于List<String>和List<Integer>擦除后是同一个类型,我们只能添加两个并不需要实际使用到的返回值才能完成重载,这是一种毫无优雅和美感可言的解决方案。同时,从Signature属性的出现我们还可以得出结论,擦除法所谓的擦除,仅仅是对方法的Code属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能通过反射手段取得参数化类型的根本依据。
分享到:
评论
39 楼 851228082 2016-05-07  
代码10-5,我使用jdk1.7 javac编译失败,这是为什么?
38 楼 lliiqiang 2015-12-02  
由于能够向父类兼容泛型,导致java的泛型只有在使用者使用泛型对象的时候才动态判断对象的类型。
ArrayList<String> str=new ArrayList<String>();
ArrayList<Object>obj=str;
obj.add(new Object());
String value=str.get(0);//这里不会报告编译错误,但是运行时会产生强制类型转换异常。

对于虚拟机来说上面的代码中由于有ArrayList<String> str=new ArrayList<String>();地声明泛型,导致虚拟机认为str.get(0)会返回String类型对象,于是String value=str.get(0);会被编译器自动转为String value=(String)str.get(0);运行时会检查str.get(0)返回的对象的类型,从而产生强制类型转换的异常。
37 楼 lliiqiang 2015-12-02  
java的泛型并不是真正的泛型,真正的泛型是指编译的时候强制类型,而java的泛型是指虚拟机编译的时候自动产生强制类型转换的代码。
36 楼 xgj1988 2011-05-05  
引用
关于方法选择的规则,请参考Java语言规范的§15.12.2.5 Choosing the Most Specific Method章节


额,呵呵。思想惯性了。谢谢icy
35 楼 IcyFenix 2011-05-04  
xgj1988 写道
如果把icy的代码改下,会得到一个奇怪的问题
public class Test {
	//	public void fn1(List<String> l1) {
	//	}
	//
	//	public void fn1(List<Integer> l1) {
	//	}

	public static String fn1(List l1) {
		System.out.println("l1");
		return "0";
	}

	public static int fn1(List<Integer> l1) {
		System.out.println("l2");
		return 0;
	}

	public static void main(String[] args) {
		fn1(null);//调用的是 public static int fn1(List<Integer> l1)
//		fn1(new ArrayList<Integer>());
	}	
}


但是如果这样,就让编译器找不到如何下手了。
/**
 * 
 * @author xgj
 * 
 */
public class Test {
	//	public void fn1(List<String> l1) {
	//	}
	//
	//	public void fn1(List<Integer> l1) {
	//	}

	public static String fn1(List<String> l1) {
		System.out.println("l1");
		return "0";
	}

	public static int fn1(List<Integer> l1) {
		System.out.println("l2");
		return 0;
	}

	public static void main(String[] args) {
		fn1(null);
//		fn1(new ArrayList<Integer>());
	}	
}


而且对于第一种情况,在JAVAC下面无法编译,在ECJ下面可以编译。但是如果是第二种情况,JAVAC和ECJ都出现同样的问题。、

reference to fn1 is ambiguous, both method fn1(java.util.List<java.lang.String>) in org.test.generic.Test and method fn1(java.util.List<java.lang.Integer>) in org.test.generic.Test match
fn1(null);


你这个例子和泛型已经没啥联系了,这个是方法静态选择的问题。和这段代码遇到的问题是一样的:
public class Foo {

	class Foo1 {

	}

	class Foo2 {

	}

	static void method(Foo1 foo) {

	}

	static void method(Foo2 foo) {

	}

	public static void main(String[] args) {
		method(null);
	}
}
关于方法选择的规则,请参考Java语言规范的§15.12.2.5 Choosing the Most Specific Method章节
34 楼 marmot 2011-05-04  
<p>谢谢icy和xgj的回复 很感谢<br>icy说可以参考 RednaxelaFX 的贴  我找到以下两个帖子</p>
<p><br>主题:java和C#的泛型比较<br>http://www.iteye.com/topic/348434</p>
<p><br>主题:Object数组到泛型数组转换的伪解决方案<br>http://www.iteye.com/topic/320161</p>
<p> </p>
<p>帖子很长 说的很也很好 我还得消化消化 呵呵</p>
33 楼 xgj1988 2011-05-04  
有点费解。
32 楼 xgj1988 2011-05-04  
如果把icy的代码改下,会得到一个奇怪的问题
public class Test {
	//	public void fn1(List<String> l1) {
	//	}
	//
	//	public void fn1(List<Integer> l1) {
	//	}

	public static String fn1(List l1) {
		System.out.println("l1");
		return "0";
	}

	public static int fn1(List<Integer> l1) {
		System.out.println("l2");
		return 0;
	}

	public static void main(String[] args) {
		fn1(null);//调用的是 public static int fn1(List<Integer> l1)
//		fn1(new ArrayList<Integer>());
	}	
}


但是如果这样,就让编译器找不到如何下手了。
/**
 * 
 * @author xgj
 * 
 */
public class Test {
	//	public void fn1(List<String> l1) {
	//	}
	//
	//	public void fn1(List<Integer> l1) {
	//	}

	public static String fn1(List<String> l1) {
		System.out.println("l1");
		return "0";
	}

	public static int fn1(List<Integer> l1) {
		System.out.println("l2");
		return 0;
	}

	public static void main(String[] args) {
		fn1(null);
//		fn1(new ArrayList<Integer>());
	}	
}


而且对于第一种情况,在JAVAC下面无法编译,在ECJ下面可以编译。但是如果是第二种情况,JAVAC和ECJ都出现同样的问题。、

reference to fn1 is ambiguous, both method fn1(java.util.List<java.lang.String>) in org.test.generic.Test and method fn1(java.util.List<java.lang.Integer>) in org.test.generic.Test match
fn1(null);
31 楼 xgj1988 2011-05-04  
刚才测了一下在eclipse使用如下代码代码。发现在ecj编译的时候并不会出错。对于我的猜测我表示抱歉。没有实验就发表了。

package org.test.generic;

import java.util.ArrayList;
import java.util.List;

/**
 * 
 * @author xgj
 * 
 */
public class Test {
	//	public void fn1(List<String> l1) {
	//	}
	//
	//	public void fn1(List<Integer> l1) {
	//	}

	public static String fn1(List<String> l1) {
		System.out.println("l1");
		return "0";
	}

	public static int fn1(List<Integer> l1) {
		System.out.println("l2");
		return 0;
	}

	public static void main(String[] args) {
		fn1(new ArrayList<String>());
		fn1(new ArrayList<Integer>());
	}
}


那么应该ECJ和JAVAC在处理这一块的时候都是一样的
30 楼 wen_jp2002 2011-05-04  
xgj1988 写道
代码清单10-5中的重载当然不是根据返回值来确定的,之所以这次能编译和执行成功,是因为两个mehtod()方法加入了不同的返回值后才能共存在一个Class文件之中。第6章介绍Class文件方法表(method_info)的数据结构时曾经提到过,方法重载要求方法具备不同的特征签名,返回值并不包含在方法的特征签名之中,所以返回值不参与重载选择,但是在Class文件格式之中,只要描述符不是完全一致的两个方法就可以共存。

虽然道理是这样,但是我觉得还是不能解释  清单10-4和清单10-5之间的区别啊。

我怀疑这里可能还是编译器的工作原理问题。
  首先Lcy说的CLASS文件判断一个方法是否是同一个方法,确实和我们编写JAVA代码的时候不同。
我怀疑JAVAC编译的时候是可以是这样的
1:先会解析一些什么语法啊,词法,注解之类的。
2:把泛型的JAVA源代码先编译成一个中间的字节码形式。
3:把中间的字节码再转换成jvm的字节码(这个时候再进行泛型的擦除)。

对于ecj来说,可能就是
1:先会解析一些什么语法啊,词法,注解之类的。
2:对泛型先擦除泛型生成中间代码。
3:然后再进行把泛型中间代码生成字节码的时候发现存在两个相同的方法了。所以报错。

我觉得如果是这样也很好解释这种为什么在ECJ和JAVAC下面不同的原因。可能他们的在实现上对泛型的解析以及编译的时机可能不同,因为如果是JVM自动生成的字节码其实他真正表示一个方法就是Lay说的那样,返回值也会算在里面。

正解:在Class文件格式之中,只要描述符不是完全一致的两个方法就可以共存
29 楼 xgj1988 2011-05-04  
引用
谢谢icy的回答 我并不想比较c#与java两者之间对于泛型的性能
我只是 在Java代码中发现有的代码 使用了泛型声明 有的没有 我想知道这两者之间的差异
我的理解是 既然采取的是擦除法 那么在Java代码中 使用泛型与不使用泛型 除了在阅读代码时语义更为清晰之外
两者在性能上是一样的
不知道这样的理解对不对



你理解是对的,只不过还有就是对如果你用IDE的话,那么这种开发速度也得到大大的提高。
import java.util.ArrayList;
import java.util.List;
public class Test {
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("1");
		list.add("2");
		list.add("3");
		for (String str : list) {
			System.out.println(str);
		}
		System.out.println("over");
	}
}


如for循环里面一样,如果是从数据库取出来的值,那么你就可以直接用foreach了,连类型转换都省略了。

下面是for循环里面的汇编代码


L4 (20)
LINENUMBER 16 L4
ALOAD 1
INVOKEINTERFACE java/util/List.iterator()Ljava/util/Iterator;  //这里其实调用的是集合的iterator
ASTORE 3
GOTO L5
L6 (25)
ALOAD 3
INVOKEINTERFACE java/util/Iterator.next()Ljava/lang/Object; //  因为泛型被擦除了,所以List里面只能方Object

CHECKCAST java/lang/String  //这里是类型转换,因为泛型被擦除了,所以List里面只能放Object,所以要类型转换成String
ASTORE 2
L7 (30)
LINENUMBER 17 L7
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 2
INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
L5 (34)
LINENUMBER 16 L5
ALOAD 3
INVOKEINTERFACE java/util/Iterator.hasNext()Z
IFNE L6



所以以上代码其实和这个相同
for (Iterator iterator = list.iterator(); iterator.hasNext();) {
			String str = (String) iterator.next();
			System.out.println(str);

		}
28 楼 IcyFenix 2011-05-04  
恩,同意楼上的说法
27 楼 marmot 2011-05-04  
IcyFenix 写道
marmot 写道
请教楼主一个问题
文章中说了对于泛型 c#采取的是膨胀法 Java采取的是擦除法
对于Java的擦除法 其实就是一个语法糖 编译后的class仍然采用的是强制类型转换

那么我们在Java编码中 如
   List list = new ArrayList();
   List<Integer> list = new ArrayList<Integer>();
使用泛型的好处 就在于 可以使得代码的语义更为明确 没有声明类型你不知道List里面存的具体是什么对象 声明泛型后可以让编译器为你检查 也使得阅读代码时能一眼就看出List里面存的具体是什么对象
  对于代码的性能,使用泛型与不使用泛型其实是一样的

不知道我这样的理解对不对


仅在java中比较,你那两句的性能是一样的。毕竟上面一句你拿到list里面的元素,要用它还是要手动转型,下面的是编译器帮你插入转型代码而已,在代码角度没什么不同,最多就是在类的元数据中有点差别而已。

如果要比较java中和c#中的泛型性能,那就是很麻烦很难准确的事情,RednaxelaFX曾经写过一篇关于泛型的文章提到这点,你有兴趣可以找找看。

ps:文章中提到过我的观点,从性能角度去看泛型实在是没什么必要= =#


谢谢icy的回答 我并不想比较c#与java两者之间对于泛型的性能
我只是 在Java代码中发现有的代码 使用了泛型声明 有的没有 我想知道这两者之间的差异
我的理解是 既然采取的是擦除法 那么在Java代码中 使用泛型与不使用泛型 除了在阅读代码时语义更为清晰之外
两者在性能上是一样的
不知道这样的理解对不对
26 楼 IcyFenix 2011-05-04  
marmot 写道
请教楼主一个问题
文章中说了对于泛型 c#采取的是膨胀法 Java采取的是擦除法
对于Java的擦除法 其实就是一个语法糖 编译后的class仍然采用的是强制类型转换

那么我们在Java编码中 如
   List list = new ArrayList();
   List<Integer> list = new ArrayList<Integer>();
使用泛型的好处 就在于 可以使得代码的语义更为明确 没有声明类型你不知道List里面存的具体是什么对象 声明泛型后可以让编译器为你检查 也使得阅读代码时能一眼就看出List里面存的具体是什么对象
  对于代码的性能,使用泛型与不使用泛型其实是一样的

不知道我这样的理解对不对


仅在java中比较,你那两句的性能是一样的。毕竟上面一句你拿到list里面的元素,要用它还是要手动转型,下面的是编译器帮你插入转型代码而已,在代码角度没什么不同,最多就是在类的元数据中有点差别而已。

如果要比较java中和c#中的泛型性能,那就是很麻烦很难准确的事情,RednaxelaFX曾经写过一篇关于泛型的文章提到这点,你有兴趣可以找找看。

ps:文章中提到过我的观点,从性能角度去看泛型实在是没什么必要= =#
25 楼 xgj1988 2011-05-04  
引用
那个…… 如果方便的话,最好把名字叫对,HLLVM圈里面叫错了N次……


十分抱歉楼主。icy,icy,icy呵呵不会错了
24 楼 marmot 2011-05-04  
请教楼主一个问题
文章中说了对于泛型 c#采取的是膨胀法 Java采取的是擦除法
对于Java的擦除法 其实就是一个语法糖 编译后的class仍然采用的是强制类型转换

那么我们在Java编码中 如
   List list = new ArrayList();
   List<Integer> list = new ArrayList<Integer>();
使用泛型的好处 就在于 可以使得代码的语义更为明确 没有声明类型你不知道List里面存的具体是什么对象 声明泛型后可以让编译器为你检查 也使得阅读代码时能一眼就看出List里面存的具体是什么对象
  对于代码的性能,使用泛型与不使用泛型其实是一样的

不知道我这样的理解对不对
23 楼 IcyFenix 2011-05-04  
那个…… 如果方便的话,最好把名字叫对,HLLVM圈里面叫错了N次……可以叫icy,但Lcy或者lay我很囧啊。

ecj先不说,javac本身就是由java代码写的,有什么疑问其实debug一下就可以(源码在JDK_SRC_HOME/langtools/src/share/classes/com/sun/tools/javac中)。主要的动作在com.sun.tools.javac.main.JavaCompiler中的compile()和compile2()方法里。下面这张图可以帮助你阅读源码,至于整个javac工作的分析过程,回头我看看总篇幅多少,如果不大的话我找个时间发blog上吧。

22 楼 xgj1988 2011-05-04  
希望lay和red都来解释一下我的那个想法是否正确,如果正确,希望给一个ecj或者javac 再编译JAVA源代码的时候执行机制的 说明就更好了。
21 楼 xgj1988 2011-05-04  
代码清单10-5中的重载当然不是根据返回值来确定的,之所以这次能编译和执行成功,是因为两个mehtod()方法加入了不同的返回值后才能共存在一个Class文件之中。第6章介绍Class文件方法表(method_info)的数据结构时曾经提到过,方法重载要求方法具备不同的特征签名,返回值并不包含在方法的特征签名之中,所以返回值不参与重载选择,但是在Class文件格式之中,只要描述符不是完全一致的两个方法就可以共存。

虽然道理是这样,但是我觉得还是不能解释  清单10-4和清单10-5之间的区别啊。

我怀疑这里可能还是编译器的工作原理问题。
  首先Lcy说的CLASS文件判断一个方法是否是同一个方法,确实和我们编写JAVA代码的时候不同。
我怀疑JAVAC编译的时候是可以是这样的
1:先会解析一些什么语法啊,词法,注解之类的。
2:把泛型的JAVA源代码先编译成一个中间的字节码形式。
3:把中间的字节码再转换成jvm的字节码(这个时候再进行泛型的擦除)。

对于ecj来说,可能就是
1:先会解析一些什么语法啊,词法,注解之类的。
2:对泛型先擦除泛型生成中间代码。
3:然后再进行把泛型中间代码生成字节码的时候发现存在两个相同的方法了。所以报错。

我觉得如果是这样也很好解释这种为什么在ECJ和JAVAC下面不同的原因。可能他们的在实现上对泛型的解析以及编译的时机可能不同,因为如果是JVM自动生成的字节码其实他真正表示一个方法就是Lay说的那样,返回值也会算在里面。
20 楼 marmot 2011-05-04  
支持 支持 很不错

相关推荐

    Java-Edge#Java-Interview-Tutorial#Java语法糖之泛型与类型擦除1

    - 泛型擦除前的例子把这段Java代码编译成Class文件,然后再用字节码反编译后,將会发现泛型都不见了,又变回了Java泛型出现之前的写法,泛型类型都变回了原

    解析Java泛型的类型擦除.pdf

    在 Java 语言中,泛型类型擦除的机制使得开发者难以理解和使用泛型,例如,在 Java 中,我们可以定义一个泛型类 `ArrayList&lt;T&gt;`,其中 `T` 是类型参数,但是,在编译后的字节码文件中,泛型类型信息已经被擦除,所有...

    Java核心知识1:泛型机制详解.pdf

    为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的...

    Java集合框架及泛型

    1. **类型擦除**: Java泛型在编译后会进行类型擦除,也就是说,所有的泛型类在运行时都会退化为未使用泛型的原始形式。这意味着在运行时无法检查泛型类型,但编译时的类型检查可以避免很多错误。 2. **边界通配符**...

    Java泛型_Java中的泛型结构_

    - 类型擦除:Java编译器会进行类型擦除,将泛型类的实例转换为无参数类型,但会在编译时进行类型检查。 3. 泛型接口: - 定义与实例化与泛型类类似,例如 `interface MyInterface&lt;T&gt; { ... }`,然后 `MyInterface...

    Java基础:泛型及其擦除性、不可协变性

     在Java SE1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。...

    [Java泛型和集合].(Java.Generics.and.Collections).文字版

    - **类型擦除**:Java泛型在编译后会被擦除,但在编译时提供了类型检查。 Maurice Naftalin和Philip Wadler的书籍会详细探讨这些概念,并通过实例解释如何在实际项目中应用泛型和集合。书中的文字版内容可能涵盖: ...

    java 泛型的使用 详细讲解

    - **泛型接口**:与泛型类相似,接口也可以有类型参数。 示例: ```java public interface List&lt;T&gt; { void add(T element); T get(int index); } ``` - **泛型方法**:在类中定义方法时,可以直接在方法...

    Java语言 泛型讲解案例代码 (泛型类、泛型接口、泛型方法、无界及上下限通配符、泛型对协变和逆变的支持、类型擦除 ...)

    学习和理解Java泛型的基本概念和语法; 实际项目中需要使用泛型来增加类型安全性和重用性的开发任务。 目标: 本代码资源的目标是帮助读者理解泛型的用法和优势,并通过实际的示例代码加深对泛型的掌握。读者可以...

    Java泛型研究.pdf

    * 泛型擦除:Java编译器在编译时会擦除泛型信息,这意味着在运行时,泛型信息将被擦除。 * 类型参数的约束:泛型的类型参数需要遵守一定的约束,例如,类型参数不能是基本类型。 * 泛型的使用需要遵守Java语言的语法...

    java 泛型方法使用示例

    需要注意的是,Java的泛型是通过类型擦除来实现的。这意味着在运行时,所有关于泛型的信息都会被删除,因此泛型只在编译时起作用。这也就意味着你不能在运行时通过反射获取到泛型的具体类型信息。 **七、总结** ...

    java泛型初探

    **类型擦除**:Java泛型在编译后会进行类型擦除,这意味着运行时不会保留任何关于类型的元数据。因此,泛型的主要作用在于编译期,用于检查类型安全。 **通配符**:在某些情况下,我们可能需要处理多种类型但又不...

    java经典教程-JDK1.5的泛型实现

    在JDK 1.5中,Java泛型的实现方式采用了类型擦除。这意味着在编译完成后,泛型信息会被删除,取而代之的是桥接方法和类型参数替换。这种设计是为了保持向后兼容性,因为Java早期版本的字节码不包含泛型信息。 **...

    Java泛型的简单实例

    * 类型擦除:Java中的泛型会在编译时被擦除,这意味着在运行时,泛型类型参数将被忽略。 * 类型约束:泛型的类型参数必须遵守某些约束,例如,不能使用基本类型作为泛型类型参数。 泛型的应用 泛型有很多应用场景...

    Java泛型技术之发展.rar

    6. 泛型擦除:由于Java的泛型是编译时的语法糖,所以在运行时,所有的泛型信息都会被擦除,转为非泛型的原始类型。这意味着在运行时无法获取到泛型的类型信息,但编译时的类型检查仍然有效。 7. 类型推断:从JDK 7...

    Generics_in_the_Java_Programming_Language.pdf

    7. 类字面量作为运行时类型令牌:可以使用.class语法获取泛型类型的Class对象,这对于反射和类型检查很有用。 8. 通配符捕获:是一个高级特性,它允许泛型代码使用某些特定的通配符类型,这在复杂的泛型代码中非常...

    java 泛型基础简单事例

    6. **类型擦除**:Java 泛型在编译后会进行类型擦除,这意味着在运行时,所有的泛型信息都会消失。因此,泛型主要提供编译时的类型检查和安全。 7. **野指针警告**:如果试图将非泛型对象赋值给泛型引用,编译器会...

    Java5.0泛型编程

    6. **类型擦除**: - Java的泛型在编译后会被擦除,生成的字节码中并不包含泛型信息。 - 这意味着在运行时,所有泛型类和泛型方法都退化为未使用泛型的等价形式。 7. **野指针警告**: - 当将非泛型集合转换为...

    java泛型学习

    - **泛型类型数组的限制**:由于泛型类型不能协变,因此不允许实例化泛型类型的数组,如 `new List[3]` 是非法的。唯一例外是使用未绑定的通配符,如 `new List[3]` 是合法的。 #### 五、构造延迟 - **类型擦除的...

Global site tag (gtag.js) - Google Analytics