面向对象编程(OOP)
面向对象编程(Object-Oriented Programming,OOP)是 Java 的核心编程范式。它将数据(属性)和行为(方法)封装在对象中,通过类、继承、多态等机制构建可维护、可复用的软件系统。Java 的几乎所有代码都运行在面向对象的框架之内,掌握 OOP 是学好 Java 的基石。
1. 类与对象
1.1 类的定义
类(Class)是对象的模板或蓝图,它描述了一类对象的共同特征(属性)和行为(方法)。
// 定义一个学生类
public class Student {
// 属性(成员变量)
String name;
int age;
String studentId;
// 方法(成员方法)
void study() {
System.out.println(name + " 正在学习");
}
void introduce() {
System.out.println("大家好,我是 " + name + ",今年 " + age + " 岁");
}
}1.2 对象的创建(new)
对象(Object)是类的实例。使用 new 关键字调用构造方法来创建对象。
public class Main {
public static void main(String[] args) {
// 使用 new 关键字创建对象
Student stu1 = new Student();
stu1.name = "张三";
stu1.age = 20;
stu1.studentId = "2024001";
Student stu2 = new Student();
stu2.name = "李四";
stu2.age = 22;
stu2.studentId = "2024002";
stu1.introduce(); // 输出:大家好,我是 张三,今年 20 岁
stu2.study(); // 输出:李四 正在学习
}
}1.3 构造方法
构造方法(Constructor)是用于初始化对象的一种特殊方法。它具有以下特点:
- 方法名与类名完全相同
- 没有返回值类型(也不写
void) - 创建对象时自动调用
- 如果没有显式定义,编译器会提供一个无参的默认构造方法
public class Student {
String name;
int age;
String studentId;
// 无参构造方法
public Student() {
System.out.println("无参构造方法被调用");
}
// 有参构造方法(构造方法重载)
public Student(String name, int age, String studentId) {
this.name = name; // this 指向当前对象,用于区分成员变量和参数
this.age = age;
this.studentId = studentId;
System.out.println("有参构造方法被调用");
}
void introduce() {
System.out.println("大家好,我是 " + name + ",今年 " + age + " 岁,学号 " + studentId);
}
public static void main(String[] args) {
// 调用无参构造
Student s1 = new Student();
s1.name = "王五";
s1.introduce();
// 调用有参构造,创建对象的同时完成属性赋值
Student s2 = new Student("赵六", 21, "2024003");
s2.introduce();
}
}运行结果:
无参构造方法被调用
大家好,我是 王五,今年 0 岁,学号 null
有参构造方法被调用
大家好,我是 赵六,今年 21 岁,学号 2024003构造方法可以重载,用于提供多种创建对象的方式。
2. 封装
封装(Encapsulation)是 OOP 的四大特性之一,它将数据和操作数据的方法绑定在一起,并对外隐藏内部的实现细节,仅暴露必要的接口。
2.1 访问控制修饰符
Java 提供了四种访问控制修饰符来控制成员的可访问范围:
| 修饰符 | 本类 | 同包 | 子类(不同包) | 任意 |
|---|---|---|---|---|
private | ✓ | ✗ | ✗ | ✗ |
| 默认(default) | ✓ | ✓ | ✗ | ✗ |
protected | ✓ | ✓ | ✓ | ✗ |
public | ✓ | ✓ | ✓ | ✓ |
2.2 private / getter / setter
封装的典型实践是:将属性声明为 private,然后提供 public 的 getter 和 setter 方法来访问和修改属性。这样可以加入验证逻辑,保护数据的完整性。
public class BankAccount {
// 私有属性,外部无法直接访问
private String accountNumber;
private String owner;
private double balance;
// 构造方法
public BankAccount(String accountNumber, String owner, double initialBalance) {
this.accountNumber = accountNumber;
this.owner = owner;
// 在构造方法中也可以使用 setter 来加入校验
if (initialBalance >= 0) {
this.balance = initialBalance;
} else {
this.balance = 0;
}
}
// Getter —— 获取属性值
public String getAccountNumber() {
return accountNumber;
}
public String getOwner() {
return owner;
}
public double getBalance() {
return balance;
}
// Setter —— 修改属性值(可加入校验逻辑)
public void setOwner(String owner) {
if (owner != null && !owner.trim().isEmpty()) {
this.owner = owner;
} else {
System.out.println("所有者姓名不能为空");
}
}
// 不提供 setBalance(),而是提供专属的业务方法,更符合封装理念
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
System.out.println("存款成功,当前余额:" + balance);
} else {
System.out.println("存款金额必须大于 0");
}
}
public void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
System.out.println("取款成功,当前余额:" + balance);
} else {
System.out.println("取款失败,余额不足或金额无效");
}
}
}
// 测试类
public class TestEncapsulation {
public static void main(String[] args) {
BankAccount account = new BankAccount("6222021234567890", "小明", 1000);
// 无法直接访问 private 属性
// account.balance = -5000; // 编译错误!
// 通过 getter 获取属性
System.out.println("账户持有人:" + account.getOwner());
System.out.println("当前余额:" + account.getBalance());
// 通过业务方法来操作数据
account.deposit(500);
account.withdraw(200);
// 通过 setter 修改,会触发校验
account.setOwner(""); // 输出:所有者姓名不能为空
}
}封装的优点:保护数据不被随意篡改、降低耦合、便于维护和修改内部实现。
3. 继承
继承(Inheritance)允许一个类(子类)基于另一个类(父类)来定义,子类会自动拥有父类的非私有属性和方法,并可以扩展或重写它们。Java 使用 extends 关键字实现继承,且只支持单继承(一个子类只能有一个直接父类)。
3.1 extends 关键字
// 父类
public class Animal {
String name;
int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println(name + " 正在吃东西");
}
public void sleep() {
System.out.println(name + " 正在睡觉");
}
}
// 子类继承父类
public class Dog extends Animal {
// 子类特有的属性
private String breed; // 品种
public Dog(String name, int age, String breed) {
super(name, age); // 调用父类的构造方法,必须放在第一行
this.breed = breed;
}
// 子类特有的方法
public void bark() {
System.out.println(name + "(" + breed + ")正在汪汪叫");
}
}
public class Cat extends Animal {
public Cat(String name, int age) {
super(name, age);
}
public void meow() {
System.out.println(name + " 正在喵喵叫");
}
}
public class TestInheritance {
public static void main(String[] args) {
Dog dog = new Dog("旺财", 3, "金毛");
Cat cat = new Cat("咪咪", 2);
// 子类可以使用父类的方法
dog.eat(); // 输出:旺财 正在吃东西
dog.sleep(); // 输出:旺财 正在睡觉
dog.bark(); // 输出:旺财(金毛)正在汪汪叫
cat.eat(); // 输出:咪咪 正在吃东西
cat.meow(); // 输出:咪咪 正在喵喵叫
}
}3.2 super 关键字
super 用于引用父类的成员,常用于:
- 调用父类构造方法:
super(参数列表)—— 必须是子类构造方法的第一条语句 - 访问父类成员变量:
super.父类属性名 - 调用父类方法:
super.父类方法名()
public class Parent {
String message = "父类的 message";
public Parent() {
System.out.println("父类无参构造方法");
}
public void print() {
System.out.println("父类的 print 方法");
}
}
public class Child extends Parent {
String message = "子类的 message";
public Child() {
super(); // 调用父类无参构造,即使不写也会隐式调用
System.out.println("子类无参构造方法");
}
public void printMessages() {
// 访问子类自己的属性
System.out.println("子类的 message: " + this.message);
// 访问父类的属性
System.out.println("父类的 message: " + super.message);
}
@Override
public void print() {
super.print(); // 调用父类被重写的方法
System.out.println("子类扩展的 print 方法");
}
}
public class TestSuper {
public static void main(String[] args) {
Child child = new Child();
child.printMessages();
child.print();
}
}运行结果:
父类无参构造方法
子类无参构造方法
子类的 message: 子类的 message
父类的 message: 父类的 message
父类的 print 方法
子类扩展的 print 方法3.3 方法重写(@Override)
当子类需要修改父类的某个方法实现时,可以对其进行重写(Override)。
重写规则:
- 方法名、参数列表必须相同
- 返回值类型必须相同或是其子类型(协变返回类型)
- 访问修饰符不能比父类更严格(如父类是
protected,子类不能是private) - 父类的
final方法不能被重写 @Override注解不是必须的,但强烈建议使用——编译器会帮你检查是否真的重写了父类方法
public class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
public final void breathe() {
System.out.println("动物在呼吸");
}
}
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("狗在汪汪叫");
}
// ❌ 编译错误:final 方法不能被重写
// @Override
// public void breathe() { ... }
}
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("猫在喵喵叫");
}
}
public class TestOverride {
public static void main(String[] args) {
Animal animal = new Animal();
animal.makeSound(); // 输出:动物发出声音
Dog dog = new Dog();
dog.makeSound(); // 输出:狗在汪汪叫
Cat cat = new Cat();
cat.makeSound(); // 输出:猫在喵喵叫
}
}4. 多态
多态(Polymorphism)是指同一个行为具有多种不同表现形式。Java 中的多态分为两类:编译时多态(方法重载)和运行时多态(方法重写)。
4.1 编译时多态(方法重载)
方法重载(Overload)是在同一个类中定义多个同名方法,但参数列表不同(参数个数、类型或顺序不同)。编译器根据调用时传入的参数来决定调用哪个方法。
public class Calculator {
// 两个整数相加
public int add(int a, int b) {
System.out.println("两个 int 相加");
return a + b;
}
// 三个整数相加
public int add(int a, int b, int c) {
System.out.println("三个 int 相加");
return a + b + c;
}
// 两个 double 相加
public double add(double a, double b) {
System.out.println("两个 double 相加");
return a + b;
}
// 整数和 double 相加
public double add(int a, double b) {
System.out.println("int + double 相加");
return a + b;
}
// 可变参数也可以作为重载的一种
public int add(int... numbers) {
System.out.println("可变参数相加,共 " + numbers.length + " 个数");
int sum = 0;
for (int num : numbers) {
sum += num;
}
return sum;
}
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(3, 5)); // 两个 int 相加 → 8
System.out.println(calc.add(3, 5, 7)); // 三个 int 相加 → 15
System.out.println(calc.add(3.5, 2.5)); // 两个 double 相加 → 6.0
System.out.println(calc.add(3, 2.5)); // int + double 相加 → 5.5
System.out.println(calc.add(1, 2, 3, 4, 5)); // 可变参数 → 15
}
}重载 vs 重写:
| 重载(Overload) | 重写(Override) | |
|---|---|---|
| 发生位置 | 同一个类中 | 子类与父类之间 |
| 方法名 | 相同 | 相同 |
| 参数列表 | 必须不同 | 必须相同 |
| 返回值 | 可以不同 | 必须相同或协变 |
| 访问修饰符 | 无关 | 不能更严格 |
| 绑定时期 | 编译时 | 运行时 |
4.2 运行时多态(方法重写)
运行时多态依赖于继承和方法重写,通过父类引用指向子类对象来实现。调用的方法版本由对象的实际类型决定,在运行时才确定。
public class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("狗在汪汪叫");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("猫在喵喵叫");
}
}
class Pig extends Animal {
@Override
public void makeSound() {
System.out.println("猪在哼哼叫");
}
}
public class TestPolymorphism {
public static void main(String[] args) {
// 多态:父类引用指向子类对象
Animal[] animals = new Animal[3];
animals[0] = new Dog();
animals[1] = new Cat();
animals[2] = new Pig();
// 遍历数组,调用的都是子类重写后的方法
for (Animal animal : animals) {
animal.makeSound(); // 运行时根据实际类型调用对应的方法
}
// 另一个多态的例子 —— 方法参数
makeItSound(new Dog());
makeItSound(new Cat());
}
// 多态参数:参数类型是父类,可以传入任意子类对象
public static void makeItSound(Animal animal) {
animal.makeSound();
}
}运行结果:
狗在汪汪叫
猫在喵喵叫
猪在哼哼叫
狗在汪汪叫
猫在喵喵叫4.3 instanceof 运算符
instanceof 用于判断一个对象是否是某个类(或其子类、实现类)的实例,常用于多态环境中安全地向下转型。
public class TestInstanceof {
public static void main(String[] args) {
Animal animal = new Dog();
// 检查类型
System.out.println(animal instanceof Animal); // true
System.out.println(animal instanceof Dog); // true
System.out.println(animal instanceof Cat); // false
// 安全的向下转型
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 向下转型
dog.bark(); // 调用子类特有的方法
}
// 使用模式匹配 instanceof(Java 16+)
if (animal instanceof Dog dog) {
dog.bark(); // 无需显式转型
}
}
}向下转型(Downcasting)前一定要先使用
instanceof进行检查,否则可能抛出ClassCastException。
5. 抽象类
5.1 抽象类与抽象方法
抽象类(Abstract Class)使用 abstract 关键字修饰,表示一种不完整的类,不能直接实例化。它可以包含抽象方法(只有声明,没有实现)和普通方法。
使用场景: 当多个子类有共同的行为模板,但某些行为的具体实现各不相同时,可以将这些行为定义为抽象方法,由子类各自实现。
// 抽象类
public abstract class Shape {
protected String color;
public Shape(String color) {
this.color = color;
}
// 普通方法(有具体实现)
public String getColor() {
return color;
}
// 抽象方法(没有方法体,子类必须实现)
public abstract double getArea();
// 抽象方法
public abstract double getPerimeter();
// 普通方法(可以调用抽象方法)
public void printInfo() {
System.out.println("颜色:" + color +
",面积:" + String.format("%.2f", getArea()) +
",周长:" + String.format("%.2f", getPerimeter()));
}
}
// 具体子类 —— 圆形
public class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
@Override
public double getPerimeter() {
return 2 * Math.PI * radius;
}
}
// 具体子类 —— 矩形
public class Rectangle extends Shape {
private double width;
private double height;
public Rectangle(String color, double width, double height) {
super(color);
this.width = width;
this.height = height;
}
@Override
public double getArea() {
return width * height;
}
@Override
public double getPerimeter() {
return 2 * (width + height);
}
}
public class TestAbstract {
public static void main(String[] args) {
// Shape shape = new Shape("红色"); // ❌ 编译错误:抽象类不能实例化
// 多态:使用父类引用指向子类对象
Shape circle = new Circle("红色", 5);
Shape rectangle = new Rectangle("蓝色", 4, 6);
circle.printInfo();
// 输出:颜色:红色,面积:78.54,周长:31.42
rectangle.printInfo();
// 输出:颜色:蓝色,面积:24.00,周长:20.00
}
}抽象类的特点总结:
- 用
abstract class声明 - 不能直接
new实例化 - 可以包含构造方法(用于子类调用)
- 可以包含普通方法、成员变量、常量、静态方法
- 抽象方法不能有方法体,子类必须实现(除非子类也是抽象类)
- 可以没有抽象方法(但既然不实例化,通常至少有一个抽象方法才有意义)
6. 接口
6.1 接口的定义与实现
接口(Interface)是一种完全抽象的"契约",它定义了一组方法签名,实现类必须提供这些方法的具体实现。Java 使用 interface 关键字定义接口,使用 implements 关键字让类实现接口。
Java 8 之前: 接口中只能包含抽象方法和常量。 Java 8+: 接口中可以包含 default 方法(默认方法)和 static 方法(静态方法)。
// 定义一个接口
public interface Flyable {
// 常量(默认是 public static final)
int MAX_SPEED = 1000;
// 抽象方法(默认是 public abstract)
void fly();
void land();
}
// 另一个接口
public interface Eatable {
void eat();
}
// 实现类:一个类可以实现多个接口
public class Bird implements Flyable, Eatable {
private String name;
public Bird(String name) {
this.name = name;
}
@Override
public void fly() {
System.out.println(name + " 正在飞翔,最高速度 " + Flyable.MAX_SPEED);
}
@Override
public void land() {
System.out.println(name + " 正在降落");
}
@Override
public void eat() {
System.out.println(name + " 正在吃虫子");
}
}
// 实现类:飞机
public class Airplane implements Flyable {
private String model;
public Airplane(String model) {
this.model = model;
}
@Override
public void fly() {
System.out.println(model + " 正在起飞");
}
@Override
public void land() {
System.out.println(model + " 正在降落");
}
}
public class TestInterface {
public static void main(String[] args) {
// 多态:接口引用指向实现类对象
Flyable bird = new Bird("麻雀");
Flyable plane = new Airplane("波音 747");
bird.fly(); // 输出:麻雀 正在飞翔,最高速度 1000
bird.land(); // 输出:麻雀 正在降落
plane.fly(); // 输出:波音 747 正在起飞
plane.land(); // 输出:波音 747 正在降落
// 调用实现类特有的接口方法
if (bird instanceof Eatable) {
((Eatable) bird).eat(); // 输出:麻雀 正在吃虫子
}
// 访问接口常量
System.out.println("最大速度:" + Flyable.MAX_SPEED);
}
}6.2 Java 8+ 默认方法和静态方法
Java 8 为接口引入了 default 方法和 static 方法,使得接口更加强大和灵活。
- 默认方法(default): 在接口中提供默认实现,实现类可以选择重写或不重写。这解决了接口添加新方法时对现有实现类的破坏性问题。
- 静态方法(static): 接口中的工具方法,通过接口名直接调用。
public interface Vehicle {
// 抽象方法
void start();
void stop();
// 默认方法:所有实现类自动继承,也可以选择重写
default void honk() {
System.out.println("车辆鸣笛:嘟嘟!");
}
// 默认方法可以有更复杂的实现
default void printStatus() {
System.out.println("车辆状态检查中...");
start();
System.out.println("车辆运行中...");
stop();
System.out.println("车辆已停止");
}
// 静态方法:通过接口名直接调用
static void serviceReminder() {
System.out.println("请定期维护您的车辆");
}
}
public class Car implements Vehicle {
private String brand;
public Car(String brand) {
this.brand = brand;
}
@Override
public void start() {
System.out.println(brand + " 汽车启动");
}
@Override
public void stop() {
System.out.println(brand + " 汽车停止");
}
// 可以选择重写默认方法
@Override
public void honk() {
System.out.println(brand + " 汽车鸣笛:滴滴!");
}
}
public class Bicycle implements Vehicle {
@Override
public void start() {
System.out.println("自行车开始骑行");
}
@Override
public void stop() {
System.out.println("自行车停止骑行");
}
// Bicycle 不重写 honk(),使用接口的默认实现
}
public class TestDefaultMethod {
public static void main(String[] args) {
Vehicle car = new Car("特斯拉");
Vehicle bike = new Bicycle();
car.honk(); // 输出:特斯拉 汽车鸣笛:滴滴!
bike.honk(); // 输出:车辆鸣笛:嘟嘟!
System.out.println("---");
car.printStatus();
System.out.println("---");
// 调用接口的静态方法
Vehicle.serviceReminder();
}
}关于默认方法的冲突解决: 如果一个类实现了两个接口,两个接口有相同的默认方法,则实现类必须重写该方法来解决冲突。
7. 抽象类 vs 接口对比
以下是抽象类和接口的详细对比表格:
| 对比维度 | 抽象类(abstract class) | 接口(interface) |
|---|---|---|
| 关键字 | abstract class | interface |
| 实例化 | 不能实例化 | 不能实例化 |
| 构造方法 | 可以有构造方法 | 不能有构造方法 |
| 成员变量 | 可以有任何类型的成员变量 | 只能是 public static final 常量 |
| 抽象方法 | 可以有 | 可以有 |
| 普通方法 | 可以有 | Java 8+ 可以有 default 方法 |
| 静态方法 | 可以有 | Java 8+ 可以有 |
| 访问修饰符 | 任意(private、protected、public) | 方法默认 public(Java 9+ 支持 private 方法) |
| 继承/实现 | 单继承(一个类只能继承一个抽象类) | 多实现(一个类可以实现多个接口) |
| 设计理念 | "是什么"(is-a 关系) | "能做什么"(can-do 能力) |
| 适用场景 | 相关类之间有共同的属性和行为模板 | 不相关的类之间共享相同的行为能力 |
选择建议
- 优先使用接口:当需要定义行为契约、需要多实现、或者不相关的类之间共享行为时。
- 使用抽象类:当子类之间有公共的属性和方法实现、需要使用非
public成员、需要构造方法时。 - 两者结合:在实际项目中,抽象类和接口经常配合使用,例如:接口定义行为规范,抽象类提供部分默认实现。
// 典型组合使用示例
public interface Flyable {
void fly();
void land();
}
// 抽象类提供部分默认实现
public abstract class AbstractBird implements Flyable {
protected String name;
public AbstractBird(String name) {
this.name = name;
}
public void eat() {
System.out.println(name + " 在吃东西");
}
// 没有实现 fly(),留给具体子类实现
}
public class Sparrow extends AbstractBird {
public Sparrow(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(name + " 在飞翔");
}
@Override
public void land() {
System.out.println(name + " 在降落");
}
}8. 内部类
内部类(Inner Class)是定义在另一个类内部的类。内部类可以访问外部类的所有成员(包括私有成员)。Java 中有四种内部类:
8.1 成员内部类
成员内部类是定义在外部类中的非静态类,相当于外部类的一个成员。
public class Outer {
private String message = "外部类的私有成员";
// 成员内部类
class Inner {
private int id;
public Inner(int id) {
this.id = id;
}
public void display() {
// 内部类可以直接访问外部类的私有成员
System.out.println("内部类 ID:" + id);
System.out.println("访问外部类:" + message);
}
}
public void createInner() {
Inner inner = new Inner(100);
inner.display();
}
public static void main(String[] args) {
Outer outer = new Outer();
// 方式一:通过外部类方法间接创建
outer.createInner();
// 方式二:直接创建内部类对象
Outer.Inner inner2 = outer.new Inner(200);
inner2.display();
}
}特点:
- 不能定义静态成员(除非是
static final常量) - 可以访问外部类的所有成员(包括 private)
- 创建内部类对象需要先有外部类对象
8.2 静态内部类
使用 static 修饰的内部类,不依赖于外部类的实例。
public class Outer {
private static String staticMessage = "外部类的静态成员";
private String instanceMessage = "外部类的实例成员";
// 静态内部类
static class StaticInner {
private String name;
public StaticInner(String name) {
this.name = name;
}
public void display() {
System.out.println("静态内部类:" + name);
// 只能访问外部类的静态成员
System.out.println("访问外部静态成员:" + staticMessage);
// ❌ 编译错误:不能直接访问外部类的实例成员
// System.out.println(instanceMessage);
}
// 静态内部类可以定义静态成员
public static void staticMethod() {
System.out.println("静态内部类的静态方法");
}
}
public static void main(String[] args) {
// 静态内部类不需要外部类实例
Outer.StaticInner inner = new Outer.StaticInner("测试");
inner.display();
// 调用静态内部类的静态方法
Outer.StaticInner.staticMethod();
}
}特点:
- 不需要外部类实例即可创建
- 只能访问外部类的静态成员
- 可以定义静态成员
8.3 匿名内部类
匿名内部类(Anonymous Inner Class)是没有名字的内部类,通常用于简化代码——当一个接口或抽象类只需要使用一次时。
import java.util.Arrays;
import java.util.Comparator;
public class TestAnonymous {
public static void main(String[] args) {
// 使用匿名内部类实现接口
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类实现的任务正在执行");
}
};
Thread thread = new Thread(task);
thread.start();
// 使用匿名内部类对数组排序
String[] names = {"Charlie", "Alice", "Bob"};
// 匿名内部类实现 Comparator
Arrays.sort(names, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
});
System.out.println("按长度排序:" + Arrays.toString(names));
// 简化:使用 Lambda 表达式(函数式接口的匿名内部类简写)
// 等价于上面 Comparator 的写法
Arrays.sort(names, (s1, s2) -> s2.length() - s1.length());
System.out.println("按长度降序:" + Arrays.toString(names));
}
}特点:
- 没有类名,在创建对象时直接定义类体
- 必须继承一个类或实现一个接口(最多一个)
- 不能定义构造方法
- 通常用于事件监听、线程、回调等场景
- 如果接口只有一个抽象方法(函数式接口),可以用 Lambda 简化
8.4 局部内部类
局部内部类(Local Inner Class)是定义在方法体或代码块内部的类。
public class TestLocalInner {
private String outerField = "外部类字段";
public void demonstrateLocalInner() {
// 局部变量(在 Java 8+ 中,被局部内部类访问的局部变量默认是 effectively final)
String localMessage = "方法中的局部变量";
// 局部内部类:定义在方法内部
class LocalInner {
private int counter;
public LocalInner(int counter) {
this.counter = counter;
}
public void print() {
// 可以访问外部类的成员
System.out.println("外部类字段:" + outerField);
// 可以访问方法中的局部变量
System.out.println("局部变量:" + localMessage);
// 访问自己的成员
System.out.println("计数:" + counter);
}
}
// 在方法内创建和使用局部内部类
LocalInner inner = new LocalInner(42);
inner.print();
// 可以多次使用
LocalInner inner2 = new LocalInner(100);
inner2.print();
}
public static void main(String[] args) {
TestLocalInner test = new TestLocalInner();
test.demonstrateLocalInner();
}
}特点:
- 作用域限定在所在的方法或代码块中
- 可以访问外部类的所有成员
- 可以访问所在方法的局部变量(必须是 effectively final 的)
- 在实际开发中使用较少,适用于在单个方法内封装逻辑
内部类对比总结
| 类型 | 定义位置 | 是否需要外部类实例 | 可访问外部类成员 | 能否定义静态成员 |
|---|---|---|---|---|
| 成员内部类 | 类内部 | 是 | 所有成员 | 否 |
| 静态内部类 | 类内部(static) | 否 | 仅静态成员 | 能 |
| 匿名内部类 | 方法/表达式内 | 取决于上下文 | 所有成员 | 否 |
| 局部内部类 | 方法/代码块内 | 取决于上下文 | 所有成员 | 否 |
小结
面向对象编程是 Java 的灵魂。本文从实践角度出发,系统性地介绍了 Java OOP 的六大核心概念:
- 类与对象:类是模板,对象是实例,构造方法用于初始化对象。
- 封装:通过
private隐藏内部细节,通过 getter/setter 提供受控访问,保障数据安全。 - 继承:通过
extends复用父类代码,super调用父类成员,@Override实现方法重写,构建层次化的类体系。 - 多态:方法重载实现编译时多态,方法重写实现运行时多态。
instanceof用于安全的类型检查与向下转型,让代码更灵活、可扩展。 - 抽象类:用
abstract定义不完整的类,为子类提供公共模板和抽象约束。 - 接口:定义行为契约,支持多实现,Java 8+ 的默认方法和静态方法让接口更强大。抽象类与接口各有适用场景,常配合使用。
- 内部类:成员内部类、静态内部类、匿名内部类和局部内部类各具特点,为代码组织提供了更多灵活性。
掌握这些概念并能灵活运用,是写出高质量 Java 代码的关键。建议读者在实际编码中多练习、多体会,逐步培养面向对象的设计思维。