面向对象编程 (OOP):深入理解继承、多态和抽象

1. 简介

        面向对象编程 (OOP) 是一种强大的编程范式,它通过将程序组织成对象的集合来简化软件设计和开发。与传统的程序设计方法相比,OOP 提供了一种更自然、更易于理解和维护的方式来构建复杂的软件系统。OOP 的核心概念包括:对象、类、继承、多态、封装。本文将深入探讨这些概念,重点讲解继承、多态、抽象类和接口,以及它们在 Java 中的应用。

2. 栈空间和堆空间

        在深入了解 OOP 之前,我们需要先了解 Java 中的内存管理机制,尤其是栈空间和堆空间。

2.1 栈空间 (Stack)

  • 定义和作用: 栈空间用于存储局部变量、方法参数和函数调用信息。 它是 Java 程序运行时最基本的内存区域之一。
  • LIFO 原理: 栈空间遵循先进后出 (LIFO) 的原则,就像一个叠放盘子的架子,最后放进去的盘子最先被拿出来。
  • 存储数据类型: 栈空间主要存储以下数据类型:
    • 局部变量: 定义在方法内部的变量,例如 int age = 25;
    • 方法参数: 传递给方法的变量,例如 void calculateSum(int a, int b) { ... } 中的 a 和 b
    • 函数调用信息: 包括方法调用时的局部变量地址、返回地址等。
  • 特点:
    • 栈空间大小通常较小,而且速度较快。
    • 栈空间的分配和回收由 Java 虚拟机 (JVM) 自动管理,程序员不需要手动进行操作。

2.2 堆空间 (Heap)

  • 定义和作用: 堆空间用于存储对象和数组。它是 Java 程序中用于动态内存分配的主要区域。
  • 特点:
    • 堆空间的空间大小通常比栈空间大得多。
    • 堆空间的分配和回收由垃圾回收器 (Garbage Collector) 自动管理,程序员一般不需要手动进行操作。
    • 堆空间通常比栈空间速度慢。

2.3 栈空间和堆空间的交互

当创建一个对象时,会发生以下步骤:

  1. 栈空间: 分配一个引用变量,该变量指向堆空间中对象的地址。例如:Car myCar = new Car(); 中的 myCar
  2. 堆空间: 为新创建的对象分配一块内存块,其中包含对象的属性和方法。
  3. 连接: 引用变量指向堆空间中分配的内存块,这样就可以通过引用变量访问对象。
// 1. 栈空间:
// 创建一个 Car 类对象,并分配了一个引用变量 myCar。
Car myCar = new Car(); 

// 2. 堆空间:
// 将新创建的 Car 对象的属性和方法存储在堆空间中。
// 这里假设 Car 有两个属性:color 和 model。
// myCar 的值指向堆空间中对象的地址。
// 此时 myCar 就是一个指向堆空间对象的引用。
//         堆空间
// +-----+-----+---------+     // 
// | color| model|  ...   |     //
// +-----+-----+---------+
//       ^
//       |
//       myCar(栈空间)

3. 面向对象编程的基础

3.1 对象和类

  • 对象: 现实世界中事物的抽象表示。在编程中,对象是数据(属性)和操作数据的方法(行为)的封装。例如,一个 "汽车" 对象可以包含属性(例如颜色、品牌、型号、速度)和方法(例如启动、加速、刹车)。
  • 类: 创建对象的模板或蓝图。它定义了对象的属性和方法。例如,"汽车" 类可以定义所有汽车共有的属性和方法,然后通过这个类创建多个不同的汽车对象。

用 Java 代码表示:

class Car {
    String color;
    String brand;
    String model;
    int speed;

    void start() {
        System.out.println("汽车启动");
    }

    void accelerate() {
        System.out.println("汽车加速");
    }

    void brake() {
        System.out.println("汽车刹车");
    }
}

public class Main {
    public static void main(String[] args) {
        Car myCar = new Car();
        myCar.color = "红色";
        myCar.brand = "宝马";
        myCar.model = "3系";
        myCar.start();
        myCar.accelerate();
    }
}

