Skip to main content

Java 面向对象编程详解

面向对象编程(Object-Oriented Programming,OOP)是Java语言的核心特性,它提供了一种组织和管理代码的方法,通过将数据和操作数据的方法封装在对象中来实现。

核心价值

OOP = 封装 + 继承 + 多态 + 抽象

  • 🔐 封装:隐藏实现细节,保护数据完整性,提供受控访问
  • 🌳 继承:实现代码重用,建立类型层次结构,促进一致性设计
  • 🔄 多态:增强灵活性,实现动态绑定,支持扩展性设计
  • 🧩 抽象:专注核心特性,简化复杂系统,提升代码可维护性
  • 🏗️ 组件化:构建模块化系统,促进团队协作,提高开发效率
本文内容概览

1. 面向对象编程基础

1.1 什么是面向对象编程

面向对象编程是一种编程范式,它将现实世界中的事物抽象为对象,每个对象都有自己的属性和行为。Java完全支持面向对象编程,提供了丰富的语法特性来实现OOP概念。

核心概念

概念说明特点
封装(Encapsulation)将数据和方法绑定在一起,隐藏内部实现细节数据隐藏、访问控制
继承(Inheritance)子类可以继承父类的属性和方法代码重用、层次结构
多态(Polymorphism)同一个接口可以有多种不同的实现方法重载、方法重写
抽象(Abstraction)提取共同特征,隐藏复杂细节接口定义、抽象类

2. 类和对象基础

2.1 类的定义

类是对象的模板,定义了对象的属性和行为。下面是一个标准Java类的结构:

Person类结构
java
1public class Person {
2 // 实例变量(属性)
3 private String name;
4 private int age;
5 private String email;
6
7 // 类变量(静态变量)
8 public static final String SPECIES = "Homo sapiens";
9 private static int totalCount = 0;
10
11 // 构造方法
12 public Person() {
13 this("Unknown", 0, "");
14 }
15
16 public Person(String name, int age, String email) {
17 this.name = name;
18 this.age = age;
19 this.email = email;
20 totalCount++; // 增加总人数
21 }
22
23 // 实例方法
24 public void introduce() {
25 System.out.println("我叫 " + name + ",今年 " + age + " 岁");
26 }
27
28 public void haveBirthday() {
29 age++;
30 System.out.println("生日快乐!现在 " + name + " 已经 " + age + " 岁了");
31 }
32
33 // 静态方法
34 public static int getTotalCount() {
35 return totalCount;
36 }
37
38 // Getter和Setter方法
39 public String getName() {
40 return name;
41 }
42
43 public void setName(String name) {
44 this.name = name;
45 }
46
47 public int getAge() {
48 return age;
49 }
50
51 public void setAge(int age) {
52 if (age >= 0 && age <= 150) {
53 this.age = age;
54 } else {
55 System.err.println("年龄必须在0-150之间");
56 }
57 }
58
59 public String getEmail() {
60 return email;
61 }
62
63 public void setEmail(String email) {
64 this.email = email;
65 }
66}

2.2 对象的创建和使用

对象是类的实例,通过 new 关键字创建。下面展示了对象的创建和使用过程:

对象创建和使用示例
java
1public class ObjectDemo {
2 public static void main(String[] args) {
3 // 创建对象
4 Person person1 = new Person("张三", 25, "zhangsan@example.com");
5 Person person2 = new Person("李四", 30, "lisi@example.com");
6
7 // 调用实例方法
8 person1.introduce();
9 person2.introduce();
10
11 // 调用静态方法
12 System.out.println("总人数: " + Person.getTotalCount());
13
14 // 修改对象状态
15 person1.haveBirthday();
16 person1.setAge(26);
17
18 // 访问静态常量
19 System.out.println("人类学名: " + Person.SPECIES);
20 }
21}
对象的生命周期
  1. 创建:通过new关键字调用构造器创建对象
  2. 使用:通过引用变量访问对象的属性和方法
  3. 不可达:当没有任何引用指向对象时
  4. 垃圾回收:JVM的垃圾回收器回收不可达对象

2.3 构造方法详解

构造方法是创建对象时自动调用的特殊方法,负责初始化对象。

构造方法特点
  • 与类同名
  • 没有返回值(连void都不写)
  • 可以重载(提供多个不同参数列表的构造方法)
  • 如果不定义构造方法,Java会提供默认的无参构造方法
  • 如果定义了构造方法,Java不会再提供默认构造方法
基本构造方法示例
java
1public class User {
2 private String username;
3 private String email;
4
5 // 默认构造方法
6 public User() {
7 username = "guest";
8 email = "guest@example.com";
9 }
10
11 // 带参数的构造方法
12 public User(String username, String email) {
13 this.username = username;
14 this.email = email;
15 }
16}

2.4 访问修饰符

Java提供了四种访问修饰符来控制类成员的可见性:

访问修饰符示例
java
1public class AccessModifierDemo {
2 public static void main(String[] args) {
3 AccessExample obj = new AccessExample();
4
5 // 可以访问public成员
6 obj.publicField = "公共字段";
7 obj.publicMethod();
8
9 // 可以访问protected成员(同包内)
10 obj.protectedField = "受保护字段";
11 obj.protectedMethod();
12
13 // 可以访问默认访问权限成员(同包内)
14 obj.defaultField = "默认字段";
15 obj.defaultMethod();
16
17 // 不能直接访问private成员
18 // obj.privateField = "私有字段"; // 编译错误
19 // obj.privateMethod(); // 编译错误
20
21 // 通过公共方法访问私有成员
22 obj.setPrivateField("通过公共方法设置私有字段");
23 System.out.println("私有字段值: " + obj.getPrivateField());
24 }
25}
26
27class AccessExample {
28 // public: 任何地方都可以访问
29 public String publicField;
30 public void publicMethod() {
31 System.out.println("公共方法");
32 }
33
34 // protected: 同包内和子类可以访问
35 protected String protectedField;
36 protected void protectedMethod() {
37 System.out.println("受保护方法");
38 }
39
40 // 默认(无修饰符): 同包内可以访问
41 String defaultField;
42 void defaultMethod() {
43 System.out.println("默认方法");
44 }
45
46 // private: 只有本类内部可以访问
47 private String privateField;
48 private void privateMethod() {
49 System.out.println("私有方法");
50 }
51
52 // 公共方法访问私有字段
53 public void setPrivateField(String value) {
54 this.privateField = value;
55 }
56
57 public String getPrivateField() {
58 return privateField;
59 }
60}
注意事项
  1. 尽量使用私有字段+公共方法的封装模式
  2. 只将必要的方法和字段设为公开
  3. protected修饰符要慎用,它会破坏封装性
  4. 内部类应当设为private或protected

3. 封装(Encapsulation)

封装是面向对象编程的核心概念之一,它将数据和方法绑定在一起,隐藏内部实现细节,只提供必要的接口。

3.1 封装的原则

封装的核心原则
  1. 信息隐藏:隐藏内部实现细节
  2. 访问控制:通过访问修饰符限制对类成员的访问
  3. 数据保护:防止对象数据被外部直接修改
  4. 接口抽象:提供简单清晰的公共接口
  5. 数据验证:通过setter方法验证数据有效性
