了解语法糖
语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家Peter.J.Landin(彼得.兰丁)发明的一个术语,指在计算机语言中添加的某种语法。语法糖是对现有语法的一个封装。语法糖让程序更加简洁,有更高的可读性。
JAVA中语法糖只存在于编译期,在编译器将将.java源文件编译成class字节码时,如果你去看com.sun.tools.javac.main.JavaCompiler的源码,你会发现compile()中有一个步骤就是调用desuger(),这个方法负责解语法糖而实现的。
在编程领域,除了语法糖还有语法盐和语法糖精。
糖块一、switch支持String与枚举
从Java7开始,Java语言中的语法糖在逐渐丰富,Java7中switch语句开始支持String。
switch自身原本支持byte、short、int、char。对于char比较其ascii码。
字符串:switch括号里其实比较的实际是哈希值。通过比较switch的字符串调用hashCode()获得哈希值,再用equels()判断case值和switch值是否相等,判断是否break。
```java
public class SwitchStringDemo {
public static void main(String[] args) {
String str;
String string = str = "world";
int n = -1;
switch (string.hashCode()) {
case 99162322: {
if (!string.equals("hello")) break;
n = 0;
break;
}
case 113318802: {
if (!string.equals("world")) break;
n = 1;
}
}
switch (n) {
case 0: {
System.out.println("hello");
break;
}
case 1: {
System.out.println("world");
break;
}
}
}
}
糖块二、泛型和类型擦除
编译器处理泛型有两种方式:Code specialization和Code sharing。
C++哈C#是使用Code specializetion的处理机制,而Java使用的Code Sharing的机制。
Code Sharing方式为每个泛型类型创建唯一的字节码标识,并且将该泛型类型都映射到这个唯一的字节码表示上。将多种泛型类型实例映射到唯一的字节码表示是通过类型擦除(type erasue)实现的。
Map<String, String> map = new HashMap<String, String>();
map.put("name", "JourWon");
map.put("wechat", "JourWon");
map.put("blog", "https://blog.csdn.net/ThinkWon");
解语法糖之后会变成:
Map map = new HashMap();
map.put("name", "JourWon");
map.put("wechat", "JourWon");
map.put("blog", "https://blog.csdn.net/ThinkWon");
糖块三、自动装箱与拆箱
将基本类型自动转换成包装类叫做自动装箱。自动调用包装类的静态方法valueOf(基本类型)转换成包装类。
将包装类自动转换成基本类型叫做自动拆箱。自动调用包装类的.xxxValue()将包装类转变成基本类型。
糖块四、方法变长参数
可变参数是在java1.5中引入的一个特性。它允许一个方法把任意数量的值作为参数。
将可变长参数转换成数组。
public static void main(String[] args) {
print("CSDN-ThinkWon", "简书-JourWon", "博客:https://blog.csdn.net/ThinkWon");
}
public static void print(String... strs) {
for (int i = 0; i < strs.length; i++) {
System.out.println(strs[i]);
}
}
```java
public static void main(String[] args) {
print(new String[]{"CSDN-ThinkWon", "\u7b80\u4e66-JourWon", "\u535a\u5ba2:https://blog.csdn.net/ThinkWon"});
}
public static void print(String[] strs) {
for (int i = 0; i < strs.length; i++)
System.out.println(strs[i]);
}
糖块五、枚举
public enum T {
SPRING,SUMMER;
}
// 反编译
public final class T extends Enum<T> {
public static final /* enum */ T SPRING = new T("SPRING", 0);
public static final /* enum */ T SUMMER = new T("SUMMER", 1);
private static final /* synthetic */ T[] $VALUES;
public static T[] values() {
return (T[])$VALUES.clone();
}
public static T valueOf(String name) {
return Enum.valueOf(T.class, name);
}
private T(String string, int n) {
super(string, n);
}
static {
$VALUES = new T[]{SPRING, SUMMER};
}
}
当我们使用enmu来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承。
糖块六、内部类
内部类又称嵌套类。内部类是编译时的概念,outer.java里面定义了内部类Inner,一旦编译成功,就会生成两个完全不同的.class文件。分别是outer.class和outer$inner.class。所以内部类的名字完全可以和外部类名字相同
糖块七、条件编译
public class ConditionalCompilation {
public static void main(String[] args) {
final boolean DEBUG = true;
if(DEBUG) {
System.out.println("Hello, DEBUG!");
}
final boolean ONLINE = false;
if(ONLINE){
System.out.println("Hello, ONLINE!");
}
}
}
//反编译后代码如下:
public class ConditionalCompilation {
public static void main(String[] args) {
boolean DEBUG = true;
System.out.println("Hello, DEBUG!");
boolean ONLINE = false;
}
糖块八、断言
public class AssertTest {
public static void main(String args[]) {
int a = 1;
int b = 1;
assert a == b;
System.out.println("CSDN-ThinkWon");
assert a != b : "ThinkWon";
System.out.println("博客:https://blog.csdn.net/ThinkWon");
}
}
public class AssertTest {
static final /* synthetic */ boolean $assertionsDisabled;
public static void main(String[] args) {
boolean a = true;
boolean b = true;
if (!$assertionsDisabled && a != b) {
throw new AssertionError();
}
System.out.println("CSDN-ThinkWon");
if (!$assertionsDisabled && a == b) {
throw new AssertionError((Object)"ThinkWon");
}
System.out.println("\u535a\u5ba2:https://blog.csdn.net/ThinkWon");
}
static {
$assertionsDisabled = !AssertTest.class.desiredAssertionStatus();
}
}
反编译之后的代码要比我们自己的代码复杂的多,使用assert这个语法糖我们节省很多代码。
糖块九、数字字面量
在java7中,字面量允许加下划线。
public class Test {
public static void main(String[] args) {
int i = 10_000;
System.out.println(i);
}
}
public class Test {
public static void main(String[] args) {
int i = 10000;
System.out.println(i);
}
}
糖块十、增强for循环
增强for循环(for-each)
public static void main(String args[]) {
String[] strs = {"CSDN-ThinkWon", "简书-JourWon", "博客:https://blog.csdn.net/ThinkWon"};
for (String s : strs) {
System.out.println(s);
}
System.out.println();
List<String> strList = Arrays.asList(strs);
for (String s : strList) {
System.out.println(s);
}
}
反编译后代码如下:
public static void main(String args[]) {
String[] strs;
String[] arrstring = strs = new String[]{"CSDN-ThinkWon", "\u7b80\u4e66-JourWon", "\u535a\u5ba2:https://blog.csdn.net/ThinkWon"};
int n = arrstring.length;
for (int i = 0; i < n; ++i) {
String s = arrstring[i];
System.out.println(s);
}
System.out.println();
List<String> strList = Arrays.asList(strs);
Iterator<String> iterator = strList.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
System.out.println(s);
}
}
糖块十一、try-with-resource语句
对于文件操作IO流。数据库连接等开销非常昂贵的资源,用完之后必须及时通过close方法将其关闭,否则资源会一直处于打开状态,可能会导致内存泄漏等问题。
关闭资源的常用方式就是在finally块里释放,即调用close方法。
public static void main(String args[]) {
BufferedReader br = null;
try {
String line;
br = new BufferedReader(new FileReader("d:\\hello.xml"));
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
// handle exception
} finally {
try {
if (br != null) {
br.close();
}
} catch (IOException ex) {
// handle exception
}
}
}
java7开始,jdk提供了一种更好的方式关闭资源,使用try-with-resources语句
public static void main(String args[]) {
try (BufferedReader br = new BufferedReader(new FileReader("d:\\ hello.xml"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
// handle exception
}
}
糖块十二、Lambda表达式
Lambda表达式不是匿名内部类的语法糖,他依赖了几个JVM底层提供的Lambda相关API
先来看一个简单的Lambda表达式,遍历一个list:
public static void main(String[] args) {
List<String> strList = new ArrayList<>();
strList.add("CSDN-ThinkWon");
strList.add("简书-JourWon");
strList.add("博客:https://blog.csdn.net/ThinkWon");
strList.forEach(s -> System.out.println(s));
}
public static void main(String[] args) {
ArrayList<String> strList = new ArrayList<String>();
strList.add("CSDN-ThinkWon");
strList.add("\u7b80\u4e66-JourWon");
strList.add("\u535a\u5ba2:https://blog.csdn.net/ThinkWon");
strList.forEach((Consumer<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$0(java.lang.String ), (Ljava/lang/String;)V)());
}
private static /* synthetic */ void lambda$main$0(String s) {
System.out.println(s);
}
可能遇到的坑
当泛型遇到重载
public class GenericTypes {
public static void method(List<String> list) {
System.out.println("invoke method(List<String> list)");
}
public static void method(List<Integer> list) {
System.out.println("invoke method(List<Integer> list)");
}
}
当泛型遇到catch
异常处理是由JVM在运行时来进行的。由于类型信息被擦除。泛型的类型参数不能用在java异常处理的catch语句。
当泛型内包含静态变量
所有泛型类实例都关联到同一份字节码上,泛型类的所有静态变量是共享的。
自动装箱与拆箱
整数值区间-128~127。只适用于自动装箱。使用构造函数创建对象不使用
增强for循环
Iterator是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出java.util.ConcurrentModificationException异常。
所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法remove()来删除对象,Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。
总结
前面介绍了12种Java中常用的语法糖。所谓语法糖就是提供给开发人员便于开发的一种语法而已。但是这种语法只有开发人员认识。要想被执行,需要进行解糖,即转成JVM认识的语法。
当我们把语法糖解糖之后,你就会发现其实我们日常使用的这些方便的语法,其实都是一些其他更简单的语法构成的。有了这些语法糖,我们在日常开发的时候可以大大提升效率,但是同时也要避免过渡使用。使用之前最好了解下原理,避免掉坑。