`
IcyFenix
  • 浏览: 358932 次
  • 性别: 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基础:泛型及其擦除性、不可协变性

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

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

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

    【java核心技术】泛型设计

    而在jdk5引入泛型后,便可以在编译器实现类型检查(实际为编译器语法糖,虚拟机没有泛型对象,所有对象都属于普通类) 泛型标识符一般为 T,E,K,V 二、泛型定义 (1)泛型类 代码示例: public class Generic { ...

    Java数据结构入门基础

    文章目录 泛型概念 泛型语法 泛型的使用 泛型的擦除机制 泛型的上界 泛型方法 通配符 泛型概念 泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化 一般的类和方法,只能使用具体的类型:

    Java中高级核心知识全面解析(精华必看)

    一、Java (一). 基础 1.Java 基本功 1.1.Java 入门(基础概念与常识) 1.1.1.Java 语言有哪些特点? 1.1.2.关于 JVM JDK 和 JRE 最详细通俗的解答 ...什么是类型擦除?介绍一下常用的通配符? ......

    深入理解_Java_虚拟机 JVM_高级特性与最佳实践

    / 259 10.2.2 解析与填充符号表 / 262 10.2.3 注解处理器 / 264 10.2.4 语义分析与字节码生成 / 264 10.3 Java语法糖的味道 / 268 10.3.1 泛型与类型擦除 / 268 10.3.2 自动装箱、拆箱与遍历循环 / 273 10.3.3...

    Java虚拟机

    10.3 Java语法糖的味道 10.3.1 泛型与类型擦除 10.3.2 自动装箱、拆箱与遍历循环 10.3.3 条件编译 10.4 实战:插入式注解处理器 10.4.1 实战目标 10.4.2 代码实现 10.4.3 运行与测试 10.4.4 其他应用案例 ...

    java笔试题算法-Thinking-in-Java:《ThinkingInjava》-Note《Java编程思想》笔记作者:[美]Bruce

    这本书不会一味地讲语法,更多的是会讲一些Java的设计思想,比如他不会一开始就教你怎么用泛型,而是先介绍为什么会出现泛型,没有泛型会有什么后果,Java设计者又为什么会选择泛型的擦除,书中对是否选择泛型的擦除...

    Java2核心技术.part5

    13.9. 2虚拟机中的泛型类型信息 附录AJava关键字 附录B更新的JDK 5.0代码 Java2核心技术II卷.高级特性 目录: 译者序 前言 第1章 多线程 1.1 什么是线程 1.2 中断线程 1.3 线程状态 1.4 线程属性 1.5 ...

    面试day1-基础.docx

    这份面试题集涵盖了Java面试中常见的一些问题,从多继承的原因到equals方法的区别,再到包装类的应用场景和类型擦除的概念,覆盖了Java语言中的多个重要方面。这些问题不仅考察了基本的语法知识,还涉及到Java的特性...

    疯狂JAVA讲义

    8.4.2 泛型方法和类型通配符的区别 306 8.4.3 设定通配符的下限 307 8.4.4 泛型方法与方法重载 309 8.5 擦除和转换 310 8.6 泛型与数组 311 8.7 本章小结 313 第9章 与运行环境交互 314 9.1 与用户互动 315 ...

    Java2核心技术.part3

    13.9. 2虚拟机中的泛型类型信息 附录AJava关键字 附录B更新的JDK 5.0代码 Java2核心技术II卷.高级特性 目录: 译者序 前言 第1章 多线程 1.1 什么是线程 1.2 中断线程 1.3 线程状态 1.4 线程属性 1.5 ...

    Java2核心技术.part1

    13.9. 2虚拟机中的泛型类型信息 附录AJava关键字 附录B更新的JDK 5.0代码 Java2核心技术II卷.高级特性 目录: 译者序 前言 第1章 多线程 1.1 什么是线程 1.2 中断线程 1.3 线程状态 1.4 线程属性 1.5 同步 1.6 ...

    Java2核心技术.part6

    13.9. 2虚拟机中的泛型类型信息 附录AJava关键字 附录B更新的JDK 5.0代码 Java2核心技术II卷.高级特性 目录: 译者序 前言 第1章 多线程 1.1 什么是线程 1.2 中断线程 1.3 线程状态 1.4 线程属性 1.5 ...

    Java2核心技术.part4

    13.9. 2虚拟机中的泛型类型信息 附录AJava关键字 附录B更新的JDK 5.0代码 Java2核心技术II卷.高级特性 目录: 译者序 前言 第1章 多线程 1.1 什么是线程 1.2 中断线程 1.3 线程状态 1.4 线程属性 1.5 ...

    Java2核心技术.part2

    13.9. 2虚拟机中的泛型类型信息 附录AJava关键字 附录B更新的JDK 5.0代码 Java2核心技术II卷.高级特性 目录: 译者序 前言 第1章 多线程 1.1 什么是线程 1.2 中断线程 1.3 线程状态 1.4 线程属性 1.5 ...

    javaSE代码实例

    第2章 基本数据类型——构建Java 大厦的基础 12 2.1 源代码注释 12 2.1.1 单行注释 12 2.1.2 区域注释 12 2.1.3 文档注释 13 2.2 基本数据类型 14 2.2.1 整型 15 2.2.2 浮点型 17 2.2.3 char型 17...

Global site tag (gtag.js) - Google Analytics