封装示例
java
1public class BankAccount {
2 // 私有字段 - 隐藏实现细节
3 private String accountNumber;
4 private double balance;
5 private String ownerName;
6 private static final double MIN_BALANCE = 0.0;
7
8 // 构造方法 - 控制对象初始化
9 public BankAccount(String accountNumber, String ownerName, double initialBalance) {
10 this.accountNumber = accountNumber;
11 this.ownerName = ownerName;
12 // 通过setter方法设置余额,确保数据验证
13 setBalance(initialBalance);
14 }
15
16 // 公共接口方法 - 只暴露必要的功能
17 public boolean deposit(double amount) {
18 if (amount > 0) {
19 balance += amount;
20 System.out.println("存款成功: +" + amount + ",当前余额: " + balance);
21 return true;
22 } else {
23 System.err.println("存款金额必须大于0");
24 return false;
25 }
26 }
27
28 public boolean withdraw(double amount) {
29 if (amount > 0 && (balance - amount) >= MIN_BALANCE) {
30 balance -= amount;
31 System.out.println("取款成功: -" + amount + ",当前余额: " + balance);
32 return true;
33 } else {
34 System.err.println("取款失败: 余额不足或金额无效");
35 return false;
36 }
37 }
38
39 // 只读访问器 - 允许读取但不修改
40 public String getAccountNumber() {
41 return accountNumber;
42 }
43
44 public double getBalance() {
45 return balance;
46 }
47
48 public String getOwnerName() {
49 return ownerName;
50 }
51
52 // 受控的setter方法 - 数据验证
53 private void setBalance(double balance) {
54 if (balance >= MIN_BALANCE) {
55 this.balance = balance;
56 } else {
57 this.balance = MIN_BALANCE;
58 System.err.println("余额不能为负数,已设置为0");
59 }
60 }
61
62 // 业务逻辑方法
63 public void transfer(BankAccount target, double amount) {
64 if (this.withdraw(amount)) {
65 target.deposit(amount);
66 System.out.println("转账成功: " + amount + " 从 " + accountNumber + " 到 " + target.accountNumber);
67 } else {
68 System.err.println("转账失败");
69 }
70 }
71
72 // 账户信息展示
73 public void displayInfo() {
74 System.out.println("账户信息:");
75 System.out.println(" 账号: " + accountNumber);
76 System.out.println(" 户名: " + ownerName);
77 System.out.println(" 余额: " + balance);
78 }
79}

3.2 封装的测试

封装测试示例
java
1public class EncapsulationDemo {
2 public static void main(String[] args) {
3 // 创建银行账户
4 BankAccount account1 = new BankAccount("001", "张三", 1000.0);
5 BankAccount account2 = new BankAccount("002", "李四", 500.0);
6
7 // 显示初始信息
8 account1.displayInfo();
9 account2.displayInfo();
10
11 // 进行各种操作
12 account1.deposit(500.0); // 存款
13 account1.withdraw(200.0); // 取款
14 account1.transfer(account2, 300.0); // 转账
15
16 // 尝试非法操作
17 account1.deposit(-100.0); // 负数存款
18 account1.withdraw(2000.0); // 超额取款
19
20 // 显示最终信息
21 System.out.println("\n最终账户状态:");
22 account1.displayInfo();
23 account2.displayInfo();
24
25 // 注意:无法直接访问私有字段
26 // System.out.println(account1.balance); // 编译错误
27 // account1.balance = -1000; // 编译错误
28 }
29}
封装的实际应用

封装在实际开发中随处可见,例如:

  1. 数据库连接类:隐藏连接细节,提供简单的CRUD方法
  2. 配置管理类:内部处理配置加载逻辑,提供简单的配置访问接口
  3. UI组件:内部处理渲染逻辑,对外提供简单的事件和属性接口

4. 继承(Inheritance)

继承允许子类继承父类的属性和方法,实现代码重用和层次结构。

4.1 基本继承

继承的关键点
  • 使用extends关键字表示继承关系
  • Java中一个类只能继承一个父类(单继承)
  • 子类可以访问父类的非私有(public、protected、默认)成员
  • 子类可以通过super关键字访问父类的方法和构造方法
  • 子类可以覆盖(重写)父类的方法
  • 所有类都隐式继承自Object
继承的语法
java
1// 父类
2public class Animal {
3 protected String name;
4 protected int age;
5 protected String species;
6
7 public Animal(String name, int age, String species) {
8 this.name = name;
9 this.age = age;
10 this.species = species;
11 }
12
13 public void eat() {
14 System.out.println(name + " 正在吃东西");
15 }
16
17 public void sleep() {
18 System.out.println(name + " 正在睡觉");
19 }
20
21 public void makeSound() {
22 System.out.println(name + " 发出声音");
23 }
24}
25
26// 子类
27public class Dog extends Animal {
28 private String breed;
29
30 public Dog(String name, int age, String breed) {
31 // 调用父类构造方法
32 super(name, age, "犬科");
33 this.breed = breed;
34 }
35
36 // 重写父类方法
37 @Override
38 public void makeSound() {
39 System.out.println(name + " 汪汪叫");
40 }
41
42 // 添加子类特有方法
43 public void fetch() {
44 System.out.println(name + " 在捡球");
45 }
46}

4.2 继承的测试

继承测试示例
java
1public class InheritanceDemo {
2 public static void main(String[] args) {
3 // 创建动物对象
4 Animal animal = new Animal("未知动物", 5, "未知");
5 animal.eat();
6 animal.sleep();
7 animal.makeSound();
8 animal.displayInfo();
9
10 System.out.println();
11
12 // 创建狗对象
13 Dog dog = new Dog("旺财", 3, "金毛", true);
14 dog.eat(); // 继承自父类
15 dog.sleep(); // 继承自父类
16 dog.makeSound(); // 重写的父类方法
17 dog.wagTail(); // 子类特有方法
18 dog.fetch(); // 子类特有方法
19 dog.displayInfo(); // 重写的父类方法
20
21 System.out.println();
22
23 // 创建猫对象
24 Cat cat = new Cat("咪咪", 2, "橘色", false);
25 cat.eat(); // 继承自父类
26 cat.sleep(); // 继承自父类
27 cat.makeSound(); // 重写的父类方法
28 cat.purr(); // 子类特有方法
29 cat.climb(); // 子类特有方法
30 cat.displayInfo(); // 重写的父类方法
31
32 System.out.println();
33
34 // 多态:父类引用指向子类对象
35 Animal[] animals = {dog, cat};
36 for (Animal a : animals) {
37 System.out.println("处理动物: " + a.name);
38 a.makeSound(); // 调用重写的方法
39 a.eat(); // 调用继承的方法
40 }
41 }
42}

4.3 方法重写(Override)

方法重写允许子类提供父类方法的特定实现:

方法重写规则

  • 方法名必须相同
  • 参数列表必须相同
  • 返回类型必须相同或是子类型
  • 访问修饰符不能更严格(可以更宽松)
  • 不能抛出更广泛的检查异常
  • 静态方法不能被重写(会被隐藏)
  • 私有方法不能被重写
  • final方法不能被重写

@Override注解

推荐在重写方法时使用@Override注解,它可以:

  • 帮助编译器验证重写是否正确
  • 防止意外定义新方法
  • 提高代码可读性和可维护性
  • 防止父类方法签名变化带来的错误
