掘金 后端 ( ) • 2024-04-15 09:28

面向过程 & 面向对象

面向对象编程(OOP)面向过程编程(POP)是两种不同的编程范式。它们各有特点,适用于不同的场景和需求。

面向过程编程(POP)

面向过程编程是一种以过程为中心的编程方式。它强调程序是由一系列可重用的函数(或称为过程、子程序)组成,每个函数负责完成特定的任务。程序的设计基于解决问题所需的步骤序列,通过函数间的调用来协同工作。

特点

  • 线性流程:面向过程着重于将一个大任务分解成一个个可执行的函数,按照程序执行的先后顺序排列。
  • 数据与函数分离:函数通常接受数据作为输入参数,并且可能返回结果,数据和操作数据的过程相对独立。
  • 重用:通过函数模块化实现代码重用,但粒度一般较粗。
  • 结构清晰:对于小型项目和简单逻辑,面向过程的程序结构清晰,易于理解和实现。

面向对象编程(OOP)

面向对象编程是以 “对象” 为核心,它将数据和处理数据的方法(即行为)打包在一个个独立的对象中。

程序由相互协作的对象组成,每个对象都有自己的属性(数据)和方法(功能)。面向对象强调通过模拟现实世界的实体和它们之间的关系来设计程序结构。

特点

  • 对象:现实世界中的实体被抽象为程序中的对象,每个对象都有自己的状态(属性)和行为(方法)。
  • 类与实例:通过定义类来描述对象的共性,类是对象的模板,而对象则是类的实例。
  • 封装:将数据和操作数据的方法封装在类中,外部通过类的公共接口访问和修改内部状态,提高安全性。
  • 继承:允许一个类(子类)从另一个类(父类)继承属性和方法,实现代码复用并支持层次结构。
  • 多态:同一接口可以有不同的实现,允许子类重写父类的方法,相同的消息发送给不同的对象会产生不同的效果。

面向过程和面相对象的区别联系

  • 设计角度

    • 面向过程从执行步骤出发,强调函数间的逻辑流程;
    • 面向对象则从对象本身出发,强调对象的职责和交互。
  • 重用和扩展:面向对象通过继承和多态等机制提供了更好的代码重用性和扩展性,适应复杂系统的变化。

  • 复杂性:面向对象编程在解决复杂问题时表现出了更高的组织能力和灵活性,但对于简单的任务,面向过程编程可能更简洁明了。

概述

Java是一种支持面向对象编程的语言。它提供了对象继承封装多态等OOP的核心概念,使得软件设计更加灵活和可维护。以下是对核心概念的说明:

  • 类(Class)

    • 类是创建对象的蓝图,用于定义一类对象对象共有的数据(属性)和行为(方法)。
    • 类包含了数据成员(fields)和成员方法(methods)。
  • 对象(Object)

    • 对象是类的实例,是根据类创建的具体实体。
    • 每个对象都有自己的状态(由属性组成)和行为(由方法实现)。对象之间的交互是通过方法调用完成的。
  • 封装(Encapsulation)

    • 封装是将对象的状态(数据)和行为(方法)捆绑在一起,并对外隐藏实现细节的过程。
    • 在开发中,通过访问修饰符(如privateprotectedpublic)来控制类成员的可见性,确保数据安全性和完整性,外部只能通过公开的接口(方法)来访问和修改对象状态。
  • 继承(Inheritance)

    • 继承允许一个类(子类)继承另一个类(父类)的属性和方法。
    • 子类可以重写(Override)父类的方法,也可以添加新的属性和方法,从而实现代码的重用和扩展。
    • Java支持单继承(一个类只能直接继承一个父类),但可以通过接口实现多重继承的功能。
  • 多态(Polymorphism)

    • 多态意味着同一个接口可以有多种不同的实现方式。
    • 在Java中,多态可以通过接口(Interface)、抽象类(Abstract Class)和方法重载(Method Overloading)来实现。
  • 抽象(Abstraction)

    • 抽象是提取对象的共性特征,并将其定义为抽象类接口
    • 抽象类可以包含抽象方法(没有具体实现的方法),强制子类去实现;
    • 接口则是一种完全抽象的类型,只包含抽象方法的定义,实现接口的类必须实现接口中所有的抽象方法。

