编译
编译有三种:
-
前端编译器("编译器的前端")
作用:将.java文件转变成.class文件
代表:Sun的Javac、Eclipse JDT中的增量式编译器(ECJ) -
虚拟机的后端运行期编译器(JIT编译器,Just In Time Compiler)
作用:把字节码转变成机器码的过程
代表:HotSpot VM的C1、C2编译器 -
静态提前编译器(AOT编译器,Ahead Of Time Compiler)
作用:直接把*.java文件编译成本地机器代码
代表:GNU Compiler for Java(GCJ)、Excelsior JET
Javac编译器的源码获取
(Javac是由java语言编写的程序)
书本上推荐->看源码。
#但是我不推荐。
#等实力达到了一定的水平,一定要看。
#但是像我这样比较菜的人,看的话,真的是压力山大。
下载:
OpenJDK6源码: http://download.java.net/openjdk/jdk6/
下载下来。
1、建立一个java工程(本人使用的是IDEA)
2、
解压下载的文件,将:
openjdk-6-src-b27-26_oct_2012\langtools\src\share\classes
中的com文件夹,整个复制到工程的源码中
然后会玩的可以畅游源码了。
理论
看不来源码的,来聊聊理论。
* 过程
0.准备过程:初始化插入式注解处理器
1.词法分析和语法分析
2.输入到符号表
3.执行注解处理
4.分析及字节码生成
标注
数据流分析
解语法糖
生成字节码
* 过程
0.准备过程:初始化插入式注解处理器
1.词法分析和语法分析
词法分析:将源代码的字符流转标为标记(Token)集合。
例如:
”int a = b+2“ 代表6个标记{int,a,=,b,+,2}
语法分析:根据Token序列来构建抽象语法树。
2.输入到符号表(并不懂...)
符号表:一组由符号地址和符号信息构成的表格。
在语义分析中,符号表所等级的内容将用于语义检查和产生中间代码。
在目标代码生成阶段,当对符号名进行地址分配时,符号表是地址分配的依据。
3.执行注解处理
提供了一组插入式注解处理器的标准API在编译器见对注解进行处理,
我们可以将它看作是一组编译器插件,它们可以读取、修改、添加抽象语法书中的任意元素。
如果这些插件在处理注解期间对语法树进行了修改,那么编译器将回到解析及填充符号表的过程重新处理,
直到所有的插入式注解处理器都没有再对语法书进行修改为止,每一个循环称为一个Round。
即下图的循环。就是从阶段1重新开始。
---->解析与填充符号表---->注解处理---->语法分析与字节码生成---->
^ |
| |
----------------------------
4.语义分析及字节码生成
(语法分析之后,编译器获得了程序代码的抽象语法树表示,
语法树能表示一个结构正确的源程序抽象,但无法保证源程序是符合逻辑的。
而#语义分析的主要任务是对结构上正确的源程序进行上下文有关性质的审查,如类型检查。
)
4.1 语义分析
4.1.1标注检查
检查诸如:变量使用前是否已被声明、变量与赋值之间数据类型是否匹配等等。
4.1.2数据流分析
数据以及控制流分析是对程序上下文逻辑的进一步验证。
检查诸如:程序局部变量在使用前是否有赋值、方法的每条路径是否都有返回值、是否所有的受查异常都被正确处理了等问题。
(
#编译时期的数据及控制流分析与类加载时的数据及控制流分析的目的基本一致,
#但校验范围有所区别,有一些校验项只有在编译期或运行期才能进行。
/*例子:
public void f(final int arg){
final int var=0;
}
public void f(int arg){
int var=0;
}
这两个函数生成的class文件不会有差别,
因为,局部变量的不变性(加了个final),实在编译器保证的
*/)
4.3解语法糖
语法糖:指的是在计算机语言中添加的某种语法,这种语法对语言的功能没有影响,
但是更方便程序员使用。通常情况下,语法糖呢个增加可读性,减少出错。
java常用的语法糖:范型,变长参数,自动装箱拆箱,等等
解语法糖:虚拟机事实上并不支持这些语法,在编译阶段被还原回简单的基础语法结构
4.4生成字节码
字节码生成:不仅将前面各个步骤生成的信息转化为字节码写入磁盘,
编译器还进行了少量的代码添加和转换工作。
/*
实例构造器
(包括默认构造器,如果用户代码不提供任何构造函数,
编译器会默认添加一个没有参数,访问性与当前类一直的默认构造函数,
在填充符号表阶段完成)
与 类型构造器。*/
在这个阶段会将实例构造器<init>方法以及类构造器<clinit>方法添加到语法树种。
以及其他依稀代码替换作用于优化程序的实现逻辑,如把字符串的加载操作替换为
StringBuffer或是StringBuilder的apend操作。
4.4的补充
#关于类型构造器和实例构造器
public class Solution {
private int m;
private static int n;
public Solution() {
m=1;//相当于this.m=1;
n=2;//猛的一看实例字段和静态字段的访问没有什么区别
}
}
public class Solution {
private static int n=1;
static{
n=2; //这里的n的最终值是1还是2呢?顺序是先初始化为1,然后被类型构造函数赋值为2
}
}
public class Solution {
public static int m;
public Solution(){
System.out.println("实例构造器"); }
static{
System.out.println("类型构造器"); }
public static void main(String[] args) {
Solution.m=1; //这个时候,Test类第一次被访问,所以会调用类型构造器,输出结果:"类型构造器"
Solution test=new Solution(); //这个时候,Test类不是第一次访问,但是创建一个类的实例,所以输出结果:"实例构造器"
//如果把上面的代码 Solution.m=1; 去掉的话,输出结果还是一样
//如果去掉Solution test=new Solution(); 那么就只有“类型构造器”了
}
}
输出:/*
类型构造器
实例构造器*/
java语法糖
- 语法糖-泛型
Java的泛型旨在程序源码中存在,在比那以后的字节码文件中,就被替换为原来的原生类型,并在相应的地方插入强制转换代码吗,因此对于运行期的java语言来说,ArrayList<String> 与ArrayList<Integer> 就是同一个类。(关键字:类型擦除,伪泛型)
情况1:
public static void main(String[] args){
Map<String,String> map =new HashMap<String,String>();
map.put("hello","喂");
map.put("how are you ?","怎么样");
System.out.println(map.get("hello"));
System.out.println(map.get("how are you"));
}
先将上面的编译成Class文件,再反编译:
public static void main(String[] args){
Map map =new HashMap();
map.put("hello","喂");
map.put("how are you ?","怎么样");
System.out.println((String)map.get("hello"));
System.out.println((String)map.get("how are you"));
}
情况2:
public class A{
public static void method(List<String> list){
System.out.println("String");
}
public static void method(List<Integer> list){
System.out.println("Integer");
}
}
上面的代码是无法编译通过的。因为擦除导致两个方法的参数类型一致。
public class A{
public static String method(List<String> list){
System.out.println("String");
return "";
}
public static int method(List<Integer> list){
System.out.println("Integer");
return 1;
}
}
书本上说可以编译执行,但是Idea中不行,可能我是java1.7吧?,不过书上说在Class文件格式中,
只要描述符不是完全一致的两个方法就可以共存。(更细节要看class文件中的格式再说)
所以编译和class文件中的检查操作合起来才是最终判断结果。
- 自动装箱、拆箱与遍历循环
public static void main(String[] args){
List<Integer> list = Arrays.asList(1,2,3,4);
int sum = 0;
for (int i:list){
sum=i;
}
}
下面是编译以后的样子:
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;
}
}
关于自动拆箱,与装箱
http://www.cnblogs.com/danne823/archive/2011/04/22/2025332.html
- 条件编译
C#中会有:#ifdef 来做条件编译
而 java更直接:
public static void main(String[] args){
if(true){
System.out.println("1");
}else{
System.out.println("2");
}
}
编译后再反编译的结果
public static void main(String[] args){
System.out.println("1");
}
网友评论