方法重写示例
java
1public class MethodOverrideDemo {
2 public static void main(String[] args) {
3 // 创建不同形状
4 Shape circle = new Circle(5.0);
5 Shape rectangle = new Rectangle(4.0, 6.0);
6 Shape triangle = new Triangle(3.0, 4.0, 5.0);
7
8 // 多态调用
9 Shape[] shapes = {circle, rectangle, triangle};
10 for (Shape shape : shapes) {
11 System.out.println("形状: " + shape.getClass().getSimpleName());
12 System.out.println(" 面积: " + shape.calculateArea());
13 System.out.println(" 周长: " + shape.calculatePerimeter());
14 shape.displayInfo();
15 System.out.println();
16 }
17 }
18}
19
20// 抽象父类
21abstract class Shape {
22 protected String name;
23
24 public Shape(String name) {
25 this.name = name;
26 }
27
28 // 抽象方法,子类必须实现
29 public abstract double calculateArea();
30 public abstract double calculatePerimeter();
31
32 // 具体方法,子类可以继承
33 public void displayInfo() {
34 System.out.println(" 名称: " + name);
35 System.out.println(" 面积: " + calculateArea());
36 System.out.println(" 周长: " + calculatePerimeter());
37 }
38}
39
40// 圆形
41class Circle extends Shape {
42 private double radius;
43
44 public Circle(double radius) {
45 super("圆形");
46 this.radius = radius;
47 }
48
49 @Override
50 public double calculateArea() {
51 return Math.PI * radius * radius;
52 }
53
54 @Override
55 public double calculatePerimeter() {
56 return 2 * Math.PI * radius;
57 }
58}
59
60// 矩形
61class Rectangle extends Shape {
62 private double width;
63 private double height;
64
65 public Rectangle(double width, double height) {
66 super("矩形");
67 this.width = width;
68 this.height = height;
69 }
70
71 @Override
72 public double calculateArea() {
73 return width * height;
74 }
75
76 @Override
77 public double calculatePerimeter() {
78 return 2 * (width + height);
79 }
80}
81
82// 三角形
83class Triangle extends Shape {
84 private double side1, side2, side3;
85
86 public Triangle(double side1, double side2, double side3) {
87 super("三角形");
88 this.side1 = side1;
89 this.side2 = side2;
90 this.side3 = side3;
91 }
92
93 @Override
94 public double calculateArea() {
95 // 海伦公式
96 double s = (side1 + side2 + side3) / 2;
97 return Math.sqrt(s * (s - side1) * (s - side2) * (s - side3));
98 }
99
100 @Override
101 public double calculatePerimeter() {
102 return side1 + side2 + side3;
103 }
104}
继承的局限性
  1. 打破封装:子类可能依赖父类的实现细节
  2. 紧耦合:父类变更可能影响所有子类
  3. 多层继承难以维护:层次过深导致复杂度增加
  4. 单一继承限制:Java只允许单一继承

解决方案:

  • 考虑使用组合代替继承
  • 控制继承深度(不超过3层)
  • 多用接口,少用类继承
  • 谨慎设计父类,保证稳定性

5. 多态(Polymorphism)

多态是面向对象编程的重要特性,它允许同一个接口有多种不同的实现。

多态的本质

多态的核心思想是"一个接口,多种实现"。在Java中,多态主要通过继承和接口实现。多态使得代码更具灵活性、可扩展性和可维护性。

多态的关键机制

  • 继承或接口实现
  • 方法的重写(覆盖)
  • 向上转型(父类引用指向子类对象)
  • 动态绑定(运行时决定调用哪个方法)

多态可分为两种主要类型:

  1. 编译时多态(静态多态):通过方法重载实现
  2. 运行时多态(动态多态):通过方法重写和继承实现

5.1 编译时多态(方法重载)

方法重载是在同一个类中定义多个名称相同但参数列表不同的方法:

方法重载示例
java
1public class MethodOverloadingDemo {
2 public static void main(String[] args) {
3 Calculator calc = new Calculator();
4
5 // 调用不同的重载方法
6 System.out.println("整数加法: " + calc.add(5, 3));
7 System.out.println("浮点数加法: " + calc.add(5.5, 3.2));
8 System.out.println("三个整数加法: " + calc.add(1, 2, 3));
9 System.out.println("字符串连接: " + calc.add("Hello", "World"));
10
11 // 自动类型转换
12 System.out.println("混合类型: " + calc.add(5, 3.5));
13 System.out.println("字符加法: " + calc.add('A', 1));
14 }
15}
16
17class Calculator {
18 // 整数加法
19 public int add(int a, int b) {
20 System.out.println("调用 int add(int, int)");
21 return a + b;
22 }
23
24 // 浮点数加法
25 public double add(double a, double b) {
26 System.out.println("调用 double add(double, double)");
27 return a + b;
28 }
29
30 // 三个整数加法
31 public int add(int a, int b, int c) {
32 System.out.println("调用 int add(int, int, int)");
33 return a + b + c;
34 }
35
36 // 字符串连接
37 public String add(String a, String b) {
38 System.out.println("调用 String add(String, String)");
39 return a + b;
40 }
41
42 // 混合类型(自动类型转换)
43 public double add(int a, double b) {
44 System.out.println("调用 double add(int, double)");
45 return a + b;
46 }
47
48 // 字符加法
49 public int add(char a, int b) {
50 System.out.println("调用 int add(char, int)");
51 return a + b;
52 }
53}

方法重载解析规则

编译器按照以下顺序解析重载方法:

  1. 精确匹配:寻找与参数类型完全匹配的方法
  2. 提升转换:原始类型按照以下顺序提升:

    • byte → short → int → long → float → double
    • char → int → long → float → double
  3. 自动装箱/拆箱:原始类型和对应包装类型之间的转换
  4. 可变参数:如果前面都没有匹配,尝试使用可变参数方法

5.2 运行时多态(方法重写)

运行时多态通过继承和方法重写实现,以下是示例:

运行时多态示例
java
1public class RuntimePolymorphismDemo {
2 public static void main(String[] args) {
3 // 父类引用指向子类对象
4 Animal myDog = new Dog("旺财", 3, "金毛", true);
5 Animal myCat = new Cat("咪咪", 2, "橘色", false);
6
7 // 运行时多态:调用的是子类重写的方法
8 System.out.println("狗的声音:");
9 myDog.makeSound(); // 调用Dog类的makeSound方法
10
11 System.out.println("猫的声音:");
12 myCat.makeSound(); // 调用Cat类的makeSound方法
13
14 // 多态数组
15 Animal[] animals = {
16 new Dog("小黑", 2, "拉布拉多", true),
17 new Cat("小花", 1, "白色", true),
18 new Dog("大黄", 4, "柴犬", false)
19 };
20
21 System.out.println("\n动物声音合唱:");
22 for (Animal animal : animals) {
23 animal.makeSound(); // 根据实际对象类型调用相应方法
24 }
25
26 // 多态在方法参数中的应用
27 AnimalTrainer trainer = new AnimalTrainer();
28 trainer.train(myDog);
29 trainer.train(myCat);
30 }
31}
32
33class AnimalTrainer {
34 public void train(Animal animal) {
35 System.out.println("训练动物: " + animal.name);
36 animal.makeSound(); // 多态调用
37
38 // 类型检查和转换
39 if (animal instanceof Dog) {
40 Dog dog = (Dog) animal;
41 dog.fetch();
42 } else if (animal instanceof Cat) {
43 Cat cat = (Cat) animal;
44 cat.climb();
45 }
46 }
47}
运行时多态要点
  1. 必要条件:继承、重写、向上转型
  2. 方法调用:根据对象的实际类型,而非引用类型
  3. 动态绑定:JVM在运行时决定调用的方法
  4. 静态方法:不参与多态(静态绑定)
  5. 私有方法:不参与多态(不能被重写)
  6. final方法:不参与多态(不能被重写)

5.3 多态的实际应用

代码灵活性

多态允许我们编写更通用的代码,处理不同类型的对象而无需知道其具体类型。这提高了代码的灵活性和可扩展性。

通过多态,我们可以传递任何子类对象到需要父类类型的地方,而无需修改代码。

可扩展性

多态支持"开闭原则":对扩展开放,对修改关闭。

可以添加新的子类实现,而无需修改使用这些类的代码。新的子类将自动与现有系统集成。

6. 抽象类(Abstract Class)

抽象类是不能被实例化的类,通常包含抽象方法和具体方法。

6.1 抽象类基础

抽象类的特点
  1. 使用abstract关键字声明
  2. 不能被实例化,只能被继承
  3. 可以包含抽象方法和具体方法
  4. 抽象方法没有方法体,子类必须实现
  5. 可以有构造方法,供子类调用
  6. 可以有成员变量和静态方法
