0%

Java 代码复用:继承与组合的艺术

在面向对象编程中,代码复用是提升开发效率、降低维护成本的核心手段。Java 中实现代码复用的两种主要方式是继承(Inheritance)组合(Composition),二者各有适用场景,理解其差异与原理是写出高质量代码的关键。

代码复用的三种关系

在 UML 设计中,类之间的复用关系主要分为三类,核心区别在于 “依赖强度” 和 “语义关系”:

关系类型 关键字 语义描述 示例
继承 extends is-a(是一个):子类是父类的特殊类型 汽车(Car)是一种交通工具(Vehicle)
组合 成员变量 has-a(有一个):新类包含现有类的对象 汽车(Car)有一个发动机(Engine)
依赖 方法参数 / 局部变量 uses-a(使用一个):临时使用其他类 汽车(Car)使用汽油(Gasoline)

继承(Inheritance):is-a 关系

继承是通过 extends 关键字让子类(Subclass)继承父类(Superclass)的属性和方法,从而复用父类代码。其核心是 “特殊化”—— 子类是父类的更具体版本。

继承的核心特性

  • 代码复用:子类自动拥有父类的非私有(public/protected/ 包访问)属性和方法。
  • 方法重写(Override):子类可重写父类方法,修改或扩展其行为(需满足 “两同两小一大” 规则:方法名、参数列表相同;返回值、异常范围更小;访问权限更大)。
  • 单继承限制:Java 中类仅支持单继承(一个子类只能有一个直接父类),避免 “菱形继承” 导致的歧义。

继承中的构造器调用

子类构造器必须先调用父类构造器,确保父类初始化完成,具体规则:

阅读全文 »

Kafka 深度解析:架构、核心概念与特性

Kafka 是一个高吞吐量、分布式的发布 - 订阅消息系统,基于 Scala 和 Java 开发,专为处理实时数据流设计。它以高可靠性、可扩展性和低延迟著称,广泛应用于日志收集、实时数据管道、流处理等场景。本文将从架构、核心概念、特性到应用场景,全面解析 Kafka 的工作原理。

Kafka 基本架构

Kafka 架构由生产者(Producer)消费者(Consumer)Broker 集群ZooKeeper 四部分构成,形成一个分布式消息处理系统:

kafka架构

1
2
3
[生产者] → [Kafka Broker 集群] → [消费者/消费者组]

[ZooKeeper](协调管理)
  • 生产者:向 Kafka 集群发送消息,可指定消息发送到的主题(Topic)和分区(Partition)。
  • Broker 集群:由多个 Kafka 实例(Broker)组成,负责存储消息并处理读写请求。
  • 消费者 / 消费者组:从集群拉取消息,消费者必须属于某个消费者组,通过组内分工实现负载均衡。
  • ZooKeeper:负责管理 Kafka 集群元数据(如 Broker 信息、主题配置、分区副本分配、消费者组偏移量等),并协调集群状态(如 Leader 选举、重平衡)。

核心概念详解

Broker(代理节点)

  • 定义:Kafka 集群中的每个服务器实例称为 Broker,是消息存储和处理的核心节点。
  • 标识:每个 Broker 有唯一的 broker.id(配置文件指定),确保集群内唯一。
  • 作用:接收生产者消息、存储消息到磁盘、处理消费者的拉取请求,以及参与副本同步。

Topic(主题)

  • 定义:消息的逻辑分类,所有消息必须发送到指定 Topic,消费者通过订阅 Topic 获取消息。
  • 分区机制:一个 Topic 可分为多个Partition(分区),分区是 Kafka 并行处理的基本单位:
    • 分区数越多,吞吐量越高(可并行写入 / 读取)。
    • 每个分区是一个有序、不可变的消息队列,消息按发送顺序追加到分区尾部。
    • 物理上,每个分区对应一个文件夹(命名规则:Topic名称-分区编号,如 test-0test-1)。

Partition(分区)与副本(Replica)

阅读全文 »

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)。显式构造器可以自定义默认值,避免对象处于无效状态。

阅读全文 »