0%

Java 初始化顺序详解:从类加载到对象创建

Java 中的初始化是一个涉及类加载对象实例化的复杂过程,其顺序严格遵循特定规则。理解初始化顺序对于排查程序启动问题、确保资源正确初始化至关重要。本文将从对象实例化和类加载两个维度,详细解析 Java 的初始化机制。

对象实例化时的初始化顺序

当通过 new 关键字创建对象时,初始化顺序遵循 “先父后子、先成员后构造” 的原则,具体步骤如下:

  1. 父类的非静态成员变量(按定义顺序初始化)
  2. 父类的非静态初始化块(按出现顺序执行)
  3. 父类的构造器主体(构造器内的代码)
  4. 本类的非静态成员变量(按定义顺序初始化)
  5. 本类的非静态初始化块(按出现顺序执行)
  6. 本类的构造器主体

示例验证:对象实例化顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 父类
class Parent {
// 父类非静态成员变量
private ParentMember p1 = new ParentMember(1);

// 父类非静态初始化块
{
System.out.println("Parent 非静态初始化块");
}

// 父类非静态成员变量(定义顺序靠后)
private ParentMember p2 = new ParentMember(2);

// 父类构造器
public Parent() {
System.out.println("Parent 构造器主体");
}
}

// 父类的成员类
class ParentMember {
public ParentMember(int id) {
System.out.println("ParentMember(" + id + ") 初始化");
}
}

// 子类
class Child extends Parent {
// 子类非静态成员变量
private ChildMember c1 = new ChildMember(1);

// 子类非静态初始化块
{
System.out.println("Child 非静态初始化块");
}

// 子类非静态成员变量(定义顺序靠后)
private ChildMember c2 = new ChildMember(2);

// 子类构造器
public Child() {
System.out.println("Child 构造器主体");
}
}

// 子类的成员类
class ChildMember {
public ChildMember(int id) {
System.out.println("ChildMember(" + id + ") 初始化");
}
}

// 测试
public class InitOrderTest {
public static void main(String[] args) {
new Child(); // 创建子类对象
}
}
执行结果:
1
2
3
4
5
6
7
8
ParentMember(1) 初始化    // 父类非静态成员(顺序1)
Parent 非静态初始化块 // 父类非静态初始化块(顺序2)
ParentMember(2) 初始化 // 父类非静态成员(顺序1,按定义顺序)
Parent 构造器主体 // 父类构造器(顺序3)
ChildMember(1) 初始化 // 子类非静态成员(顺序4)
Child 非静态初始化块 // 子类非静态初始化块(顺序5)
ChildMember(2) 初始化 // 子类非静态成员(顺序4,按定义顺序)
Child 构造器主体 // 子类构造器(顺序6)
结论:
  • 非静态成员变量和非静态初始化块的执行顺序由定义 / 出现顺序决定(先定义的先执行)。
  • 非静态初始化逻辑(成员变量 + 初始化块)总是在构造器主体之前执行(构造器的作用是 “收尾” 初始化)。
  • 继承场景下,父类完全初始化后才会初始化子类(确保子类使用父类成员时已就绪)。

类加载时的静态初始化顺序

阅读全文 »

Java 访问权限修饰符详解

在 Java 中,访问权限修饰符用于控制类、属性、方法的可见范围,是封装特性的核心实现手段。合理使用访问权限可以隐藏内部实现细节、保护数据安全,并降低代码耦合度。Java 提供四种访问权限,从最大到最小依次为:publicprotected包访问权限(default,无修饰符)private

包(Package):访问权限的基础

包是类的组织单位,用于避免类名冲突并划分访问边界。一个类的全名是 “包名 + 类名”(如 java.util.ArrayList)。通过 import 语句可以简化类的使用(如 import java.util.ArrayList)。

  • 同包类:位于同一包下的类,默认具有包访问权限。
  • 不同包类:位于不同包下的类,需通过 publicprotected 权限才能访问。

四种访问权限详解

1. public(公共权限)

  • 可见范围:所有类(本类、同包类、子类、非子类)均可访问。
  • 适用场景:暴露给外部的接口(如 java.util.Listadd 方法)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class PublicDemo {
public String publicField = "公共属性";

public void publicMethod() {
System.out.println("公共方法");
}
}

// 任意类均可访问
public class OtherClass {
public static void main(String[] args) {
PublicDemo demo = new PublicDemo();
System.out.println(demo.publicField); // 合法
demo.publicMethod(); // 合法
}
}

2. protected(保护权限)

  • 可见范围:本类、同包类、子类(无论是否同包)可访问,非子类不可访问。
  • 适用场景:仅允许子类继承或同包类协作的成员(如模板方法中的辅助方法)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.example.parent;
public class ProtectedDemo {
protected String protectedField = "保护属性";

protected void protectedMethod() {
System.out.println("保护方法");
}
}

// 同包类:可访问
package com.example.parent;
public class SamePackageClass {
public static void main(String[] args) {
ProtectedDemo demo = new ProtectedDemo();
System.out.println(demo.protectedField); // 合法
}
}

// 不同包子类:可访问
package com.example.child;
import com.example.parent.ProtectedDemo;
public class SubClass extends ProtectedDemo {
public void test() {
System.out.println(protectedField); // 合法(继承自父类)
protectedMethod(); // 合法
}
}

// 不同包非子类:不可访问
package com.example.other;
import com.example.parent.ProtectedDemo;
public class OtherClass {
public static void main(String[] args) {
ProtectedDemo demo = new ProtectedDemo();
// System.out.println(demo.protectedField); // 编译报错
}
}

3. 包访问权限(default,无修饰符)

  • 可见范围:仅本类和同包类可访问,子类(不同包)和非子类不可访问。
  • 适用场景:包内部的辅助类或成员,不对外暴露(如工具类的内部方法)。