抽象类定义示例
java
1// 抽象类定义
2public abstract class Shape {
3 // 成员变量
4 protected String name;
5 protected String color;
6
7 // 构造方法
8 public Shape(String name, String color) {
9 this.name = name;
10 this.color = color;
11 }
12
13 // 抽象方法 - 没有方法体
14 public abstract double calculateArea();
15 public abstract double calculatePerimeter();
16
17 // 具体方法 - 有完整实现
18 public void displayInfo() {
19 System.out.println("形状名称: " + name);
20 System.out.println("形状颜色: " + color);
21 System.out.println("面积: " + calculateArea());
22 System.out.println("周长: " + calculatePerimeter());
23 }
24
25 // Getter和Setter
26 public String getName() {
27 return name;
28 }
29
30 public void setName(String name) {
31 this.name = name;
32 }
33
34 public String getColor() {
35 return color;
36 }
37
38 public void setColor(String color) {
39 this.color = color;
40 }
41}
42
43// 具体子类
44public class Circle extends Shape {
45 private double radius;
46
47 public Circle(String color, double radius) {
48 super("圆形", color);
49 this.radius = radius;
50 }
51
52 // 实现抽象方法
53 @Override
54 public double calculateArea() {
55 return Math.PI * radius * radius;
56 }
57
58 @Override
59 public double calculatePerimeter() {
60 return 2 * Math.PI * radius;
61 }
62
63 // 子类特有方法
64 public double getRadius() {
65 return radius;
66 }
67
68 public void setRadius(double radius) {
69 this.radius = radius;
70 }
71}

6.2 抽象类示例

抽象类完整示例
java
1public class AbstractClassDemo {
2 public static void main(String[] args) {
3 // 不能直接创建抽象类对象
4 // Vehicle vehicle = new Vehicle(); // 编译错误
5
6 // 创建具体子类对象
7 Vehicle car = new Car("红色", "汽油");
8 Vehicle bicycle = new Bicycle("蓝色", "人力");
9 Vehicle motorcycle = new Motorcycle("黑色", "汽油");
10
11 // 多态调用
12 Vehicle[] vehicles = {car, bicycle, motorcycle};
13 for (Vehicle vehicle : vehicles) {
14 vehicle.start();
15 vehicle.stop();
16 vehicle.displayInfo();
17 System.out.println();
18 }
19 }
20}
21
22// 抽象类:交通工具
23abstract class Vehicle {
24 protected String color;
25 protected String powerSource;
26
27 public Vehicle(String color, String powerSource) {
28 this.color = color;
29 this.powerSource = powerSource;
30 }
31
32 // 抽象方法:子类必须实现
33 public abstract void start();
34 public abstract void stop();
35
36 // 具体方法:子类可以继承
37 public void displayInfo() {
38 System.out.println("交通工具信息:");
39 System.out.println(" 类型: " + this.getClass().getSimpleName());
40 System.out.println(" 颜色: " + color);
41 System.out.println(" 动力源: " + powerSource);
42 }
43
44 // 具体方法:子类可以重写
45 public void honk() {
46 System.out.println("按喇叭");
47 }
48
49 // 静态方法
50 public static void showVehicleCount() {
51 System.out.println("交通工具总数: 3");
52 }
53}
54
55// 具体子类:汽车
56class Car extends Vehicle {
57 private int doors;
58
59 public Car(String color, String powerSource) {
60 super(color, powerSource);
61 this.doors = 4;
62 }
63
64 @Override
65 public void start() {
66 System.out.println("汽车启动,发动机轰鸣");
67 }
68
69 @Override
70 public void stop() {
71 System.out.println("汽车停止,刹车灯亮起");
72 }
73
74 @Override
75 public void honk() {
76 System.out.println("汽车喇叭: 滴滴");
77 }
78
79 @Override
80 public void displayInfo() {
81 super.displayInfo();
82 System.out.println(" 车门数: " + doors);
83 }
84}
85
86// 具体子类:自行车
87class Bicycle extends Vehicle {
88 private int wheels;
89
90 public Bicycle(String color, String powerSource) {
91 super(color, powerSource);
92 this.wheels = 2;
93 }
94
95 @Override
96 public void start() {
97 System.out.println("自行车开始骑行");
98 }
99
100 @Override
101 public void stop() {
102 System.out.println("自行车停止,脚踩地");
103 }
104
105 @Override
106 public void displayInfo() {
107 super.displayInfo();
108 System.out.println(" 轮子数: " + wheels);
109 }
110}
111
112// 具体子类:摩托车
113class Motorcycle extends Vehicle {
114 private boolean hasSidecar;
115
116 public Motorcycle(String color, String powerSource) {
117 super(color, powerSource);
118 this.hasSidecar = false;
119 }
120
121 @Override
122 public void start() {
123 System.out.println("摩托车启动,引擎咆哮");
124 }
125
126 @Override
127 public void stop() {
128 System.out.println("摩托车停止,侧支架放下");
129 }
130
131 @Override
132 public void displayInfo() {
133 super.displayInfo();
134 System.out.println(" 侧斗: " + (hasSidecar ? "有" : "无"));
135 }
136}
抽象类设计注意事项
  1. 避免过度抽象:只提取真正通用的功能到抽象类
  2. 层次适中:控制继承层次,避免过深的继承结构
  3. 明确职责:每个抽象类应当有明确的单一职责
  4. 设计稳定:抽象类应该是稳定的,避免频繁修改
  5. 考虑替代方案:某些场景下接口+默认方法可能更合适

6.3 抽象类与接口对比

抽象类

  • 使用 abstract class 关键字
  • 可以包含抽象方法和具体方法
  • 可以有构造方法、成员变量
  • 可以有 private、protected 成员
  • 一个类只能继承一个抽象类
  • 适用于关系紧密的类
  • 强调"是什么"(is-a关系)
  • 提供部分实现和共同行为

接口

  • 使用 interface 关键字
  • 主要包含抽象方法(Java 8+支持默认方法)
  • 不能有构造方法,只能有常量
  • 所有成员默认 public
  • 一个类可以实现多个接口
  • 适用于不相关类需要共同行为
  • 强调"能做什么"(can-do关系)
  • 提供行为契约
抽象类与接口对比示例
java
1// 抽象类示例
2abstract class Animal {
3 // 成员变量
4 protected String name;
5
6 // 构造方法
7 public Animal(String name) {
8 this.name = name;
9 }
10
11 // 抽象方法
12 public abstract void makeSound();
13
14 // 具体方法
15 public void sleep() {
16 System.out.println(name + " 正在睡觉");
17 }
18
19 // protected方法
20 protected void breathe() {
21 System.out.println(name + " 正在呼吸");
22 }
23}
24
25// 接口示例
26interface Swimmer {
27 // 常量
28 int MAX_DEPTH = 100; // 隐式 public static final
29
30 // 抽象方法
31 void swim(); // 隐式 public abstract
32
33 // 默认方法 (Java 8+)
34 default void dive() {
35 System.out.println("潜水中...");
36 }
37
38 // 静态方法 (Java 8+)
39 static boolean canSwimInDeepWater(int depth) {
40 return depth <= MAX_DEPTH;
41 }
42}
43
44// 既继承抽象类又实现接口
45class Fish extends Animal implements Swimmer {
46 public Fish(String name) {
47 super(name);
48 }
49
50 // 实现抽象类的抽象方法
51 @Override
52 public void makeSound() {
53 System.out.println(name + " 吐泡泡");
54 }
55
56 // 实现接口的抽象方法
57 @Override
58 public void swim() {
59 System.out.println(name + " 在水中游动");
60 }
61}

7. 接口(Interface)

接口定义了类必须实现的方法契约,支持多重继承。

7.1 基本接口

