Java的语法糖,就是编译器为了方便大家写代码,对于一些代码,做了一些简化的语法,可以让大家在平时写代码的时候,更方便写出容易阅读的代码,但是在编译完之后,编译器会把这些代码,再还原回来。在Java中,有1个大家最经常使用的语法糖,就是整形变量的自动装箱,Integer a = 10;很明显10不是一个对象,Integer是一个对象,不同的东西,不能赋值,为啥还可以这么做呢? 这个东西就是Java的语法糖。
Java中的语法糖有以下几种:
-
泛型(JDK1.5)
-
自动装箱和拆箱(JDK1.5)
-
变长参数(JDK1.5)
-
实现Iterable接口的foreach语法(JDK1.5)
-
接口AutoCloseable支持的try-with-resource用法(JDK1.7)
-
switch支持枚举和String(JDK1.7)
1、泛型(JDK1.5)
泛型是1个编程语言是否友好的重要标志,时下火热的Go语言,至今还没有实现泛型,Java中的泛型是通过类型擦除实现的,并不是真正意义上的泛型,由于本文不打算深入讲解泛型,在此不过多叙述。看以下泛型编译之后,被JVM实际执行的代码是什么样子的
源代码(1.5之前)
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之前的代码,老代码可以保持不变,新代码如果是:
List<String> arr = new ArrayList<String>();
arr.add("this is first");
arr.add(123);
会报编译器错误,原理是一样的,都会在编译期转变成Object对象,然后强制转换成需要的类型。
2、自动装箱和拆箱(JDK1.5)
直接上代码吧,这个比较容易理解,就是JAVA会自动在基本类型和基本类型的包装类之间,进行自动转换,当然这只是一种语法糖,代码编译成class文件的时候,就会自动把这个转换成对应的方法:
//源代码
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;
其它的如:
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的这个变长参数是怎么实现的(强制转换为数组),不过平时我用的也不多。
//源代码:
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接口的类,才可以使用这个语法糖。
// 源代码
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{} 这个结构。
//源代码:
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的比较多。
//源代码:
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中的一个小小的插曲,不会对你的编程生涯造成什么太大的影响,即使不专门学习,也能在项目中学习到,因为用到的实在太多了,学了一下,感觉也没有提升太多的知识,归根到底,还是因为这个东西太简单了,如果说非要有一点要注意的地方,就是整形的自动装箱和拆箱,会有一个小小的坑,那就是日常使用的时候,经常会写这样的代码:
Integer a = 10;
int b = 20;
if(a != null && a == b) { // a != null 如果省略了,可能会空指针
do something
} else {
do something
}
当我们在比较a和b的值的时候,需要心里清楚,这是Java的自动拆箱,有的时候也会有空指针异常,所以一定要保证a != null 说到包装类的比较,还有一个小知识点,需要注意的地方,就是包装类的缓存:
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的内部类。
文本完。
关注我的博客,获取更多Java编程知识: 双King的技术博客