编译期语法糖(三)

编译期语法糖(三)

try-with-resources

1
2
3
4
5
6
7
8
9
10
11
public class Demo06_1 {
public static void main(String[] args) {
try (InputStream is = new FileInputStream("d:\\1.txt")){
System.out.println(is);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

使用try-catch-resources 可以不用写finally语句块,编译期会帮助生成关闭资源代码.

代码会被转换为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class Demo06_1 {
public Demo06_1() {
}

public static void main(String[] args) {
try {
InputStream is = new FileInputStream("d:\\1.txt");
Throwable var2 = null;

try {
System.out.println(is);
} catch (Throwable var13) {
//var2 我们代码出现异常
var2 = var13;
throw var13;
} finally {
if (is != null) {
if (var2 != null) {
try {
is.close();
} catch (Throwable var12) {
//如果close 出现异常,作为被压制异常
var2.addSuppressed(var12);
}
} else {
//如果我们代码没有异常,close 出现异常就是最后catch块中的异常
is.close();
}
}

}
} catch (FileNotFoundException var15) {
var15.printStackTrace();
} catch (IOException var16) {
var16.printStackTrace();
}

}
}

可以看出,jvm帮我们生成了finally代码,并且未防止close和我们的代码同时出现异常,异常丢失的情况,还做了优化.

我们可以测试一下我们的代码,和资源close同时抛出异常的情况.

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Demo06_2 {
public static void main(String[] args) {
try (MyResource resource = new MyResource()){
int i = 1/0;
} catch (Exception e) {
e.printStackTrace();
}
}
}
class MyResource implements AutoCloseable{

@Override
public void close() throws Exception {
throw new Exception("资源Close时异常!");
}
}

运行结果如下:

1
2
3
4
5
java.lang.ArithmeticException: / by zero
at demo06.Demo06_2.main(Demo06_2.java:6)
Suppressed: java.lang.Exception: 资源Close时异常!
at demo06.MyResource.close(Demo06_2.java:16)
at demo06.Demo06_2.main(Demo06_2.java:7)

所以可以看出,我们自己代码的异常/ by zero ,以及资源Close时异常资源Close时异常! 都有保留,并且正常抛出.

方法重写时的桥接方法

我们都知道,方法重写时的返回值分两种情况.

  1. 父子类的返回值完全一致
  2. 子类的返回值可以是父类返回值的子类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Demo06_3 {
public Number n(){
return 1;
}
}
class B extends Demo06_3{
/**
* 子类n方法的返回值是Integer
* 父类的返回值是Number
* Integer是Number的子类
* @return
*/
@Override
public Integer n() {
return 2;
}
}

对于子类,java编译器会做如下处理:

1
2
3
4
5
6
7
8
9
10
11
class B extends Demo06_3{

public Integer n() {
return 2;
}
//这个桥接方法,才是真正的重写的方法
//桥接方法是JVM自动生成的方法,对程序猿不可见
public synthetic bridge Number n(){
return n();
}
}

我们可以使用反射来验证一下

1
2
3
4
5
6
7
public static void main(String[] args) {
Method[] declaredMethods = B.class.getDeclaredMethods();
for (Method method :
declaredMethods) {
System.out.println(method);
}
}

打印结果:

1
2
public java.lang.Integer demo06.B.n()
public java.lang.Number demo06.B.n()

可以看出,确实有两个方法.一个方法是我们自己写的,另一个方法是合成后的桥接方法.

匿名内部类

1
2
3
4
5
6
7
8
9
10
public class Demo06_4 {
public static void main(String[] args) {
Runnable r =new Runnable() {
@Override
public void run() {
System.out.println(1);
}
};
}
}

转换后代码:

1
2
3
4
5
6
7
8
//额外生成的类
final class Demo06_4$1 implements Runnable{

@Override
public void run() {
System.out.println(1);
}
}
1
2
3
4
5
public class Demo06_4 {
public static void main(String[] args) {
Runnable r =new Demo06_4$1();
}
}

原来的main方法,就新建了一个额外生成的类来调用.


我们再来看下使用外部参数的匿名内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Demo06_5 {
public static void main(String[] args) {
final int i = 10;
test(i);
}

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

转换后代码:

1
2
3
4
5
6
7
8
9
10
11
12
//额外生成的类
final class Demo06_5$1 implements Runnable{
private int val$1;

public Demo06_5$1(int val$1) {
this.val$1 = val$1;
}
@Override
public void run() {
System.out.println(val$1);
}
}
1
2
3
4
public static void test(final int i){
Runnable run = new Demo06_5$1(i);
run.run();
}

为了使匿名内部流可以使用外部的变量,在编译期,会生成对应的参数

然后通过构造方法传给额外生成的类.

这也是为什么匿名内部类只能使用final参数的原因.

0%