基本接口示例
java
1public class InterfaceDemo {
2 public static void main(String[] args) {
3 // 创建实现类对象
4 MediaPlayer mp3Player = new MP3Player();
5 MediaPlayer videoPlayer = new VideoPlayer();
6 MediaPlayer radio = new Radio();
7
8 // 多态调用
9 MediaPlayer[] players = {mp3Player, videoPlayer, radio};
10 for (MediaPlayer player : players) {
11 player.play();
12 player.pause();
13 player.stop();
14 player.displayInfo();
15 System.out.println();
16 }
17
18 // 接口作为方法参数
19 MediaController controller = new MediaController();
20 controller.control(mp3Player);
21 controller.control(videoPlayer);
22 }
23}
24
25// 媒体播放器接口
26interface MediaPlayer {
27 // 常量(默认public static final)
28 String VERSION = "1.0";
29
30 // 抽象方法(默认public abstract)
31 void play();
32 void pause();
33 void stop();
34
35 // 默认方法(Java 8+)
36 default void displayInfo() {
37 System.out.println("媒体播放器 - 版本: " + VERSION);
38 }
39
40 // 静态方法(Java 8+)
41 static void showVersion() {
42 System.out.println("媒体播放器接口版本: " + VERSION);
43 }
44}
45
46// 可充电接口
47interface Chargeable {
48 void charge();
49 boolean isCharged();
50}
51
52// MP3播放器实现
53class MP3Player implements MediaPlayer, Chargeable {
54 private boolean isCharged = true;
55
56 @Override
57 public void play() {
58 System.out.println("MP3播放器播放音乐");
59 }
60
61 @Override
62 public void pause() {
63 System.out.println("MP3播放器暂停音乐");
64 }
65
66 @Override
67 public void stop() {
68 System.out.println("MP3播放器停止播放");
69 }
70
71 @Override
72 public void charge() {
73 System.out.println("MP3播放器充电中");
74 isCharged = true;
75 }
76
77 @Override
78 public boolean isCharged() {
79 return isCharged;
80 }
81
82 @Override
83 public void displayInfo() {
84 System.out.println("MP3播放器 - 支持音频播放");
85 System.out.println(" 充电状态: " + (isCharged ? "已充满" : "需要充电"));
86 }
87}
88
89// 视频播放器实现
90class VideoPlayer implements MediaPlayer {
91 @Override
92 public void play() {
93 System.out.println("视频播放器播放视频");
94 }
95
96 @Override
97 public void pause() {
98 System.out.println("视频播放器暂停视频");
99 }
100
101 @Override
102 public void stop() {
103 System.out.println("视频播放器停止播放");
104 }
105
106 @Override
107 public void displayInfo() {
108 System.out.println("视频播放器 - 支持视频播放");
109 }
110}
111
112// 收音机实现
113class Radio implements MediaPlayer {
114 private String frequency = "FM 101.7";
115
116 @Override
117 public void play() {
118 System.out.println("收音机播放广播,频率: " + frequency);
119 }
120
121 @Override
122 public void pause() {
123 System.out.println("收音机暂停广播");
124 }
125
126 @Override
127 public void stop() {
128 System.out.println("收音机关闭");
129 }
130
131 @Override
132 public void displayInfo() {
133 System.out.println("收音机 - 支持广播接收");
134 System.out.println(" 当前频率: " + frequency);
135 }
136}
137
138// 媒体控制器
139class MediaController {
140 public void control(MediaPlayer player) {
141 System.out.println("控制媒体播放器:");
142 player.play();
143 player.pause();
144 player.stop();
145 }
146}

7.2 接口的默认方法和静态方法

接口默认方法和静态方法示例
java
1public class InterfaceAdvancedDemo {
2 public static void main(String[] args) {
3 // 调用接口静态方法
4 Logger.showVersion();
5
6 // 创建实现类对象
7 FileLogger fileLogger = new FileLogger();
8 ConsoleLogger consoleLogger = new ConsoleLogger();
9 DatabaseLogger dbLogger = new DatabaseLogger();
10
11 // 使用默认方法
12 fileLogger.log("文件日志信息");
13 consoleLogger.log("控制台日志信息");
14 dbLogger.log("数据库日志信息");
15
16 // 调用重写的默认方法
17 fileLogger.logWithTimestamp("带时间戳的文件日志");
18 consoleLogger.logWithTimestamp("带时间戳的控制台日志");
19
20 // 接口作为类型
21 Logger[] loggers = {fileLogger, consoleLogger, dbLogger};
22 for (Logger logger : loggers) {
23 logger.log("批量日志信息");
24 }
25 }
26}
27
28// 日志接口
29interface Logger {
30 // 常量
31 String DEFAULT_LEVEL = "INFO";
32
33 // 抽象方法
34 void log(String message);
35
36 // 默认方法
37 default void logWithTimestamp(String message) {
38 String timestamp = java.time.LocalDateTime.now().toString();
39 System.out.println("[" + timestamp + "] " + message);
40 }
41
42 default void logWithLevel(String message, String level) {
43 System.out.println("[" + level + "] " + message);
44 }
45
46 // 静态方法
47 static void showVersion() {
48 System.out.println("Logger接口版本: 2.0");
49 }
50
51 static Logger createLogger(String type) {
52 switch (type.toLowerCase()) {
53 case "file":
54 return new FileLogger();
55 case "console":
56 return new ConsoleLogger();
57 case "database":
58 return new DatabaseLogger();
59 default:
60 return new ConsoleLogger();
61 }
62 }
63}
64
65// 文件日志实现
66class FileLogger implements Logger {
67 @Override
68 public void log(String message) {
69 System.out.println("文件日志: " + message);
70 }
71
72 @Override
73 public void logWithTimestamp(String message) {
74 String timestamp = java.time.LocalDateTime.now().toString();
75 System.out.println("文件日志 [" + timestamp + "]: " + message);
76 }
77}
78
79// 控制台日志实现
80class ConsoleLogger implements Logger {
81 @Override
82 public void log(String message) {
83 System.out.println("控制台日志: " + message);
84 }
85 }
86
87// 数据库日志实现
88class DatabaseLogger implements Logger {
89 @Override
90 public void log(String message) {
91 System.out.println("数据库日志: " + message);
92 }
93}

8. 内部类(Inner Classes)

内部类是在另一个类内部定义的类,提供了更好的封装和逻辑组织。

8.1 成员内部类

成员内部类特点
  • 定义在类内部、方法外部
  • 可以访问外部类的所有成员(包括私有成员)
  • 必须先创建外部类实例,才能创建内部类实例
  • 内部类可以被所有访问修饰符修饰(public、private等)
  • 外部类可以直接访问内部类的所有成员
成员内部类示例
java
1public class OuterClass {
2 private String outerField = "外部类字段";
3 private static String staticOuterField = "静态外部字段";
4
5 // 成员内部类
6 public class InnerClass {
7 private String innerField = "内部类字段";
8
9 // 内部类可以访问外部类的所有成员
10 public void accessOuterMembers() {
11 System.out.println("访问外部类字段: " + outerField);
12 System.out.println("访问外部类静态字段: " + staticOuterField);
13 outerMethod(); // 调用外部类方法
14 }
15
16 public void displayInfo() {
17 System.out.println("内部类字段: " + innerField);
18 }
19 }
20
21 public void outerMethod() {
22 System.out.println("外部类方法被调用");
23 }
24
25 // 外部类访问内部类
26 public void accessInnerClass() {
27 InnerClass inner = new InnerClass();
28 inner.displayInfo();
29 System.out.println("访问内部类字段: " + inner.innerField);
30 }
31}
32
33// 使用内部类
34public class InnerClassDemo {
35 public static void main(String[] args) {
36 // 创建外部类实例
37 OuterClass outer = new OuterClass();
38
39 // 创建内部类实例(需要外部类实例)
40 OuterClass.InnerClass inner = outer.new InnerClass();
41
42 // 调用内部类方法
43 inner.accessOuterMembers();
44 inner.displayInfo();
45 }
46}

8.2 局部内部类和匿名内部类

局部内部类特点
  • 定义在方法内部
  • 作用域仅限于定义它的方法内
  • 可以访问外部类的所有成员
  • 可以访问方法中的final或effectively final局部变量
  • 不能使用方法中的非final局部变量
