Java面向对象核心概念:继承、多态、抽象与接口深度解析
一、继承机制
在Java中,继承是面向对象三大特征之一,它允许一个类(子类)获取另一个类(父类)的属性和方法。这有助于实现代码的复用和层次结构的建立。
1.1 成员变量的访问规则
当子类和父类中存在同名的成员变量时,访问的优先级遵循"就近原则":
- 直接访问变量名(
fieldName):首先在当前方法所在的局部作用域查找,若无则向上查找当前类成员变量,再向上查找父类成员变量。 - 使用
this关键字(this.fieldName):从当前对象的成员变量开始查找,若无则向上查找父类成员变量。 - 使用
super关键字(super.fieldName):直接从父类的成员变量开始查找。
1.2 方法重写(Override)
当子类需要修改或扩展父类方法的行为时,可以对父类方法进行重写。重写方法必须保持与父类方法相同的名称、参数列表和返回类型(或其子类型)。建议使用@Override注解,编译器会检查是否符合重写规则。
方法重写的本质是子类在运行时替换了父类的相应方法实现,这意味着当通过多态引用调用该方法时,会执行子类的实现。

package com.example.inheritance;
// 父类:基础人物角色
class Character {
public void performAction() {
System.out.println("角色执行默认动作。");
}
public void sayHello() {
System.out.println("角色说:你好!");
}
}
// 子类:玩家角色
class PlayerCharacter extends Character {
@Override
public void performAction() {
System.out.println("玩家角色执行攻击动作。");
}
public void demonstrateActions() {
System.out.println("--- 玩家角色行动演示 ---");
this.performAction(); // 调用子类重写的方法
super.performAction(); // 调用父类的原始方法
this.sayHello(); // 调用继承的方法
}
}
public class InheritanceDemo {
public static void main(String[] args) {
PlayerCharacter player = new PlayerCharacter();
player.demonstrateActions();
}
}
输出示例:
--- 玩家角色行动演示 ---
玩家角色执行攻击动作。
角色执行默认动作。
角色说:你好!
1.3 构造器的继承特性
- 父类的构造器不会被子类继承。
- 子类的所有构造器在执行前,都会默认首先调用父类的无参构造器。如果父类没有无参构造器,子类必须显式调用父类的有参构造器。
- 子类构造器的第一行默认包含
super(),即使不手动书写也存在,且必须是构造器中的第一条语句。 - 若需调用父类的有参构造器,必须手动在子类构造器中写出
super(...),并放置在第一行。

