JVM学习4 - 语法糖

十一. 语法糖

java编译器把源码编译成class字节码的过程中,自动生成和转换的一些代码。(直接转换成了字节码,不会转换出Java源码)

1. 默认构造器

class 中如果没有无参构造器,自动生成无参构造器。

public class Test{
}

转成字节码后,源码实际如下:

public class Test{
	public Test(){
		super();//调用父类Object的无参构造方法
	}
}

2. 自动拆装箱

Java基本类型与包装类型间的转换

Integer x = 1; // Integer x = Integer.valueOf(1)
int y = x; // y = x.intValue()

3. 泛型擦除

Java 在编译泛型代码后会执行泛型擦除的操作,即泛型信息在编译字节码之后就丢失了,实际的类型都当作 Object 类型处理。

List<Integer> list = new ArrayList<>();
list.add(10); //实际调用的是 list.add(Object e)
Integer x = list.get(0); //实际调用的是 Object obj = list.get(int index); 之后会自动做一次强制类型转换

4. 可变参数

String… args 自动转变为 String[] args。根据可变参数的个数定义数组长度,如果是无参,则创建一个空的数组。

5. foreach 循环

  • 遍历数组:foreach 会转变成 for 循环的字节码
int[] array = {1, 2, 3, 4, 5}; //字节码为 new int[]{1, 2, 3, 4, 5}
for(int e : array) { //for(int i = 0; i < array.length; ++i)
	System.out.println(e); // int e = array[i]; System.out.println(e);
}
  • 遍历列表
List<Integer> list = Arrays.asList(1,2,3,4,5);
for(Integer i : list) { 
	System.out.println(i);
}

foreach 会转换成迭代器

List<Integer> list = Arrays.asList(1,2,3,4,5);
Iterator iter = list.iterator();
while(iter.hasNext()) {
	Integer e = (Integer)iter.next();
	System.out.println(e);
}

所以,foreach 循环的写法能够配合数组,以及所有实现了 iterable 接口(用于获取iterator迭代器)的集合类一起使用。

6. switch 字符串

jdk 7 开始,switch可以用于字符串和枚举类,这个也是通过语法糖实现。

  • 字符串 switch
public static void choose(String str){
	switch(str):
		case "hello": {
			System.out.println("h");
			break;
		}
		case "world": {
			System.out.println("w");
			break;
		}
}

转换如下,将自动将字符串的 switch 转换为 2 个整型的 switch:

public static void choose(String str){
	byte x = -1;
	switch(str.hashCode()):
		case 99162322: { // hello 的 hashCode
			if(str.equals("hello")){
				x = 0;
			}
			break;
		}
		case 113318802: { // world 的 hashCode
			if(str.equals("world")){
				x = 1;
			}
			break;
		}
	switch(x) {
		case 0:
			System.out.println("h");
			break;
		case 1: 
			System.out.println("w");
			break;
	}
}

注意,为什么要先用 hashCode 比较再用 equals 比较呢?
使用 hashCode 是为了提高比较效率,equals 是为了防止 hash 冲突。

  • 枚举 switch
enum Sex {
	MALE, FEMALE
}
......
public static void test(Sex sex) {
	switch(sex) {
		case MALE:
			System.out.println("男");
			break;
		case FEMALE:
			System.out.println("女");
			break;
	}
}

语法糖转换为

static class $MAP{ //生成一个合成类(仅jvm使用,对我们不可见)
	static int[] map = new int[2];
	static {
		map[Sex.MALE.ordinal()] = 1; // ordinal 表示枚举对象的序号,从0开始
		map[Sex.FEMALE.ordinal()] = 2;
	}
}
public static void test(Sex sex) {
	int x = $MAP.map[sex.ordinal()];
	switch(x) {
		case 1:
			System.out.println("男");
			break;
		case 2:
			System.out.println("女");
			break;
	}
}

7. try-with-resource

自动释放资源。

public static void main(String[] args){
	// 文件将自动关闭
	try(InputStream is = new FileInputStream("d:\\1.txt")) {
		System.out.println(is);
	} catch (IOException e) {
		e.printStackTrace();
	}
}

语法糖转换为:

public static void main(String[] args){
	try {
		InputStream is = new FileInputStream("d:\\1.txt");
		Throwable t = null;
		try {
			System.out.println(is);
		} catch (Throwable e1) {
			t = e1;
			throw e1;
		} finally {
			if (is != null) { // is为null则无需关闭
				if (t != null) { //说明代码执行部分抛出了异常
					try {
						is.close();
					} catch (Throwable e2) {
						t.addSuppressed(e2); //如果文件关闭时还出现异常,则会作为被压制异常添加,而不会丢失
					}
				} else { //说明是文件关闭时出现了异常
					is.close();
				}
			} 
		}
	} catch (IOException e) {
		e.printStackTrace();
	}
}

8. 方法重写桥接

方法重写时,子类返回值可以是父类返回值的子类

class A {
	public Number m() {
		return 1;
	}
}
class B extends A {
	@Override
	public Integer m() { //Integer是Number的子类
		return 2;
	}
}

语法糖转化为:

class B extends A {
	public Integer m() { //Integer是Number的子类
		return 2;
	}
	//此方法才是真正重写了父类的 public Number m() 方法
	public synthetic bridge Number m() {
		return m();
	}
}

实际通过反射获取类B的方法,也会发现它有 public Integer m() 和 public Number m() 两个方法。

9. 匿名内部类

引用局部变量的匿名内部类

public class Candy11 {
	public static void test(final int x) {
		Runnable runnable = new Runnable() {
			@Override
			public void run() {
				System.out.println(x);
			}
		}
	}
}

语法糖转换为:

//额外生成的类
final class Candy11$1 implements Runnable {
	int val$x; //变量通过定义一个内部变量传入
	Candy11$1(int x) {
		this.val$x = x;
	}
	public void run() {
		System.out.println(this.val$x);
	}
}
public class Candy11 {
	public static void test(final int x) {
		Runnable runnable = new Candy11$1(x);
	}
}

这解释了为什么匿名内部类引用局部变量时一定要使用 final 类型。因为在创建了 Candy11$1 对象后,val$x 属性将没有机会再和 x 一起变化。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值