局部内部类示例
java
1public class LocalInnerClassDemo {
2 private String outerField = "外部类字段";
3
4 public void method(final int param) {
5 final String localVar = "局部变量";
6 String effectivelyFinalVar = "实际上的final变量"; // 没有被修改,视为effectively final
7
8 // 局部内部类
9 class LocalInner {
10 private String innerField = "局部内部类字段";
11
12 public void display() {
13 // 访问外部类成员
14 System.out.println("外部类字段: " + outerField);
15
16 // 访问方法的参数和局部变量(必须是final或effectively final)
17 System.out.println("方法参数: " + param);
18 System.out.println("局部变量: " + localVar);
19 System.out.println("实际上的final变量: " + effectivelyFinalVar);
20
21 // 局部内部类的字段
22 System.out.println("内部类字段: " + innerField);
23 }
24 }
25
26 // 创建并使用局部内部类
27 LocalInner inner = new LocalInner();
28 inner.display();
29
30 // effectivelyFinalVar = "修改"; // 如果取消注释,会导致内部类无法引用此变量
31 }
32
33 public static void main(String[] args) {
34 LocalInnerClassDemo demo = new LocalInnerClassDemo();
35 demo.method(10);
36 }
37}
局部变量限制

局部内部类只能访问方法中的final或effectively final变量。这是因为:

  1. 生命周期不同:方法执行完后局部变量消失,但内部类实例可能继续存在
  2. 值捕获:内部类实际上捕获的是变量的副本,而非变量本身
  3. 一致性保证:确保内部类和方法中的变量值保持一致

8.3 内部类的应用场景

内部类的优势

  • 封装性增强:可以将内部类完全隐藏在外部类中
  • 访问控制:内部类可以访问外部类的所有成员
  • 逻辑分组:将相关联的类组织在一起
  • 回调实现:匿名内部类是回调的优雅实现
  • 减少类文件数量:避免过多单独的类文件

适用场景

  • UI事件处理:处理按钮点击等事件
  • 适配器模式:实现接口或抽象类
  • 迭代器模式:为集合类提供迭代器
  • 策略模式:提供算法的不同实现
  • 构建器模式:使用静态内部类作为构建器
  • 单例模式:使用静态内部类实现懒加载单例
内部类实际应用示例
java
1// 构建器模式
2public class Computer {
3 // 必选参数
4 private final String cpu;
5 private final int memory;
6 // 可选参数
7 private final int storage;
8 private final String gpu;
9 private final boolean hasBluetooth;
10
11 private Computer(Builder builder) {
12 this.cpu = builder.cpu;
13 this.memory = builder.memory;
14 this.storage = builder.storage;
15 this.gpu = builder.gpu;
16 this.hasBluetooth = builder.hasBluetooth;
17 }
18
19 // 静态内部类作为构建器
20 public static class Builder {
21 // 必选参数
22 private final String cpu;
23 private final int memory;
24 // 可选参数
25 private int storage = 256;
26 private String gpu = "Integrated";
27 private boolean hasBluetooth = false;
28
29 public Builder(String cpu, int memory) {
30 this.cpu = cpu;
31 this.memory = memory;
32 }
33
34 public Builder storage(int storage) {
35 this.storage = storage;
36 return this;
37 }
38
39 public Builder gpu(String gpu) {
40 this.gpu = gpu;
41 return this;
42 }
43
44 public Builder hasBluetooth(boolean hasBluetooth) {
45 this.hasBluetooth = hasBluetooth;
46 return this;
47 }
48
49 public Computer build() {
50 return new Computer(this);
51 }
52 }
53
54 // 方法用于显示电脑信息
55 public void displaySpecs() {
56 System.out.println("Computer Specifications:");
57 System.out.println("CPU: " + cpu);
58 System.out.println("Memory: " + memory + "GB");
59 System.out.println("Storage: " + storage + "GB");
60 System.out.println("GPU: " + gpu);
61 System.out.println("Bluetooth: " + (hasBluetooth ? "Yes" : "No"));
62 }
63
64 // 使用示例
65 public static void main(String[] args) {
66 Computer computer = new Computer.Builder("Intel i7", 16)
67 .storage(512)
68 .gpu("NVIDIA RTX 3070")
69 .hasBluetooth(true)
70 .build();
71
72 computer.displaySpecs();
73 }
74}
75
76// 单例模式(使用静态内部类实现懒加载)
77public class Singleton {
78 // 私有构造方法
79 private Singleton() { }
80
81 // 静态内部类持有单例实例
82 private static class SingletonHolder {
83 private static final Singleton INSTANCE = new Singleton();
84 }
85
86 // 公共获取实例方法
87 public static Singleton getInstance() {
88 return SingletonHolder.INSTANCE;
89 }
90}

9. 枚举(Enum)

枚举是一种特殊的类,用于表示一组固定的常量。

9.1 基本枚举

枚举特点
  • 使用enum关键字定义
  • 所有枚举值必须在开头定义
  • 每个枚举常量都是该枚举类型的实例
  • 枚举类型隐式继承自java.lang.Enum
  • 枚举是类型安全的常量
  • 可以在switch语句中使用
  • 自动实现了Comparable接口
基本枚举示例
java
1// 基本枚举定义
2public enum Day {
3 MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
4}
5
6// 使用枚举
7public class EnumBasicDemo {
8 public static void main(String[] args) {
9 // 声明枚举变量
10 Day today = Day.MONDAY;
11
12 // 在switch中使用
13 switch (today) {
14 case MONDAY:
15 System.out.println("星期一,工作日");
16 break;
17 case TUESDAY:
18 System.out.println("星期二,工作日");
19 break;
20 case WEDNESDAY:
21 System.out.println("星期三,工作日");
22 break;
23 case THURSDAY:
24 System.out.println("星期四,工作日");
25 break;
26 case FRIDAY:
27 System.out.println("星期五,工作日");
28 break;
29 case SATURDAY:
30 case SUNDAY:
31 System.out.println("周末");
32 break;
33 }
34
35 // 遍历所有枚举值
36 System.out.println("\n一周的所有天:");
37 for (Day day : Day.values()) {
38 System.out.println(day);
39 }
40
41 // 从字符串获取枚举
42 Day friday = Day.valueOf("FRIDAY");
43 System.out.println("\n" + friday + " 的序数: " + friday.ordinal());
44
45 // 比较枚举值
46 if (today.compareTo(Day.FRIDAY) < 0) {
47 System.out.println("今天在星期五之前");
48 }
49 }
50}

9.2 带属性和方法的枚举

带属性和方法的枚举示例
java
1// 带属性和方法的枚举
2public enum DayOfWeek {
3 MONDAY("星期一", true),
4 TUESDAY("星期二", true),
5 WEDNESDAY("星期三", true),
6 THURSDAY("星期四", true),
7 FRIDAY("星期五", true),
8 SATURDAY("星期六", false),
9 SUNDAY("星期日", false);
10
11 private final String chineseName;
12 private final boolean workday;
13
14 // 枚举构造方法
15 DayOfWeek(String chineseName, boolean workday) {
16 this.chineseName = chineseName;
17 this.workday = workday;
18 }
19
20 // 获取中文名称
21 public String getChineseName() {
22 return chineseName;
23 }
24
25 // 判断是否工作日
26 public boolean isWorkday() {
27 return workday;
28 }
29
30 // 添加自定义方法
31 public String getDescription() {
32 return this.name() + " (" + chineseName + ")" +
33 (workday ? " - 工作日" : " - 休息日");
34 }
35
36 // 获取下一天
37 public DayOfWeek nextDay() {
38 int nextOrdinal = (this.ordinal() + 1) % values().length;
39 return values()[nextOrdinal];
40 }
41}
42
43// 枚举使用示例
44public class EnhancedEnumDemo {
45 public static void main(String[] args) {
46 DayOfWeek today = DayOfWeek.MONDAY;
47
48 // 使用枚举属性
49 System.out.println("今天: " + today);
50 System.out.println("中文名称: " + today.getChineseName());
51 System.out.println("是否工作日: " + today.isWorkday());
52 System.out.println("描述: " + today.getDescription());
53
54 // 使用枚举方法
55 DayOfWeek tomorrow = today.nextDay();
56 System.out.println("\n明天: " + tomorrow.getDescription());
57
58 // 遍历带属性的枚举
59 System.out.println("\n一周的所有天:");
60 for (DayOfWeek day : DayOfWeek.values()) {
61 System.out.println(day.getDescription());
62 }
63 }
64}

