深入理解Java虚拟机——Java语法糖

1. 泛型与类型擦除

  • 泛型是 JDK 1.5 的一项新特性,它的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、 接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。
  • 泛型技术在 C# 和 Java 之中的使用方式看似相同,但实现上却有着根本性的分歧
    • C# 里面泛型无论在程序源码中、编译后的 IL 中(Intermediate Language,中间语言,这时候泛型是一个占位符)还是运行期的 CLR 中都是切实存在
    • List<int>与List<String>就是两个不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型被称为真实泛型
    • Java 语言中的泛型则不一样,它只在程序源码中存在,在编译后的字节码文件中, 就已经被替换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码
    • 对于运行期的 Java 语言来说,ArrayList<int>与 ArrayList<String>就是同一个类,Java 语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型被称为伪泛型

1.1 类型擦除

  • 是一段简单的Java泛型例子,我们可以看一下它编译后的结果是怎样的?
Map<String, String>map=new HashMap<String, String>();
map.put("hello""你好");
System.out.println(map.get("hello"));

把这段Java代码编译成Class文件,然后再用字节码反编译工具进行反编译:

Map map=new HashMap();
map.put("hello""你好");
System.out.println((String)map.get("hello"));

类型擦除!!!

1.2 当泛型遇见重载

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>,擦除动作导致这两个方法的特征签名变得一模一样
  • 无法重载的原因已经找到了,但是真的就是如此吗?事实上,只能说,泛型擦除成相同的原生类型只是无法重载的一部分原因,请再接着看一看代码
  • 补充:Java 语法层面和 JVM 层面上方法特征签名
    • Java 语法层面的方法特征签名:特征签名 = 方法名 + 参数类型 + 参数顺序;
    • JVM 层面的方法特征签名:特征签名 = 方法名 + 参数类型 + 参数顺序 + 返回值类型
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)

  • 两段代码的差别是两个 method 方法添加了不同的返回值,由于这两个返回值的加入,方法重载居然成功了,即这段代码可以被编译和执行了。 这是我们对 Java 语言中返回值不参与重载选择的基本认知的挑战吗?
  • 方法重载要求方法具备不同的特征签名,返回值并不包含在方法的特征签名之中,所以返回值不参与重载选择
  • 但是在Class文件格式之中,只要描述符不是完全一致的两个方法就可以共存。也就是说两个方法如果有相同的名称和特征签名,但返回值不同,那它们也是可以合法地共存于一个Class文件中的

2. 自动装箱、拆箱

	List<Integer>list=Arrays.asList(1234);
	//如果在JDK 1.7中,还有另外一颗语法糖,
	//能让上面这句代码进一步简写成List<Integer>list=[1,2,3,4];
	int sum=0forint i:list){
		sum+=i;
	}
	System.out.println(sum);

编译之后

	List list=Arrays.asList(new Integer[]{
		Integer.valueOf(1),
		Integer.valueOf(2),
		Integer.valueOf(3),
		Integer.valueOf(4};
	int sum=0;
	for(Iterator localIterator=list.iterator(); localIterator.hasNext();{
		int i=((Integer)localIterator.next()).intValue();
		sum+=i;
	}
	System.out.println(sum);

自动装箱、拆箱在编译之后被转化成了对应的包装和还原方法,对应 Integer.valueOf()与Integer.intValue()方法,很简单

  • 一些注意的地方:
public static void main(String[]args){
	Integer a=1;
	Integer b=2;
	Integer c=3;
	Integer d=3;
	Integer e=321;
	Integer f=321;
	Long g=3L;
	
	System.out.println(c==d);
	System.out.println(e==f);
	System.out.println(c==(a+b));
	System.out.println(c.equals(a+b));
	System.out.println(g==(a+b));
	System.out.println(g.equals(a+b));
}

输出为:

true
false
true
true
true
false
true

简单解释下:

  1. java中会有一个 Integer 缓存池,缓存的大小是:-128~127
  2. 使用 == 的情况:
    • 如果比较的是 Integer变量,默认比较的是地址值
    • Java 的 Integer 维护了从 -128~127 的缓存池
    • 如果比较的某一边有操作表达式(如 a+b),则比较的是具体数值
  3. 使用 equals() 的情况:
    • 无论是 Integer 还是 Long 的 equals() 默认比较的是数值
    • Long 的 equals() 方法,会先判断是否是 Long 类型(因此上面 g==(a+b) 返回的是 false)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值