文章目录
1. 语法糖
语法糖,指java编译器把
.java
源码文件编译为
.class
字节码文件的过程中,自动生成和转换的一些代码,主要是为了减轻程序员的负担,算是java编译器给我们的一个额外福利(给糖吃)。
1.1. 默认构造方法
如果没有写任何构造方法,编译器会自动生成一个默认的无参构造方法
public class Test {
}
编译成 class 后的代码:
public class Test {
// 这个无参构造是编译器帮助我们加上的
public Test() {
// 即调用父类Object的无参构造方法,即调用java/lang/0bject. "<init>":()V
super();
}
}
1.2. 自动拆装箱
包装类型和基本类型,在 JDK5 之后能自动转换。
public class Test {
public static void main(String[] args) {
Integer x = 1;
int y = x;
}
}
编译成 class 后的代码:
public class Test {
public static void main(String[] args) {
Integer x = Integer.valueOf(1); // 自动装箱
int y = x.intValue(); // 自动拆箱
}
}
1.3. 泛型取值
JDK5 在编译泛型代码时,会执行
类型擦除
操作,即泛型信息在编译为字节码之后就丢失了,实际的类型都当做了
Object
类型来处理。
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
// 实际调用 List.add(Object e);
list.add(10);
// 实际调用 Object obj = List.get(int index);
Integer x = list.get(0);
}
}
所以在取值时,编译器真正生成的字节码中,还要额外做一个类型转换的操作:
// 需要将Object转为Integer
Integer x = (Integer)list.get(0);
如果前面的
x
变量类型修改为
int
基本类型那么最终生成的字节码是:
//需要将Object转为Integer, 并执行拆箱操作
int x = ((Integer)list.get(0)).intValue();
虽然会有类型擦除,但是会在 class 文件中的
LocalVariableTypeTable
中,保存泛型信息,用来做自动类型转换。因此可以使用反射获取泛型信息。
public Set<Integer> test(List<String> list, Map<Integer, Object> map) {
}
Method test = Test.class.getMethod("test", List.class, Map.class);
Type[] types = test.getGenericParameterTypes();
for (Type type : types) {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Ѕуѕtеm.оut.рrіntln("原始类型 - " + раrаmеtеrіzеdТуре.gеtRаwТуре());
Type[] arguments = parameterizedType.getActualTypeArguments() ;
for (int i = 0; i < arguments.length; i++) {
System.out.printf("泛型参数[%d] - %s\n", i, arguments[i]);
}
}
}
输出
原始类型 – interface java.util.List
泛型参数[0] – class java.lang.String
原始类型 – interface java.util.Map
泛型参数[0] – class java.lang.Integer
泛型参数[1] – class java.lang.Object
1.4. 可变参数
可变参数
String... args
会在编译期转换为
String[] args
。
public class Test {
public static void main(String[] args) {
// 实际上是 test(new String[]{"Hello", "World!"});
test("Hello", "World!");
// 实际上是 test(new String[]{});
test();
}
public static void test(String... args) {
String[] array = args;
System.out.рrіntln(array);
}
}
1.5. foreach 循环
public class Test {
public static void main(String[] args) {
int[] array = {1, 2, 3, 4, 5};
for (int e : array) {
System.out.println(e);
}
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
for (Integer e : list) {
System.out.println(e);
}
}
}
编译后:
public class Test {
public Test() {
}
public static void main(String[] args) {
// 对于数组,直接使用下标迭代
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);
}
// 对于集合,则使用迭代器迭代
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);
}
}
}
1.6. switch 字符串
从 JDK7 开始,
switch
语句可以作用于字符串和枚举类。
public class Test {
public void test(String s) {
switch (s) {
case "你好": {
System.out.println("你好");
break;
}
case "世界": {
System.out.println("世界");
break;
}
}
}
}
编译后:
public class Test {
public Test() {
}
public void test(String s) {
byte var2 = -1;
// 先计算字符串的hash值,再匹配得到一个变量值
switch(s.hashCode()) {
case 649718:
if (s.equals("世界")) var2 = 1;
break;
case 652829:
if (s.equals("你好")) var2 = 0;
}
// 再匹配变量值
switch(var2) {
case 0:
System.out.println("你好");
break;
case 1:
System.out.println("世界");
}
}
}
执行了两遍
switch
语句,第一遍是根据字符串的
hashCode
和
equals
将字符串的转换为相应
byte
类型,第二遍才是利用
byte
执行进行比较。
hashCode
是为了提高效率,减少可能的比较;而
equals
为了防止
hashCode
冲突。
例如
BM
和
C.
这两个字符串的 hashCode 值都是 2123。
public class Test {
public void test(String s) {
switch (s) {
case "BM": {
System.out.println("BM");
break;
}
case "C.": {
System.out.println("C.");
break;
}
}
}
}
编译后:
public class Test {
public Test() {
}
public void test(String s) {
byte var2 = -1;
switch(s.hashCode()) {
case 2123: // hashcode 值可能相同,需要进一步比较
if (s.equals("C.")) var2 = 1;
else if (s.equals("BM")) var2 = 0;
default:
switch(var2) {
case 0:
System.out.println("BM");
break;
case 1:
System.out.println("C.");
}
}
}
}
1.7. switch 枚举
public enum Sex {
MALE, FEMALE
}
public class Test {
public void test(Sex sex) {
switch (sex) {
case MALE: {
System.out.println("男");
break;
}
case FEMALE: {
System.out.println("女");
break;
}
}
}
}
编译后:
public class Test {
public Test() {
}
// 会生成一个合成的内部类(JVM可见,我们不可见)
static class $MAP {
// 数组大小即为枚举元素个数,里面存储case用来对比的数字
static int[] map = new int[2];
static {
map[Sex.MALE.ordinal()] = 1;
map[Sex.FEMALE.ordinal()] = 2;
}
}
public void test(Sex sex) {
// 从数组中取出对应的值
int x = $MAP.map[sex.ordinal()];
switch(x) {
case 1:
System.out.println("男");
break;
case 2:
System.out.println("女");
}
}
}
1.8. 枚举
public enum Sex {
MALE, FEMALE
}
编译后:
// 本质上是一个class
public final class Sex extends Enum<Sex> [
public static final Sex MALE; // 对应枚举值
public static final Sex FEMALE; // 对应枚举值
private static final Sex[] $VALUES;
static {
MALE = new Sex("MALE", 0); // 对应枚举值
FEMALE = new Sex("FEMALE", 1); // 对应枚举值
$VALUES = new Sex[]{MALE, FEMALE};
}
private Sex(String name, int ordinal) {
super(name, ordinal);
}
public static Sex[] values() {
return $VALUES.clone();
}
public static Sex valueOf(String name) {
return Enum.valueOf(Sex.class, name);
}
}
1.9. try-with-resources
JDK 7 开始新增了对需要关闭的资源处理的特殊语法
try-with-resources
:
try(资源变量 = 创建资源对象){
} catch() {
}
其中资源对象需要实现
AutoCloseable
接口,例如
Inputstream
、
OutputStream
、
Connection
、
Statement
、
ResultSet
等接口都实现了 AutoCloseable,使用
try-with-resources
可以不用写
finally
语句块,编译器会帮助生成关闭资源代码,例如:
public class Test {
public void test() {
try (InputStream is = new FileInputStream("D:\\test.txt")) {
System.out.println(is);
} catch (IOException e) {
e.printStackTrace();
}
}
}
编译后:
public class Test {
public void test() {
try {
FileInputStream is = new FileInputStream("D:\\test.txt");
try {
System.out.println(is);
} catch (Throwable e1) {
// 我们自己的代码出现异常
try {
// 尝试关闭
is.close();
} catch (Throwable e2) {
// close出现异常,作为被压制的异常添加
e1.addSuppressed(e2);
}
// 抛出异常
throw e1;
}
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.10. 方法重写时的桥接方法
方法重写时,对返回值分两种情况:
- 子类返回值和父类返回值完全一致。
- 子类返回值是父类返回值的子类。
class A {
public Number m() {
return 1;
}
}
class B extends A {
//子类方法的返回值Integer,是父类方法返回值Number子类
@Override
public Integer m() {
return 2;
}
}
编译后:
class B extends A {
public Integer m() {
return 2;
}
// 此方法オ是真正重写了父类m方i,JVM可见,我们不可见
public synthetic bridge Number m() {
// 调用 public Integer m()
return m();
}
}
1.11. 匿名内部类
public class Test {
// 匿名内部类实现接口并重写接口方法
Runnable runnable = new Runnable() {
@Override
public void run() {
}
};
// 匿名内部类重写类方法
Object object = new Object() {
};
// 静态常量 + 匿名内部类实现接口并重写接口方法
static final Runnable runnable2 = new Runnable() {
@Override
public void run() {
}
};
}
编译后会生成三个内部类 class:
Test$1.class
即
new Runnable() {}
的内容:
import com.company.Test;
class Test$1 implements Runnable {
// 持有外部类的引用
final Test this$0;
// 外部类的引用通过构造方法传入
Test$1(Test this$0) {
this.this$0 = this$0;
}
public void run() {}
}
Test$2.class
即
new Object() {}
的内容:
import com.company.Test;
class Test$2 {
// 持有外部类的引用
final Test this$0;
// 外部类的引用通过构造方法传入
Test$1(Test this$0) {
this.this$0 = this$0;
}
}
Test$3.class
即
static final Runnable runnable2 = new Runnable() {}
的内容:
class Test$3 implements Runnable {
public void run() {}
}
前两种写法,内部类会持有外部类的引用。如果内部类的生命周期比外部类长,则外部类容易导致内存泄漏。