3.2 封装

  • 概念: 将对象的属性和方法结合起来,并隐藏对象的内部实现细节,只暴露接口供外部访问。
  • 目的:
    • 提高代码安全性: 防止外部代码直接修改对象的私有属性。
    • 提升代码可维护性: 修改对象的内部实现细节不会影响外部代码的使用。
    • 增强代码可重用性: 可以根据需要创建不同的对象,而无需关注其内部实现细节。

访问修饰符:

修饰符描述访问范围
public任何地方都可以访问整个程序
private只有类内部可以访问类内部
protected继承的类和同一个包内的类可以访问继承类和同一个包
default (无修饰符)只有同一个包内的类可以访问同一个包

用 Java 代码示例:

class Person {
    private String name; // 私有属性,只能在 Person 类内部访问
    private int age; // 私有属性,只能在 Person 类内部访问

    public String getName() { // 公共方法,可以在任何地方访问
        return name;
    }

    public void setName(String name) { // 公共方法,可以在任何地方访问
        this.name = name;
    }

    public int getAge() { // 公共方法,可以在任何地方访问
        return age; 
    }

    public void setAge(int age) { // 公共方法,可以在任何地方访问
        this.age = age;
    }
}

4. 继承

  • 概念: 继承是 OOP 中一个重要的特性,它允许一个类继承另一个类的属性和方法。继承建立了 “is-a” 关系,子类拥有父类的所有属性和方法,并且可以添加自己的属性和方法。
  • 目的:
    • 代码复用: 避免重复编写相同的功能代码。
    • 可扩展性: 通过继承,可以在父类的基础上创建新的子类,实现新的功能。
    • 代码组织: 将相关的功能组织到不同的层次结构中,使代码更易于理解和维护。

4.1 继承的概念

  • 父类 (基类或超类): 被继承的类。
  • 子类 (派生类或扩展类): 继承自另一个类的类。
class Animal {
    void eat() {
        System.out.println("动物在吃");
    }
}

class Dog extends Animal { // Dog 继承了 Animal 类
    void bark() {
        System.out.println("狗在叫");
    }
}

4.2 继承的优势

  • 代码复用: 继承允许子类复用父类的代码,避免重复编写相同的功能。例如,在 Dog 类中,eat 方法不需要重新编写,可以直接继承自父类 Animal
  • 可扩展性: 继承使得添加新的功能变得更容易。例如,我们可以通过继承 Animal 类,创建新的子类,比如 Cat 类,来添加猫的相关功能,例如 meow() 方法。
  • 代码组织: 继承可以帮助我们更好地组织代码,将相关的类组织到不同的层次结构中。例如,我们可以创建一个 Pet 类作为父类,然后创建 DogCat 等子类,这样可以使代码更加清晰、易于维护。

4.3 继承的类型

  • 单继承: 在 Java 中,一个子类只能继承一个父类。
  • 多继承: 一个子类可以继承多个父类。 Java 不支持真正的多继承,但可以使用接口来实现类似功能。
  • 层次化继承: 父类可以有子类,子类可以有孙类,形成层次结构。

4.4 继承中的方法重写 (Overriding)

  • 概念: 子类可以选择重写父类的方法,以便在子类中提供不同的实现。
  • 条件: 方法重写必须满足以下条件:
    • 方法名相同。
    • 参数列表相同。
    • 返回值类型相同(或者返回值类型是父类返回值类型的子类)。
    • 访问修饰符的权限不能比父类更严格。
class Animal {
    void sound() {
        System.out.println("动物发出声音");
    }
}

class Dog extends Animal {
    @Override
    void sound() {  // 重写父类的 sound 方法
        System.out.println("狗叫");
    }
}

4.5 继承中的构造函数

  • 子类构造函数: 子类构造函数必须调用父类构造函数,才能初始化父类继承的属性。
  • super() 方法: 在子类构造函数中,可以使用 super() 方法调用父类的构造函数。

4.6 继承中的方法隐藏

  • 概念: 子类的方法与父类的某个方法具有相同的方法名,但参数列表不同,这种情况称为方法隐藏。
  • 区别: 方法隐藏与方法重写不同,方法隐藏不会改变方法的实现,而方法重写会改变方法的实现。
