java学习分享之代码块

第一次尝试分享学习笔记,本人还是小白,不喜勿喷 代码块 代码块可以简单的理解成只有方法体的方法,在加载类或创建对象时隐式调用。 代码块不能被程序员手动调用。 java中最主要的两种代码块为: 静态代码块:在加载类时执行。(在代码块前加上static) 构造代码块:在创建对象时(构造方法执行之前)执行...
java学习分享之代码块
java学习分享之代码块

第一次尝试分享学习笔记,本人还是小白,不喜勿喷

代码块

代码块可以简单的理解成只有方法体的方法,在加载类或创建对象时隐式调用。
代码块不能被程序员手动调用。
java中最主要的两种代码块为:

每个代码块都有自己执行的明确时机,不需要也不能被人类调用。

那代码块到底能做什么?为什么要设计出来这种东西?

静态代码块的必要性

有时,初始化一个静态成员需要执行一段逻辑,而非简单赋值,比如读文件等。

import java.sql.*;
public class DatabaseConnection {
    // 静态变量:整个程序共享一个数据库连接
    private static Connection conn;
​
    // 必须使用静态代码块,因为初始化需要复杂的逻辑和异常处理
    static {
        try {
            // 加载驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 建立连接(这个过程可能抛出异常)
            conn = DriverManager.getConnection(
                "jdbc:mysql://localhost:3306/mydb", "user", "pass");
        } catch (ClassNotFoundException | SQLException e) {
            // 可以在这里记录日志或处理异常
            System.err.println("数据库连接初始化失败:" + e.getMessage());
            // 甚至可以抛出包装后的运行时异常
            throw new RuntimeException(e);
        }
    }
    
    public static Connection getConnection() {
        return conn;
    }
}

构造代码块的必要性

多个构造函数有共同前置逻辑

如果多个构造函数有共同前置逻辑,就考虑放在构造代码块里,方便修改,不用一次性修改所有构造函数。

PS:代码块调用顺序先于构造器。

public class User {
    private String name;
    private int loginCount;
​
    // 构造代码块:所有构造方法在执行前都会先执行这里
    {
        System.out.println("一个新的User对象正在创建..."); // 公共日志
        this.loginCount = 0; // 公共默认值
    }
​
    // 构造方法1
    public User() {
        this.name = "匿名用户";
        System.out.println("调用了无参构造");
    }
​
    // 构造方法2
    public User(String name) {
        this.name = name;
        System.out.println("调用了单参构造");
    }
​
    // 构造方法3
    public User(String name, int loginCount) {
        this.name = name;
        this.loginCount = loginCount;
        System.out.println("调用了双参构造");
    }
}

构造匿名内部类

这部分没太看懂,暂时不写

代码块一些细节

代码块执行时机

static代码块随着类的加载而执行,只执行一次;构造代码块,每次类初始化就执行。

类何时被加载

  1. 创建对象实例时

  2. 创建子类对象实例,父类也会被加载

  3. 使用类的静态成员时

构造器隐含了…

构造器其实最开始一定隐含了 super和普通代码块的调用,这就是为什么父类普通代码块和属性、父类构造,要早于子类的普通代码块、属性、子类构造

创建一个对象时,一个类内部的调用顺序

  1. 静态代码块和静态属性,具体顺序看定义顺序,写在前面的先执行

  2. 普通代码块和普通属性,具体顺序看定义顺序,写在前面的先执行

  3. 构造器

假如是有父类的,顺序如下

  1. 父类静态代码块和属性

  2. 子类静态代码块和属性

  3. 父类普通代码块和属性

  4. 父类构造

  5. 子类普通代码块和属性

  6. 子类构造

整个过程其实是这样

  1. 子类被实例化了,因此子类先要被加载,但是发现加载子类需要先知道父类信息,因此首先加载父类,此时是加载类信息,因此只处理静态的代码块和属性

  2. 然后是子类的加载,此时是加载类信息,因此只处理静态的代码块和属性

  3. 然后处理子类的实例化逻辑,首先调用构造器,构造器开始隐含了 super和普通代码块的调用,因此先调用了父类的构造器,然后父类也有super(假设父类的super执行完了)和普通代码块的调用,在进入父类构造器体之后、执行父类构造器的其他代码之前,会先执行父类的普通代码块和属性初始化,最后才执行父类构造器体中剩余的代码。

  4. 父类构造器结束后,子类构造器开始执行普通代码块和属性的初始化,然后执行子类的构造器

老生常谈

静态代码块只能调用静态成员,普通代码块可以调用所有成员

练习题

第一题

package com.hspedu.sta.tic;
​
​
class Person{
    public static int total;
    static {
        total = 100;
        System.out.println("In static block");
    }
}
​
public class Test {
    public static void main(String[] args) {
        System.out.println(Person.total);
        System.out.println(Person.total);
    }
}

输出

In static block
100
100

思路

  1. 首先"In static block"位于静态代码块中,在Person类加载时就已经被执行,因此第一个输出。

  2. 后面两处打印Person.total,本质上都是对静态变量的输出,值相同

第二题

package com.hspedu.sta.tic;
​
class Simple {
    Simple() {
        System.out.println("Simple的构造器被调用");
    }
​
    Simple(String s) {
        System.out.println("Simple的构造器被调用,参数是:" + s);
    }
}
​
​
public class Test {
​
    Simple simple1 = new Simple("sim1成员变量初始化");
    static Simple simple2 = new Simple("静态sim2成员变量初始化");
​
    static {
        System.out.println("静态代码块被调用");
        if (simple2 == null)
            System.out.println("simple2是null");
    };
​
    Test() {
        System.out.println("Test的构造器被调用");
    }
​
    public static void main(String[] args) {
        Test a = new Test();
    }
}

答案

Simple的构造器被调用,参数是:静态sim2成员变量初始化
静态代码块被调用
Simple的构造器被调用,参数是:sim1成员变量初始化
Test的构造器被调用

思路:

  1. 首先程序肯定得加载main所在的类,不然无法执行代码

  2. 加载main所在的类,首先要处理静态代码块和静态属性,根据顺序先处理simple2,其调用了Simple的有参构造;然后是静态代码快,显然simple2不是null了,因此输出“静态代码块被调用”(ps:如果把两个静态顺序调转一下,会发现直接报错,Cannot read value of field ‘simple2’ before the field’s definition)

  3. Test类加载完了,接下来执行main方法,首先new Test,进入Test的无参构造,在执行System.out.println(“Test的构造器被调用”);之前,先执行构造器最开始的“super”和“普通代码块的调用”(这两个也有先后顺序),因此先进行simple1的初始化,进入Simple的有参构造,输出“Simple的构造器被调用,参数是:sim1成员变量初始化”

  4. 最后才执行Test的构造器

1 个帖子 - 1 位参与者

阅读完整话题

来源: LinuxDo 最新话题查看原文