Java面向对象编程:封装、继承、抽象、接口和多态
摘要
本文档详细介绍Java面向对象编程的五大核心概念:封装、继承、抽象类、接口和多态。通过理论阐述和代码示例,帮助读者深入理解Java面向对象编程的精髓和实际应用。
目录
封装
定义
封装是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。这是面向对象编程的基本原则之一。
权限修饰符
Java提供了四种访问权限修饰符来控制成员的访问级别:
| 修饰符 | 访问范围 | 描述 |
|---|---|---|
private |
仅本类内部 | 私有的,只在本类中有效 |
default(默认) |
同包内 | 包访问权限,同一包内可访问 |
protected |
同包内 + 子类 | 保护的,只有子类可以调用 |
public |
所有地方 | 公开的,对所有用户开放 |
权限修饰符使用规则
类的权限修饰符
- 类 只能使用
public和default(默认)权限修饰符 public类可以被任何地方访问default类只能在同一包内被访问
成员的权限修饰符
- 成员变量和方法可以使用所有四种权限修饰符
- 权限修饰符置于类的成员定义前,限定其他对象的访问权限
封装示例
java
public class Student {
// 私有属性,外部无法直接访问
private String name;
private int age;
// 公共的getter方法
public String getName() {
return name;
}
// 公共的setter方法,可以添加验证逻辑
public void setName(String name) {
if (name != null && !name.trim().isEmpty()) {
this.name = name;
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age > 0 && age < 150) {
this.age = age;
}
}
}
继承
定义
继承 是类与类之间的关系,子类可以直接使用父类的方法和属性。在Java中使用 extends 关键字实现继承。
java
子类 extends 父类
继承的作用
- 提高代码的复用性 - 子类可以重用父类的代码
- 建立类与类之间的关系 - 形成继承层次结构
继承使用注意事项
⚠️ 重要原则:千万不要为了获取其他类的功能、简化代码而继承!
- 必须是类与类之间有关系或有共性才可以继承
- 继承应该体现"is-a"关系
单继承
Java只支持单继承,不支持多继承:
java
// ✅ 正确:单继承
class Dog extends Animal { }
// ❌ 错误:多继承(Java不支持)
// class Dog extends Animal, Pet { }
为什么不支持多继承?
- 多继承容易带来隐患
- 当多个父类定义相同功能但内容不同时,子类对象不确定运行哪个
- Java通过接口的多实现来完成多继承的表示
成员变量访问
当子类中出现与父类同名的成员变量时:
| 关键字 | 访问对象 | 说明 |
|---|---|---|
this |
本类对象 | 访问子类中的同名变量 |
super |
父类对象 | 访问父类中的同名变量 |
java
class Parent {
String name = "父类";
}
class Child extends Parent {
String name = "子类";
public void showNames() {
System.out.println("子类名称: " + this.name); // 输出: 子类
System.out.println("父类名称: " + super.name); // 输出: 父类
}
}
方法重写(Override)
当子类中出现与父类完全相同的方法时,子类对象调用该方法会运行子类方法的内容。
重载 vs 重写对比
| 特性 | 重载(Overload) | 重写(Override) |
|---|---|---|
| 英文名 | Overload | Override |
| 发生位置 | 同一个类中 | 父子类之间 |
| 方法名 | 相同 | 相同 |
| 参数列表 | 不同 | 相同 |
| 返回值 | 可以不同 | 必须相同(或协变) |
| 访问修饰符 | 可以不同 | 不能更严格 |
java
class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
@Override // 建议使用注解
public void makeSound() {
System.out.println("汪汪汪");
}
}
构造函数与继承
重要规则
- 子类对象初始化时,父类的构造函数也会运行
- 子类所有构造函数默认第一行都有隐式语句:
super(); - 会优先访问父类中的无参构造函数
构造函数调用顺序
java
class Parent {
public Parent() {
System.out.println("父类无参构造函数");
}
public Parent(String name) {
System.out.println("父类有参构造函数: " + name);
}
}
class Child extends Parent {
public Child() {
// 隐式调用 super();
System.out.println("子类无参构造函数");
}
public Child(String name) {
super(name); // 显式调用父类有参构造函数
System.out.println("子类有参构造函数");
}
}
特殊情况处理
当父类没有无参构造函数时:
java
class Parent {
// 只有有参构造函数,没有无参构造函数
public Parent(String name) {
this.name = name;
}
}
class Child extends Parent {
public Child() {
super("默认名称"); // 必须显式调用父类构造函数
}
public Child(String name) {
super(name);
}
}
抽象类
定义
当多个类中出现相同功能,但功能主体不同时,可以向上抽取。只抽取功能定义,而不抽取功能主体,这就形成了抽象类。
abstract修饰符
| 修饰对象 | 描述 | 特点 |
|---|---|---|
| 抽象类 | 由abstract修饰的类 |
无法被直接实例化 |
| 抽象方法 | 由abstract修饰的方法 |
只有声明,没有实现 |
抽象类的实现
抽象类必须有子类完全重写所有抽象方法才能使用:
java
// 抽象类
abstract class Animal {
// 普通方法
public void sleep() {
System.out.println("动物在睡觉");
}
// 抽象方法
public abstract void makeSound();
public abstract void move();
}
// 具体实现类
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("汪汪汪");
}
@Override
public void move() {
System.out.println("狗在跑");
}
}
抽象类特点
- ✅ 用
abstract修饰 - ✅ 类中可以有抽象方法
- ❌ 无法被直接创建对象
- ✅ 可以有普通方法和成员变量
- ✅ 可以有构造方法(供子类调用)
抽象类使用示例
java
abstract class Shape {
protected String color;
public Shape(String color) {
this.color = color;
}
// 抽象方法
public abstract double getArea();
public abstract double getPerimeter();
// 普通方法
public void displayInfo() {
System.out.println("形状颜色: " + color);
System.out.println("面积: " + getArea());
System.out.println("周长: " + getPerimeter());
}
}
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;
}
}
接口
定义
接口 可以认为是一个特殊的抽象类,接口中的方法全部都是抽象方法。子类使用 implements 关键字来实现接口。
接口成员定义
接口中的成员有固定的修饰符:
| 成员类型 | 默认修饰符 | 说明 |
|---|---|---|
| 常量 | public static final |
必须初始化,不可修改 |
| 方法 | public abstract |
抽象方法,无实现 |
为什么是这些修饰符?
- public:接口需要被实现,必须公开
- static:接口不能被实例化,常量只能是静态的
- final:接口不能有对象,常量必须是固定的
- abstract:接口中只有抽象方法
接口定义示例
java
interface Drawable {
// 常量(自动加上 public static final)
int MAX_SIZE = 100;
// 抽象方法(自动加上 public abstract)
void draw();
void resize(int width, int height);
}
接口特点
- ❌ 接口不能创建对象(因为有抽象方法)
- ✅ 必须被子类完全实现所有抽象方法
- ✅ 支持多实现(一个类可以实现多个接口)
- ✅ 接口可以继承接口(支持多继承)
接口多实现示例
java
interface Flyable {
void fly();
}
interface Swimmable {
void swim();
}
// 类可以实现多个接口
class Duck implements Flyable, Swimmable {
@Override
public void fly() {
System.out.println("鸭子在飞");
}
@Override
public void swim() {
System.out.println("鸭子在游泳");
}
}
接口继承接口
java
interface A {
void methodA();
}
interface B {
void methodB();
}
// 接口可以继承多个接口
interface C extends A, B {
void methodC();
}
// 实现类必须实现所有方法
class MyClass implements C {
@Override
public void methodA() { /* 实现A */ }
@Override
public void methodB() { /* 实现B */ }
@Override
public void methodC() { /* 实现C */ }
}
⚠️ 注意:多个父接口不能有不同返回值类型的同名方法。
多态
定义
多态可以理解为事物存在的多种体现形态。在Java中,父类的引用可以接收子类对象。
多态的体现
核心表现:父类的引用指向子类对象
java
// 最简单的例子
List<String> list = new ArrayList<>(); // List是ArrayList的父接口
// 动物多态示例
Animal animal1 = new Dog(); // 父类引用指向子类对象
Animal animal2 = new Cat(); // 同一个引用类型,不同的实际对象
多态的前提
- ✅ 必须有继承关系(继承或实现)
- ✅ 必须有方法重写(这样多态使用才有意义)
多态的好处
提高程序的扩展性:前期定义父类引用,后期可以调用新的子类对象。
java
public class AnimalManager {
// 面向父类编程,支持所有子类
public void makeAnimalSound(Animal animal) {
animal.makeSound(); // 具体调用哪个取决于传入的对象类型
}
}
// 使用时的扩展性
AnimalManager manager = new AnimalManager();
manager.makeAnimalSound(new Dog()); // 输出: 汪汪汪
manager.makeAnimalSound(new Cat()); // 输出: 喵喵喵
manager.makeAnimalSound(new Bird()); // 新增类型,无需修改代码
多态的局限性
只能访问父类成员:多态中只能使用父类的引用访问父类中的成员,无法获取子类特有成员。
java
class Animal {
public void eat() { System.out.println("动物吃东西"); }
}
class Dog extends Animal {
public void eat() { System.out.println("狗吃骨头"); }
public void bark() { System.out.println("汪汪汪"); } // 子类特有方法
}
public class TestPolymorphism {
public static void main(String[] args) {
Animal animal = new Dog();
animal.eat(); // ✅ 可以调用,运行子类重写的方法
// animal.bark(); // ❌ 编译错误!父类引用无法调用子类特有方法
}
}
多态中成员调用规则
方法调用规则
| 情况 | 调用规则 | 示例 |
|---|---|---|
| 子类重写了父类方法 | 调用子类方法 | B子类的method1 |
| 子类没有重写 | 调用父类方法 | A父类的method2 |
| 父类没有该方法 | 编译时错误 | 无法编译 |
| 静态方法 | 调用父类静态方法 | A父类的static方法 |
详细示例
java
// 定义父类A
class A {
void method1() {
System.out.println("A父类的method1");
}
void method2() {
System.out.println("A父类的method2");
}
static void methodS() {
System.out.println("A父类的static方法");
}
}
// 定义子类B继承了A
class B extends A {
@Override
void method1() {
System.out.println("B子类的method1");
}
void method3() {
System.out.println("B子类的method3");
}
static void methodS() {
System.out.println("B子类的static方法");
}
}
public class PolymorphismDemo {
public static void main(String[] args) {
A a = new B();
a.method1(); // 输出: B子类的method1 (子类重写了)
a.method2(); // 输出: A父类的method2 (子类没重写)
a.methodS(); // 输出: A父类的static方法 (静态方法看引用类型)
// a.method3(); // 编译错误!父类引用无法获取子类特有成员
}
}
成员变量调用规则
重要 :成员变量不存在重写,无论编译还是运行都参考引用类型(父类)。
java
class A {
String s = "A父类";
}
class B extends A {
String s = "B子类";
}
public class PolymorphismDemo {
public static void main(String[] args) {
A a = new B();
System.out.println(a.s); // 输出: A父类
}
}
多态的应用
类型转换
向上转型(自动)
java
Animal animal = new Dog(); // 自动向上转型
向下转型(强制)
java
Animal animal = new Dog();
Dog dog = (Dog) animal; // 强制向下转型
dog.bark(); // 现在可以调用子类特有方法了
安全的向下转型
java
Animal animal = new Dog();
// 使用 instanceof 判断类型
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.bark();
} else if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.meow();
}
多态实际应用示例
java
// 图形绘制系统
abstract class Shape {
public abstract void draw();
public abstract double getArea();
}
class Circle extends Shape {
private double radius;
public Circle(double radius) { this.radius = radius; }
@Override
public void draw() { System.out.println("绘制圆形"); }
@Override
public double getArea() { return Math.PI * radius * radius; }
}
class Rectangle extends Shape {
private double width, height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public void draw() { System.out.println("绘制矩形"); }
@Override
public double getArea() { return width * height; }
}
// 图形管理器 - 体现多态的威力
class ShapeManager {
public void drawAllShapes(Shape[] shapes) {
for (Shape shape : shapes) {
shape.draw(); // 多态调用,具体调用哪个draw取决于实际对象类型
System.out.println("面积: " + shape.getArea());
}
}
}
public class PolymorphismApplication {
public static void main(String[] args) {
Shape[] shapes = {
new Circle(5.0),
new Rectangle(4.0, 6.0),
new Circle(3.0)
};
ShapeManager manager = new ShapeManager();
manager.drawAllShapes(shapes); // 多态的实际应用
}
}
总结
Java面向对象五大特性关系图
markdown
面向对象编程
├── 封装(Encapsulation)
│ ├── 隐藏内部实现
│ ├── 提供公共接口
│ └── 控制访问权限
├── 继承(Inheritance)
│ ├── 代码复用
│ ├── 建立类层次
│ └── 单继承限制
├── 抽象(Abstraction)
│ ├── 抽象类
│ ├── 抽象方法
│ └── 模板模式
├── 接口(Interface)
│ ├── 规范定义
│ ├── 多实现
│ └── 接口继承
└── 多态(Polymorphism)
├── 编译时多态(重载)
├── 运行时多态(重写)
└── 类型转换
核心概念对比
| 概念 | 关键字 | 主要作用 | 特点 |
|---|---|---|---|
| 封装 | private/protected/public |
隐藏实现细节 | 控制访问权限 |
| 继承 | extends |
代码复用 | 单继承,is-a关系 |
| 抽象 | abstract |
定义规范 | 不能实例化 |
| 接口 | interface/implements |
规范约束 | 多实现,全抽象 |
| 多态 | 重写@Override |
灵活扩展 | 父类引用指向子类对象 |
设计原则
- 封装原则:高内聚,低耦合
- 继承原则:遵循is-a关系,避免为了复用而继承
- 抽象原则:面向抽象编程,而非具体实现
- 接口原则:依赖抽象,而非具体
- 多态原则:开闭原则,对扩展开放,对修改关闭
实战建议
-
合理使用封装:
- 成员变量设为private
- 提供public的getter/setter方法
- 在setter中添加验证逻辑
-
正确使用继承:
- 确保是is-a关系
- 避免为了复用而继承
- 优先使用组合而非继承
-
善用抽象和接口:
- 抽象类用于有共同实现的场景
- 接口用于定义规范和约束
- 面向接口编程
-
充分利用多态:
- 参数和返回值使用父类型
- 利用多态实现可扩展的设计
- 合理使用类型转换
通过深入理解和实践这五大特性,能够编写出更加优雅、可维护和可扩展的Java代码。