当前位置:首页 > 技术 > 正文内容

Java面向对象核心概念:继承、多态、抽象与接口深度解析

访客 技术 2026年8月15日 1

一、继承机制

在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关键字示意图

  • this:指代当前对象实例的引用。
  • super:指代当前对象的父类实例部分。

二、多态性

多态(Polymorphism)指同一类型的对象,在不同情境下或不同子类实现中表现出不同的行为形态。

2.1 多态的实现形式与前提

多态的主要表现形式是:父类类型引用指向子类对象

实现多态必须满足以下三个条件:

  1. 存在继承关系。
  2. 父类引用指向子类对象(ParentType obj = new ChildType();)。
  3. 子类重写了父类的方法。
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关键字用于修饰类、方法和变量,表示"最终的"、"不可改变的"。

  • 修饰方法: 表示该方法是最终方法,不能被子类重写。
  • 修饰类: 表示该类是最终类,不能被继承。
  • 修饰变量: 变量变为常量,只能被赋值一次。
    • 修饰基本数据类型:变量存储的数据值一旦赋值后不可改变。
    • 修饰引用数据类型:变量存储的地址值一旦赋值后不可改变,但其指向对象内部的属性可以改变。

五、访问修饰符

访问修饰符用于控制类、方法、变量和构造器的可见性,决定了它们可以被哪些代码访问。

Java访问修饰符对照表

从左到右,访问权限逐渐降低(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单继承的限制)。

接口应用示例图

九、适配器设计模式

当一个接口包含大量抽象方法,而我们只需要使用其中一部分时,直接实现接口会导致代码冗余(需要为空实现所有不用的方法)。适配器设计模式可以解决这个问题。

实现步骤:

  1. 创建一个抽象的"适配器"类(如XXXAdapter),实现目标接口。
  2. 在适配器类中,将接口的所有抽象方法都提供空实现。
  3. 让真正的业务实现类继承这个适配器类,然后只重写其需要的方法即可。

适配器类通常被声明为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("动作执行完毕。");
    }
}

输出示例:

直接创建的匿名内部类执行动作。
--------------------
开始执行传入的动作:
作为方法参数的匿名内部类执行动作。
动作执行完毕。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

linux screen 用法详情 (nohup 的替代方案)

一、screen 是什么?能干嘛?screen 是一个终端复用器,可以:在一个 SSH 会话中开多个“虚拟终端”SSH 断线后,程序仍然在后台运行随时重新连接到原来的会话特别适合:nohup 的替代方案跑脚本 / 爬虫 / 训练模型运维、远程开发二、安装 screen# CentOS / Rocky / Almayum install -y screen# Debian / Ubuntuapt i...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。