9.3 抽象方法和状态机

枚举可以定义抽象方法,每个枚举值必须实现该方法:

带抽象方法的枚举
java
1public enum Operation {
2 ADD("+") {
3 @Override
4 public double apply(double x, double y) {
5 return x + y;
6 }
7 },
8
9 SUBTRACT("-") {
10 @Override
11 public double apply(double x, double y) {
12 return x - y;
13 }
14 },
15
16 MULTIPLY("*") {
17 @Override
18 public double apply(double x, double y) {
19 return x * y;
20 }
21 },
22
23 DIVIDE("/") {
24 @Override
25 public double apply(double x, double y) {
26 if (y == 0) throw new ArithmeticException("除数不能为零");
27 return x / y;
28 }
29 };
30
31 private final String symbol;
32
33 Operation(String symbol) {
34 this.symbol = symbol;
35 }
36
37 public String getSymbol() {
38 return symbol;
39 }
40
41 // 抽象方法,每个枚举值必须实现
42 public abstract double apply(double x, double y);
43}
44
45// 使用带抽象方法的枚举
46public class OperationDemo {
47 public static void main(String[] args) {
48 double x = 10;
49 double y = 5;
50
51 for (Operation op : Operation.values()) {
52 System.out.println(x + " " + op.getSymbol() + " " + y + " = " + op.apply(x, y));
53 }
54 }
55}

9.4 EnumSet和EnumMap

Java提供了两个专门用于枚举的集合类:EnumSetEnumMap

EnumSet

EnumSet是Set接口的高性能实现,专为枚举类型设计:

  • 内部使用位向量实现,性能极高
  • 所有元素必须来自同一个枚举
  • 不允许null元素
  • 线程不安全,需要外部同步
  • 迭代顺序与枚举常量的定义顺序一致

EnumMap

EnumMap是Map接口的高性能实现,专为枚举类型键设计:

  • 内部使用数组实现,性能极高
  • 所有键必须来自同一个枚举
  • 不允许null键,但允许null值
  • 线程不安全,需要外部同步
  • 迭代顺序与枚举常量的定义顺序一致
EnumSet和EnumMap示例
java
1import java.util.*;
2
3// 枚举定义
4enum DayOfWeek {
5 MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
6}
7
8public class EnumCollectionsDemo {
9 public static void main(String[] args) {
10 // EnumSet示例
11 System.out.println("==== EnumSet示例 ====");
12
13 // 创建包含所有枚举值的EnumSet
14 EnumSet<DayOfWeek> allDays = EnumSet.allOf(DayOfWeek.class);
15 System.out.println("所有天: " + allDays);
16
17 // 创建空的EnumSet
18 EnumSet<DayOfWeek> noDays = EnumSet.noneOf(DayOfWeek.class);
19 System.out.println("空集合: " + noDays);
20
21 // 创建包含特定范围的EnumSet
22 EnumSet<DayOfWeek> workDays = EnumSet.range(DayOfWeek.MONDAY, DayOfWeek.FRIDAY);
23 System.out.println("工作日: " + workDays);
24
25 // 创建包含指定元素的EnumSet
26 EnumSet<DayOfWeek> weekend = EnumSet.of(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY);
27 System.out.println("周末: " + weekend);
28
29 // 集合运算
30 EnumSet<DayOfWeek> businessDays = EnumSet.complementOf(weekend); // 补集
31 System.out.println("工作日(补集): " + businessDays);
32
33 EnumSet<DayOfWeek> copy = EnumSet.copyOf(workDays); // 复制
34 System.out.println("工作日副本: " + copy);
35
36 // EnumMap示例
37 System.out.println("\n==== EnumMap示例 ====");
38
39 EnumMap<DayOfWeek, String> daySchedule = new EnumMap<>(DayOfWeek.class);
40
41 // 添加键值对
42 daySchedule.put(DayOfWeek.MONDAY, "开始新一周工作");
43 daySchedule.put(DayOfWeek.WEDNESDAY, "周会");
44 daySchedule.put(DayOfWeek.FRIDAY, "提交周报");
45 daySchedule.put(DayOfWeek.SATURDAY, "休息");
46 daySchedule.put(DayOfWeek.SUNDAY, "休息");
47
48 // 获取值
49 System.out.println("星期一的安排: " + daySchedule.get(DayOfWeek.MONDAY));
50
51 // 遍历EnumMap
52 System.out.println("\n一周安排:");
53 for (Map.Entry<DayOfWeek, String> entry : daySchedule.entrySet()) {
54 System.out.println(entry.getKey() + ": " + entry.getValue());
55 }
56
57 // 检查键是否存在
58 System.out.println("\n星期二有安排吗? " + daySchedule.containsKey(DayOfWeek.TUESDAY));
59 System.out.println("星期五有安排吗? " + daySchedule.containsKey(DayOfWeek.FRIDAY));
60 }
61}
枚举优势总结
  1. 类型安全:比普通常量更安全,编译时检查
  2. 更强的表达能力:可以添加方法和字段
  3. 更好的封装:可以实现自定义行为
  4. 内置功能:自带序列化、比较、字符串转换等功能
  5. 专用集合:高性能的EnumSet和EnumMap
  6. IDE支持:更好的自动完成和重构支持

10. 设计模式应用

设计模式是解决特定问题的经验总结,在面向对象编程中广泛应用。

10.1 单例模式

单例模式

单例模式确保一个类在JVM中只有一个实例,并提供全局访问点。

关键特点

  • 私有构造方法,防止外部实例化
  • 静态方法或字段提供全局访问点
  • 线程安全考虑
  • 延迟加载或预加载

适用场景

  • 资源共享(数据库连接、线程池)
  • 全局配置管理
  • 日志记录器
  • 缓存管理

10.2 工厂模式

工厂模式

工厂模式将对象的创建逻辑封装在工厂类中,使客户端代码与具体类解耦。

工厂模式家族

  • 简单工厂:由一个工厂类负责创建所有产品
  • 工厂方法:定义创建对象的接口,子类决定实例化的类
  • 抽象工厂:创建相关或依赖对象的家族,无需指定具体类

适用场景

  • 不确定使用哪个具体类
  • 创建对象的过程复杂
  • 希望将创建和使用逻辑分离
  • 系统需要更灵活地创建对象

11. 最佳实践和注意事项

11.1 设计原则

SOLID原则

  • S - 单一职责原则:一个类应该只有一个引起它变化的原因
  • O - 开闭原则:对扩展开放,对修改关闭
  • L - 里氏替换原则:子类必须能够替换父类
  • I - 接口隔离原则:使用多个专门的接口,而不是单一的总接口
  • D - 依赖倒置原则:依赖于抽象而不是具体实现

其他设计原则

  • DRY原则:不要重复自己
  • KISS原则:保持简单明了
  • YAGNI原则:你不会需要它
  • 组合优于继承:优先使用对象组合而非继承
  • 迪米特法则:最少知识原则
设计原则最佳实践
  1. 识别变化:找出系统中可能变化的部分,将它们封装起来
  2. 接口分离:根据客户端需求定义接口,避免客户端依赖不需要的接口
  3. 抽象层次:在适当的抽象层次上工作,不要过度抽象或过度具体
  4. 关注点分离:将不同关注点的代码分开,减少耦合
  5. 避免过度设计:从简单开始,需要时再添加复杂性

11.2 常见陷阱和解决方案

