早起编译优化主要指编译期进行的优化。前端
java的编译期可能指的如下三种:java
本文中涉及到的编译器都仅限于第一类,第二类编译器跟java语言的关系不大。javac这类编译器对代码的运行效率几乎没有任何优化措施,但javac作了许多针对java语言代码过程的优化措施来改善程序员的编码风格和提升编码效率,java许多的语法特性都是靠编译器的语法糖来实现的。git
Sun javac编译器的编译过程能够分为3个过程:程序员
解析步骤包括了经典程序编译原理中的词法分析与语法分析两个过程。github
词法、语法分析:词法分析是将源代码的字符流转变为标记(Token)集合,单个字符是程序编写过程的最小元素,而标记则是编译过程的最小元素,关键字、变量名、字面量、运算符均可以成为标记 语法分析是根据Token序列构造抽象语法树的过程,抽象语法树(Abstract Syntax Tree,AST)是一种用来描述程序代码语法结构的树形表示方式,语法树的每个节点都表明着程序代码中的一个语法结构(Construct),例如包、类型、修饰符、运算符、接口、返回值甚至代码注释等均可以是一个语法结构。面试
填充符号表:符号表(Symbol Table)是由一组符号地址和符号信息构成的表格,能够想象成K-V的形式。符号表中所登记的信息在编译的不一样阶段都要用到。在语义分析中,符号表所登记的内容将用于语义检查和产生中间代码。在目标代码生成阶段,当对符号名进行地址分配时,符号表是地址分配的依据编程
注解处理器是用于提供对注解的支持,能够将其当作一组编译器的插件。后端
语法分析后,编译器得到了程序代码的抽象语法树表示,语法树能表示一个结构正确的源程序的抽象,但没法保证源程序是符合逻辑的。数组
这部分主要分以下几步,完成语义分析与字节码生成:缓存
标注检查检查的内容包括变量使用前是否已被声明、变量与赋值之间的数据类型是否可以匹配等。在标注检查中,还有一个重要的动做称为常量折叠,这使得a=1+2比起a=3不会增长任何运算量
数据及控制流分析是对程序上下文逻辑更进一步的验证,能够检查出诸如程序局部变量在使用前是否赋值、方法的每条路径是否都有返回值、是否全部的受查异常都被正确处理等
语法糖(Syntactic Sugar),也称糖衣语法,指在计算机语言中添加的某种语法,这种语法对语言的功能并无影响,但方便使用。java在现代编程语言中属于低糖语言,java中的主要语法糖包括泛型、可变参数、自动装箱/拆箱等,虚拟机运行时不支持这些语法,它们在编译阶段还原回简单的基础语法结构,这个过程称为解语法糖
字节码生成阶段不只仅时把前面各个步骤所生成的信息(语法树、符号表)转化成字节码写到磁盘中,编译器还进行了少许的代码添加和转换工做
语法糖主要是为了方便程序员的代码开发,这些语法糖并不会提供实质性的功能改进,可是他们能提升效率。
如下介绍了Java中经常使用的语法糖。
Java中的参数化类型只在源码中存在,在编译后的字节码中,已经被替换为原来的原生类型了,而且在相应的地方插入了强制转换代码。对于运行期的Java 语言来讲,ArrayList和ArrayList就是同一个类。因此说泛型技术实际上就是 Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型。
如下两个方法,在编译时,因为类型擦除,变成了同样的原生类型List,所以方法的特征签名变得一致,致使没法编译。
void method(List<Integer> list);
void method (List<String> list);
复制代码
可是若是二者的返回值不一致,在JDK1.6中则能够编译经过,并非由于返回值不一样,因此重载成功。只是由于加入返回值后,两个方法的字节码特征签名不同了,因此能够共存。可是在JDK1.7和1.8中,依然没法经过,会报两个方法在类型擦除后具备相同的特征签名。
Java代码中的方法特征签名只包含方法名称、参数顺序和参数类型,而在字节码中的特征签名还包括方法返回值及受查异常表。 方法重载要求方法具有不一样的特征签名,返回值并不包含在方法的特征签名中,因此返回值不参与重载选择。可是在Class字节码文件中,只要描述符不是彻底一致的两个方法就能够共存。
自动装箱和拆箱实现了基本数据类型与对象数据类型之间的隐式转换。
public void autobox() {
Integer one = 1;
if (one == 1) {
System.out.println(one);
}
}
复制代码
下面对自动装箱和自动拆箱进行详细介绍:
自动装箱就是Java自动将原始类型值转换成对应的对象,好比将int的变量转换成Integer对象,这个过程叫作装箱,反之将Integer对象转换成int类型值,这个过程叫作拆箱。由于这里的装箱和拆箱是自动进行的非人为转换,因此就称做为自动装箱和拆箱。原始类型byte,short,char,int,long,float,double和boolean对应的封装类为Byte,Short,Character,Integer,Long,Float,Double,Boolean。
什么时候发生自动装箱和拆箱,
Integer iObject = 3; //autobxing - primitive to wrapper conversion
int iPrimitive = iObject; //unboxing - object to primitive conversion
复制代码
public static Integer show(Integer iParam){
System.out.println("autoboxing example - method invocation i: " + iParam);
return iParam;
}
//autoboxing and unboxing in method invocation
show(3); //autoboxing
int result = show(3); //unboxing because return type of method is Integer
复制代码
自动装箱的弊端,
自动装箱有一个问题,那就是在一个循环中进行自动装箱操做的时候,以下面的例子就会建立多余的对象,影响程序的性能。
Integer sum = 0;
for(int i=1000; i<5000; i++){
sum+=i;
}
复制代码
自动装箱与比较:
下面程序的输出结果是什么?
public class Main {
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
Long h = 2L;
System.out.println(c==d);
System.out.println(e==f);
System.out.println(c==(a+b));
System.out.println(c.equals(a+b));
System.out.println(g==(a+b));
System.out.println(g.equals(a+b));
System.out.println(g.equals(a+h));
}
}
复制代码
在解释具体的结果时,首先必须明白以下两点:
下面是程序的具体输出结果:
true
false
true
true
true
false
true
复制代码
注意到对于Integer和Long,Java中,会对-128到127的对象进行缓存,当建立新的对象时,若是符合这个这个范围,而且已有存在的相同值的对象,则返回这个对象,不然建立新的Integer对象。
对于上面的结果:
c==d:指向相同的缓存对象,因此返回true; e==f:不存在缓存,是不一样的对象,因此返回false; c==(a+b):直接比较的数值,所以为true; c.equals(a+b):比较的对象,因为存在缓存,因此两个对象同样,返回true; g==(a+b):直接比较的数值,所以为true; g.equals(a+b):比较对象,因为equals也不会进行类型转换,a+b为Integer,g为Long,所以为false; g.equals(a+h):和上面不同,a+h时,a会进行类型转换,转成Long,接着比较两个对象,因为Long存在缓存,因此两个对象一致,返回true。
关于equals和==:
.equals(...)
will only compare what it is written to compare, no more, no less.- If a class does not override the equals method, then it defaults to the
equals(Object o)
method of the closest parent class that has overridden this method.- If no parent classes have provided an override, then it defaults to the method from the ultimate parent class, Object, and so you're left with the
Object#equals(Object o)
method. Per the Object API this is the same as ==; that is, it returns true if and only if both variables refer to the same object, if their references are one and the same. Thus you will be testing for object equality and not functional equality.- Always remember to override hashCode if you override equals so as not to "break the contract". As per the API, the result returned from the hashCode() method for two objects must be the same if their equals methods show that they are equivalent. The converse is not necessarily true.
遍历循环语句是java5的新特征之一,在遍历数组、集合方面,为开发人员提供了极大的方便。
public void circle() {
Integer[] array = { 1, 2, 3, 4, 5 };
for (Integer i : array) {
System.out.println(i);
}
}
复制代码
在编译后的版本中,代码还原成了迭代器的实现,这也是为遍历循环须要被遍历的类实现Iterable接口的缘由。
Arrays.asList(1, 2, 3, 4, 5);
复制代码
条件编译也是java语言的一种语法糖,根据布尔常量值的真假,编译器将会把分支中不成立的代码块消除掉。
public void ifdef() {if (true) {
System.out.println("true");
} else {//此处有警告--DeadCode
System.out.println("false");
}
}
复制代码
public void enumStringSwitch() {
String str = "fans";
switch (str) {
case "fans":
break;case "leiwen":
break;default:
break;
}
}
复制代码
在try语句中定义和关闭资源 jdk7提供了try-with-resources,能够自动关闭相关的资源(只要该资源实现了AutoCloseable接口,jdk7为绝大部分资源对象都实现了这个接口)。
staticStringreadFirstLineFromFile(Stringpath)throwsIOException{
try(BufferedReaderbr=newBufferedReader(newFileReader(path))){
returnbr.readLine();
}
}
复制代码
本文由『后端精进之路』原创,首发于博客 teckee.github.io/ , 转载请注明出处
搜索『后端精进之路』关注公众号,马上获取最新文章和价值2000元的BATJ精品面试课程。