代码块
代码块可以简单的理解成只有方法体的方法,在加载类或创建对象时隐式调用。
代码块不能被程序员手动调用。
java中最主要的两种代码块为:
-
静态代码块:在加载类时执行。(在代码块前加上static)
-
构造代码块:在创建对象时(构造方法执行之前)执行。(无static标记)
每个代码块都有自己执行的明确时机,不需要也不能被人类调用。
那代码块到底能做什么?为什么要设计出来这种东西?
静态代码块的必要性
有时,初始化一个静态成员需要执行一段逻辑,而非简单赋值,比如读文件等。
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代码块随着类的加载而执行,只执行一次;构造代码块,每次类初始化就执行。
类何时被加载
-
创建对象实例时
-
创建子类对象实例,父类也会被加载
-
使用类的静态成员时
构造器隐含了…
构造器其实最开始一定隐含了 super和普通代码块的调用,这就是为什么父类普通代码块和属性、父类构造,要早于子类的普通代码块、属性、子类构造
创建一个对象时,一个类内部的调用顺序
-
静态代码块和静态属性,具体顺序看定义顺序,写在前面的先执行
-
普通代码块和普通属性,具体顺序看定义顺序,写在前面的先执行
-
构造器
假如是有父类的,顺序如下
-
父类静态代码块和属性
-
子类静态代码块和属性
-
父类普通代码块和属性
-
父类构造
-
子类普通代码块和属性
-
子类构造
整个过程其实是这样
-
子类被实例化了,因此子类先要被加载,但是发现加载子类需要先知道父类信息,因此首先加载父类,此时是加载类信息,因此只处理静态的代码块和属性
-
然后是子类的加载,此时是加载类信息,因此只处理静态的代码块和属性
-
然后处理子类的实例化逻辑,首先调用构造器,构造器开始隐含了 super和普通代码块的调用,因此先调用了父类的构造器,然后父类也有super(假设父类的super执行完了)和普通代码块的调用,在进入父类构造器体之后、执行父类构造器的其他代码之前,会先执行父类的普通代码块和属性初始化,最后才执行父类构造器体中剩余的代码。
-
父类构造器结束后,子类构造器开始执行普通代码块和属性的初始化,然后执行子类的构造器
老生常谈
静态代码块只能调用静态成员,普通代码块可以调用所有成员
练习题
第一题
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
思路
-
首先"In static block"位于静态代码块中,在Person类加载时就已经被执行,因此第一个输出。
-
后面两处打印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的构造器被调用
思路:
-
首先程序肯定得加载main所在的类,不然无法执行代码
-
加载main所在的类,首先要处理静态代码块和静态属性,根据顺序先处理simple2,其调用了Simple的有参构造;然后是静态代码快,显然simple2不是null了,因此输出“静态代码块被调用”(ps:如果把两个静态顺序调转一下,会发现直接报错,Cannot read value of field ‘simple2’ before the field’s definition)
-
Test类加载完了,接下来执行main方法,首先new Test,进入Test的无参构造,在执行System.out.println(“Test的构造器被调用”);之前,先执行构造器最开始的“super”和“普通代码块的调用”(这两个也有先后顺序),因此先进行simple1的初始化,进入Simple的有参构造,输出“Simple的构造器被调用,参数是:sim1成员变量初始化”
-
最后才执行Test的构造器
1 个帖子 - 1 位参与者