阅读全文 »

Java 构造器(Constructor)详解:对象初始化的核心

构造器是 Java 类中用于创建和初始化对象的特殊方法,是面向对象编程中实例化对象的核心机制。即使在类中未显式定义构造器,编译器也会自动生成默认构造器,确保对象能够被正确创建。本文将深入解析构造器的特性、用法及重要性。

构造器的基本特性

  1. 名称与类名一致:构造器的方法名必须和所在类的名称完全相同(包括大小写)。
  2. 无返回值:与普通方法不同,构造器没有返回值类型(甚至不能写 void)。
  3. 自动调用:构造器通过 new 关键字调用(如 new Person()),在对象创建时自动执行,用于初始化对象状态。
  4. 默认构造器:若类中未显式定义任何构造器,编译器会自动生成一个无参构造器(无参数、无方法体)。

示例:基本构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Person {
private String name;
private int age;

// 显式定义无参构造器
public Person() {
// 初始化逻辑(如默认值)
this.name = "未知";
this.age = 0;
}

// 显式定义有参构造器
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}

// 使用构造器创建对象
Person p1 = new Person(); // 调用无参构造器
Person p2 = new Person("张三", 20); // 调用有参构造器

构造器的核心作用:对象初始化

构造器的核心职责是在对象创建时初始化其状态(即给属性赋值),确保对象在使用前处于合法状态。

初始化属性默认值

即使没有显式赋值,构造器也会隐式初始化属性(如 int 默认为 0,String 默认为 null)。显式构造器可以自定义默认值,避免对象处于无效状态。

阅读全文 »

面向对象编程三大特性:封装、继承、多态

面向对象编程(OOP)是一种以 “对象” 为核心的编程范式,其三大核心特性 ——封装继承多态—— 是构建灵活、可维护代码的基础。本文将从概念、实现、作用及示例等方面,详细解析这三大特性。

封装(Encapsulation)

封装是指将对象的状态(属性)和行为(方法)捆绑在一起,并通过访问控制隐藏内部实现细节,仅暴露必要的接口供外部交互。其核心思想是 “信息隐藏”,目的是保护数据完整性、降低代码耦合度。

封装的实现方式

  • 访问修饰符:通过private、protected、public和默认(包访问)修饰符控制属性和方法的可见性。
    • private:仅当前类可见(隐藏内部状态的核心);
    • protected:当前类、子类及同包类可见;
    • public:所有类可见(暴露的接口);
    • 默认(无修饰符):仅同包类可见。
  • getter/setter 方法:对私有属性(private)提供公共的 get(读取)和 set(修改)方法,在方法中可添加校验逻辑,确保数据合法。

示例:封装一个学生类

阅读全文 »

MyBatis 核心组件:SqlSession 全解析(创建、实现、线程安全与四大执行对象)

SqlSession 是 MyBatis 中连接开发者与底层数据库操作的核心接口,封装了数据库 CRUD、事务管理、缓存控制等核心能力。本文基于 SqlSession 接口定义、DefaultSqlSession 实现及 SqlSessionManager 源码,从 “接口定位→创建流程→实现逻辑→线程安全→底层协作” 五个维度,彻底拆解 SqlSession 的工作机制,同时关联 Executor 等四大核心对象,帮你掌握 MyBatis 会话管理的本质。

SqlSession 接口:MyBatis 会话的 “入口协议”

SqlSession 相当于 JDBC 中的 Connection,但功能更丰富 —— 不仅是数据库连接的封装,还提供了 Mapper 接口绑定、缓存管理、事务控制等能力。其接口定义按功能可分为 5 大类方法,覆盖所有数据库交互场景。

1. 核心方法分类与解析

(1)查询方法:select* 系列

查询方法是 SqlSession 最常用的功能,支持单结果、多结果、Map 结果、游标(Cursor)等多种返回形式,核心差异在于结果处理方式:

方法签名 功能说明 适用场景
<T> T selectOne(String statement) 执行 SQL,返回单个结果(无参数) 无参单结果查询(如 select count(*)
<T> T selectOne(String statement, Object param) 执行 SQL,传入参数,返回单个结果 有参单结果查询(如 selectById(1)
<E> List<E> selectList(String statement) 执行 SQL,返回结果列表(无参数) 无参多结果查询(如 selectAll()
<E> List<E> selectList(..., RowBounds rb) 传入 RowBounds(起始索引 + 条数),实现逻辑分页 逻辑分页查询(不推荐生产,见前文)
<K,V> Map<K,V> selectMap(..., String mapKey) 将结果列表转为 Map,mapKey 为 Map 的键(需是结果中的某个属性) 需快速通过某个属性查找结果(如按 id 查 User)
<T> Cursor<T> selectCursor(...) 返回游标对象,支持流式读取(避免一次性加载大量数据到内存) 大数据量查询(如导出 10 万条数据)
void select(..., ResultHandler handler) 无返回值,结果由自定义 ResultHandler 处理(灵活控制结果封装) 特殊结果处理(如直接写入文件)

关键细节

  • selectOne() 本质是调用 selectList() 后取第一个元素,若结果集 size > 1 会抛出 TooManyResultsException
  • RowBounds 实现逻辑分页(先查全表再截取),生产环境需用物理分页(如 PageHelper);
  • Cursor 是 MyBatis 3.4+ 新增功能,基于 JDBC 的 ResultSet 流式读取,适合大数据量场景,避免 OOM。
(2)修改方法:insert/update/delete

MyBatis 将 insert、update、delete 统一归为 “修改操作”,底层均委托给 Executor.update() 方法,返回值为受影响的行数

阅读全文 »