Java中静态代码块、构造方法、代码块、父类与子类之间执行顺序及父类子类实例化对象

简介: Java中静态代码块、构造方法、代码块、父类与子类之间执行顺序及父类子类实例化对象

【1】几个概念

① 静态代码块

在java中使用static关键字声明的代码块。每个静态代码块只会执行一次。JVM在加载类时会执行静态代码块,静态代码块先于主方法执行。

  static{
    System.out.println("这是静态代码块");
  }


注意: 静态代码块不能存在于任何方法体内。


② 构造代码块(实例初始化块):


直接在类中定义且没有加static关键字的代码块称为{}构造代码,在创建实例对象的时候先于构造函数被调用执行

class Test{
    int id;
    String name;
  // JVM加载class时执行
  static{
    System.out.println("这是静态代码块");
  }
    // 新建对象的时候执行,构造代码块会先于构造函数被调用时执行
    {
        this.id= 5;
        this.name = "测试";
        System.out.println("这是构造代码块");
    }
    Test(int id){
        this.id = id;
    }
    public String toString(){
        return "name: "+this.name +"  ,   "+"id: "+ this.id ;    
    }
}


③ 普通代码块

在方法或语句内出现的{}就称为普通代码块。普通代码块和一般的语句执行顺序由他们在代码中出现的次序决定–“先出现先执行”。

public class GeneralCodeBlock01{
    public static void main(String[] args){
        //如下为普通代码块
          {
            int x=3;
            System.out.println("1,普通代码块内的变量x="+x);    
          }
          int x=1;
          System.out.println("主方法内的变量x="+x);
          {
             int y=7;
             System.out.println("2,普通代码块内的变量y="+y);    
          }
        }
  }



属性、方法、构造方法和代码块都是类中的成员,在创建对象时,各成员的执行顺序如下:


  • 父类静态成员和静态初始化块,按在代码中出现的顺序依次执行;
  • 子类静态成员和静态初始化块,按在代码中出现的顺序依次执行;
  • 父类实例成员和实例初始化块,按在代码中出现的顺序依次执行;
  • 执行父类构造方法;
  • 子类实例成员和实例初始化块,按在代码中出现的顺序依次执行;
  • 执行子类构造方法。


【2】父类、子类之间代码块与构造方法

示例代码如下:

package com.web.test2;
public class HelloA {
    static{
        System.out.println("static A");
    }
    {System.out.println("I'm A class");}
    public HelloA(){
        System.out.println("HelloA");
    }
    public HelloA(String s){
        System.out.println(s+"HelloA");
    }
    public static void main(String[] args) {
        new HelloB();
    }
}
 class HelloB extends HelloA{
    public HelloB () {
      //只能调用一个父类的构造方法
//      super();
      super("parent");
        System.out.println("HelloB");
    }
    {System.out.println("I'm B class");}
    static{
        System.out.println("static B");
    }
}


执行结果:

static A
static B
I'm A class
parentHelloA
I'm B class
HelloB



总结如下:


① 代码块于构造方法之前执行,静态于非静态之前;


② 在类第一次被调用时,加载该类,静态代码块只执行一次;


③ 项目启动时,只加载需要加载的类(比如xml中显示配置的bean,或者web.xml中的listener等),并不会将所有的类都加载(这是很可怕的事情);


④ 静态代码块只能调用静态变量;静态方法只能调用静态变量;


⑤ 非静态代码块或非静态方法可以调用任何(静态+非静态)变量。


⑥ 非静态代码块在实例化对象时,于构造方法之前执行。

【3】父类、子类与super()

示例代码如下:

public class People {
  String name;
  public People() {
    System.out.println(1);
  }
  public People(String name) {
    System.out.println(2);
    this.name = name;
  }
}
 class Child extends People{
   People father;
  public Child () {
    //super()系统会默认添加的
    System.out.println(4);
  }
  public Child (String name) {
    //super()系统会默认添加的
    System.out.println(3);
    this.name = name;
    father = new People(name+":F");
  }
  public static void main(String[] args) {
    new Child("mike");
  }
}