this:指代当前对象实例的引用。super:指代当前对象的父类实例部分。
二、多态性
多态(Polymorphism)指同一类型的对象,在不同情境下或不同子类实现中表现出不同的行为形态。
2.1 多态的实现形式与前提
多态的主要表现形式是:父类类型引用指向子类对象。
实现多态必须满足以下三个条件:
- 存在继承关系。
- 父类引用指向子类对象(
ParentType obj = new ChildType();)。 - 子类重写了父类的方法。
package com.example.polymorphism;
// 基类:员工
public class Employee {
private String name;
private int employeeId;
public Employee() {}
public Employee(String name, int employeeId) {
this.name = name;
this.employeeId = employeeId;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getEmployeeId() { return employeeId; }
public void setEmployeeId(int employeeId) { this.employeeId = employeeId; }
public void introduce() {
System.out.println("我是员工: " + this.getName() + ", 工号: " + this.getEmployeeId());
}
}
// 派生类:开发人员
package com.example.polymorphism;
public class Developer extends Employee {
public Developer() {}
public Developer(String name, int employeeId) {
super(name, employeeId);
}
@Override
public void introduce() {
System.out.println("我是开发人员: " + this.getName() + ", 工号: " + this.getEmployeeId() + ",我擅长编程。");
}
}
// 派生类:测试人员
package com.example.polymorphism;
public class Tester extends Employee {
public Tester() {}
public Tester(String name, int employeeId) {
super(name, employeeId);
}
@Override
public void introduce() {
System.out.println("我是测试人员: " + this.getName() + ", 工号: " + this.getEmployeeId() + ",我负责软件质量。");
}
}
// 多态测试类
package com.example.polymorphism;
public class PolymorphismTest {
public static void main(String[] args) {
Employee dev = new Developer("张码", 1001); // 父类引用指向子类对象
Employee tester = new Tester("李测", 2002); // 父类引用指向子类对象
// 调用统一方法,表现出不同行为
processEmployee(dev);
processEmployee(tester);
}
// 接受父类类型作为参数,可以处理所有子类对象
public static void processEmployee(Employee e) {
e.introduce(); // 运行时根据实际对象类型调用不同的introduce方法
}
}
输出示例:
我是开发人员: 张码, 工号: 1001,我擅长编程。
我是测试人员: 李测, 工号: 2002,我负责软件质量。
2.2 多态中成员的访问特点
- 访问成员变量: 编译时期看父类,运行时期也看父类。即无论实际对象是哪个子类,调用的都是引用类型(父类)中定义的成员变量。
- 访问成员方法: 编译时期看父类,运行时期看子类(实际对象)。即如果子类重写了方法,运行时会调用子类的实现。
2.3 多态的优缺点与instanceof
优势:
- 解耦与扩展性: 父类型作为参数,可接受所有子类对象,提高代码的灵活性和可维护性。
- 代码简洁: 统一的接口处理不同类型的对象。
弊端:
- 通过父类引用无法直接访问子类特有的成员(变量或方法),因为编译时只能识别父类中定义的成员。
instanceof关键字:
为了解决多态的弊端,当需要访问子类特有功能时,可以使用instanceof关键字判断对象的实际类型,然后进行强制类型转换。
package com.example.polymorphism;
// 基类:动物
class Animal {
public void makeSound() {
System.out.println("动物发出声音。");
}
}
// 派生类:狗
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("狗叫:汪汪!");
}
public void guardHouse() {
System.out.println("狗在看家。");
}
}
// 派生类:猫
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("猫叫:喵喵!");
}
public void catchMouse() {
System.out.println("猫在抓老鼠。");
}
}
public class TypeCastingDemo {
public static void main(String[] args) {
Animal myPet = new Dog(); // 多态引用
myPet.makeSound(); // 调用子类重写的方法
// 传统方式判断类型并转换
if (myPet instanceof Dog) {
Dog specificDog = (Dog) myPet; // 强制类型转换
specificDog.guardHouse(); // 调用子类特有方法
} else if (myPet instanceof Cat) {
Cat specificCat = (Cat) myPet;
specificCat.catchMouse();
} else {
System.out.println("未知宠物类型。");
}
System.out.println("--- Java 14+ instanceof 模式匹配 ---");
// Java 14+ 的 instanceof 模式匹配,简化代码
if (myPet instanceof Dog specificDog) { // 同时判断并赋值
specificDog.guardHouse();
} else if (myPet instanceof Cat specificCat) {
specificCat.catchMouse();
} else {
System.out.println("未知宠物类型。");
}
}
}
输出示例:
狗叫:汪汪!
狗在看家。
--- Java 14+ instanceof 模式匹配 ---
狗在看家。
三、包(Package)
包是Java中用来组织和管理类、接口和其他类型的一种机制,类似于操作系统中的文件夹。
- 作用: 避免类名冲突,提供更好的模块化和访问控制。
- 使用规则:
- 同一个包中的类可以直接使用,无需导入。
java.lang包下的类(如String,System)无需导入。- 其他包中的类在使用前需要通过
import语句导入。 - 若不同包中存在同名类,需要使用全限定类名(包名.类名)来区分。
四、final关键字
final关键字用于修饰类、方法和变量,表示"最终的"、"不可改变的"。
- 修饰方法: 表示该方法是最终方法,不能被子类重写。
- 修饰类: 表示该类是最终类,不能被继承。
- 修饰变量: 变量变为常量,只能被赋值一次。
- 修饰基本数据类型:变量存储的数据值一旦赋值后不可改变。
- 修饰引用数据类型:变量存储的地址值一旦赋值后不可改变,但其指向对象内部的属性可以改变。
五、访问修饰符
访问修饰符用于控制类、方法、变量和构造器的可见性,决定了它们可以被哪些代码访问。

