java的程序运行是从一个带main方法的类中启动主进程的,然后由这个住进程再把相关类加载进来执行其它的代码逻辑。
1)类的加载
那么java程序是怎么知道要执行哪些类的代码呢,首先它要找到哪些类需要加载到jvm中供后续程序使用。java采用的是一个叫类加载器的东东,所有的类都是通过类加载器加载进来,类加载器分为三种:启动类加载器、扩展类加载器和应用类加载器。其中启动类加载器是所有加载器的父加载器,java的类加载器采用双亲委派方式,也就是当要加载某个类时,当前的应用类加载器会先让自己的父加载器去加载,以此类推,当加载不到类时,才由当前的类加载器加载。这样保证了不会重复加载共有的一些基础类(例如jdk自己的类库)。
一个class在什么情况下会触发类加载器来加载它呢?这个时机是程序按照代码顺序执行时一旦碰到jvm不认识的class声明类时,就会由当前类加载器去加载到jvm中来。一般是一个类名头次出现时,它可以是定义一个变量的类型限定时,也可以是由
Class.forName(classname)显示指定。
2)类的实例化
class类加载到jvm中后,如何生成这个class的具体实例呢?
类的实例是通过该类的构造函数来实例化的,如果一个类没写任何构造函数,java会默认给它加上一个没有参数的构造函数,里面的实际步骤就是调用该类的父类无参构造函数。
如果定义了构造函数,java就不会给你生成默认的无参数的构造函数,当该类实例化时就调用你定义的对应参数的构造方法。
在一个类实例的初始化过程中最主要要搞清楚类里面各个元素的初始化顺序,大致顺序如下:
静态部分(含静态变量和静态块)-->基础类型的实例变量(即类成员中的一些基础类型属性)-->构造函数
所有基础类型的变量会有一个默认初始化的值,就算没有显示赋给它初始值,所以如果这些变量在构造函数里指定初始值的话,实际上它已经进行了两次赋值(默认的赋值和构造函数里指定的赋值)
构造函数的第一行可以是调用父类对应的构造函数super(),否则不会调用父类的构造函数执行。
执行过程是严格按代码先后次序执行的。后执行的可以用前面已初始化的属性,反之不行。
这里比较绕的一种情况是:构造函数里调用其它方法,而其它方法中用到了其中还没有被构造函数初始化的其它属性,更复杂的还会加上调用父类的构造方法。但是万变不离其中,记得上面的初始化次序即可。程序执行到某行代码时你可以清楚知道该属性是否在静态区,是否是基础类型,是否已经在先前的调用过程中执行过初始化了。
有父类和子类同名方法时还要考虑多态性,父类构造函数中引用的属性是它自己的变量(因此是父类初始化顺序中的值),而子类构造函数中引用的是子类的属性(因此是子类初始化顺序中的值)
静态变量,只在它第一次要使用时才真正初始化(也就是代码运行到对静态变量的引用时才会触发初始化过程),否则即使装载了类,也不会执行静态区域代码。
3)动态绑定和静态绑定
动态绑定是指声明类和实际的实例化类不是同一个(而是声明类型的子类),那么代码中调用该对象的某个方法时编译期就不知道是执行声明类的方法还是实例类的方法,只有在执行时才知道它原来是子类的对象,因此调用的是那个子类的同名方法,这就实现了多态的特性。
静态绑定是指声明类和实际的实例化类在编译期就已经确定无疑的知道了,比较明显的例子就是static修饰的方法和区域,因为它们可以直接用类名来访问,因此和声明的类一一对应,编译器知道它的引用者是哪个。
对于方法来说,上面的说明已经很好理解了,可是java对于属性(或者叫成员变量)还有特殊化,它把成员变量这个也当成静态绑定,不支持动态绑定成员变量。
4)举个栗子
父类:
public class Super {
public String name = "father";
public String getName(){
return name;
}
public void setName(){
name = "this name is set by super method";
}
public Super(){
System. out.println(Foo. step++ + " before super initial:" + name);
setName();
System. out.println(Foo. step++ + " after super initial:" + name);
}
}
public String name = "father";
public String getName(){
return name;
}
public void setName(){
name = "this name is set by super method";
}
public Super(){
System. out.println(Foo. step++ + " before super initial:" + name);
setName();
System. out.println(Foo. step++ + " after super initial:" + name);
}
}
子类:
public class Sub
extends Super {
public String name = "children";
public String getName(){
return name;
}
public void setName(){
name = "this name is set by sub method";
}
public Sub(){
super();
System. out.println(Foo. step++ + " before sub initial:" + name);
setName();
System. out.println(Foo. step++ + " after sub initial:" + name);
}
}
public String name = "children";
public String getName(){
return name;
}
public void setName(){
name = "this name is set by sub method";
}
public Sub(){
super();
System. out.println(Foo. step++ + " before sub initial:" + name);
setName();
System. out.println(Foo. step++ + " after sub initial:" + name);
}
}
测试:
public class Foo {
public static int step = 0;
public static void main(String[] args) {
Super parent = new Super();
System. out.println(Foo. step++ + " -------分割线--------");
Super child = new Sub();
System. out.println(Foo. step++ + " -------分割线--------");
System. out.println(Foo. step++ + " " + parent. name);
System. out.println(Foo. step++ + " " + child. name);
System. out.println(Foo. step++ + " " + parent.getName());
System. out.println(Foo. step++ + " " + child.getName());
}
}
public static int step = 0;
public static void main(String[] args) {
Super parent = new Super();
System. out.println(Foo. step++ + " -------分割线--------");
Super child = new Sub();
System. out.println(Foo. step++ + " -------分割线--------");
System. out.println(Foo. step++ + " " + parent. name);
System. out.println(Foo. step++ + " " + child. name);
System. out.println(Foo. step++ + " " + parent.getName());
System. out.println(Foo. step++ + " " + child.getName());
}
}
输出结果:
0 before super initial:father
1 after super initial:this name is set by super method
2 -------分割线--------
3 before super initial:father
4 after super initial:father
5 before sub initial:children
6 after sub initial:this name is set by sub method
7 -------分割线--------
8 this name is set by super method
9 father
10 this name is set by super method
11 this name is set by sub method
需要说明点:
第5步,因为是在sub的构造函数里引用name,所以是动态绑定的name值children
第9步,因为是使用成员变量访问,而成员变量是静态绑定的,所以name是声明类的值father
总结:只要是通过方法访问的都动态绑定了,通过成员变量访问的是静态绑定。