Java 面向对象编程详解
面向对象编程(Object-Oriented Programming,OOP)是Java语言的核心特性,它提供了一种组织和管理代码的方法,通过将数据和操作数据的方法封装在对象中来实现。
OOP = 封装 + 继承 + 多态 + 抽象
- 🔐 封装:隐藏实现细节,保护数据完整性,提供受控访问
- 🌳 继承:实现代码重用,建立类型层次结构,促进一致性设计
- 🔄 多态:增强灵活性,实现动态绑定,支持扩展性设计
- 🧩 抽象:专注核心特性,简化复杂系统,提升代码可维护性
- 🏗️ 组件化:构建模块化系统,促进团队协作,提高开发效率
1. 面向对象编程基础
1.1 什么是面向对象编程
面向对象编程是一种编程范式,它将现实世界中的事物抽象为对象,每个对象都有自己的属性和行为。Java完全支持面向对象编程,提供了丰富的语法特性来实现OOP概念。
核心概念
- 概念总览
- 封装
- 继承
- 多态
- 抽象
| 概念 | 说明 | 特点 |
|---|---|---|
| 封装(Encapsulation) | 将数据和方法绑定在一起,隐藏内部实现细节 | 数据隐藏、访问控制 |
| 继承(Inheritance) | 子类可以继承父类的属性和方法 | 代码重用、层次结构 |
| 多态(Polymorphism) | 同一个接口可以有多种不同的实现 | 方法重载、方法重写 |
| 抽象(Abstraction) | 提取共同特征,隐藏复杂细节 | 接口定义、抽象类 |
封装是面向对象编程的基本特性,它通过将数据和对数据的操作组合在一个单元(类)内部,并隐藏对象的内部状态和实现细节,只对外提供有限的接口。
关键机制:
- 访问修饰符:
private、protected、默认、public - getter/setter方法:控制对私有属性的访问
- 信息隐藏:隐藏实现细节,只暴露必要的接口
优势:
- 提高代码安全性
- 简化接口,降低复杂度
- 支持可维护性和灵活性
继承允许一个类(子类)获取另一个类(父类)的属性和方法,形成类的层次结构,实现代码重用。
关键机制:
- 使用
extends关键字实现继承 - 子类可以访问父类的非私有成员
- 可以通过
super关键字调用父类方法或构造器
优势:
- 代码重用
- 建立类的层次结构
- 实现"是一种"关系
多态允许不同的对象对同一消息作出不同的响应,体现为"一个接口,多种实现"。
实现方式:
- 方法重写:子类重写父类的方法,覆盖原有实现
- 方法重载:同一个类中定义多个同名但参数不同的方法
- 接口实现:不同类实现同一接口的不同行为
优势:
- 提高代码的灵活性和可扩展性
- 简化客户端代码
- 支持"开闭原则"
抽象是提取事物共同特征而忽略非本质细节的过程,在Java中通过抽象类和接口实现。
实现方式:
- 抽象类:使用
abstract关键字声明,可以包含抽象方法和具体方法 - 接口:使用
interface关键字声明,定义一组方法规范
优势:
- 分离接口和实现
- 提供设计蓝图
- 支持程序的可扩展性
2. 类和对象基础
2.1 类的定义
类是对象的模板,定义了对象的属性和行为。下面是一个标准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}Java类的基本组成部分
- 字段/属性:存储对象的状态
- 方法:定义对象的行为
- 构造方法:创建对象时初始化
- 内部类:嵌套在类内部的类
- 代码块:初始化代码块
- 接口实现:实现接口的方法
- 类是对象的模板或蓝图
- 对象是类的具体实例
- 类定义了对象的属性和行为
- 对象是类在内存中的表示
| 修饰符 | 同一类 | 同一包 | 子类 | 其他包 |
|---|---|---|---|---|
| private | ✅ | ❌ | ❌ | ❌ |
| 默认(无修饰符) | ✅ | ✅ | ❌ | ❌ |
| protected | ✅ | ✅ | ✅ | ❌ |
| public | ✅ | ✅ | ✅ | ✅ |
2.2 对象的创建和使用
对象是类的实例,通过 new 关键字创建。下面展示了对象的创建和使用过程:
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}- 创建:通过
new关键字调用构造器创建对象 - 使用:通过引用变量访问对象的属性和方法
- 不可达:当没有任何引用指向对象时
- 垃圾回收:JVM的垃圾回收器回收不可达对象
2.3 构造方法详解
构造方法是创建对象时自动调用的特殊方法,负责初始化对象。
- 基础知识
- 构造方法链
- 初始化块
- 与类同名
- 没有返回值(连void都不写)
- 可以重载(提供多个不同参数列表的构造方法)
- 如果不定义构造方法,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}构造方法可以通过this关键字调用同一个类中的其他构造方法:
1public class Product {2 private String name;3 private double price;4 private String category;5 6 // 主构造方法7 public Product(String name, double price, String category) {8 this.name = name;9 this.price = price;10 this.category = category;11 }12 13 // 调用主构造方法14 public Product(String name, double price) {15 this(name, price, "未分类");16 }17 18 // 调用另一个构造方法19 public Product(String name) {20 this(name, 0.0);21 }22 23 // 无参构造方法24 public Product() {25 this("未命名产品");26 }27}this()构造器调用必须是构造方法的第一条语句- 不能形成循环调用链
除了构造方法,Java还提供了初始化块来初始化对象:
1public class InitializerExample {2 private int instanceVar;3 private static int staticVar;4 5 // 静态初始化块 - 类加载时执行一次6 static {7 System.out.println("静态初始化块执行");8 staticVar = 100;9 }10 11 // 实例初始化块 - 每次创建对象时执行12 {13 System.out.println("实例初始化块执行");14 instanceVar = 200;15 }16 17 // 构造方法18 public InitializerExample() {19 System.out.println("构造方法执行");20 }21}初始化顺序:
- 静态初始化块(类加载时)
- 实例初始化块(实例创建时)
- 构造方法(实例创建时)
2.4 访问修饰符
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}2627class 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}- 尽量使用私有字段+公共方法的封装模式
- 只将必要的方法和字段设为公开
- protected修饰符要慎用,它会破坏封装性
- 内部类应当设为private或protected
3. 封装(Encapsulation)
封装是面向对象编程的核心概念之一,它将数据和方法绑定在一起,隐藏内部实现细节,只提供必要的接口。
3.1 封装的原则
- 基本原则
- 封装优势
- 封装模式
- 信息隐藏:隐藏内部实现细节
- 访问控制:通过访问修饰符限制对类成员的访问
- 数据保护:防止对象数据被外部直接修改
- 接口抽象:提供简单清晰的公共接口
- 数据验证:通过setter方法验证数据有效性
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}封装的优势
- 安全性:防止对象内部状态被无效值破坏
- 模块化:实现细节和接口分离,便于模块开发
- 维护性:实现可以更改而不影响使用它的代码
- 灵活性:可以对数据进行验证,保持一致性
- 复用性:隐藏实现细节使代码更易于复用
1. JavaBeans模式
1public class User {2 private String name;3 private int age;4 private String email;5 6 // 无参构造方法7 public User() { }8 9 // Getter和Setter方法10 public String getName() { return name; }11 public void setName(String name) { this.name = name; }12 13 public int getAge() { return age; }14 public void setAge(int age) {15 if (age >= 0 && age <= 150) {16 this.age = age;17 }18 }19 20 public String getEmail() { return email; }21 public void setEmail(String email) { this.email = email; }22}2. 不可变对象模式
1public final class ImmutablePoint {2 private final double x;3 private final double y;4 5 public ImmutablePoint(double x, double y) {6 this.x = x;7 this.y = y;8 }9 10 public double getX() { return x; }11 public double getY() { return y; }12 13 // 不提供setter,返回新对象而不修改原对象14 public ImmutablePoint translate(double dx, double dy) {15 return new ImmutablePoint(x + dx, y + dy);16 }17}3. 构建器模式
1public class Person {2 // 必选参数3 private final String firstName;4 private final String lastName;5 // 可选参数6 private final int age;7 private final String email;8 private final String phone;9 10 private Person(Builder builder) {11 this.firstName = builder.firstName;12 this.lastName = builder.lastName;13 this.age = builder.age;14 this.email = builder.email;15 this.phone = builder.phone;16 }17 18 // 构建器19 public static class Builder {20 // 必选参数21 private final String firstName;22 private final String lastName;23 // 可选参数24 private int age;25 private String email;26 private String phone;27 28 public Builder(String firstName, String lastName) {29 this.firstName = firstName;30 this.lastName = lastName;31 }32 33 public Builder age(int val) {34 age = val; return this;35 }36 37 public Builder email(String val) {38 email = val; return this;39 }40 41 public Builder phone(String val) {42 phone = val; return this;43 }44 45 public Person build() {46 return new Person(this);47 }48 }49}5051// 使用构建器52Person person = new Person.Builder("张", "三")53 .age(25)54 .email("zhangsan@example.com")55 .phone("123-4567-8901")56 .build();3.2 封装的测试
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}封装在实际开发中随处可见,例如:
- 数据库连接类:隐藏连接细节,提供简单的CRUD方法
- 配置管理类:内部处理配置加载逻辑,提供简单的配置访问接口
- UI组件:内部处理渲染逻辑,对外提供简单的事件和属性接口
4. 继承(Inheritance)
继承允许子类继承父类的属性和方法,实现代码重用和层次结构。
4.1 基本继承
- 继承基础
- super关键字
- 继承类型
- 使用
extends关键字表示继承关系 - Java中一个类只能继承一个父类(单继承)
- 子类可以访问父类的非私有(public、protected、默认)成员
- 子类可以通过
super关键字访问父类的方法和构造方法 - 子类可以覆盖(重写)父类的方法
- 所有类都隐式继承自
Object类
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}2526// 子类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 @Override38 public void makeSound() {39 System.out.println(name + " 汪汪叫");40 }41 42 // 添加子类特有方法43 public void fetch() {44 System.out.println(name + " 在捡球");45 }46}super关键字用于引用父类的成员:
1public class Child extends Parent {2 private int childField;3 4 public Child(int parentField, int childField) {5 // 调用父类构造方法6 super(parentField);7 this.childField = childField;8 }9 10 @Override11 public void display() {12 // 调用父类方法13 super.display();14 System.out.println("Child Field: " + childField);15 }16 17 public void accessParentField() {18 // 访问父类字段19 System.out.println("Parent Field: " + super.parentField);20 }21}2223class Parent {24 protected int parentField;25 26 public Parent(int parentField) {27 this.parentField = parentField;28 }29 30 public void display() {31 System.out.println("Parent Field: " + parentField);32 }33}super()必须是构造方法中的第一条语句- 如果父类没有无参构造方法,子类必须显式调用父类的有参构造方法
super和this不能同时用作构造方法的第一条语句
Java中的继承有多种形式:
1. 单继承
Java只允许一个类继承一个父类,但可以实现多个接口。
1public class Car extends Vehicle { }2. 多层继承
允许一个类继承另一个类,而该类又继承另一个类,形成继承链。
1class Animal { }2class Mammal extends Animal { }3class Dog extends Mammal { }3. 层次继承
多个类可以继承同一个父类。
1class Animal { }2class Dog extends Animal { }3class Cat extends Animal { }4. 混合继承
结合多层继承和层次继承的特点。
1class Animal { }2class Mammal extends Animal { }3class Bird extends Animal { }4class Dog extends Mammal { }5class Cat extends Mammal { }4.2 继承的测试
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注解,它可以:
- 帮助编译器验证重写是否正确
- 防止意外定义新方法
- 提高代码可读性和可维护性
- 防止父类方法签名变化带来的错误
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}1920// 抽象父类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}3940// 圆形41class Circle extends Shape {42 private double radius;43 44 public Circle(double radius) {45 super("圆形");46 this.radius = radius;47 }48 49 @Override50 public double calculateArea() {51 return Math.PI * radius * radius;52 }53 54 @Override55 public double calculatePerimeter() {56 return 2 * Math.PI * radius;57 }58}5960// 矩形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 @Override72 public double calculateArea() {73 return width * height;74 }75 76 @Override77 public double calculatePerimeter() {78 return 2 * (width + height);79 }80}8182// 三角形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 @Override94 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 @Override101 public double calculatePerimeter() {102 return side1 + side2 + side3;103 }104}- 打破封装:子类可能依赖父类的实现细节
- 紧耦合:父类变更可能影响所有子类
- 多层继承难以维护:层次过深导致复杂度增加
- 单一继承限制:Java只允许单一继承
解决方案:
- 考虑使用组合代替继承
- 控制继承深度(不超过3层)
- 多用接口,少用类继承
- 谨慎设计父类,保证稳定性
5. 多态(Polymorphism)
多态是面向对象编程的重要特性,它允许同一个接口有多种不同的实现。
- 多态概述
- 编译时多态
- 运行时多态
多态的核心思想是"一个接口,多种实现"。在Java中,多态主要通过继承和接口实现。多态使得代码更具灵活性、可扩展性和可维护性。
多态的关键机制:
- 继承或接口实现
- 方法的重写(覆盖)
- 向上转型(父类引用指向子类对象)
- 动态绑定(运行时决定调用哪个方法)
多态可分为两种主要类型:
- 编译时多态(静态多态):通过方法重载实现
- 运行时多态(动态多态):通过方法重写和继承实现
编译时多态(又称静态多态)通过方法重载实现,编译器在编译时根据方法签名(方法名和参数列表)决定调用哪个方法。
关键特点:
- 发生在同一个类中
- 方法名相同,参数列表不同(类型、数量或顺序)
- 返回类型可以相同也可以不同
- 在编译阶段即可确定调用哪个方法
运行时多态(又称动态多态)通过方法重写和继承实现,JVM在运行时根据对象的实际类型决定调用哪个方法。
关键特点:
- 必须有继承关系
- 子类重写父类的方法
- 父类引用指向子类对象
- 只有在运行时才能确定调用哪个方法
- 是Java OOP的核心机制之一
5.1 编译时多态(方法重载)
方法重载是在同一个类中定义多个名称相同但参数列表不同的方法:
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}1617class 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}方法重载解析规则
编译器按照以下顺序解析重载方法:
- 精确匹配:寻找与参数类型完全匹配的方法
提升转换:原始类型按照以下顺序提升:
- byte → short → int → long → float → double
- char → int → long → float → double
- 自动装箱/拆箱:原始类型和对应包装类型之间的转换
- 可变参数:如果前面都没有匹配,尝试使用可变参数方法
5.2 运行时多态(方法重写)
运行时多态通过继承和方法重写实现,以下是示例:
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}3233class 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}- 必要条件:继承、重写、向上转型
- 方法调用:根据对象的实际类型,而非引用类型
- 动态绑定:JVM在运行时决定调用的方法
- 静态方法:不参与多态(静态绑定)
- 私有方法:不参与多态(不能被重写)
- final方法:不参与多态(不能被重写)
5.3 多态的实际应用
- 多态优势
- 设计模式应用
- 虚方法机制
代码灵活性
多态允许我们编写更通用的代码,处理不同类型的对象而无需知道其具体类型。这提高了代码的灵活性和可扩展性。
通过多态,我们可以传递任何子类对象到需要父类类型的地方,而无需修改代码。
可扩展性
多态支持"开闭原则":对扩展开放,对修改关闭。
可以添加新的子类实现,而无需修改使用这些类的代码。新的子类将自动与现有系统集成。
多态在许多设计模式中扮演核心角色:
1. 策略模式
1// 策略接口2interface SortStrategy {3 void sort(int[] array);4}56// 具体策略7class QuickSort implements SortStrategy {8 public void sort(int[] array) {9 System.out.println("使用快速排序");10 // 快速排序实现...11 }12}1314class MergeSort implements SortStrategy {15 public void sort(int[] array) {16 System.out.println("使用归并排序");17 // 归并排序实现...18 }19}2021// 上下文22class Sorter {23 private SortStrategy strategy;24 25 public void setStrategy(SortStrategy strategy) {26 this.strategy = strategy;27 }28 29 public void sort(int[] array) {30 strategy.sort(array);31 }32}2. 工厂方法模式
1// 产品接口2interface Product {3 void operation();4}56// 具体产品7class ConcreteProductA implements Product {8 public void operation() {9 System.out.println("产品A的操作");10 }11}1213class ConcreteProductB implements Product {14 public void operation() {15 System.out.println("产品B的操作");16 }17}1819// 创建者抽象类20abstract class Creator {21 public abstract Product createProduct();22 23 // 使用产品的方法24 public void someOperation() {25 Product product = createProduct();26 product.operation();27 }28}Java的多态通过虚方法表(Virtual Method Table, VMT)实现:
每个类都有一个虚方法表,包含该类的所有虚方法的地址:
Animal的虚方法表:
- makeSound() → Animal.makeSound()
- eat() → Animal.eat()
- sleep() → Animal.sleep()
Dog的虚方法表:
- makeSound() → Dog.makeSound() (覆盖父类方法)
- eat() → Animal.eat() (继承自父类)
- sleep() → Animal.sleep() (继承自父类)
- fetch() → Dog.fetch() (子类特有方法)
当通过父类引用调用方法时,JVM查找实际对象的虚方法表来确定应该调用哪个方法。
6. 抽象类(Abstract Class)
抽象类是不能被实例化的类,通常包含抽象方法和具体方法。
6.1 抽象类基础
- 定义与特点
- 抽象类 vs 具体类
- 何时使用
- 使用
abstract关键字声明 - 不能被实例化,只能被继承
- 可以包含抽象方法和具体方法
- 抽象方法没有方法体,子类必须实现
- 可以有构造方法,供子类调用
- 可以有成员变量和静态方法
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和Setter26 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}4243// 具体子类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 @Override54 public double calculateArea() {55 return Math.PI * radius * radius;56 }57 58 @Override59 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}| 特性 | 抽象类 | 具体类 |
|---|---|---|
| 实例化 | ❌ 不能直接实例化 | ✅ 可以直接实例化 |
| 抽象方法 | ✅ 可以包含 | ❌ 不能包含 |
| 方法实现 | 可以包含抽象方法和具体方法 | 只能包含具体方法 |
| 构造方法 | ✅ 可以有 | ✅ 可以有 |
| 用途 | 作为基类,定义通用接口 | 定义可直接使用的对象 |
| 子类要求 | 子类必须实现所有抽象方法 | 子类可以直接继承所有方法 |
1// 抽象类2abstract class Animal {3 abstract void makeSound(); // 子类必须实现4 5 void sleep() { // 具体实现6 System.out.println("动物睡觉");7 }8}910// 具体类11class Dog extends Animal {12 @Override13 void makeSound() {14 System.out.println("汪汪叫"); // 必须实现父类抽象方法15 }16 17 // 可以添加更多方法18 void fetch() {19 System.out.println("捡球");20 }21}使用抽象类的场景
- 共享代码:当多个相关类共享公共方法实现,但某些方法的实现因子类而异
- 部分实现:当类只能部分实现某个功能,其余功能需要由子类实现
- 模板模式:定义算法骨架,将某些步骤延迟到子类中实现
- 框架设计:定义系统框架,强制子类实现特定功能
- 代码复用:避免在多个子类中重复编写相同代码
模板方法模式示例
1abstract class DataProcessor {2 // 模板方法 - 定义算法骨架3 public final void process() {4 readData();5 processData();6 writeData();7 }8 9 // 抽象方法 - 子类必须实现10 protected abstract void readData();11 protected abstract void processData();12 13 // 具体方法 - 公共实现14 protected void writeData() {15 System.out.println("写入处理结果到标准输出");16 }17}1819class FileDataProcessor extends DataProcessor {20 @Override21 protected void readData() {22 System.out.println("从文件读取数据");23 }24 25 @Override26 protected void processData() {27 System.out.println("处理文件数据");28 }29}3031class DatabaseDataProcessor extends DataProcessor {32 @Override33 protected void readData() {34 System.out.println("从数据库读取数据");35 }36 37 @Override38 protected void processData() {39 System.out.println("处理数据库数据");40 }41 42 // 重写公共方法43 @Override44 protected void writeData() {45 System.out.println("写入处理结果到数据库");46 }47}6.2 抽象类示例
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}2122// 抽象类:交通工具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}5455// 具体子类:汽车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 @Override65 public void start() {66 System.out.println("汽车启动,发动机轰鸣");67 }68 69 @Override70 public void stop() {71 System.out.println("汽车停止,刹车灯亮起");72 }73 74 @Override75 public void honk() {76 System.out.println("汽车喇叭: 滴滴");77 }78 79 @Override80 public void displayInfo() {81 super.displayInfo();82 System.out.println(" 车门数: " + doors);83 }84}8586// 具体子类:自行车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 @Override96 public void start() {97 System.out.println("自行车开始骑行");98 }99 100 @Override101 public void stop() {102 System.out.println("自行车停止,脚踩地");103 }104 105 @Override106 public void displayInfo() {107 super.displayInfo();108 System.out.println(" 轮子数: " + wheels);109 }110}111112// 具体子类:摩托车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 @Override122 public void start() {123 System.out.println("摩托车启动,引擎咆哮");124 }125 126 @Override127 public void stop() {128 System.out.println("摩托车停止,侧支架放下");129 }130 131 @Override132 public void displayInfo() {133 super.displayInfo();134 System.out.println(" 侧斗: " + (hasSidecar ? "有" : "无"));135 }136}- 避免过度抽象:只提取真正通用的功能到抽象类
- 层次适中:控制继承层次,避免过深的继承结构
- 明确职责:每个抽象类应当有明确的单一职责
- 设计稳定:抽象类应该是稳定的,避免频繁修改
- 考虑替代方案:某些场景下接口+默认方法可能更合适
6.3 抽象类与接口对比
抽象类
- 使用
abstract class关键字 - 可以包含抽象方法和具体方法
- 可以有构造方法、成员变量
- 可以有 private、protected 成员
- 一个类只能继承一个抽象类
- 适用于关系紧密的类
- 强调"是什么"(is-a关系)
- 提供部分实现和共同行为
接口
- 使用
interface关键字 - 主要包含抽象方法(Java 8+支持默认方法)
- 不能有构造方法,只能有常量
- 所有成员默认 public
- 一个类可以实现多个接口
- 适用于不相关类需要共同行为
- 强调"能做什么"(can-do关系)
- 提供行为契约
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}2425// 接口示例26interface Swimmer {27 // 常量28 int MAX_DEPTH = 100; // 隐式 public static final29 30 // 抽象方法31 void swim(); // 隐式 public abstract32 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}4344// 既继承抽象类又实现接口45class Fish extends Animal implements Swimmer {46 public Fish(String name) {47 super(name);48 }49 50 // 实现抽象类的抽象方法51 @Override52 public void makeSound() {53 System.out.println(name + " 吐泡泡");54 }55 56 // 实现接口的抽象方法57 @Override58 public void swim() {59 System.out.println(name + " 在水中游动");60 }61}7. 接口(Interface)
接口定义了类必须实现的方法契约,支持多重继承。
7.1 基本接口
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}2425// 媒体播放器接口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}4546// 可充电接口47interface Chargeable {48 void charge();49 boolean isCharged();50}5152// MP3播放器实现53class MP3Player implements MediaPlayer, Chargeable {54 private boolean isCharged = true;55 56 @Override57 public void play() {58 System.out.println("MP3播放器播放音乐");59 }60 61 @Override62 public void pause() {63 System.out.println("MP3播放器暂停音乐");64 }65 66 @Override67 public void stop() {68 System.out.println("MP3播放器停止播放");69 }70 71 @Override72 public void charge() {73 System.out.println("MP3播放器充电中");74 isCharged = true;75 }76 77 @Override78 public boolean isCharged() {79 return isCharged;80 }81 82 @Override83 public void displayInfo() {84 System.out.println("MP3播放器 - 支持音频播放");85 System.out.println(" 充电状态: " + (isCharged ? "已充满" : "需要充电"));86 }87}8889// 视频播放器实现90class VideoPlayer implements MediaPlayer {91 @Override92 public void play() {93 System.out.println("视频播放器播放视频");94 }95 96 @Override97 public void pause() {98 System.out.println("视频播放器暂停视频");99 }100 101 @Override102 public void stop() {103 System.out.println("视频播放器停止播放");104 }105 106 @Override107 public void displayInfo() {108 System.out.println("视频播放器 - 支持视频播放");109 }110}111112// 收音机实现113class Radio implements MediaPlayer {114 private String frequency = "FM 101.7";115 116 @Override117 public void play() {118 System.out.println("收音机播放广播,频率: " + frequency);119 }120 121 @Override122 public void pause() {123 System.out.println("收音机暂停广播");124 }125 126 @Override127 public void stop() {128 System.out.println("收音机关闭");129 }130 131 @Override132 public void displayInfo() {133 System.out.println("收音机 - 支持广播接收");134 System.out.println(" 当前频率: " + frequency);135 }136}137138// 媒体控制器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 接口的默认方法和静态方法
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}2728// 日志接口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}6465// 文件日志实现66class FileLogger implements Logger {67 @Override68 public void log(String message) {69 System.out.println("文件日志: " + message);70 }71 72 @Override73 public void logWithTimestamp(String message) {74 String timestamp = java.time.LocalDateTime.now().toString();75 System.out.println("文件日志 [" + timestamp + "]: " + message);76 }77}7879// 控制台日志实现80class ConsoleLogger implements Logger {81 @Override82 public void log(String message) {83 System.out.println("控制台日志: " + message);84 }85 }86 87// 数据库日志实现88class DatabaseLogger implements Logger {89 @Override90 public void log(String message) {91 System.out.println("数据库日志: " + message);92 }93}8. 内部类(Inner Classes)
内部类是在另一个类内部定义的类,提供了更好的封装和逻辑组织。
8.1 成员内部类
- 成员内部类
- 静态内部类
- 定义在类内部、方法外部
- 可以访问外部类的所有成员(包括私有成员)
- 必须先创建外部类实例,才能创建内部类实例
- 内部类可以被所有访问修饰符修饰(public、private等)
- 外部类可以直接访问内部类的所有成员
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}3233// 使用内部类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}- 使用
static关键字修饰的内部类 - 不需要外部类实例即可创建
- 只能访问外部类的静态成员
- 不能访问外部类的实例成员
- 可以包含静态和非静态成员
1public class OuterClass {2 private String instanceField = "实例字段";3 private static String staticField = "静态字段";4 5 // 静态内部类6 public static class StaticInnerClass {7 private String innerField = "内部类字段";8 private static String staticInnerField = "静态内部类静态字段";9 10 // 只能访问外部类的静态成员11 public void accessOuterMembers() {12 // System.out.println(instanceField); // 编译错误13 System.out.println("访问外部类静态字段: " + staticField);14 }15 16 public static void staticInnerMethod() {17 System.out.println("静态内部类的静态方法");18 }19 20 public void displayInfo() {21 System.out.println("内部类字段: " + innerField);22 System.out.println("静态内部类静态字段: " + staticInnerField);23 }24 }25 26 public void accessStaticInnerClass() {27 // 可以直接创建静态内部类实例28 StaticInnerClass inner = new StaticInnerClass();29 inner.displayInfo();30 31 // 访问静态内部类的静态成员32 StaticInnerClass.staticInnerMethod();33 }34}3536// 使用静态内部类37public class StaticInnerClassDemo {38 public static void main(String[] args) {39 // 创建静态内部类实例(无需外部类实例)40 OuterClass.StaticInnerClass inner = new OuterClass.StaticInnerClass();41 42 // 调用内部类方法43 inner.accessOuterMembers();44 inner.displayInfo();45 46 // 调用静态方法47 OuterClass.StaticInnerClass.staticInnerMethod();48 }49}8.2 局部内部类和匿名内部类
- 局部内部类
- 匿名内部类
- 定义在方法内部
- 作用域仅限于定义它的方法内
- 可以访问外部类的所有成员
- 可以访问方法中的final或effectively final局部变量
- 不能使用方法中的非final局部变量
1public class LocalInnerClassDemo {2 private String outerField = "外部类字段";3 4 public void method(final int param) {5 final String localVar = "局部变量";6 String effectivelyFinalVar = "实际上的final变量"; // 没有被修改,视为effectively final7 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变量。这是因为:
- 生命周期不同:方法执行完后局部变量消失,但内部类实例可能继续存在
- 值捕获:内部类实际上捕获的是变量的副本,而非变量本身
- 一致性保证:确保内部类和方法中的变量值保持一致
- 没有名称的内部类
- 在创建时同时定义和实例化
- 只能创建一个实例
- 可以扩展类或实现接口,但不能同时做两者
- 不能有构造方法(没有类名)
- 不能是静态的
1public class AnonymousInnerClassDemo {2 // 接口3 interface Greeting {4 void greet();5 }6 7 // 抽象类8 abstract class Person {9 abstract void introduce();10 11 void sleep() {12 System.out.println("人在睡觉...");13 }14 }15 16 public void demo() {17 // 1. 实现接口的匿名内部类18 Greeting chineseGreeting = new Greeting() {19 @Override20 public void greet() {21 System.out.println("你好!");22 }23 };24 25 // 使用接口匿名实现26 chineseGreeting.greet();27 28 // 2. 扩展抽象类的匿名内部类29 Person student = new Person() {30 @Override31 void introduce() {32 System.out.println("我是一名学生");33 }34 35 // 可以添加额外方法,但只能在内部使用36 void study() {37 System.out.println("学生正在学习");38 }39 };40 41 // 使用抽象类匿名实现42 student.introduce();43 student.sleep();44 // student.study(); // 编译错误,无法访问匿名类特有的方法45 46 // 3. 扩展具体类的匿名内部类47 Runnable runnable = new Runnable() {48 @Override49 public void run() {50 System.out.println("线程任务执行中...");51 }52 };53 54 // 使用匿名内部类创建线程55 new Thread(runnable).start();56 57 // 或者更简洁地58 new Thread(new Runnable() {59 @Override60 public void run() {61 System.out.println("另一个线程任务执行中...");62 }63 }).start();64 65 // Java 8+ Lambda表达式(更简洁,但仅适用于函数式接口)66 Greeting englishGreeting = () -> System.out.println("Hello!");67 englishGreeting.greet();68 }69 70 public static void main(String[] args) {71 new AnonymousInnerClassDemo().demo();72 }73}从Java 8开始,对于函数式接口(只有一个抽象方法的接口),可以使用Lambda表达式代替匿名内部类,语法更简洁:
1// 匿名内部类2Runnable r1 = new Runnable() {3 @Override4 public void run() {5 System.out.println("Running...");6 }7};89// 等价的Lambda表达式10Runnable r2 = () -> System.out.println("Running...");8.3 内部类的应用场景
内部类的优势
- 封装性增强:可以将内部类完全隐藏在外部类中
- 访问控制:内部类可以访问外部类的所有成员
- 逻辑分组:将相关联的类组织在一起
- 回调实现:匿名内部类是回调的优雅实现
- 减少类文件数量:避免过多单独的类文件
适用场景
- UI事件处理:处理按钮点击等事件
- 适配器模式:实现接口或抽象类
- 迭代器模式:为集合类提供迭代器
- 策略模式:提供算法的不同实现
- 构建器模式:使用静态内部类作为构建器
- 单例模式:使用静态内部类实现懒加载单例
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}7576// 单例模式(使用静态内部类实现懒加载)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接口
1// 基本枚举定义2public enum Day {3 MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY4}56// 使用枚举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}每个枚举类型都隐式继承自java.lang.Enum,具有以下方法:
| 方法 | 返回类型 | 描述 |
|---|---|---|
name() | String | 返回枚举常量的名称 |
ordinal() | int | 返回枚举常量的序数(从0开始的位置) |
valueOf(String name) | 枚举类型 | 返回指定名称的枚举常量 |
values() | 枚举类型[] | 返回包含所有枚举常量的数组 |
compareTo(E o) | int | 比较枚举常量的序数 |
1public enum Direction {2 NORTH, EAST, SOUTH, WEST3}45public class EnumMethodsDemo {6 public static void main(String[] args) {7 // 使用内置方法8 Direction dir = Direction.NORTH;9 10 System.out.println("名称: " + dir.name());11 System.out.println("序数: " + dir.ordinal());12 System.out.println("toString: " + dir.toString());13 14 // 获取所有枚举值15 Direction[] allDirections = Direction.values();16 System.out.println("\n所有方向:");17 for (Direction d : allDirections) {18 System.out.println(d + " at position " + d.ordinal());19 }20 21 // valueOf方法22 Direction west = Direction.valueOf("WEST");23 System.out.println("\n西方向: " + west);24 25 // 比较枚举26 Direction south = Direction.SOUTH;27 System.out.println("\n比较NORTH和SOUTH: " + dir.compareTo(south));28 }29}9.2 带属性和方法的枚举
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}4243// 枚举使用示例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 抽象方法和状态机
- 抽象方法
- 状态机
枚举可以定义抽象方法,每个枚举值必须实现该方法:
1public enum Operation {2 ADD("+") {3 @Override4 public double apply(double x, double y) {5 return x + y;6 }7 },8 9 SUBTRACT("-") {10 @Override11 public double apply(double x, double y) {12 return x - y;13 }14 },15 16 MULTIPLY("*") {17 @Override18 public double apply(double x, double y) {19 return x * y;20 }21 },22 23 DIVIDE("/") {24 @Override25 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}4445// 使用带抽象方法的枚举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}枚举非常适合实现状态机模式:
1public enum TrafficLight {2 RED(30) {3 @Override4 public TrafficLight next() {5 return GREEN;6 }7 8 @Override9 public String getAction() {10 return "停车等待";11 }12 },13 14 GREEN(45) {15 @Override16 public TrafficLight next() {17 return YELLOW;18 }19 20 @Override21 public String getAction() {22 return "可以通行";23 }24 },25 26 YELLOW(5) {27 @Override28 public TrafficLight next() {29 return RED;30 }31 32 @Override33 public String getAction() {34 return "减速准备停车";35 }36 };37 38 private final int durationSeconds;39 40 TrafficLight(int durationSeconds) {41 this.durationSeconds = durationSeconds;42 }43 44 public int getDurationSeconds() {45 return durationSeconds;46 }47 48 // 抽象方法:获取下一个状态49 public abstract TrafficLight next();50 51 // 抽象方法:获取当前状态应执行的动作52 public abstract String getAction();53}5455// 状态机演示56public class StateMachineDemo {57 public static void main(String[] args) {58 TrafficLight light = TrafficLight.RED;59 60 // 模拟5次状态转换61 for (int i = 0; i < 5; i++) {62 System.out.println("当前信号灯: " + light);63 System.out.println("持续时间: " + light.getDurationSeconds() + "秒");64 System.out.println("驾驶员动作: " + light.getAction());65 66 light = light.next(); // 状态转换67 System.out.println();68 }69 }70}使用枚举实现状态机有以下优势:
- 类型安全:状态转换在编译时检查
- 代码清晰:每个状态的行为封装在一起
- 易于维护:添加新状态只需添加新的枚举值
- 状态间转换明确:通过方法明确定义状态转换规则
9.4 EnumSet和EnumMap
Java提供了两个专门用于枚举的集合类:EnumSet和EnumMap。
EnumSet
EnumSet是Set接口的高性能实现,专为枚举类型设计:
- 内部使用位向量实现,性能极高
- 所有元素必须来自同一个枚举
- 不允许null元素
- 线程不安全,需要外部同步
- 迭代顺序与枚举常量的定义顺序一致
EnumMap
EnumMap是Map接口的高性能实现,专为枚举类型键设计:
- 内部使用数组实现,性能极高
- 所有键必须来自同一个枚举
- 不允许null键,但允许null值
- 线程不安全,需要外部同步
- 迭代顺序与枚举常量的定义顺序一致
1import java.util.*;23// 枚举定义4enum DayOfWeek {5 MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY6}78public class EnumCollectionsDemo {9 public static void main(String[] args) {10 // EnumSet示例11 System.out.println("==== EnumSet示例 ====");12 13 // 创建包含所有枚举值的EnumSet14 EnumSet<DayOfWeek> allDays = EnumSet.allOf(DayOfWeek.class);15 System.out.println("所有天: " + allDays);16 17 // 创建空的EnumSet18 EnumSet<DayOfWeek> noDays = EnumSet.noneOf(DayOfWeek.class);19 System.out.println("空集合: " + noDays);20 21 // 创建包含特定范围的EnumSet22 EnumSet<DayOfWeek> workDays = EnumSet.range(DayOfWeek.MONDAY, DayOfWeek.FRIDAY);23 System.out.println("工作日: " + workDays);24 25 // 创建包含指定元素的EnumSet26 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 // 遍历EnumMap52 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}- 类型安全:比普通常量更安全,编译时检查
- 更强的表达能力:可以添加方法和字段
- 更好的封装:可以实现自定义行为
- 内置功能:自带序列化、比较、字符串转换等功能
- 专用集合:高性能的EnumSet和EnumMap
- IDE支持:更好的自动完成和重构支持
10. 设计模式应用
设计模式是解决特定问题的经验总结,在面向对象编程中广泛应用。
10.1 单例模式
- 模式概述
- 实现方式
- 代码示例
单例模式确保一个类在JVM中只有一个实例,并提供全局访问点。
关键特点:
- 私有构造方法,防止外部实例化
- 静态方法或字段提供全局访问点
- 线程安全考虑
- 延迟加载或预加载
适用场景:
- 资源共享(数据库连接、线程池)
- 全局配置管理
- 日志记录器
- 缓存管理
饿汉式
特点:类加载时创建实例
- ✅ 线程安全
- ✅ 实现简单
- ❌ 不支持延迟加载
- ❌ 可能造成资源浪费
适用:创建实例开销小,需要保证线程安全
懒汉式
特点:首次使用时创建实例
- ✅ 延迟加载
- ✅ 资源利用效率高
- ❌ 需要额外处理线程安全
- ❌ 实现复杂
适用:创建实例开销大,需要延迟加载
静态内部类
特点:利用类加载机制保证线程安全
- ✅ 线程安全
- ✅ 延迟加载
- ✅ 实现简单
- ❌ 不支持参数化实例化
适用:最佳实践,兼顾线程安全和延迟加载
枚举实现
特点:最简洁的实现方式
- ✅ 线程安全
- ✅ 防止反射和序列化攻击
- ✅ 实现最简单
- ❌ 不支持延迟加载
适用:对抗反射和序列化攻击的场景
1// 1. 饿汉式单例2public class EagerSingleton {3 // 类加载时创建实例4 private static final EagerSingleton INSTANCE = new EagerSingleton();5 6 // 私有构造方法7 private EagerSingleton() { }8 9 // 公共访问点10 public static EagerSingleton getInstance() {11 return INSTANCE;12 }13}1415// 2. 懒汉式单例(线程安全)16public class LazySingleton {17 private static volatile LazySingleton instance;18 19 private LazySingleton() { }20 21 public static LazySingleton getInstance() {22 if (instance == null) {23 synchronized (LazySingleton.class) {24 if (instance == null) {25 instance = new LazySingleton();26 }27 }28 }29 return instance;30 }31}3233// 3. 静态内部类单例34public class StaticInnerSingleton {35 private StaticInnerSingleton() { }36 37 // 静态内部类持有实例38 private static class SingletonHolder {39 private static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton();40 }41 42 public static StaticInnerSingleton getInstance() {43 return SingletonHolder.INSTANCE;44 }45}4647// 4. 枚举单例48public enum EnumSingleton {49 INSTANCE;50 51 // 可以添加方法和字段52 private String data;53 54 public String getData() {55 return data;56 }57 58 public void setData(String data) {59 this.data = data;60 }61}10.2 工厂模式
- 模式概述
- 简单工厂
- 工厂方法
工厂模式将对象的创建逻辑封装在工厂类中,使客户端代码与具体类解耦。
工厂模式家族:
- 简单工厂:由一个工厂类负责创建所有产品
- 工厂方法:定义创建对象的接口,子类决定实例化的类
- 抽象工厂:创建相关或依赖对象的家族,无需指定具体类
适用场景:
- 不确定使用哪个具体类
- 创建对象的过程复杂
- 希望将创建和使用逻辑分离
- 系统需要更灵活地创建对象
1// 产品接口2interface Vehicle {3 void drive();4}56// 具体产品7class Car implements Vehicle {8 @Override9 public void drive() {10 System.out.println("开车中...");11 }12}1314class Motorcycle implements Vehicle {15 @Override16 public void drive() {17 System.out.println("骑摩托车中...");18 }19}2021class Bicycle implements Vehicle {22 @Override23 public void drive() {24 System.out.println("骑自行车中...");25 }26}2728// 简单工厂29class VehicleFactory {30 public static Vehicle createVehicle(String type) {31 switch (type.toLowerCase()) {32 case "car": return new Car();33 case "motorcycle": return new Motorcycle();34 case "bicycle": return new Bicycle();35 default: throw new IllegalArgumentException("未知的车辆类型: " + type);36 }37 }38}3940// 客户端代码41public class SimpleFactoryDemo {42 public static void main(String[] args) {43 // 使用工厂创建对象44 Vehicle car = VehicleFactory.createVehicle("car");45 Vehicle motorcycle = VehicleFactory.createVehicle("motorcycle");46 Vehicle bicycle = VehicleFactory.createVehicle("bicycle");47 48 // 使用对象49 car.drive();50 motorcycle.drive();51 bicycle.drive();52 }53}1// 产品接口2interface Product {3 void use();4}56// 具体产品7class ConcreteProductA implements Product {8 @Override9 public void use() {10 System.out.println("使用产品A");11 }12}1314class ConcreteProductB implements Product {15 @Override16 public void use() {17 System.out.println("使用产品B");18 }19}2021// 抽象工厂22abstract class Creator {23 // 工厂方法24 public abstract Product createProduct();25 26 // 使用产品的方法27 public void anOperation() {28 Product product = createProduct();29 product.use();30 }31}3233// 具体工厂34class ConcreteCreatorA extends Creator {35 @Override36 public Product createProduct() {37 return new ConcreteProductA();38 }39}4041class ConcreteCreatorB extends Creator {42 @Override43 public Product createProduct() {44 return new ConcreteProductB();45 }46}4748// 客户端代码49public class FactoryMethodDemo {50 public static void main(String[] args) {51 Creator creatorA = new ConcreteCreatorA();52 Creator creatorB = new ConcreteCreatorB();53 54 // 创建并使用产品55 creatorA.anOperation();56 creatorB.anOperation();57 }58}11. 最佳实践和注意事项
11.1 设计原则
SOLID原则
- S - 单一职责原则:一个类应该只有一个引起它变化的原因
- O - 开闭原则:对扩展开放,对修改关闭
- L - 里氏替换原则:子类必须能够替换父类
- I - 接口隔离原则:使用多个专门的接口,而不是单一的总接口
- D - 依赖倒置原则:依赖于抽象而不是具体实现
其他设计原则
- DRY原则:不要重复自己
- KISS原则:保持简单明了
- YAGNI原则:你不会需要它
- 组合优于继承:优先使用对象组合而非继承
- 迪米特法则:最少知识原则
- 识别变化:找出系统中可能变化的部分,将它们封装起来
- 接口分离:根据客户端需求定义接口,避免客户端依赖不需要的接口
- 抽象层次:在适当的抽象层次上工作,不要过度抽象或过度具体
- 关注点分离:将不同关注点的代码分开,减少耦合
- 避免过度设计:从简单开始,需要时再添加复杂性
11.2 常见陷阱和解决方案
- 继承陷阱
- 封装陷阱
- 多态陷阱
继承的陷阱
- 脆弱基类问题:修改父类可能意外破坏子类
- 继承层次过深:导致系统难以理解和维护
- 方法爆炸:基类累积过多方法,变得臃肿
- 多重继承冲突:接口中默认方法的冲突
- 横切关注点:有些功能难以通过继承实现
解决方案:
- 使用组合:将功能委托给组合对象,而不是继承
- 接口继承:继承接口而非实现
- 装饰器模式:动态添加功能,而不是通过继承
- 使用工具类:将共用功能放在工具类中
- 控制继承深度:保持继承层次不超过3层
封装的陷阱
- 过度暴露:暴露过多内部实现细节
- Getter/Setter滥用:为每个字段都添加访问器
- 内部状态泄露:返回可变对象的引用
- 不变性破坏:外部可以修改对象状态
- 封装层次不一致:类的部分功能高度封装,部分又完全开放
解决方案:
- 最小特权原则:仅暴露必要的成员
- 返回防御性副本:返回可变对象的副本而非引用
- 不可变对象:创建不可变类,状态无法修改
- 封装集合:不直接暴露集合字段
- 使用构建器模式:控制对象创建过程
多态的陷阱
- 类型转换错误:不安全的向下转型
- 重写混乱:混淆重写和重载
- 静态方法冲突:尝试多态调用静态方法
- 构造方法中调用多态方法:导致未初始化字段被访问
- equals和hashCode不一致:导致集合操作异常
解决方案:
- 使用instanceof检查:转型前检查类型
- 使用@Override注解:明确标记重写方法
- 避免在构造方法中调用可重写方法
- 统一重写equals和hashCode方法
- 使用泛型减少类型转换
11.3 性能优化建议
对象创建
- 对象池化重用频繁创建的对象
- 使用享元模式共享不可变对象
- 采用延迟初始化策略
- 减少临时对象的创建
- 合理使用StringBuilder
内存占用
- 优先使用基本类型而非包装类
- 减少实例变量数量
- 使用数组替代集合(适用场景)
- 避免过深的继承层次
- 重用常量和静态资源
访问效率
- 避免过度使用getter和setter
- 减少不必要的多态调用
- 使用final修饰不变方法
- 考虑方法内联
- 缓存频繁计算的结果
13. 最佳实践总结
- 设计优先:先考虑对象职责和交互,再考虑实现
- 封装内部状态:不要暴露内部细节,提供良好的接口
- 组合优于继承:优先使用对象组合而非继承关系
- 接口优于抽象类:尽量使用接口定义行为
- 遵循SOLID原则:写出健壮、可维护的代码
- 保持简单:不要过度设计,保持代码简单明了
- 类职责单一:每个类只负责一个功能领域
- 正确使用访问修饰符:使用最小必要的可见性
- 编写可测试代码:设计时考虑可测试性
- 重视文档和注释:为公共API提供详细文档
恭喜你!现在你已经全面掌握了Java面向对象编程的核心概念和最佳实践。通过理解封装、继承、多态和抽象,以及掌握内部类、枚举、设计模式等高级特性,你已经具备了使用Java进行面向对象开发的能力。记住,面向对象编程是一种思维方式,熟练掌握它不仅能帮助你写出优雅的代码,还能让你更好地理解和使用Java生态系统中的各种框架和库。继续练习,不断应用这些原则,你将成为一名出色的Java开发者!
14. 面试题精选
14.1 面向对象编程的四大特征是什么?请详细解释。
答案: 面向对象编程的四大特征是封装、继承、多态和抽象:
-
封装(Encapsulation):
- 定义:将数据和操作数据的方法绑定在一起,对外部隐藏实现细节
- 实现方式:通过访问修饰符(private、protected、public)和getter/setter方法
- 优势:提高安全性、降低耦合度、提高代码可维护性
- 示例:
private int id; public int getId() { return id; }
-
继承(Inheritance):
- 定义:子类继承父类的属性和方法,实现代码重用
- 实现方式:通过extends关键字,Java只支持单继承,通过接口可以实现多继承效果
- 优势:代码复用、构建类的层次结构、支持多态
- 示例:
public class Dog extends Animal { }
-
多态(Polymorphism):
- 定义:同一个操作可以作用于不同的对象,产生不同的结果
- 实现方式:方法重载(编译时多态)和方法重写(运行时多态)
- 优势:提高代码的灵活性和可扩展性、降低耦合度
- 示例:
Animal animal = new Dog(); animal.makeSound();
-
抽象(Abstraction):
- 定义:提取共同特征,隐藏复杂的实现细节
- 实现方式:通过抽象类和接口
- 优势:简化复杂系统、提高代码可复用性
- 示例:
abstract class Shape { abstract void draw(); }
14.2 Java中方法重载和方法重写的区别是什么?
答案: 方法重载(Overloading)和方法重写(Overriding)的主要区别:
| 特点 | 方法重载 | 方法重写 |
|---|---|---|
| 定义 | 同一个类中定义多个同名但参数不同的方法 | 子类重新实现父类中已有的方法 |
| 参数 | 必须不同(类型、个数、顺序) | 必须相同 |
| 返回类型 | 可以不同 | 必须相同或是父类返回类型的子类型 |
| 访问修饰符 | 可以不同 | 不能比父类方法更严格 |
| 异常 | 可以不同 | 不能抛出比父类方法更多的异常 |
| 绑定 | 编译时绑定(静态绑定) | 运行时绑定(动态绑定) |
| 多态体现 | 编译时多态 | 运行时多态 |
示例:
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}78// 方法重写9class Animal {10 void makeSound() { System.out.println("Animal sound"); }11}1213class Dog extends Animal {14 @Override15 void makeSound() { System.out.println("Woof"); }16}14.3 Java中的抽象类和接口有什么区别?何时使用抽象类,何时使用接口?
答案: 抽象类和接口的主要区别:
| 特点 | 抽象类 | 接口 |
|---|---|---|
| 关键字 | abstract | interface |
| 实现方法 | 可以有具体方法实现 | 只能有抽象方法(Java 8+可以有默认和静态方法) |
| 成员变量 | 可以有实例变量 | 只能有常量(public static final) |
| 构造器 | 可以有构造器 | 不能有构造器 |
| 继承 | 单继承,只能继承一个类 | 可以实现多个接口 |
| 访问修饰符 | 可以用所有访问修饰符 | 方法默认public |
| 设计目的 | 表示"是什么"关系 | 表示"能做什么"关系 |
使用抽象类的场景:
- 需要在几个相关类之间共享代码
- 需要访问和修改非final成员变量
- 类之间存在"是一种"的关系,且有公共行为
- 想要声明非public成员
使用接口的场景:
- 不相关的类需要实现相同的行为
- 需要指定特定行为但不关心具体实现
- 需要利用多继承的优势
- 需要对行为进行解耦
14.4 Java中的访问修饰符有哪些?它们的访问范围分别是什么?
答案: Java中有四种访问修饰符,按照访问范围从小到大依次是:
-
private:
- 范围:仅在声明它的类内部可见
- 适用于:变量、方法、构造器、内部类
- 不适用于:类(外部类)、接口、接口的方法和变量
-
默认(无修饰符):
- 范围:同一包内可见
- 适用于:类、接口、变量、方法、构造器
-
protected:
- 范围:同一包内和不同包的子类可见
- 适用于:变量、方法、构造器、内部类
- 不适用于:类(外部类)、接口
-
public:
- 范围:所有类可见
- 适用于:类、接口、变量、方法、构造器
访问权限对比:
| 修饰符 | 同一类 | 同一包 | 不同包子类 | 不同包非子类 |
|---|---|---|---|---|
| private | 可访问 | 不可访问 | 不可访问 | 不可访问 |
| 默认 | 可访问 | 可访问 | 不可访问 | 不可访问 |
| protected | 可访问 | 可访问 | 可访问 | 不可访问 |
| public | 可访问 | 可访问 | 可访问 | 可访问 |
14.5 什么是组合(Composition)?为什么说组合优于继承?
答案: 组合是一种类之间的关系,它表示"有一个"(has-a)的关系,即一个类包含另一个类的实例作为其成员变量。
组合优于继承的原因:
-
更高的灵活性:组合可以在运行时动态改变行为,而继承关系在编译时就已确定
-
避免紧耦合:继承创建了紧耦合,子类依赖于父类的实现;而组合通过接口交互,降低了耦合度
-
防止继承层次过深:过深的继承层次会导致系统难以理解和维护
-
更好的封装性:组合可以只暴露必要的接口,而继承会暴露所有可访问的父类方法
-
符合开闭原则:组合更容易扩展功能而不修改现有代码
示例:
1// 使用继承2class Bird extends Animal {3 void fly() { ... }4}56// 使用组合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}在实际开发中,应优先考虑使用组合,只有在确实存在"是一个"关系且需要利用多态性时才使用继承。
参与讨论