文章目录
十一. 语法糖
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 一起变化。