之前的一篇博客里我写了关于在一个类中的程序初始化顺序, 但是在 Java 的面向对象里, 类之间还存在着继承的关系. 所以关于程序的初始化顺序, 我们可以再细划分为: 父类静态变量, 父类的静态代码块, 父类构造器, 父类非静态变量, 父类非静态代码块, 子类静态变量, 子类静态代码块, 子类构造器, 子类非静态成员变量和子类非静态代码块.
本篇博客我们讨论的就是关于程序初始化的过程中, 上述的成员在初始化加载先后顺序.
在此前我们讨论得出的结论: 在一个类中, Java 程序加载的顺序是: 静态变量 --> 静态代码块 --> 非静态变量 --> 非静态代码块 --> 构造器.
父类的代码:
- public class SuperClass {
- // 父类与子类都在一个包中, 这里我们就使用 default 修饰符
- // 这是一个父类的静态变量, 此时还是初始化的默认值 null
- static String superStaticVariale;
- // 静态代码块, 给 String 赋值
- static {
- superStaticVariale = "父类静态代码块赋值成功";
- System.out.println("此时运行的是父类的静态代码块:"+superStaticVariale);
- }
- // 无参构造, 覆盖静态代码块的值
- SuperClass(){
- superStaticVariale = "父类构造器赋值成功";
- System.out.println("此时运行的是父类的构造器:"+superStaticVariale);
- }
- // 定义一个非静态变量
- String superVariale;
- // 定义一个非静态代码块
- {
- superVariale = "父类非静态代码块赋值";
- System.out.println("此时运行的是父类的非静态代码块:"+superVariale);
- }
- }
子类的代码:
- public class SubClass extends SuperClass{
- static String subStaticVariale;
- // 静态代码块, 给 String 赋值
- static {
- subStaticVariale = "子类静态代码块赋值成功";
- System.out.println("此时运行的是子类的静态代码块:"+subStaticVariale);
- }
- // 无参构造, 覆盖静态代码块的值
- SubClass(){
- superStaticVariale = "子类构造器赋值成功";
- System.out.println("此时运行的是子类的构造器:"+superStaticVariale);
- }
- // 定义一个非静态变量
- String subVariale;
- // 定义一个非静态代码块
- {
- subVariale = "子类非静态代码块赋值";
- System.out.println("此时运行的是子类非静态代码块:"+subVariale);
- }
- }
测试代码:
- public class Main {
- public static void main(String[] args) {
- SubClass s = new SubClass();
- }
- }
运行结果:
```
此时运行的是父类的静态代码块: 父类静态代码块赋值成功
此时运行的是子类的静态代码块: 子类静态代码块赋值成功
此时运行的是父类的非静态代码块: 父类非静态代码块赋值
此时运行的是父类的构造器: 父类构造器赋值成功
此时运行的是子类非静态代码块: 子类非静态代码块赋值
此时运行的是子类的构造器: 子类构造器赋值成功
```
很显然, 在继承关系中, 代码的加载顺序是: 父类的静态变量 --> 父类的静态代码块 --> 子类静态变量 --> 子类的静态代码块 --> 父类非静态变量 --> 父类的非静态代码块 --> 父类的构造器 --> 子类非静态变量 --> 子类非静态代码块 --> 子类构造器
进一步测试:
- public class Main {
- public static void main(String[] args) {
- SubClass s = new SubClass();
- SubClass s1 = new SubClass();
- SubClass s2 = new SubClass();
- }
- }
运行结果:
```
此时运行的是父类的静态代码块: 父类静态代码块赋值成功
此时运行的是子类的静态代码块: 子类静态代码块赋值成功
此时运行的是父类的非静态代码块: 父类非静态代码块赋值
此时运行的是父类的构造器: 父类构造器赋值成功
此时运行的是子类非静态代码块: 子类非静态代码块赋值
此时运行的是子类的构造器: 子类构造器赋值成功
此时运行的是父类的非静态代码块: 父类非静态代码块赋值
此时运行的是父类的构造器: 父类构造器赋值成功
此时运行的是子类非静态代码块: 子类非静态代码块赋值
此时运行的是子类的构造器: 子类构造器赋值成功
此时运行的是父类的非静态代码块: 父类非静态代码块赋值
此时运行的是父类的构造器: 父类构造器赋值成功
此时运行的是子类非静态代码块: 子类非静态代码块赋值
此时运行的是子类的构造器: 子类构造器赋值成功
```
得出结论:
父类与子类的静态代码都只执行一次, 然后非静态代码块与构造器是组合出现的.
简化一下代码:
- public class Main {
- public static void main(String[] args) {
- C c= new C();
- }
- }
- class A{
- A(){
- System.out.println("A 的无参构造器");
- }
- }
- class B extends A{
- // B(int a){
- B(){
- System.out.println("B 的无参构造器");
- }
- }
- class C extends B{
- C(){
- System.out.println("C 的无参构造器");
- }
- }
运行结果:
```text
A 的无参构造器
B 的无参构造器
C 的无参构造器
```
调用 C 的构造器生成 C 的实例对象会从最上级的父类的无参构造器开始逐层调用, 那么我们的类都继承了一个超级父类 Object, 也就是在我们最初的错误代码中, 我们调用 Student 的无参构造创建一个对象时, 首先会调用这个对象的父类 Object 的无参构造器,
- class Student{
- String name;
- {
- name = "老大";
- }
- Student(){
- this(name);// 这样会报错
- super();
- System.out.println("题目要求写一个无参的构造器");
- }
- Student(String name){
- this.name = name;
- System.out.println(name);
- }
- }
子类实例化默认调用父类的无参构造器, 也就是如上 this 调用在 super() 之前 (实际中这两者不会同时出现),name 此时是非静态属性, 此时会报错错误: 无法在调用超类型构造器之前引用 name.
- class Student{
- static String name;
- {
- name = "老大";
- }
- Student(){
- this(name);
- System.out.println("题目要求写一个无参的构造器");
- }
- Student(String name){
- this.name = name;
- System.out.println(name);
- }
- }
当 name 是静态属性时, 代码块是非静态时, 编译通过, 调用子类的无参构造器时 this(name), 输出结果是:
```text
null
题目要求写一个无参的构造器
```
此时的 this() 调用实参构造并没有赋值成功.
- class Student{
- static String name;
- static{
- name = "老大";
- }
- Student(){
- this(name);
- System.out.println("题目要求写一个无参的构造器");
- }
- Student(String name){
- this.name = name;
- System.out.println(name);
- }
- }
此时运行结果:
```text
老大
题目要求写一个无参的构造器
```
这样赋值成功. 由此证明我们的结论是正确的, this() 是在子类父类构造器之前进行的操作 super(), 当子类代码块是非静态时, 子类非静态代码块会在执行父类构造器之后执行, 所以 this(name) 时 name 还没有被赋值, 所以打印是 null.
结论:
1. 一个类中可以在无参构造器中调用此类的有参构造器 (顺序反过来);
2. 在执行子类的无参构造器时会默认调用最高级父类无参构造, 并逐级调用直至子类的无参构造;
3. Java 程序的加载顺为父类的静态变量 --> 父类的静态代码块 --> 子类静态变量 --> 子类的静态代码块 --> 父类非静态变量 --> 父类的非静态代码块 --> 父类的构造器 --> 子类非静态变量 --> 子类非静态代码块 --> 子类构造器, 且静态变量或代码块无论构造器调用多少次, 他只会执行一次, 后面再调用构造器则会执行非静态属性及代码块构造器.
最后关于为什么子类会调用父类的构造器, 这个从设计着的角度来看是为了给从父类继承的属性初始化, 子类需要知道父类是如何给属性初始化的.
来源: https://www.cnblogs.com/Jeffding/p/9490510.html