面向对象的特点:

  • 一切皆对象:在Java中,所有的数据和功能都是通过对象实现的。即使是基本数据类型也有对应的包装类。
  • 类的定义和使用:Java程序通常由一系列类的定义开始,然后通过创建这些类的对象来执行程序。
  • 方法的调用:对象之间通过方法调用进行交互。方法可以是实例方法(作用于特定对象)或静态方法(属于类本身)。
  • 继承和接口:Java支持单继承(一个子类只能继承一个父类),但可以通过实现多个接口来实现多继承的效果。
  • 运行时多态:Java在运行时确定对象的实际类型,这允许方法根据调用它的对象的实际类型来执行不同的代码。

面相对象的优势:

  • 模块化:通过将数据和相关操作封装在对象中,代码变得更加模块化,易于管理和维护。
  • 可重用性:继承和多态性使得代码可以重用,减少了重复编写代码的需要。
  • 易于理解和维护:对象模型更接近现实世界,使得程序设计和理解变得更加直观。
  • 灵活性和扩展性:多态性提供了接口的灵活性,使得程序更容易扩展和适应变化。

封装

封装性是面向对象编程的三大核心特性之一,另外两个是继承多态。封装主要涉及数据隐藏和信息封装,旨在保证对象的内部状态安全,防止未经授权的访问和修改,并通过接口控制数据的访问。

封装的含义与目的

封装的核心思想是将数据(属性)和对数据的操作(方法)结合在一起,并对外界隐藏数据的具体实现细节。这样做的好处在于:

  • 提高安全性:通过限制外界对内部数据的直接访问,可以防止意外或恶意篡改数据。
  • 提高模块化程度:将数据和操作封装在类中,使得代码更便于管理和复用。
  • 易于维护和扩展:一旦内部实现发生变化,只要保持对外接口不变,不影响其它使用该类的代码。

封装的实现方式

  1. 使用访问修饰符:Java提供了四种访问修饰符来控制对类成员的访问级别。

    • private:成员只能被同一个类中的其他成员访问。
    • protected:成员可以被同一个包内的其他类访问,以及子类。
    • public:成员可以被任何其他类访问。
    • 默认(无修饰符):成员只能被同一个包内的其他类访问。

    通常,对象的状态(属性)被声明为private,以防止外部直接访问,而提供public的方法(通常称为getter和setter)来获取和设置属性的值。

  2. 使用getter和setter方法

    • getter用于获取私有变量的值;
    • setter用于设置私有变量的值;
    • 这些方法可以在需要的时候添加验证和业务逻辑,例如验证数据的有效性。
  3. 构造方法

    • 在创建对象时,可以通过构造方法初始化私有变量,进一步控制对象状态。
  4. 内部类与嵌套类

    • 内部类也可以看作是一种封装形式,因为它将一些逻辑或辅助数据与外部类紧密结合在一起,且对外部类之外的世界隐藏。

代码示例:

package project03_Features;
​
/**
 * @description: 面向对象封装性代码示例 - 汽车类
 * @author: mixia
 * @version: 1.0
 * @date: 2024/4/12 0012 10:36
 */
public class Car {
    // 私有属性,外部无法字节访问
    private String brand; // 品牌
    private String color; // 颜色
​
    // 公共getter方法
    public String getBrand() { // 通过getter方法,可以从外部读取私有属性
        return brand;
    }
​
    // 公共setter方法
    public void setBrand(String brand) {// 通过setter方法,可以从外部修改私有属性
        // 添加验证逻辑 ,brand 不能为空
        if (brand != null && !brand.isEmpty()) {
            this.brand = brand;
        } else {
            throw new IllegalArgumentException("Invalid data.");
        }
    }
​
​
    public String getColor() {
        return color;
    }
​
    public void setColor(String color) {
        this.color = color;
    }
​
    // 有参构造方法 , 初始化全部私有属性
    public Car(String brand, String color) {
        this.brand = brand;
        this.color = color;
    }
}

