做网站的人是什么职位/百度竞价培训班
前端编译器Javac
从Javac代码的总体结构来看,编译过程大致可以分为1个准备过程和3个处理过程,它们分别如下所示。
-
准备过程:初始化插入式注解处理器。
-
解析与填充符号表过程,包括:
- 词法、语法分析。将源代码的字符流转变为标记集合,构造出抽象语法树。
- 填充符号表。产生符号地址和符号信息。
-
插入式注解处理器的注解处理过程:插入式注解处理器的执行阶段
-
分析与字节码生成过程,包括:
- 标注检查。对语法的静态信息进行检查。
- 数据流及控制流分析。对程序动态运行过程进行检查。
- 解语法糖。将简化代码编写的语法糖还原为原有的形式。
- 字节码生成。将前面各个步骤所生成的信息转化成字节码。
后面3个处理过程里,执行插入式注解时又可能会产生新的符号,如果有新的符号产生,就必须转回到之前的解析、填充符号表的过程中重新处理这些新符号.
从总体来看,三者之间的关系与交互顺序如图所示:
入口是 com.sun.tools.javac.main.JavaCompiler类,代码逻辑集中在这个类的 compile() 和 compile2() 方法里
其中主体代码如图所示:
语法糖
泛型
类型擦除式泛型
-
对方法的Code属性中的泛型字节码进行擦除,即在 new 一个泛型对象时,对类型参数进行擦除。(实际上反编译还能看到方法体里有泛型,是因为LocalVariableTypeTable,而这个和 LocalVariableTable 一样,都是为了调试方便,可以通过增加 javac 的命令行 -g:none 关掉)。
-
如果类或方法定义了泛型 T,那么会在 class 文件的Signature属性中,记录包含泛型信息的方法或者类的签名(反编译可以看到方法或者类的定义上有泛型,也是因为该属性,但 Signature 属性仅限于获取泛型的签名,调用方法时的 Methodref 仍然不包含泛型信息)。
- 如果父类的泛型是 T,子类对泛型 T 进行了定义,那么可以通过 Class#getGenericInterfaces() 或者 Class#getGenericSuperclass() 得到 ParameterizedType,然后通过getActualTypeArguments() 就可以获得子类定义的泛型信息。
- 如果当前类定义的泛型就是 T ,那么是没法获得当前类的泛型信息的。
static class A<T1,T2>{} static class B extends A<String,Integer>{}public static void main(String[] args) {Type superclass = B.class.getGenericSuperclass();if(superclass instanceof ParameterizedType){Type[] actualTypeArguments = ((ParameterizedType)superclass).getActualTypeArguments();for(Type t: actualTypeArguments){System.out.println(t.getTypeName());}} }// java.lang.String // java.lang.Integer
类型擦除坏处
-
直接导致无法支持原始类型,因为擦除之后类型都是 Object,那么如果 <int>、<double>,这种泛型在擦除后,其里面的元素无法转换为 Object,所以 java 的解决方案就是“既然没法转换那就索性别支持原生类型的泛型了吧”,所以就又提供了原始类型的自动装拆箱,这也就是java泛型慢和开销大的一个重要原因
-
无法获得 new 该泛型对象时传递的类型参数(即运行期无法取到泛型类)
public class TypeErasureGenerics<E> {public void doSomething(Object item) {if (item instanceof E) { // 不合法,无法对泛型进行实例判断}E newItem = new E(); // 不合法,无法使用泛型创建对象E[] itemArray = new E[10]; // 不合法,无法使用泛型创建数组} }
- 解决办法,直接在方法参数里传递 Class 对象,或者通过 T 的对象 getClass
-
带来了一些模棱两可的模糊状况(重载遇上泛型)
-
参数只是泛型的类型参数不同,但是返回值相同(不能编译)
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, // 类型擦除导致这两个方法的特征签名变得一模一样。
-
参数只是泛型的类型参数不同,且是返回值不同(可以编译,但与之前的重载规定的只看特征签名,不看返回值矛盾)
public class GenericTypes {public static String method(List<String> list) {System.out.println("invoke method(List<String> list)");return "";}public static int method(List<Integer> list) {System.out.println("invoke method(List<Integer> list)");return 1;}public static void main(String[] args) {method(new ArrayList<String>());method(new ArrayList<Integer>());} }// invoke method(List<String> list) // invoke method(List<Integer> list)
-
其余常用语法糖
自动装箱拆箱:自动调用包装类的 .valueOf() 方法
- 补充:遇到 算数运算符(即 + - * / 这类),会自动拆箱
- 遇到 ==,如果有一边是基本类型,那么都会拆箱成基本类型
- 遇到 equals,都是装箱后比较
遍历循环:必须实现 Iterator 接口,自动调用的 iterator#hasNext 和 iterator#next 进行的遍历
变长数组:解语法糖为 new Type[] { … }
案例
-
javac 编译前
public static void main(String[] args) {List<Integer> list = Arrays.asList(1, 2, 3, 4);int sum = 0;for (int i : list) {sum += i;}System.out.println(sum); }
-
javac 编译后
public static void main(String[] args) {List list = Arrays.asList( new Integer[] {Integer.valueOf(1),Integer.valueOf(2),Integer.valueOf(3),Integer.valueOf(4) });int sum = 0;for (Iterator localIterator = list.iterator(); localIterator.hasNext(); ) {int i = ((Integer)localIterator.next()).intValue();sum += i;}System.out.println(sum); }