语法糖
所谓的 语法糖 ,其实就是指 java 编译器把 *.java
源码编译为 *.class
字节码的过程中,自动生成和转换的一些代码,主要是为了减轻程序员的负担,算是 java 编译器给我们的一个额外福利。
注意,以下代码的分析,借助了 javap 工具,idea 的反编译功能,idea 插件 jclasslib 等工具。另外,编译器转换的结果直接就是 class 字节码,只是为了便于阅读,给出了 几乎等价 的 java 源码方式,并不是编译器还会转换出中间的 java 源码。
默认构造
在java文件当中进行编写一个不提供构造方法,在进行编译的时候会自动加上一个默认无参构造方法。使用javac命令进行编译,编译后如下:
自动拆装箱
这个特性是 JDK 5 开始加入的,也就是基本数据类型和包装数据类型之间是可以进行相互转换的,也就是说如下代码所示:这种事可以进行编译通过的,而在jdk5版本之前要自己进行数据类型转换。
泛型集合
泛型也是在 JDK 5 开始加入的特性,但 java 在编译泛型代码后会执行 泛型擦除 的动作,即泛型信息在编译为字节码之后就丢失了,实际的类型都当做了 Object 类型来处理:
package Class_load.init;
import java.util.ArrayList;
public class generic_paradigm {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(10);
Integer x = list.get(0);
}
}
编译后的字节码
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: bipush 10
11: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
14: invokevirtual #5 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
17: pop
18: aload_1
19: iconst_0
20: invokevirtual #6 // Method java/util/ArrayList.get:(I)Ljava/lang/Object;
23: checkcast #7 // class java/lang/Integer
26: astore_2
27: return
可以看到这这里我们的int和integer类型之间不需要自动转换,而且在这里进行插入、取出都是用object对象进行,在23行字节码当中,checkcast表示将object进行强制类型转换Integer。
擦除的是字节码上的泛型信息,可以看到 LocalVariableTypeTable 仍然保留了方法参数泛型的信息
使用反射,仍然能够获得这些信息:如下代碼所示:
//添加一個方法
public Set<Integer> test(List<Integer> list, Map<String, Integer> map) {
return null;
}
// 在main當中進行測試
Method test = generic_paradigm.class.getMethod("test", List.class, Map.class);
Type[] types = test.getGenericParameterTypes();
for (Type type : types) {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
System.out.println("原始类型 - " + parameterizedType.getRawType());
Type[] arguments = parameterizedType.getActualTypeArguments();
for (int i = 0; i < arguments.length; i++) {
System.out.printf("泛型参数[%d] - %s\n", i, arguments[i]);
}
}
}
查看輸出:
可变参数
可变参数也是 JDK 5 开始加入的新特性: 例如:
public class change_true {
public static void foo(String... args) {
String[] array = args; // 直接赋值 System.out.println(array);
}
public static void main(String[] args) {
foo("hello", "world");
}
}
可变参数 String… args 其实是一个 String[] args ,从代码中的赋值语句中就可以看出来。
如果调用了 foo() 则等价代码为 foo(new String[]{}) ,创建了一个空的数组,而不会传递 null 进去。
foreach 循环
仍是 JDK 5 开始引入的语法糖,数组的循环:
public class foreach {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
for (int i : arr) {
System.out.println(i);
}
}
}
对这个代码进行编译后,新增的语法糖,在数组初始化和for循环增强都会进行编译过来:
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);
}
对简单的数组进行循环是直接使用for循环进行遍历,而对List集合进行for循环增强又会编译成什么呢?如下List集合,
List<Integer> list = Arrays.asList(1,2,3,4,5);
for (Integer j :list){
System.out.println(j);
}
在这里会被编译器转换为对迭代器的调用:
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 接口的集合类一起使用,其
中 Iterable 用来获取集合的迭代器( Iterator )
Switch case 分支
从 JDK 7 开始,switch 可以作用于字符串和枚举类,这个功能其实也是语法糖,例如:
public static void choose(String str) {
switch (str) {
case "1":
System.out.println("this is one");
break;
case "2":
System.out.println("this is two");
break;
}
}
public static void main(String[] args) {
choose("1");
}
可以看到,执行了两遍 switch,第一遍是根据字符串的 hashCode 和 equals 将字符串的转换为相应byte 类型,第二遍才是利用 byte 执行进行比较。
为什么第一遍时必须既比较 hashCode,又利用 equals 比较呢?hashCode 是为了提高效率,减少可能的比较;而 equals 是为了防止 hashCode 冲突,
Switch enum 枚举 和 枚举类
使用一个switch 枚举的例子,原始代码:
package Class_load.init;
public class switch_enum {
enum Sex {
men, women
}
public static void foo(Sex sex) {
switch (sex) {
case men:
System.out.println("男");
break;
case women:
System.out.println("女");
break;
}
}
public static void main(String[] args) {
foo(Sex.men);
}
}
编译代码得到class文件,进行查看,可以发现枚举当中有了一个构造方法,也就是说在这里枚举被当成了一个类。并且修饰符使用的是private,避免使用该类时被创建新的对象。
try-with-resources
JDK 7 开始新增了对需要关闭的资源处理的特殊语法 try-with-resources。
其中资源对象需要实现 AutoCloseable 接口,例如 InputStream 、 OutputStream 、 Connection 、 Statement 、 ResultSet 等接口都实现了 AutoCloseable ,使用 try-with- resources 可以不用写 finally 语句块,编译器会帮助生成关闭资源代码,例如:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class resource {
public static void main(String[] args) {
try (InputStream is = new FileInputStream("d:\\1.txt")) {
System.out.println(is);
} catch (IOException e) {
e.printStackTrace();
}
}
}
查看对应的class文件,
为什么要设计一个 addSuppressed(Throwable e) (添加被压制异常)的方法呢?是为了防止异常信息的丢失(想想 try-with-resources 生成的 fianlly 中如果抛出了异常):
public class resource_catch {
public static void main(String[] args) {
try (MyResource resource = new MyResource()) {
int i = 1 / 0;
} catch (Exception e) {
e.printStackTrace();
}
}
}
class MyResource implements AutoCloseable {
public void close() throws Exception {
throw new Exception("close 异常");
}
}
查看抛出的异常:异常没有丢失,两个异常都被抛出。
方法重写时的桥接方法
方法重写时对返回值分两种情况:
- 父子类的返回值完全一致
- 子类返回值可以是父类返回值的子类
匿名内部类
为什么匿名内部类引用局部变量时,局部变量必须是 final 的:因为在创建类对象时,将 x 的值赋值给了类对象的val。