编译期处理(语法糖)
java编译器把.java
源码编译成.class
字节码的过程,自动生成和转换的一些代码。
默认构造器
java
public class Candy01 {
}
编译成class后的代码
java
public class Candy1 {
public Candy1(){
super();
}
}
自动拆装箱(jdk5加入)
java
public class Candy02 {
public static void main(String[] args) {
/**
* jdk5之前:
* Integer x = Integer.valueOf(1);
* int y = x.intValue();
*/
Integer x = 1; // 自动拆箱
int y = x; // 自动装箱
}
}
泛型集合取值
java
public class Candy03 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(10); // 实际调用:List.add(Object e)
Integer x = list.get(0); // 实际调用的是 Object obj = List.get(int idx);
}
}
编译器在获取真正字节码时,需要额外做一个类型转换的操作:
java
Integer x = (Integer)list.get(0);
可变参数
java
public class Candy04 {
public static void foo(String... args) {
String[] array = args;
System.out.println(array);
}
public static void main(String[] args) {
foo("hello", "world");
}
}
编译后的代码:
java
public class Candy04 {
public static void foo(String[] args) {
String[] array = args;
System.out.println(array);
}
public static void main(String[] args) {
foo(new String[]{"hello", "world"});
}
}
如果调用了
foo()
,则等价于foo(new String[]{})
,创建了一个空数组,而不会直接传null进去
数组 - foreach循环
java
public class Candy05_01 {
public static void main(String[] args) {
int[] array = {1, 2, 3, 4, 5};
for(int e : array) {
System.out.println(e);
}
}
}
会被编译为:
java
int[] array = new int[]{1, 2, 3, 4, 5};
for(int i = 0; i < array.length; i++) {
int e = array[i];
System.out.println(e);
}
List - foreach循环
java
public class Candy05_02 {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
for(Integer i : list) {
System.out.println(i);
}
}
}
会被编译成:
java
Iterator it = list.iterator();
while(it.hasNext()) {
Integer e = (Integer) it.next();
System.out.println(e);
}
foreach循环写法可以配合数组,以及所有实现了Iterator接口的集合类一起使用
switch - 字符串
java
public class Candy06_01 {
public static void choose(String str) {
switch(str) {
case "hello": {
System.out.println("h");
break;
}
case "world": {
System.out.println("w");
break;
}
}
}
}
会被编译成:
java
public class Candy06_01 {
public static void choose(String str) {
byte x = -1;
switch(str.hashCode()) {
case 99162322: // "hello"的hash值
if (str.equals("hello")) {
x = 0;
}
break;
case 113318802: // "world"的hash值
if (str.equals("world")) {
x = 1;
}
}
switch(x) {
case 0:
System.out.println("h");
break;
case 1:
System.out.println("w");
}
}
}
编译后其实是执行了两次switch
- 第一次是根据hash和equals将字符串转换为相应的byte类型
- 第二次才是利用byte进行比较。
第一遍使用hashCode进行比较,是为了提高效率,较少可能的比较;而equals是为了防止hash冲突。
switch - 枚举类
java
enum Sex {
MALE, FEMALE
}
public class Candy06_02 {
public static void foo(Sex sex) {
switch(sex) {
case MALE: {
System.out.println("男");
break;
}
case FEMALE: {
System.out.println("女");
break;
}
}
}
}
编译后代码:
java
public class Candy06_02 {
public static void foo(Sex sex) {
// 获取枚举的序号(MALE.ordinal() = 0, FEMALE.ordinal() = 1)
int ordinal = sex.ordinal();
switch (ordinal) {
case 0: // MALE
System.out.println("男");
break;
case 1: // FEMALE
System.out.println("女");
break;
}
}
}
枚举类
java
enum Sex {
MALE, FEMALE
}
转换后代码:
java
public final class Sex extends java.lang.Enum<Sex> {
// 枚举常量(public static final)
public static final Sex MALE;
public static final Sex FEMALE;
// 私有构造函数(enum 不能外部实例化)
private Sex(String name, int ordinal) {
super(name, ordinal);
}
// 静态初始化块(初始化所有枚举值)
static {
MALE = new Sex("MALE", 0);
FEMALE = new Sex("FEMALE", 1);
$VALUES = new Sex[]{MALE, FEMALE};
}
// 自动生成的方法:values() 返回所有枚举值
public static Sex[] values() {
return (Sex[])$VALUES.clone();
}
// 自动生成的方法:valueOf(String) 根据名字返回枚举
public static Sex valueOf(String name) {
return (Sex)Enum.valueOf(Sex.class, name);
}
// 内部存储所有枚举值的数组
private static final Sex[] $VALUES;
}
try-with-resources简化资源关闭
java
public class Candy07 {
public static void main(String[] args) {
/*
try(资源变量 = 创建资源对象) {
}catch() {
}
*/
try(InputStream is = new FileInputStream("d:\\test.txt")) {
System.out.println(is);
}catch (Exception e) {
e.printStackTrace();
}
}
}
其中:资源对象需要实现
AutoCloseable
接口,例如:InputStream
、OutPutStream
、Connection
、Statement
、ResultSet
等接口都实现了AutoCloseable接口,使用try-with-resources可以不用写finally语句,编译器会帮助我们生成资源关闭的代码。
上边代码被编译为:
java
public class Candy07 {
public static void main(String[] args) {
try {
InputStream is = new FileInputStream("d:\\test.txt");
Throwable t = null;
try {
System.out.println(is);
}catch (Throwable e1){
t = e1; // t是我们代码中出现的异常
throw e1;
} finally {
// 判断了资源不为空
if(is != null) {
// 如果我们的代码有异常
if(t != null) {
try {
is.close();
} catch (Throwable e2) {
// 如果close出现异常,作为被压制异常添加
t.addSuppressed(e2);
}
}else {
// 如果代码没有异常,close出现的异常就是最后catch块中的e
is.close();
}
}
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
如果我们的代码出现了异常,并且关闭资源的时候又出现了异常,如果想让这两个异常都显示,可以使用
压制异常
。
例如:
java
public class Candy08 {
public static void main(String[] args) {
try (MyResource resource = new MyResource()) {
int i = 1 / 0; // 除0异常
}catch (Exception e) {
e.printStackTrace();
}
}
}
class MyResource implements AutoCloseable {
@Override
public void close() throws Exception {
throw new Exception("close异常"); // 资源关闭异常
}
}
输出:
两个异常都会被抛出
方法重写时的桥接方法
java
class A {
public Number m() {
return 1;
}
}
class B extends A {
@Override
// 子类m方法的返回值是Integer,是父类m方法返回值Number的子类
public Integer m() {
return 2;
}
}
编译后的代码:
java
class B extends A {
public Integer m() {
return 2;
}
// 此方法才是真正重写了父类public Number m()方法
public synthetic bridge Number m() {
// 调用public Integer m()
return m();
}
}
桥接方法比较特殊,只对java虚拟机可见,与原来的public Integer m(),没有命名冲突。
无参的匿名内部类
java
public class Candy08 {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("ok");
}
};
}
}
编译后代码:
java
// 额外生成的类
final class Candy08$1 implements Runnable {
Candy08$1() {}
@Override
public void run() {
System.out.println("ok");
}
}
public class Candy08 {
public static void main(String[] args) {
Runnable runnable = new Candy08$1();
}
}
引用局部变量的匿名内部类
java
public class Candy10 {
public static void test(final int x) {
Runnable runnable = new Runnable() {
public void run() {
System.out.println(x);
}
};
}
}
编译后:
java
// 额外生成的类
final class Candy10$1 implements Runnable {
int val$x;
Candy10$1(int x) {
this.val$x = x;
}
public void run() {
System.out.println(this.val$x);
}
}
public class Candy10 {
public static void test(final int x) {
Runnable runnable = new Candy10$1(x);
}
}
这也解释了为什么匿名内部类里边使用的变量必须使用final修饰:因为在创建
Candy10$1
对象时,将x的值赋值给了Candy10$1
对象的val$x
属性,所以x的值不会在发生变化了。如果发生变化,那么val$x
属性没有机会再跟着一起变化了。