java语法糖
可能会用到的链接:
什么是语法糖?
指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。
简而言之,语法糖让程序更加简洁,有更高的可读性。
常见的语法糖
Java 虚拟机并不支持这些语法糖。这些语法糖在编译阶段就会被还原成简单的基础语法结构,这个过程就是解语法糖。
Java 中最常用的语法糖主要有泛型、变长参数、条件编译、自动拆装箱、内部类等。
switch 支持 String 与枚举
对于编译器来说 switch
只能支持整型,任何类型的比较都要转换成整型。
public class switchDemoString {
public static void main(String[] args) {
String str = "world";
switch (str) {
case "hello":
System.out.println("hello");
break;
case "world":
System.out.println("world");
break;
default:
break;
}
}
}
反编译之后为:
public class switchDemoString
{
public switchDemoString()
{
}
public static void main(String args[])
{
String str = "world";
String s;
switch((s = str).hashCode())
{
default:
break;
case 99162322:
if(s.equals("hello"))// 进行安全检查,因为可能有hash碰撞
System.out.println("hello");
break;
case 113318802:
if(s.equals("world"))// 进行安全检查,因为可能有hash碰撞
System.out.println("world");
break;
}
}
}
则字符串的 switch 是通过 equals()
和 hashCode()
方法来实现的。性能会稍微降低,但影响不大。
泛型
一个编译器处理泛型有两种方式:Code specialization
(C++、C#)和 Code sharing
(java)
Code sharing 方式为每个泛型类型创建唯一的字节码表示,并且将该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类形实例映射到唯一的字节码表示是通过类型擦除(type erasue
)实现的。
类型擦除的规则:
- 将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。
- 移除所有的类型参数。
Map<String, String> map = new HashMap<String, String>();
解语法糖之后会变成:Map map = new HashMap();
public static <A extends Comparable<A>> A max(Collection<A> xs) {
Iterator<A> xi = xs.iterator();
A w = xi.next();
while (xi.hasNext()) {
A x = xi.next();
if (w.compareTo(x) < 0)
w = x;
}
return w;
}
类型擦除后会变成:
public static Comparable max(Collection xs){
Iterator xi = xs.iterator();
Comparable w = (Comparable)xi.next();
while(xi.hasNext())
{
Comparable x = (Comparable)xi.next();
if(w.compareTo(x) < 0)
w = x;
}
return w;
}
虚拟机中没有泛型,只有普通类和普通方法,所有泛型类的类型参数在编译时都会被擦除,泛型类并没有自己独有的 Class
类对象。
比如并不存在
List<String>.class
或是List<Integer>.class
,而只有List.class
。
自动装箱与拆箱
在装箱的时候自动调用的是 Integer
的 valueOf(int)
方法。而在拆箱的时候自动调用的是 Integer
的 intValue
方法。
装箱过程是通过调用包装器的 valueOf
方法实现的,而拆箱过程是通过调用包装器的 xxxValue
方法实现的。
可变长参数
public static void main(String[] args)
{
print("Holis", "公众号:Hollis", "博客:www.hollischuang.com", "QQ:907607222");
}
public static void print(String... strs)
{
for (int i = 0; i < strs.length; i++)
{
System.out.println(strs[i]);
}
}
反编译后:
public static void main(String args[])
{
print(new String[] {
"Holis", "\u516C\u4F17\u53F7:Hollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com", "QQ\uFF1A907607222"
});
}
public static transient void print(String strs[])
{
for(int i = 0; i < strs.length; i++)
System.out.println(strs[i]);
}
可变参数在被使用的时候,他首先会创建一个数组,数组的长度就是调用该方法是传递的实参的个数,然后再把参数值全部放到这个数组当中,然后再把这个数组作为参数传递到被调用的方法中。
感觉很容易猜到吧。
注:trasient 仅在修饰成员变量时有意义,此处 “修饰方法” 是由于在 javassist 中使用相同数值分别表示 trasient 以及 vararg,见 此处
后面可以加快点进度了,这个部分很简单。
枚举 :
public enum t {
SPRING,SUMMER;
}
反编译后为:
public final class T extends Enum
{
private T(String s, int i)
{
super(s, i);
}
public static T[] values()
{
T at[];
int i;
T at1[];
System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);
return at1;
}
public static T valueOf(String s)
{
return (T)Enum.valueOf(demo/T, s);
}
public static final T SPRING;
public static final T SUMMER;
private static final T ENUM$VALUES[];
static
{
SPRING = new T("SPRING", 0);
SUMMER = new T("SUMMER", 1);
ENUM$VALUES = (new T[] {
SPRING, SUMMER
});
}
}
该类继承了 Enum
类,且不能被继承。
Other...
内部类
outer.java
里面定义了一个内部类 inner
,一旦编译成功,就会生成两个完全不同的.class
文件了,分别是 outer.class
和 outer$inner.class
。所以内部类的名字完全可以和它的外部类名字相同。
条件编译
Java 语法的条件编译,是通过判断条件为常量的 if 语句实现的。其原理也是 Java 语言的语法糖。
编译器直接把分支为 false
的代码块消除。
通过该方式实现的条件编译,必须在方法体内实现,而无法在整个 Java 类的结构或者类的属性上进行条件编译,这与 C/C++的条件编译相比,确实更有局限性。
断言
其实断言的底层实现就是 if 语言,如果断言结果为 true,则什么都不做,程序继续执行,如果断言结果为 false,则程序抛出 AssertError
来打断程序的执行。
-enableassertions
会设置 $assertionsDisabled
字段的值。(默认没有开启,需要打开开关,否则不会执行断言语句)
数值字面量
在 java 7 中,数值字面量,不管是整数还是浮点数,都允许在数字之间插入任意多个下划线。这些下划线不会对字面量的数值产生影响,目的就是方便阅读。
反编译后就是把_删除了。也就是说编译器并不认识在数字字面量中的_,需要在编译阶段把他去掉。
for-each
for-each
的实现原理其实就是使用了普通的 for 循环和迭代器。
try-with-resource
关闭资源的常用方式就是在 finally
块里是释放,即调用 close
方法。
jdk 7
开始 jdk 提供了一种更好的方式关闭资源,使用 try-with-resources
语句
例如:
//try-catch-finally
public static void main(String[] args) {
BufferedReader br = null;
try {
String line;
br = new BufferedReader(new FileReader("d:\\hollischuang.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
}
}
}
//try-with-resources
public static void main(String... args) {
try (BufferedReader br = new BufferedReader(new FileReader("d:\\ hollischuang.xml"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
// handle exception
}
}
即那些我们没有做的关闭资源的操作,编译器都帮我们做了。
Lambda 表达式
Lambda 表达式不是匿名内部类的语法糖,但是他也是一个语法糖。实现方式其实是依赖了几个 JVM 底层提供的 lambda
相关 api。
//反编译前
public static void main(String... args) {
List<String> strList = ImmutableList.of("Hollis", "公众号:Hollis", "博客:www.hollischuang.com");
List HollisList = strList.stream().filter(string -> string.contains("Hollis")).collect(Collectors.toList());
HollisList.forEach( s -> { System.out.println(s); } );
}
//反编译后
public static /* varargs */ void main(String ... args) {
ImmutableList strList = ImmutableList.of((Object)"Hollis", (Object)"\u516c\u4f17\u53f7\uff1aHollis", (Object)"\u535a\u5ba2\uff1awww.hollischuang.com");
List<Object> HollisList = strList.stream().filter((Predicate<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)Z, lambda$main$0(java.lang.String ), (Ljava/lang/String;)Z)()).collect(Collectors.toList());
HollisList.forEach((Consumer<Object>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$1(java.lang.Object ), (Ljava/lang/Object;)V)());
}
private static /* synthetic */ void lambda$main$1(Object s) {
System.out.println(s);
}
private static /* synthetic */ boolean lambda$main$0(String string) {
return string.contains("Hollis");
}
两个 lambda 表达式分别调用了 lambda$main$1
和lambda$main$0
两个方法。
在编译阶段,编译器会把 lambda 表达式进行解糖,转换成调用内部 api 的方式。
可能遇到的坑
泛型
重载
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)");
}
}
这样是编译不过的,参数 List<Integer>
和 List<String>
编译之后都被擦除了,变成了一样的原生类型 List,擦除动作导致这两个方法的特征签名变得一模一样。
catch
泛型的类型参数不能用在 Java 异常处理的 catch 语句中。因为异常处理是由 JVM 在运行时刻来进行的。由于类型信息被擦除,JVM 是无法区分两个异常类型 MyException<String>
和 MyException<Integer>
的
静态变量
public class StaticTest{
public static void main(String[] args){
GT<Integer> gti = new GT<Integer>();
gti.var=1;
GT<String> gts = new GT<String>();
gts.var=2;
System.out.println(gti.var);
}
}
class GT<T>{
public static int var=0;
public void nothing(T x){}
}
//out:2
由于经过类型擦除,所有的泛型类实例都关联到同一份字节码上,泛型类的静态变量是共享的。GT<Integer>.var
和 GT<String>.var
其实是一个变量。
增强 for 循环
for (Student stu : students) {
if (stu.getId() == 2)
students.remove(stu);
}
会抛出 ConcurrentModificationException
异常。Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException
异常。所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法 remove()
来删除对象,Iterator.remove()
方法会在删除当前迭代对象的同时维护索引的一致性。