继承的陷阱

  • 脆弱基类问题:修改父类可能意外破坏子类
  • 继承层次过深:导致系统难以理解和维护
  • 方法爆炸:基类累积过多方法,变得臃肿
  • 多重继承冲突:接口中默认方法的冲突
  • 横切关注点:有些功能难以通过继承实现

解决方案

  1. 使用组合:将功能委托给组合对象,而不是继承
  2. 接口继承:继承接口而非实现
  3. 装饰器模式:动态添加功能,而不是通过继承
  4. 使用工具类:将共用功能放在工具类中
  5. 控制继承深度:保持继承层次不超过3层

11.3 性能优化建议

对象创建

  • 对象池化重用频繁创建的对象
  • 使用享元模式共享不可变对象
  • 采用延迟初始化策略
  • 减少临时对象的创建
  • 合理使用StringBuilder

内存占用

  • 优先使用基本类型而非包装类
  • 减少实例变量数量
  • 使用数组替代集合(适用场景)
  • 避免过深的继承层次
  • 重用常量和静态资源

访问效率

  • 避免过度使用getter和setter
  • 减少不必要的多态调用
  • 使用final修饰不变方法
  • 考虑方法内联
  • 缓存频繁计算的结果

13. 最佳实践总结

面向对象编程最佳实践
  1. 设计优先:先考虑对象职责和交互,再考虑实现
  2. 封装内部状态:不要暴露内部细节,提供良好的接口
  3. 组合优于继承:优先使用对象组合而非继承关系
  4. 接口优于抽象类:尽量使用接口定义行为
  5. 遵循SOLID原则:写出健壮、可维护的代码
  6. 保持简单:不要过度设计,保持代码简单明了
  7. 类职责单一:每个类只负责一个功能领域
  8. 正确使用访问修饰符:使用最小必要的可见性
  9. 编写可测试代码:设计时考虑可测试性
  10. 重视文档和注释:为公共API提供详细文档

恭喜你!现在你已经全面掌握了Java面向对象编程的核心概念和最佳实践。通过理解封装、继承、多态和抽象,以及掌握内部类、枚举、设计模式等高级特性,你已经具备了使用Java进行面向对象开发的能力。记住,面向对象编程是一种思维方式,熟练掌握它不仅能帮助你写出优雅的代码,还能让你更好地理解和使用Java生态系统中的各种框架和库。继续练习,不断应用这些原则,你将成为一名出色的Java开发者!

14. 面试题精选

14.1 面向对象编程的四大特征是什么?请详细解释。

答案: 面向对象编程的四大特征是封装、继承、多态和抽象:

  1. 封装(Encapsulation)

    • 定义:将数据和操作数据的方法绑定在一起,对外部隐藏实现细节
    • 实现方式:通过访问修饰符(private、protected、public)和getter/setter方法
    • 优势:提高安全性、降低耦合度、提高代码可维护性
    • 示例:private int id; public int getId() { return id; }
  2. 继承(Inheritance)

    • 定义:子类继承父类的属性和方法,实现代码重用
    • 实现方式:通过extends关键字,Java只支持单继承,通过接口可以实现多继承效果
    • 优势:代码复用、构建类的层次结构、支持多态
    • 示例:public class Dog extends Animal { }
  3. 多态(Polymorphism)

    • 定义:同一个操作可以作用于不同的对象,产生不同的结果
    • 实现方式:方法重载(编译时多态)和方法重写(运行时多态)
    • 优势:提高代码的灵活性和可扩展性、降低耦合度
    • 示例:Animal animal = new Dog(); animal.makeSound();
  4. 抽象(Abstraction)

    • 定义:提取共同特征,隐藏复杂的实现细节
    • 实现方式:通过抽象类和接口
    • 优势:简化复杂系统、提高代码可复用性
    • 示例:abstract class Shape { abstract void draw(); }

14.2 Java中方法重载和方法重写的区别是什么?

答案: 方法重载(Overloading)和方法重写(Overriding)的主要区别:

特点方法重载方法重写
定义同一个类中定义多个同名但参数不同的方法子类重新实现父类中已有的方法
参数必须不同(类型、个数、顺序)必须相同
返回类型可以不同必须相同或是父类返回类型的子类型
访问修饰符可以不同不能比父类方法更严格
异常可以不同不能抛出比父类方法更多的异常
绑定编译时绑定(静态绑定)运行时绑定(动态绑定)
多态体现编译时多态运行时多态

示例:

java
1// 方法重载
2class Calculator {
3 int add(int a, int b) { return a + b; }
4 double add(double a, double b) { return a + b; }
5 int add(int a, int b, int c) { return a + b + c; }
6}
7
8// 方法重写
9class Animal {
10 void makeSound() { System.out.println("Animal sound"); }
11}
12
13class Dog extends Animal {
14 @Override
15 void makeSound() { System.out.println("Woof"); }
16}

14.3 Java中的抽象类和接口有什么区别?何时使用抽象类,何时使用接口?

答案: 抽象类和接口的主要区别:

特点抽象类接口
关键字abstractinterface
实现方法可以有具体方法实现只能有抽象方法(Java 8+可以有默认和静态方法)
成员变量可以有实例变量只能有常量(public static final)
构造器可以有构造器不能有构造器
继承单继承,只能继承一个类可以实现多个接口
访问修饰符可以用所有访问修饰符方法默认public
设计目的表示"是什么"关系表示"能做什么"关系

使用抽象类的场景

  1. 需要在几个相关类之间共享代码
  2. 需要访问和修改非final成员变量
  3. 类之间存在"是一种"的关系,且有公共行为
  4. 想要声明非public成员

使用接口的场景

  1. 不相关的类需要实现相同的行为
  2. 需要指定特定行为但不关心具体实现
  3. 需要利用多继承的优势
  4. 需要对行为进行解耦

14.4 Java中的访问修饰符有哪些?它们的访问范围分别是什么?

答案: Java中有四种访问修饰符,按照访问范围从小到大依次是:

  1. private

    • 范围:仅在声明它的类内部可见
    • 适用于:变量、方法、构造器、内部类
    • 不适用于:类(外部类)、接口、接口的方法和变量
  2. 默认(无修饰符)

    • 范围:同一包内可见
    • 适用于:类、接口、变量、方法、构造器
  3. protected

    • 范围:同一包内和不同包的子类可见
    • 适用于:变量、方法、构造器、内部类
    • 不适用于:类(外部类)、接口
  4. public

    • 范围:所有类可见
    • 适用于:类、接口、变量、方法、构造器

访问权限对比:

修饰符同一类同一包不同包子类不同包非子类
private可访问不可访问不可访问不可访问
默认可访问可访问不可访问不可访问
protected可访问可访问可访问不可访问
public可访问可访问可访问可访问

14.5 什么是组合(Composition)?为什么说组合优于继承?

答案: 组合是一种类之间的关系,它表示"有一个"(has-a)的关系,即一个类包含另一个类的实例作为其成员变量。

组合优于继承的原因

  1. 更高的灵活性:组合可以在运行时动态改变行为,而继承关系在编译时就已确定

  2. 避免紧耦合:继承创建了紧耦合,子类依赖于父类的实现;而组合通过接口交互,降低了耦合度

  3. 防止继承层次过深:过深的继承层次会导致系统难以理解和维护

  4. 更好的封装性:组合可以只暴露必要的接口,而继承会暴露所有可访问的父类方法

  5. 符合开闭原则:组合更容易扩展功能而不修改现有代码

示例:

java
1// 使用继承
2class Bird extends Animal {
3 void fly() { ... }
4}
5
6// 使用组合
7class Bird {
8 private Animal animal; // 组合
9 private Wings wings; // 组合
10
11 void makeSound() {
12 animal.makeSound();
13 }
14
15 void fly() {
16 wings.flap();
17 }
18}

在实际开发中,应优先考虑使用组合,只有在确实存在"是一个"关系且需要利用多态性时才使用继承。

参与讨论