class Animal {
    void sound() { // 父类方法
        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {
    void sound(int age) { // 子类方法,与父类方法同名,但参数不同
        System.out.println("Dog barks, age: " + age);
    }
}

4.7 继承中 final 关键字

  • final 关键字: 用于修饰类、方法和变量,表示它们是最终的,不能被继承或重写。
    • final 类: 表示该类不能被继承。
    • final 方法: 表示该方法不能被子类重写。
    • final 变量: 表示该变量是一个常量,其值一旦被赋值就不能再改变。

5. 多态

  • 概念: 多态是指同一个操作在不同的对象上会产生不同的行为。
  • 目的:
    • 代码灵活性和可扩展性: 可以通过父类引用指向子类对象,调用不同的实现方法。
    • 提高代码可读性: 可以使用更简洁、更灵活的方式来编写代码。

5.1 多态的概念

  • 父类引用: 可以使用父类类型的变量来引用子类对象。
  • 方法调用: 当调用父类引用中的方法时,实际执行的是子类重写后的方法。
class Animal { 
    void sound() {
        System.out.println("动物发出声音");
    }
}

class Dog extends Animal {
    @Override
    void sound() {
        System.out.println("狗叫");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal myDog = new Dog(); // 父类引用指向子类对象
        myDog.sound(); // 调用的是 Dog 类的 sound 方法
    }
}

5.2 多态的类型

  • 方法重载 (Overloading): 同一个类中,方法名相同,参数列表不同。 编译器会根据参数类型和数量选择合适的重载方法。
  • 方法重写 (Overriding): 子类重写父类的方法。 当父类引用指向子类对象时,调用的是子类重写后的方法。

5.3 多态的优势

  • 代码灵活性和可扩展性: 多态可以使代码更加灵活和易于扩展。
  • 代码可读性: 多态可以使代码更加简洁和易于理解。

5.4 多态的应用场景

        多态在实际开发中有很多应用场景,例如:

  • 工厂模式: 通过工厂类创建不同类型的对象。
  • 策略模式: 定义一组算法,并将它们封装为独立的类,以便在运行时选择合适的算法。

5.5 抽象类

  • 概念: 抽象类是用 abstract 关键字修饰的类,它不能被直接实例化,只能被子类继承。抽象类可以包含抽象方法和普通方法。
  • 特点:
    • 抽象方法: 没有方法体,以 abstract 关键字修饰。 子类必须重写抽象方法才能实例化。
    • 不能被直接实例化: 只能通过子类来实例化。
abstract class Shape { // 抽象类
    abstract void draw(); // 抽象方法,没有方法体

    void print() { // 普通方法
        System.out.println("这是一个形状");
    }
}

class Circle extends Shape {
    @Override
    void draw() {
        System.out.println("画一个圆形");
    }
}

public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle(); // 实例化子类 Circle
        circle.draw(); // 调用子类重写的 draw 方法
        circle.print(); // 调用父类的 print 方法
    }
}

5.6 接口

  • 概念: 接口是使用 interface 关键字声明的,它是一种特殊的抽象类,其中只包含抽象方法和常量。接口不能被直接实例化,只能被类实现。
  • 特点:
    • 只能包含抽象方法和常量。
    • 可以被多个类实现。
    • 提高代码的可扩展性和灵活性。
interface Drawable { // 接口
    void draw(); // 抽象方法
}

class Circle implements Drawable { // 实现接口
    @Override
    public void draw() {
        System.out.println("画一个圆形"); 
    }
}

public class Main {
    public static void main(String[] args) {
        Drawable circle = new Circle(); // 实例化 Circle 对象
        circle.draw();
    }
}

6. 总结

概念描述优势
对象现实世界中事物的抽象表示,包含属性和方法提供了一种更自然、更易于理解和维护的编程方式
创建对象的模板,定义对象的属性和方法定义了对象的结构和行为
封装将对象的属性和方法结合起来,隐藏实现细节提高代码安全性、可维护性和可重用性
继承允许子类继承父类的属性和方法代码复用、可扩展性、代码组织
多态同一个操作在不同的对象上会产生不同的行为代码灵活性和可扩展性、提高代码可读性
抽象类用 abstract 修饰的类,不能被直接实例化,只能被子类继承定义公共方法和属性,并强制子类实现抽象方法
接口用 interface 关键字定义,包含抽象方法和常量,可以被多个类实现提高代码的可扩展性和灵活性

  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值