疯狂java读书笔记
当创建任何java对象时,程序总会先调用每个父类非静态初始化块、父类的构造器(总是从Object开始)执行初始化,最后才调用本类的非静态初始化块、构造器执行初始化。
隐式调用和显式调用
当调用某个类的构造器来创建java对象时,系统总会调用父类的非静态初始化块进行初始化,这个调用是隐式执行的。接着会调用父类的一个或多个构造函数执行初始化,可以通过super进行显示调用,也可以是隐式调用。最后调用本类的非静态构造块、构造函数,最后的最后返回本类的实例。
关于调用父类的哪个构造器执行初始化问题:
1.子类构造器执行体的第一行代码使用super显示调用父类构造器,系统将根据super调用里传入的实参列表来确定调用父类哪个构造器
2.子类构造器执行体的第一行代码使用this显示调用本类中重载的构造器,系统将根据this调用里传入的实参列表确定本类的另一个构造器
3.子类构造器中既没有super调用,也没有this调用,系统将会在执行子类构造器之前,隐式调用父类无参数的构造器。
关于静态初始化块:静态初始化块是在类被jvm第一次加载时调用的,只调用一次,并且先调用父类的静态初始化块,然后是子类的,当所有静态初始化块被调用完后,才执行非静态初始化块和构造函数。
super调用用于显示调用父类的构造器,this调用构造函数用于显示调用本类中另一个重载的构造器。super调用和this调用构造器都只能在构造器中使用,而且都必须作为构造器的第一行代码,因此构造器中的super调用和this调用最多只能使用其中之一,而且this调用构造函数在一个类中最多只能使用一次(所以不会产生循环调用构造器的现象)。
访问子类对象的实例变量
子类的方法可以访问父类的实例变量,但是父类的方法不能访问子类的实例变量,因为父类根本无从知道它将被哪个子类继承,它的子类将会增加怎样的成员变量。
但是,在极端的情况下,可能出现父类访问子类变量的情况。
class Base{
private int i = 2;
public Base(){
this.display();
}
public void display(){
System.out.println(i);
}
}
class Derived extends Base{
private int i = 22;
public Derived(){
i = 222;
}
public void display(){
System.out.println(i);
}
}
public class Test {
public static void main(String[] args){
new Derived();
}
}
这个程序输出是2? 22? 222?。实际上,输出结果为0。
首先澄清一个概念:java对象是由构造器创建的吗?
实际情况是:构造器只负责对java对象实例变量执行初始化(也就是赋初始值),在执行构造器代码之前,该对象所占的内存已经被分配下来。这些内存里的值都是默认值。
上例中,this代表谁
当this在构造器中时,this代表正在初始化的java对象。 此时的情况是:从源代码来看,此时的this位于Base()构造器内,但这些代码实际放在Derived()构造器内执行——是Derived()构造器隐式调用了Base()构造器的代码。由此可见,此时的this应该是Derived对象,而不是Base对象。
有一个问题,修改Base构造函数如下
public Base(){
this.display();
System.out.println(this.i);
System.out.println(this.getClass());
}
此时再执行程序,会发现输出:
0
2
class Derived
为什么直接this.i时会输出2呢?
因为:这个this虽然代表Derived对象,但它却位于Base构造器中,它的编译时类型是Base,而实际引用一个Derived对象。通过输出this.getClass()可以发现
编译时类型vs运行时类型
当变量的编译时类型和运行时类型不同时,通过该变量访问它引用的对象的实例变量时,该实例变量的值由声明该变量的类型决定。当通过该变量调用它引用的对象的实例方法时,该方法行为将由它实际所引用的对象来决定。
调用被子类重写的方法
在访问权限允许的情况下,子类可以调用父类的方法,但是父类不能调用子类的方法,因为父类根本不知道它将被哪个子类继承,它的子类会增加什么方法。
但是,总有例外,有一种特殊的情况,当子类方法重写父类方法后,父类表面上只调用属于自己的、被子类重写的方法,但随着执行context的改变,将会变成父类实际调用子类的方法。
注意下面这个程序:
class Animal{
private String desc;
public Animal(){
this.desc = getDesc();
}
public String getDesc(){
return "Animal";
}
public String toString(){
return desc;
}
}
public class Wolf extends Animal{
private String name;
private String weight;
public Wolf(String name,String weight){
this.name = name;
this.weight = weight;
}
@Override
public String getDesc(){
return "Wolf [name:"+this.name+" weight:"+this.weight+"]";
}
public static void main(String[] args){
System.out.println(new Wolf("灰太狼","52.2l"));
}
}
这个程序输出的是:Wolf [name:null weight:null]
很明显,父类的构造函数调用了子类的方法,这里父类的构造函数中调用方法时省略了this,原理和前面讲的一样。都是构造函数和this的问题。至于为什么name和weight的值为null的问题,在前面2.1中实例变量和类变量中有介绍,在调用子类的构造器前,会调用父类的构造器。如果要输出name和weight的值不为null,则这个程序该怎么改进呢?
把父类的toString()方法改成如下就行了:
public String toString(){
return getDesc();
}
同时,应避免在构造器中调用被子类重写过的方法。
再申明一遍:构造器只负责对java对象实例变量执行初始化,在执行构造器代码之前,该对象所占的内存已经被分配下来,这些内存里的值是默认值。
阅读(2361) | 评论(0) | 转发(0) |