从左到右,访问权限逐渐降低(public > protected > default (包私有) > private)。
六、代码块
代码块是一组用大括号{}括起来的语句。
- 局部代码块: 定义在方法内部,生命周期最短,用于限制变量作用域。
- 构造代码块(实例初始化块): 定义在类成员位置(方法之外、类之内),没有
static修饰。在每次创建对象时,先于构造器执行。 - 静态代码块: 使用
static{}修饰,定义在类成员位置。它在类加载时自动执行,且只执行一次。常用于在类加载时进行静态资源的初始化。
package com.example.blocks;
public class Product {
private String modelName;
private double price;
static {
System.out.println("--- 静态代码块执行: Product类被加载,进行静态资源初始化 ---");
// 可以在这里加载驱动,读取配置文件等
}
{ // 构造代码块
System.out.println("--- 构造代码块执行: 创建Product对象时,在构造器之前执行 ---");
}
public Product() {
System.out.println("--- 无参构造器执行 ---");
this.modelName = "未知型号";
this.price = 0.0;
}
public Product(String modelName, double price) {
System.out.println("--- 有参构造器执行 ---");
this.modelName = modelName;
this.price = price;
}
public String getModelName() { return modelName; }
public void setModelName(String modelName) { this.modelName = modelName; }
public double getPrice() { return price; }
public void setPrice(double price) { this.price = price; }
}
package com.example.blocks;
public class BlockDemo {
public static void main(String[] args) {
System.out.println("Main方法开始。");
Product p1 = new Product();
System.out.println("创建p1完成。");
System.out.println("--------------------");
Product p2 = new Product("Laptop Pro", 1299.99);
System.out.println("创建p2完成。");
System.out.println("Main方法结束。");
}
}
输出示例:
--- 静态代码块执行: Product类被加载,进行静态资源初始化 ---
Main方法开始。
--- 构造代码块执行: 创建Product对象时,在构造器之前执行 ---
--- 无参构造器执行 ---
创建p1完成。
--------------------
--- 构造代码块执行: 创建Product对象时,在构造器之前执行 ---
--- 有参构造器执行 ---
创建p2完成。
Main方法结束。
七、抽象类与抽象方法
当父类中的某个方法需要子类提供具体实现,但在父类中无法给出通用实现时,该方法可以被定义为抽象方法。如果一个类中包含抽象方法,那么该类必须被声明为抽象类。
- 抽象方法定义:
public abstract 返回值类型 方法名(参数列表);(没有方法体)。 - 抽象类定义:
public abstract class 类名 {}。

抽象类的特点:
- 抽象类不能直接实例化。
- 抽象类可以包含抽象方法和普通方法、成员变量、构造器。
- 子类继承抽象类时,必须重写抽象父类中的所有抽象方法,否则子类也必须声明为抽象类。
八、接口
接口是行为的抽象,它定义了一组规范,描述了类应该具有哪些行为,但不提供这些行为的具体实现。
8.1 接口的定义与使用
- 定义: 使用
interface关键字,如public interface InterfaceName {}。 - 实例化: 接口不能直接实例化。
- 实现: 类通过
implements关键字来实现接口,如public class ClassName implements InterfaceName {}。 - 实现类: 实现接口的类被称为实现类(或接口的子类)。实现类必须重写接口中的所有抽象方法,或者声明为抽象类。

package com.example.interfaces;
// 定义一个可飞行接口
public interface Flyable {
public abstract void takeOff(); // 起飞
public abstract void fly(); // 飞行
public abstract void land(); // 降落
}
// 定义一个鸟类,实现可飞行接口
class Bird implements Flyable {
private String species;
public Bird(String species) {
this.species = species;
}
@Override
public void takeOff() {
System.out.println(species + "扇动翅膀起飞。");
}
@Override
public void fly() {
System.out.println(species + "在空中自由飞翔。");
}
@Override
public void land() {
System.out.println(species + "平稳降落在地面。");
}
}
public class InterfaceDemo {
public static void main(String[] args) {
Flyable eagle = new Bird("老鹰");
eagle.takeOff();
eagle.fly();
eagle.land();
}
}
输出示例:
老鹰扇动翅膀起飞。
老鹰在空中自由飞翔。
老鹰平稳降落在地面。
8.2 接口中成员的特点

8.3 接口与类之间的关系