故执行结果:132


【4】类中添加静态变量


静态变量与静态代码块一样,都是在类被加载的时候赋值/被执行,而且静态变量与静态代码块执行顺序是按照代码上次次序进行执行的。

示例代码如下:

public class ExA {  
  //静态成员
    private static ExA a = new ExA();  
    //静态代码块
    static {  
        System.out.println("父类--静态代码块");  
    }  
    //构造方法
    public ExA() {  
        System.out.println("父类--构造函数");  
    }  
    // 实例化代码块
    {  
        System.out.println("父类--非静态代码块");  
    }  
    public static void main(String[] args) {  
        new ExB();  
    }  
}  
class ExB extends ExA {  
    private static ExB b = new ExB();
    static {  
        System.out.println("子类--静态代码块");  
    }  
    {  
        System.out.println("子类--非静态代码块");  
    }  
    public ExB() {  
        System.out.println("子类--构造函数");  
    }  
}  



  • result as follows :
// 父类静态成员
父类--非静态代码块
父类--构造函数
//父类静态代码块
父类--静态代码块
父类--非静态代码块
父类--构造函数
子类--非静态代码块
子类--构造函数
子类--静态代码块
父类--非静态代码块
父类--构造函数
子类--非静态代码块
子类--构造函数



分析如下:

① 首先加载父类静态成员和静态代码块

 private static ExA a = new ExA();  
 static {  
     System.out.println("父类--静态代码块");  
 }  


这里静态成员赋值为实例化A对象,故而需要先执行A的实例化代码块和构造方法:

 public ExA() {  
    System.out.println("父类--构造函数");  
  }  
 // 实例化代码块
  {  
      System.out.println("父类--非静态代码块");  
  }  


此时输入结果为:

// 父类静态成员
父类--非静态代码块
父类--构造函数
//父类静态代码块
父类--静态代码块


② 其次加载子类静态成员和静态代码块

private static ExB b = new ExB();
static {  
    System.out.println("子类--静态代码块");  
}  



子类静态成员赋值为实例化B对象,按照博文最上面子类实例化对象过程可知,此时该加载父类实例化块和构造方法,此时结果更新为:

// 父类静态成员
父类--非静态代码块
父类--构造函数
//父类静态代码块
父类--静态代码块
父类--非静态代码块
父类--构造函数


静态变量赋值还没有执行完!!该执行子类实例化代码块和构造方法,此时结果更新为:

# 1. 父类静态成员赋值和静态代码块
// 父类静态成员
父类--非静态代码块
父类--构造函数
//父类静态代码块
父类--静态代码块
# 2. 子类静态成员赋值和静态代码块
// 每次调用父类构造方法前都会调用父类实例化代码块
父类--非静态代码块
父类--构造函数这里写代码片
子类--非静态代码块
子类--构造函数


子类B静态变量赋值完,该执行子类静态代码块,此时结果更新如下:

# 1. 父类静态成员赋值和静态代码块
// 父类静态成员
父类--非静态代码块
父类--构造函数
//父类静态代码块
父类--静态代码块
# 2. 子类静态成员赋值和静态代码块
// 每次调用父类构造方法前都会调用父类实例化代码块
父类--非静态代码块
父类--构造函数这里写代码片
子类--非静态代码块
子类--构造函数
# 3. 子类静态代码块
子类--静态代码块


③ 静态完了,该非静态了

按照如下步骤,该执行第3/4/5/6:

父类静态成员和静态初始化块,按在代码中出现的顺序依次执行;
子类静态成员和静态初始化块,按在代码中出现的顺序依次执行;
父类实例成员和实例初始化块,按在diam中出现的顺序依次执行;
执行父类构造方法;
子类实例成员和实例初始化块,按在代码中出现的顺序依次执行;
执行子类构造方法。