继承

继承是面向对象编程的三大核心特性之一,它允许一个类(子类/派生类)继承另一个类(父类/超类)的属性和方法,从而实现代码复用和层次化结构的设计。下面详细讲解Java中的继承特性:

继承的定义与作用

继承通过关键字 extends 来实现。子类可以继承父类的非私有(非private)属性、方法以及构造器(除了私有成员和父类的构造器不能直接继承外)。

方法的重写(Override)

当子类从父类继承的方法不能满足自身需求时,子类可以重新定义这个方法,这称为方法的重写(Override)。重写的方法必须具有与父类方法相同的签名(返回类型、方法名和参数列表)。

继承的优点

  • 代码复用:子类无需重新编写已经存在于父类中的代码,可以直接继承和使用父类的属性和方法。
  • 扩展性:子类可以通过重写父类的方法来自定义功能,增加新的行为,而不会影响父类的其他子类。
  • 体系结构:继承可以帮助我们建立类的层级结构,使得代码更加清晰和易于管理。

继承的局限

  • 单继承:Java 不支持多继承,即一个类只能有一个直接父类。但是,通过接口(interface)可以实现类似的效果,一个类可以实现多个接口。
  • 菱形继承问题:虽然Java不直接支持多继承,但如果存在一个多级继承链,可能会导致多路径继承下出现冲突问题,不过Java通过接口和抽象类的组合可以有效避免这个问题。

构造方法的调用

  • 子类构造方法在初始化时会隐式地调用父类的无参构造方法,如果需要调用带参数的父类构造方法,则需要使用super关键字显式调用。

代码示例

定义一个父类(超类):

public class Animal {// 动物
​
    private String name;
​
    // 构造方法
    public Animal(String name) {
        this.name = name;
    }
​
    // 父类中的通用方法
    public void eat() {
        System.out.println(this.name + " 爱吃饭.");
    }
​
    // 父类的特定方法
    public void sleep() {
        System.out.println(this.name + " 去睡觉了.");
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
}

义一个子类(派生类),继承Animal类:

public class Dog extends Animal{
​
    // 子类特有的属性
    private int age;
​
    // 子类的构造器,会自动调用父类的构造器
    public Dog(String name,int age) {
        super(name);
        this.age = age;
    }
​
    // 重写父类的方法,实现子类特有的行为
    @Override
    public void eat() {
        System.out.println(this.getName() + " 修狗爱吃肉.");
    }
​
    // 显示年龄的方法
    public void displayAge() {
        System.out.println(this.getName() + " 今年 " + this.age + " 岁了.");
    }
​
    public int getAge() {
        return age;
    }
​
    public void setAge(int age) {
        this.age = age;
    }
}

主函数,测试继承特性:

public class Test {
​
    public static void main(String[] args) {
        Dog myDog = new Dog("小黑", 2);
​
        // 调用Dog对象的方法,包括从Animal继承来的eat方法(已被重写)和sleep方法
        myDog.eat();
        myDog.sleep();
​
        // 调用Dog类特有的方法
        myDog.displayAge();
    }
}

多态

多态是面向对象编程的三大核心特性之一。它指的是同一类型的对象在不同情境下表现出不同的形态或行为。

多态性体现了对象的可替代性,使得代码可以写出更通用的形式,增强了程序的灵活性和可扩展性。

多态在Java中有两种主要形式:编译时多态运行时多态

编译时多态(Static Polymorphism)

  • 编译时多态主要通过方法重载(Overloading)实现。在同一类中,可以定义多个具有相同名称但参数列表不同的方法。编译器根据传入参数的类型和数量决定调用哪个方法。

代码示例:

public class Example {
    public void display(int number) {
        System.out.println("显示整数: " + number);
    }
​
    public void display(String text) {
        System.out.println("显示字符串: " + text);
    }
}
​
Example e = new Example();
e.display(123); // 调用显示整数的方法
e.display("Hello"); // 调用显示字符串的方法

运行时多态(Dynamic Polymorphism)

