JAVA新手入门08~语法糖


Java的语法糖,就是编译器为了方便大家写代码,对于一些代码,做了一些简化的语法,可以让大家在平时写代码的时候,更方便写出容易阅读的代码,但是在编译完之后,编译器会把这些代码,再还原回来。在Java中,有1个大家最经常使用的语法糖,就是整形变量的自动装箱,Integer a = 10;很明显10不是一个对象,Integer是一个对象,不同的东西,不能赋值,为啥还可以这么做呢? 这个东西就是Java的语法糖。

Java中的语法糖有以下几种:

  1. 泛型(JDK1.5)

  2. 自动装箱和拆箱(JDK1.5)

  3. 变长参数(JDK1.5)

  4. 实现Iterable接口的foreach语法(JDK1.5)

  5. 接口AutoCloseable支持的try-with-resource用法(JDK1.7)

  6. switch支持枚举和String(JDK1.7)


1、泛型(JDK1.5)

泛型是1个编程语言是否友好的重要标志,时下火热的Go语言,至今还没有实现泛型,Java中的泛型是通过类型擦除实现的,并不是真正意义上的泛型,由于本文不打算深入讲解泛型,在此不过多叙述。看以下泛型编译之后,被JVM实际执行的代码是什么样子的

源代码(1.5之前)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
List arr = new ArrayList();
arr.add("this is first");
arr.add(123);
for(Object obj : arr) {
System.out.println(obj);
}
// Class文件(1.5之前):
List arr = new ArrayList();
arr.add("this is first");
arr.add(123);
Iterator var6 = arr.iterator();

while(var6.hasNext()) {
  Object obj = var6.next();
  System.out.println(obj);
}

JDK1.5之后,为了兼容1.5之前的代码,老代码可以保持不变,新代码如果是:

1
2
3
List<String> arr = new ArrayList<String>();
arr.add("this is first");
arr.add(123);

会报编译器错误,原理是一样的,都会在编译期转变成Object对象,然后强制转换成需要的类型。


2、自动装箱和拆箱(JDK1.5)

直接上代码吧,这个比较容易理解,就是JAVA会自动在基本类型和基本类型的包装类之间,进行自动转换,当然这只是一种语法糖,代码编译成class文件的时候,就会自动把这个转换成对应的方法:

1
2
3
4
5
6
7
8
9
10
11
//源代码
int a = 1290;
Integer b = 1000;
Integer a1 = a; // 自动装箱
int b1 = b.intValue(); //自动拆箱

// Class文件:
int a = 1290;
Integer b = new Integer(1000);
Integer a1 = Integer.valueOf(a);
int b1 = b; 

其它的如:

1
2
3
4
5
6
7
8
9
10
11
Character ch = 'a'
String s = "abc";

Integer a = 10;
int b = a + 100//操作符做运算触发自动拆箱

//函数调用参数是包装类,触发自动拆箱/装箱
int c = add(10,30);
private int add(Integer a,Integer b) {
return a + b;
}

Java中的以下几种类型都支持自动拆箱,装箱。


  • Primitive type Wrapper class
  • boolean Boolean
  • byte Byte
  • char Character
  • float Float
  • int Integer
  • long Long
  • short Short
  • double Double

资料来源:
https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html

3、变长参数(JDK1.5)

变长参数指的是,一个方法的参数,可以有多个,具体有多少个,不确定,如果没有语法糖,我们一般会传一个List的引用进去,下面来看看,Java的这个变长参数是怎么实现的(强制转换为数组),不过平时我用的也不多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//源代码:
private void printStr(String... arr) {
for (String s : arr) {
System.out.println(s);
}
}
//class文件:
private void printStr(String... arr) {
String[] var2 = arr; //这里可以看出来,先把参数转为数组,然后读取数组来做的
int var3 = arr.length;

for(int var4 = 0; var4 < var3; ++var4) {
String s = var2[var4];
System.out.println(s);
}
}

4、实现Iterable接口的foreach语法(JDK1.5)