无需多考虑,结果更新如下:

# 1. 父类静态成员赋值和静态代码块
// 父类静态成员
父类--非静态代码块
父类--构造函数
//父类静态代码块
父类--静态代码块
# 2. 子类静态成员赋值和静态代码块
// 每次调用父类构造方法前都会调用父类实例化代码块
父类--非静态代码块
父类--构造函数这里写代码片
子类--非静态代码块
子类--构造函数
# 3. 子类静态代码块
子类--静态代码块
# 4. 父类非静态
父类--非静态代码块
父类--构造函数
# 5. 子类非静态
子类--非静态代码块
子类--构造函数


确实比较难理解,需要特别注意父类和子类静态变量赋值的时候取值为实例化对象,非常量或者null值!!


【5】不仅仅父子,还有祖孙

示例如下:

package com.web.test2;
public class Creature {
    public Creature(){
        System.out.println("空的");
    }
    public static void main(String[] args) {
        new wolf();
    }
}
class Animal extends Creature{
    public Animal(String name) {
        super();
        System.out.println("一个参数"+name);
    }
    public Animal(String name,int age){
        //super();错误
        this(name);
        System.out.println("这个动物带了两个属性"+age);
    }
}
class wolf extends Animal{
    public wolf(){
        super("灰太狼",3);
        System.out.println("狼带了三个属性");
    }
}


执行结果如下:

空的
一个参数灰太狼
这个动物带了两个属性3
狼带了三个属性


默认会调用父类无参构造方法,如果父类还有parent,则继续向上搜寻,直到Creature。

this()与super()在一个方法中只能存在一个。


【6】如果父类没有空参构造器呢?

父类如下所示:

public class Parent {
    private Integer id;
    private Integer age;
    public  Parent(Integer id,Integer age){
        this.id=id;
        this.age=age;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
}


子类代码如下所示:

public class Child extends  Parent {
    private Integer id;
    private String name;
    @Override
    public void setId(Integer id) {
        this.id = id;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public Integer getId() {
        return id;
    }
    public String getName() {
        return name;
    }
}


此时,直接报编译错误,提示如下:

There is no default constructor available in 'com.jane.model.Parent'

即,父类没有默认构造器,你需要重写父类构造器。修改代码如下:

public class Child extends  Parent {
    private Integer id;
    private String name;
    public Child(Integer id, Integer age) {
        super(id, age);
    }
    public Child(Integer id, Integer age, Integer id1, String name) {
        super(id, age);
        this.id = id1;
        this.name = name;
    }
    @Override
    public void setId(Integer id) {
        this.id = id;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public Integer getId() {
        return id;
    }
    public String getName() {
        return name;
    }
}


此时子类同样不能存在空参构造器!!


【7】子类对象实例化是否会实例化父类对象

答案当然是否定的!!


首先举反例证明

如果父类是个抽象类呢?实例化子类的时候实例化父类,很显然矛盾!故而,实例化子类的时候不会实例化父类对象!!


其次相关理论


① 构造器只是负责对java对象实例变量执行初始化(也就是赋初始值),在执行构造器代码之前,该对象所占的内存已经被分配下来,这些内存里值都默认是空值——对于基本类型的变量,默认的空值就是0或false,对于引用类型的变量默认的空值就是null。


② 在处理java类中的成员变量时,并不是采用运行时绑定,而是一般意义上的静态绑定,即成员变量被关联到了类上。必须明确,运行时(动态)绑定针对的范畴只是对象的方法。(方法的话只有static和final(所有private默认是final的)是静态绑定的.)


如果虚拟机在子类对象中找不到某个属性的时候,就到包含该构造方法中的类去找静态绑定相应的属性。


③ 当创建任何java对象时,程序总会先一次调用每个类父类非静态初始化块、父类构造器(总是从Object开始)执行初始化,最后才调用本类的非静态初始化块、构造器执行初始化。


具体请参考博文上面讲述过程。


此时当程序执行到①时,系统会先为父类中的私有属性在堆内存开辟空间


注意,这里并不会实例化父类对象,仅仅是为父类中的属性在堆中开辟了一段内存空间。


然后再为子类在堆内存空间,此时调用父类的构造方法(不写super()的话会隐式的调用),此时通过准备知识1中我们已经知道构造方法仅仅只是负责对java对象实例变量执行初始化,而不会实例化父类。


参考博文:JVM创建对象的奥秘

【8】子类继承父类什么?


常见的一句话,子类对象只能继承父类非私有的属性及方法;还有另外一句话–子类继承父类,子类拥有父类所有的属性和方法。


是否矛盾?该怎样理解?先看看第【7】部分,然后继续往下。


使用程序去验证,发现父类的私有属性和私有方法,子类是不能访问的,当然一些父类的私有属性可能可以通过相应的方法访问到,但是私有的方法似乎不能简单的访问,这里暂不考虑Java反射机制。


子类不能通过object.field的方式获取父类私有属性,这说明不属于子类成员变量!但是子类可以使用父类的public方法为父类的私有属性赋值并获取该私有属性值!这可以理解为子类拥有父类私有属性。



如下图所示,Child继承自父类Parent:


目录
相关文章
|
17天前
|
安全 Java 编译器
Java对象一定分配在堆上吗?
本文探讨了Java对象的内存分配问题,重点介绍了JVM的逃逸分析技术及其优化策略。逃逸分析能判断对象是否会在作用域外被访问,从而决定对象是否需要分配到堆上。文章详细讲解了栈上分配、标量替换和同步消除三种优化策略,并通过示例代码说明了这些技术的应用场景。
Java对象一定分配在堆上吗?
|
21天前
|
Java API
Java 对象释放与 finalize 方法
关于 Java 对象释放的疑惑解答,以及 finalize 方法的相关知识。
42 17
|
13天前
|
Java 程序员 数据库连接
Java执行顺序大揭秘:静态块、非静态块和构造方法谁先谁后?
本文详细介绍了Java中的初始化块,包括静态初始化块和非静态初始化块的概念、执行顺序和实际应用场景。通过具体示例,帮助读者理解这两种初始化块的区别和使用场景,让面试官对你刮目相看。
26 0
Java执行顺序大揭秘:静态块、非静态块和构造方法谁先谁后?
|
20天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
29天前
|
存储 Java 数据管理
Java零基础-Java对象详解
【10月更文挑战第7天】Java零基础教学篇,手把手实践教学!
24 6
|
1月前
|
Oracle Java 关系型数据库
重新定义 Java 对象相等性
本文探讨了Java中的对象相等性问题,包括自反性、对称性、传递性和一致性等原则,并通过LaptopCharger类的例子展示了引用相等与内容相等的区别。文章还介绍了如何通过重写`equals`方法和使用`Comparator`接口来实现更复杂的相等度量,以满足特定的业务需求。
20 3
|
1月前
|
设计模式 Java 测试技术
Java零基础-构造方法详解
【10月更文挑战第5天】Java零基础教学篇,手把手实践教学!
19 1
|
1月前
|
存储 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第9天】在Java的世界里,对象序列化是连接数据持久化与网络通信的桥梁。本文将深入探讨Java对象序列化的机制、实践方法及反序列化过程,通过代码示例揭示其背后的原理。从基础概念到高级应用,我们将一步步揭开序列化技术的神秘面纱,让读者能够掌握这一强大工具,以应对数据存储和传输的挑战。
|
1月前
|
存储 Java 数据管理
Java零基础-Java对象详解
【10月更文挑战第3天】Java零基础教学篇,手把手实践教学!
16 1
|
1月前
|
Java 数据安全/隐私保护
java类和对象
java类和对象
25 5