jdk16全面学习之jls的第八章Classes[下]
这是关于classes的最后一部分,主要学习一下类成员的初始化.不同于cpp,java在语言层面并不是任何变量都有默认的初始值,有些可能会报错,同时各类型的成员也要按照一定的顺序完成初始化.
1 构造方法的初始化
首先是类的实例构造方法.构造方法本事属于一类方法,如同其它方法一样,也有方法签名.普通的初始化方法较简单,因此这里首先说明一下默认初始化构造方法,之后说明一下内部类的初始化.
1.1 默认的初始化方法
默认的初始化方法都知道,就是省略不写的初始化方法,其具体的调用其实还是有些不同.
如下类Point的默认初始化为
public class Point {
int x, y;
}
等同于,实际上也是默认的初始化方法调用为
public class Point {
int x, y;
public Point() { super(); }
}
可见,super()是所有初始化时必须调用的方法,不论是否重写了构造方法,都要有一个默认的构造方法,这也是第一个要调用的方法,后面会详细说明.
1.2 内部类的初始化
运行如下代码InitialInnerStackOverFlow.java
public class InitialInnerStackOverFlow {
}
class Outer {
class Inner {
Inner() {
Outer outer=new Outer(){
{
System.out.println("create outer in inner constructor");
}
};
}
}
Outer() {
inner=new Inner() {
{
System.out.println("create inner in outer constructor");
}
};
}
}
class ChildOfInner extends Outer.Inner {
ChildOfInner() { (new Outer()).super(); }
}
class Test_8_3 {
public static void main(String[] args) {
final ChildOfInner childOfInner = new ChildOfInner();
}
}
运行结果如下
这里出现了一个栈溢出,由于内部类的构造和外部类的构造在调用时发生了循环调用,因此这种典型的内部类和外部类的构造器引起的栈溢出甚至是死锁的情况较容易出出现,也需要小心避免.
2 实例方法必须在实例化后调用的原则
这里依然沿用[中]的MethodShadow.java代码,不过加上初始化方法,如下所示
public class MethodShadow {
}
class Super {
Super(Super s) {
System.out.println(s.name()+" in super constructor");
}
Super() {
System.out.println("super default constructor");
}
static String greeting() { return "super greeting"; }
String name() { return "super"; }
}
class Sub extends Super {
static String greeting() { return "sub greeting"; }
@Override
String name() { return "sub"; }
}
class Test_8_2 {
public static void main(String[] args) {
Super s = new Sub();
System.out.println(new Super(s).greeting());
System.out.println(s.greeting() + " "+s.name());
}
}
运行结果如下
运行后,可以看到父子类的初始化顺序,这里面就存在一个很重要的原则问题,首先来看这个父类的构在方法
Super(Super s)
这是不是很奇怪?在父类的实例还没有创造出来,就需要有一个实例来作为参数参与构造,这看上去就是矛盾的.所以java在语言层规定了这么个原则,就是实例化方法必须在实例化后进行调用,这也是在[中]的最后提到的为什么方法的屏蔽会和类型与实例有关.
这也从另一个逻辑的角度说明了为什么此时s.name()会返回子类的名字,因为此时s并不是真正意义上的父类的实例,所以不能使用父类实例的方法.
3 属性的初始化
属性的初始化是按照类的继承顺序来进行的,当然要注意之前在[中]这篇文章中说的屏蔽的方法,如果在构造方法中使用了静态的方法,就要注意此时静态方法是和类型绑定的,而不是和实例绑定的,如上介绍的一样.
截止到属性初始化之前,需要调用的方法有
1 父类默认构造器
2 子类的指定构造器
之后就是静态成员和实例成员的初始化了,这就变得简单多了.
4 总结
Jvm有一套完整的类系统,从编译源文件到加载,到解析和配置.整个类系统在语言层面就是java的类,一个重要的原则就是实例化的方法必须在实例化后调用.一些屏蔽的规则也是基于此产生的.
因为类系统是从jvm的实现而来的,所以这里主要从cpp的角度来分析了java语言层面的一些特性,这也是从贴合本源做出的分析,力争从根本上解释这些特性的来龙去脉,这些特性背后是什么原则在主导着这一切.
恪守原则是java的一大特点.但是,这也是某种束缚,这个原则越不容突破,那么束缚也就越大.延伸的说,使用面向对象的一个缺陷就是一些原则需要被遵守,实际上可能不需要这么做.关于这一点后续章节会深入的说明.