- 类与类:单继承。
- 类与接口:多实现(一个类可以实现多个接口)。
- 接口与接口:多继承(一个接口可以继承多个接口)。
8.4 JDK 8 之后接口新增的方法
为解决接口升级问题和增强接口功能,JDK 8 以后接口可以定义非抽象方法。
- 默认方法(Default Method):
- 使用
default关键字修饰:public default 返回值类型 方法名(参数列表) { /* 方法体 */ }。 - 不是抽象方法,因此实现类不强制重写。重写时需去除
default关键字。 - 若实现类实现多个接口,且多个接口中存在同名默认方法,实现类必须重写该方法以解决冲突。
- 使用
- 静态方法(Static Method):
- 使用
static关键字修饰:public static 返回值类型 方法名(参数列表) { /* 方法体 */ }。 - 只能通过接口名直接调用,不能通过实现类对象调用。
- 使用
package com.example.interfaces;
public interface NewFeaturesInterface {
public abstract void mandatoryMethod();
// JDK 8 默认方法
public default void optionalAction() {
System.out.println("NewFeaturesInterface: 执行一个默认的可选操作。");
}
// JDK 8 静态方法
public static void interfaceInfo() {
System.out.println("NewFeaturesInterface: 这是一个包含新特性的接口。");
}
}
class MyClass implements NewFeaturesInterface {
@Override
public void mandatoryMethod() {
System.out.println("MyClass: 实现强制方法。");
}
// 可以选择重写默认方法
@Override
public void optionalAction() {
System.out.println("MyClass: 重写了接口的默认可选操作。");
}
}
public class InterfaceNewFeaturesDemo {
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.mandatoryMethod();
obj.optionalAction(); // 调用实现类重写后的默认方法
NewFeaturesInterface.interfaceInfo(); // 通过接口名调用静态方法
// obj.interfaceInfo(); // 错误:不能通过对象调用接口静态方法
}
}
输出示例:
MyClass: 实现强制方法。
MyClass: 重写了接口的默认可选操作。
NewFeaturesInterface: 这是一个包含新特性的接口。
8.5 JDK 9 之后接口新增的方法
- 私有方法(Private Method):
- 用于在接口内部封装共同的逻辑,供默认方法或静态方法调用,不暴露给外部实现类。
- 分为实例私有方法:
private 返回值类型 方法名(参数列表) { /* 方法体 */ }。 - 和静态私有方法:
private static 返回值类型 方法名(参数列表) { /* 方法体 */ }。 - 私有方法不能被外部直接调用,也不能被实现类继承或重写。
8.6 接口的应用场景
接口广泛应用于:
- 定义规范和行为。
- 实现多态和解耦。
- 作为回调机制的基础。
- 实现插件式架构。
- 实现多重继承的效果(弥补Java单继承的限制)。

九、适配器设计模式
当一个接口包含大量抽象方法,而我们只需要使用其中一部分时,直接实现接口会导致代码冗余(需要为空实现所有不用的方法)。适配器设计模式可以解决这个问题。
实现步骤:
- 创建一个抽象的"适配器"类(如
XXXAdapter),实现目标接口。 - 在适配器类中,将接口的所有抽象方法都提供空实现。
- 让真正的业务实现类继承这个适配器类,然后只重写其需要的方法即可。
适配器类通常被声明为abstract,以防止其被直接实例化。
十、内部类
内部类(Inner Class)是定义在另一个类内部的类,它提供了更好的封装性,并可以访问外部类的成员。

10.1 成员内部类
定义在外部类成员位置(方法之外,类之内)的类,它是外部类的一个成员,可以被private, public, protected, default以及static修饰。
- 创建对象:
OuterClass.InnerClass innerObj = new OuterClass().new InnerClass(); - 内部类可以访问外部类的所有成员(包括私有)。
- 外部类需要通过内部类对象来访问内部类的成员。

10.2 静态内部类
使用static关键字修饰的成员内部类。它不依赖于外部类的实例,可以直接通过外部类名访问。
- 创建对象:
OuterClass.StaticInnerClass innerObj = new OuterClass.StaticInnerClass(); - 静态内部类只能直接访问外部类的静态成员。

10.3 局部内部类
定义在方法内部、代码块内部的类。作用域仅限于定义它的方法或代码块。
- 不能使用任何访问修饰符(
public,private等),也不能使用static修饰。 - 可以访问外部类的所有成员以及所在方法中被
final修饰(或事实上的final)的局部变量。

10.4 匿名内部类
一种没有名称的内部类,通常用于简化代码,在只需要使用一次某个接口的实现或类的子类时非常有用。
- 格式:
new 父类名/接口名() { // 重写/实现方法 }。 - 它实际上是一个匿名子类(或匿名实现类)的对象。
- 使用场景: 当方法的参数是接口或类类型时,可以直接传入匿名内部类的实例。
package com.example.innerclasses;
// 定义一个行为接口
interface ActionPerformer {
void perform();
}
public class AnonymousInnerClassDemo {
public static void main(String[] args) {
// 1. 直接创建并使用匿名内部类对象
new ActionPerformer() {
@Override
public void perform() {
System.out.println("直接创建的匿名内部类执行动作。");
}
}.perform(); // 直接调用其方法
System.out.println("--------------------");
// 2. 将匿名内部类对象作为参数传递给方法
executeAction(new ActionPerformer() {
@Override
public void perform() {
System.out.println("作为方法参数的匿名内部类执行动作。");
}
});
}
public static void executeAction(ActionPerformer action) {
System.out.println("开始执行传入的动作:");
action.perform();
System.out.println("动作执行完毕。");
}
}
输出示例:
直接创建的匿名内部类执行动作。
--------------------
开始执行传入的动作:
作为方法参数的匿名内部类执行动作。
动作执行完毕。