  • 运行时多态主要是通过方法重写(Overriding)和向上转型(Upcasting)实现。子类继承父类并重写父类的方法,然后通过父类引用指向子类对象,调用的方法根据对象的实际类型确定。

代码示例:

// 父类
public class Animal {
    public void makeSound() {
        System.out.println("动物开始叫了.");
    }
}
​
// 子类
public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("修狗汪汪叫.");
    }
}
​
// 测试类
public class TestPolymorphism {
    public static void main(String[] args) {
        Animal myAnimal = new Dog(); // 向上转型,父类引用指向子类对象
        myAnimal.makeSound(); // 运行时调用的是Dog类的makeSound方法
    }
}

抽象

接口抽象类是面向对象编程中两种不同的抽象机制,它们都用于定义规范或契约,以便多个类能够共享一套公共的方法定义和约束,实现代码的复用和一致性。下面分别详细讲解接口和抽象类:

接口(Interface)

接口是一种完全抽象的类型,它只包含抽象方法(在Java 8之前)和常量(默认为public static final)。在Java 8之后,接口还可以包含默认方法(default methods)和静态方法

特性

  • 一个类可以实现多个接口,通过关键字 implements 来实现。
  • 实现接口的类必须提供接口中所有抽象方法的实现,否则该类必须声明为抽象类。
  • 接口强调的是设计上的纯粹抽象和行为约定,它主要用于定义对象的一组动作,而不在乎具体实现。

使用场景:

当你希望定义一个标准的行为规范,多个不相关的类可以遵守这个规范时,适合使用接口。比如,多个动物类都可以实现 MakeSoundable 接口来实现发声功能。

代码示例:

public interface Flyable {
    void fly();
}
​
public class Bird implements Flyable {
    public void fly() {
        System.out.println("The bird is flying.");
    }
}
​
public class Main {
    public static void main(String[] args) {
        Bird bird = new Bird();
        bird.fly(); // 输出: The bird is flying.
    }
}

抽象类(Abstract Class)

抽象类是可以包含抽象方法具体方法的类,用关键字 abstract 标记。抽象类不能被实例化,只能被其他类继承。

特性

  • 一个类只能继承一个抽象类,通过关键字 extends 来继承。
  • 抽象类既可以包含抽象方法(没有方法体的方法),也可以包含具体方法(有方法体的方法)。
  • 如果一个类包含至少一个抽象方法,那么这个类必须声明为抽象类。
  • 抽象类可以有构造方法,但不能被直接实例化,而是用于初始化继承它的子类对象。

使用场景:

当你需要定义一个类的骨架,包含了一些共性方法和属性,但又不打算直接创建这个类的实例时,适合使用抽象类。比如,Animal 抽象类可以包含所有动物的共性方法和属性,具体动物类如 DogCat 继承 Animal 并填充各自特有的方法和属性。

代码示例:

public abstract class Animal {
    private String name;
​
    public Animal(String name) {
        this.name = name;
    }
​
    public abstract void makeSound(); // 抽象方法
​
    public void sleep() { // 具体方法
        System.out.println(name + " is sleeping.");
    }
}
​
public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }
​
    @Override
    public void makeSound() { // 具体实现抽象方法
        System.out.println(name + " barks.");
    }
}
​
public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog("Buddy");
        dog.makeSound(); // 输出: Buddy barks.
        dog.sleep(); // 输出: Buddy is sleeping.
    }
}

接口与抽象类的比较:

  • 实现数量:一个类可以实现多个接口,但只能继承一个抽象类(除非有多重继承通过接口和Java 8引入的接口默认方法来间接实现)。
  • 抽象程度:接口比抽象类更抽象,因为接口不允许包含任何实现代码(除Java 8引入的默认方法和静态方法外)。
  • 访问权限:接口中的方法默认为public,而抽象类中的方法可以是任意访问级别。
  • 数据成员:接口中的成员变量默认为public static final(即常量),抽象类中可以定义各种类型的成员变量,包括实例变量和静态变量。
  • 构造器:接口没有构造器,而抽象类可以有构造器,用于子类初始化时使用。