类的初始化顺序
当类被实例化时,在底层到底是先初始化静态变量还是实例变量还是构造器呢?他们的顺序时什么?
现在我们就来测试一下:
class A{
static{
System.out.println("静态块初始化");
}//静态块
static int i; //静态变量
{
System.out.println(i);//打印静态变量
System.out.println("实例块初始化");
} //实例块
public int j; //实例变量
A(){
System.out.println(j); //打印实例变量
System.out.println("构造器初始化");
}
public class test { //测试类
public static void main(String[] args) {
A a = new A();
}
}
结果如下:
那么我们可以暂时先猜测 初始化顺序为:静态块,静态变量,实例块,实例变量,构造器
我们可以测试一下其他可能,如果顺序真的跟我们所说的一样,那么实例变量实在实例块之后初始化,我们在实例块中,是无法引用实例变量的。
那么我们试试结果如何
确实是非法引用。所以顺序我们可以敲定了。
类的初始化顺序为:静态块,静态变量,实例块,实例变量,构造器
值得注意的是:静态块以及静态变量 只会在第一次创建该类的对象时初始化一次,再创建一个对象静态块和静态变量是不会初始化的。
继承与重写
什么叫做继承与重写呢?
我们从人类的进化史来描述继承
很久以前人猿具有的能力是,爬行,攀岩,吃饭,
过了一段时间由于迁徙,下一代的人员不再具有攀岩能力,他们具有的能力是爬行,吃饭,穿越丛林
再下一代,人猿的脑部逐渐发达,并且由于迁徙不再每时每刻都需要四爪着地的爬行能力和穿越丛林能力
那么这一代的人猿的能力则是吃饭,制作工具。
我们可以看出来人猿的进化,是能力的改变,我们可以理解为方法不够用,我们需要新的方法。下一代为了适应生存,会舍弃上一代的某些方法,并且拥有更多的方法技能,而上一代的方法就会变成哪一个时代特有的方法(可以理解为private),同时上一代的方法我们也在不断的运用,并不会被舍弃,因此这个方法是被我们继承下来的(可以理解为protected关键字的运用)。同样,虽然是一样的方法,但使用起来却有所不同(这就是重写),上一代用手抓着食物吃,这一代则有可能是使用叉子辅助进食。
那么在Java里我们把这些概念去抽象出来就是我们所说的继承关系。
1.父类(基类)和子类(导出类)
2.子类的方法和父类的方法,有一些相同的方法,并且子类可以调用(就是父类中用protected修饰的方法)
3父类会有自己特有的方法,子类无法调用(父类中private修饰的方法)
4并且子类拥有父类不具有的方法。也就是子类的新方法。子类比父类要更强大。
方法重写发生在子类和父类之间,方法名 参数列表都相同,方法体不同的方法叫做方法重写(Override)。(子类的权限修饰符要比父类的权限修饰符范围要大或者一样)
5.Java是单继承,无法多重继承
6.父类的静态方法如果被子类重写,子类调用该方法时,那么父类的方法就会被隐藏
我们来实现一下
class father{ //父类
protected String name;
private void home(){ //父类特有的方法
System.out.println("父亲有一个家庭");
}
father(){ //父类默认构造器必须有!
this("父亲"); //调用有参构造器
}
father(String name){ //父类的构造器
this.name = name;
}
protected void eat(){ //子类也可以使用的方法
System.out.println("吃饭");
}
protected void job(){ //父类和子类都会有工作,但工作未必相同
System.out.println("父亲的工作是:机械工程师");
}
}
class son extends father{ //用 extends 关键字去实现继承关系
private String Schoolname;
son(String name1,String name2 ){ //子类构造器
super(name1); //用super()调用父类构造器
this.Schoolname = name2;
}
public void getname(){ //额外方法 父类并没有此方法
System.out.println(super.name); //用super关键字去访问父类用protected修饰的方法
//或者属性,由于这个方法或属性是我们继承下来的,并不是我们或者类中的
//所以必须要用super关键字去访问
}
public void job(){ //对父类方法的重写 public的权限范围比protected大
System.out.println("儿子的工作是:程序员");
}
public static void main(String[] args) {
Father f = new Father();
f.job();
Son s = new Son("张三","xx大学");
s.getname();
s.job();
}
结果:
那么我们在初始化子类的时候父类也会被初始化么???
我们来实操一下
class A{
static{
System.out.println("父类静态块初始化");
}
{
System.out.println("父类实例块初始化");
}
A(){
System.out.println("父类构造器初始化");
}
}
class B extends A{
static{
System.out.println("子类静态块初始化");
}
{
System.out.println("子类实例块初始化");
}
B(){
System.out.println("子类构造器初始化");
}
}
public static void main(String[] args) {
new B();
}
结果:
顺序如上,因为子类继承了父类的所有方法,所以子类同样具有父类的静态块,实例块和构造器,根据JVM的初始化顺序,首先初始化静态块,所以首先会初始化父类的静态块(因为子类的静态块的初始化中可能有的变量时依赖于父类的初始化,所以必须先初始化父类的静态块),再初始化子类的静态块,当我们初始化完静态块时,我们就会初始化实例块,同样我们先初始化父类的实例块,但当我们初始化父类实例块之后,紧接着就是父类的构造器,(不论你是否打算产生一个父类(基类)的对象,它都会发生),其次再是我们的子类实例块和构造器。
多态
什么叫做多态?
举个例子,所有的动物都会移动(move()方法),但是每个动物移动的方法不一样,人移动的方式是走,鸟移动的方法是飞。我们现在定义一个动物类,用鸟去实例化,那么我怎么知道这个鸟的 move() 方法,调用的是走还是飞呢????
但是由于Java多态的这一特性,使得我们有时需要把一个类不当作该类来看待,而是当作父类来看待时,我们可以编写出不依赖于类型的代码。这就是多态。
我们来实现一下
class Animal{ //定义一个动物类
public void move(){ //移动方法
}
}
class People extends Animal{
public void move(){
System.out.println("走"); //人实现move()方法
}
}
class Birds extends Animal{
public void move(){
System.out.println("飞"); //鸟实现move()方法
}
}
public static void main(String[] args) { //测试
Animal A = new People(); //向上转型
A.move();
}
结果:
那么实现多态的条件我们就可以总结出来了,
1.继承
2.重写
3.向上转型
那么在底层是怎么实现的呢???
多态底层主要使用的是方法表
方法表在方法区中, JVM 执行字节码文件会把类的类型信息(成员,方法等等)放在方法区里
方法区会有一个方法表(保存类的相应方法)
类型信息有一个指针,指向方法表
多态底层实现机制,首先我们要具有以上所述的多态的三个条件。
其次,我们需要明白我们大多数实现的都是动多态(也就是后期绑定,编译器只有在运行时才知道调用哪一行代码)
静多态也就是前期绑定,编译器在编译时期就能确定我们再调用哪一行代码,也就是方法的重载
最后,调用方法时先把实例方法的符号引用解析成直接指向方法表的指针(偏移量),JVM通过对象得到方法区中的方法表的入口 ,虽然我们声明的是父类,但调用的是子类方法的方法表(子类重写父类的方法实际上就是在子类的方法表中对父类方法的覆盖),通过前面解析的偏移量,就可以直接调用到这个方法。(注:Class对象在JDK1.7版本之后存放在堆区)