这个是平时用到的最多的一个语法糖了,如果你刚开始写Java,会遇到很多这种代码,算是用的比较多的一个了,其实上面代码中有体现,直接转Iterator迭代实现,所以需要实现了Iterable接口的类,才可以使用这个语法糖。

1
2
3
4
5
6
7
8
9
10
11
// 源代码
List<String> arr = new ArrayList<String>();
for (String s : arr) {
System.out.println(s);
}
//class文件:
Iterator var6 = arr.iterator();
while(var6.hasNext()) {
  String s = (String)var6.next();
  System.out.println(s);
}

5、接口AutoCloseable支持的try-with-resource用法(JDK1.7)

这个用的就更少了,我很少在实际的项目中见到这样的代码,也可能是因为平时读取资源的文件不多,平时很多时候都是CRUD了,仅仅是1个糖,编译后,又还原了最初的样子,还是传统的try{} catch() {} 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
40
41
//源代码:

String content = "this is what you want to write to file";
try (BufferedWriter out = new BufferedWriter(new
FileWriter("/tmp/a.txt", true))) {
out.write(content);
System.out.println("content is :" + content);
} catch (IOException e) {
// error processing code
} finally {

}

//class
try {
BufferedWriter out = new BufferedWriter(new FileWriter("/tmp/a.txt", true));
Throwable var8 = null;
try {
out.write(content);
System.out.println("content is :" + content);
} catch (Throwable var26) {
var8 = var26;
throw var26;
} finally {
if (out != null) {
if (var8 != null) {
try {
out.close();
} catch (Throwable var25) {
var8.addSuppressed(var25);
     }
   } else {
out.close();
      }
}
}
} catch (IOException var28) {

finally {

}

6、switch支持枚举和String(JDK1.7)

这个就比较冷门了,平时编程使用switch的时候,很少使用String来做枚举,一般都是用int的比较多。

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
//源代码:
String s = "abc";
switch (s) {
case "abc":
System.out.println("this is abc");
break;
case "c":
System.out.println("this is c");
break;
    default:
break;
}


//class文件:
String s = "abc";
byte var3 = -1;
switch(s.hashCode()) {
case 99:
if (s.equals("c")) {
var3 = 1;
}
    break;
case 96354:
if (s.equals("abc")) {
var3 = 0;
      }
}

switch(var3) {
case 0:
System.out.println("this is abc");
break;
case 1:
      System.out.println("this is c");
}

可见是计算了string的hashcode,然后把hashcode转换为了整型的switch,为了处理hash冲突,使用equals来做最后的确认,并不是在底层就支持String,所以具体在使用的时候,需要知道原理是什么,如果String串特别长,很明显性能就不太好,不适合使用。

结尾

语法糖是java中的一个小小的插曲,不会对你的编程生涯造成什么太大的影响,即使不专门学习,也能在项目中学习到,因为用到的实在太多了,学了一下,感觉也没有提升太多的知识,归根到底,还是因为这个东西太简单了,如果说非要有一点要注意的地方,就是整形的自动装箱和拆箱,会有一个小小的坑,那就是日常使用的时候,经常会写这样的代码:

1
2
3
4
5
6
7
Integer a = 10;
int b = 20;
if(a != null && a == b) { // a != null 如果省略了,可能会空指针
do something
} else {
do something
}

当我们在比较a和b的值的时候,需要心里清楚,这是Java的自动拆箱,有的时候也会有空指针异常,所以一定要保证a != null 说到包装类的比较,还有一个小知识点,需要注意的地方,就是包装类的缓存:

1
2
3
4
5
6
7
8
9
10
Integer a = 127;
Integer b = 127;
boolean b1 = a == b;

Integer a0 = 128;
Integer b0 = 128;
boolean b2 = a0 == b0;

System.out.println("b1 is :" + b1);
System.out.println("b2 is :" + b2)

执行上面的代码,b1和b2是true还是false呢? 实际的结果是b1 is : true b2 is : false,为什么呢? 因为Java对于包装类,有1个缓存,a和b指向的是同1个对象,这个缓存的范围是 -128 到 127 之间,在这个范围内,2个包装类都是相等的。感兴趣的话可以看Integer的源码,里面有1个IntegerCache的内部类。

文本完。