Java 编程从入门到实战:面向对象编程核心

本文是系列第三篇,深入剖析类与对象、封装继承多态等关键概念,助力构建面向对象编程思维体系

一、引言

面向对象编程(OOP)是 Java 语言的核心编程范式,通过创建 ** 类(Class)对象(Object)** 来模拟现实世界中的实体及其交互关系。熟练掌握 OOP 的三大特性 —— 封装、继承和多态,是迈向 Java 进阶开发的关键一步。在本文中,我们将借助大量实际案例,深入解析 OOP 的核心概念,为后续学习 Java 框架和进行企业级开发筑牢根基。

二、类与对象:OOP 的基石

2.1 类的定义与实例化

类的结构

类是对现实世界中一类具有相同属性和行为的事物的抽象描述。以Student类为例:

public class Student {  
    // 成员变量(属性)
    private String name;  
    private int age;  
    private boolean isGraduated;  

    // 成员方法(行为)
    public void study() {  
        System.out.println(name + "正在学习Java");  
    }  

    public void setName(String n) {  
        name = n;  
    }  
}  

上述代码定义了一个Student类,其中包含了姓名、年龄和毕业状态等属性,以及学习和设置姓名的方法。

对象创建

对象是类的具体实例。通过new关键字创建对象,并调用其方法:

// 实例化对象
Student stu = new Student();  
// 调用方法
stu.setName("张三");  
stu.study();  // 输出:张三正在学习Java  
内存模型

在 Java 中,对象存储在堆内存中,而对象的引用变量(如stu)则存储在栈内存中。当执行new Student()时,会在堆内存中开辟一块空间来存储Student对象的属性值,栈内存中的stu变量则指向堆内存中的这个对象实例。

2.2 构造方法与初始化块

构造方法(Constructor)

构造方法用于初始化对象的成员变量。它具有以下特点:

  • 方法名与类名完全相同,且没有返回值类型(包括void也不能有)。
  • 可以进行重载,即通过不同的参数列表来定义多个构造方法。
  • 如果类中没有显式定义构造方法,编译器会自动生成一个无参构造方法。
    public class Student {  
        // 有参构造
        public Student(String name, int age) {  
            this.name = name;  // this指向当前正在创建的对象
            this.age = age;  
        }  
    
        // 无参构造(显式定义)
        public Student() {}  
    }  

初始化块

初始化块分为静态初始化块和实例初始化块:

  • 静态初始化块:使用static关键字修饰,在类加载时执行,仅执行一次,通常用于初始化静态变量。
    static {  
        System.out.println("Student类已加载");  
    }  

  • 实例初始化块:没有修饰符,在创建对象时执行,先于构造方法执行,用于初始化对象的实例变量。
    {  
        isGraduated = false;  // 初始化默认值
    }  

三、封装:数据的保护层

3.1 访问修饰符

访问修饰符用于控制类、成员变量和方法的访问权限:

修饰符

类内

同包

子类(不同包)

其他包

使用场景

private

✔️

类内私有数据(如成员变量)

default(无)

✔️

✔️

同包内可见(默认修饰符)

protected

✔️

✔️

✔️

子类跨包继承时可见

public

✔️

✔️

✔️

✔️

对外公开的类 / 方法 / 变量

例如,在BankAccount类中,通过将balance变量设置为private,并提供getBalance和setBalance方法来控制对余额的访问,实现数据校验:

public class BankAccount {  
    private double balance;  

    // 对外提供访问接口
    public double getBalance() {  
        return balance;  
    }  

    public void setBalance(double balance) {  
        if (balance >= 0) {  // 数据校验
            this.balance = balance;  
        }  
    }  
}  

3.2 包(Package)机制

包机制用于组织代码结构,避免命名冲突:

  • 作用:将相关的类和接口组织在一起,方便管理和维护代码。
  • 规范
    package com.ds.cn.entity;  
    import com.ds.cn.util.Logger;  // 跨包需导入

    • 包名通常采用反向域名的形式,如com.ds.cn.oop。
    • 同一包内的类无需导入即可直接使用,不同包的类需要使用import语句导入。

四、继承:代码复用的桥梁

4.1 继承语法

Java 中通过extends关键字实现类的继承,一个子类只能继承一个父类(单继承):

// 子类继承父类(单继承)
public class Dog extends Animal {  
    private String breed;  

    // 重写父类方法
    @Override  
    public void speak() {  
        System.out.println("汪汪汪");  
    }  
}  

// 父类
class Animal {  
    protected String color;  

    public void eat() {  
        System.out.println("正在进食");  
    }  

    public void speak() {  // 父类默认实现
        System.out.println("动物发出声音");  
    }  
}  

4.2 关键特性

  • 向上转型:子类对象可以赋值给父类引用,在运行时根据对象的实际类型调用方法,这就是动态绑定。
    Animal animal = new Dog();  // 运行时类型为Dog
    animal.speak();  // 调用子类重写的方法(动态绑定)

  • super 关键字:用于访问父类的成员,包括属性和方法。
    super.eat();  // 调用父类的eat方法
    super.color = "白色";  // 访问父类属性

  • final 关键字
    • final类:不能被继承,例如String类就是final类。
    • final方法:不能被重写。
    • final变量:常量,其值一旦初始化就不能改变。

五、多态:同一接口,不同实现

5.1 多态实现方式

(1)方法重写(Override)

子类重新实现父类的非静态、非final方法,前提是满足 “is-a” 关系,比如Dog是一种Animal。

(2)接口实现(Interface)

类通过implements关键字实现接口,接口中的方法默认是public abstract,变量默认是public static final。

(3)抽象类继承(Abstract Class)

抽象类包含抽象方法,需要子类去实现这些抽象方法。

5.2 动态绑定机制

Java 虚拟机在运行时会根据对象的实际类型来调用方法,而不是根据引用变量的类型。例如:

Animal[] animals = {new Dog(), new Cat()};  
for (Animal a : animals) {  
    a.speak();  // 输出:汪汪汪 / 喵喵喵(动态分派)
}  

这里animals数组中存储了不同子类的对象,在遍历调用speak方法时,会根据每个对象的实际类型调用对应的方法实现。

六、抽象类与接口:设计模式的基石

6.1 抽象类(Abstract Class)

抽象类是一种不能被实例化的类,用abstract关键字修饰:

  • 特点
    abstract class Shape {  
        abstract double getArea();  // 抽象方法
        public void draw() {  // 具体方法
            System.out.println("绘制图形");  
        }  
    }  
    
    class Circle extends Shape {  
        private double radius;  
        @Override  
        public double getArea() {  
            return Math.PI * radius * radius;  
        }  
    }  

    • 可以包含抽象方法,即只有方法声明,没有方法体,需要子类去实现。
    • 也可以包含具体方法和构造方法。

6.2 接口(Interface)

接口是一种特殊的抽象类型,完全由抽象方法和常量组成,用interface关键字声明:

  • 特点
    interface Drawable {  
        void draw();  // 隐式抽象方法
        int COLOR_RED = 1;  // 隐式常量
    }  
    
    class Rectangle implements Drawable {  
        @Override  
        public void draw() {  
            System.out.println("绘制矩形");  
        }  
    }  

    • 接口中的方法默认是public abstract,可以省略不写。
    • 接口中的变量默认是public static final常量。
    • 一个类可以实现多个接口,弥补了 Java 单继承的局限性。

6.3 核心区别

特性

抽象类

接口

实例化

不能(需子类实现)

不能(需实现类实例化)

方法类型

可包含抽象 + 具体方法

只能是抽象方法(JDK8 + 支持默认方法)

继承方式

单继承(extends)

多实现(implements)

使用场景

定义领域内的公共属性和行为

定义跨领域的通用规范

七、常见问题与最佳实践

7.1 封装陷阱

  • 避免直接暴露成员变量:始终通过setter和getter方法来访问和修改私有成员变量,这样可以更好地控制数据的访问和修改逻辑。
  • 在构造方法中初始化必要属性:确保对象创建时,关键属性都有合理的初始值,避免出现空指针异常。

7.2 继承原则

  • 遵循 “is-a” 关系:子类必须在逻辑上是父类的一种,例如 “狗是动物” 是合理的继承关系,而 “狗是颜色” 则不合理。
  • 优先使用组合(Composition)而非继承:当一个类 “拥有” 另一个类的功能时,应优先考虑使用组合关系,比如Car类包含Engine类,而不是让Car继承Engine。

7.3 多态设计

  • 面向接口编程:将方法参数定义为接口类型,这样可以提高代码的扩展性和灵活性,使方法能够接受所有实现该接口的对象。
    public void process(Drawable d) {  
        d.draw();  // 接受所有实现Drawable的对象
    }  

八、总结与实战建议

核心收获

  1. 深刻理解类与对象的关系,熟练掌握构造方法与初始化逻辑。
  1. 能够熟练运用访问修饰符实现数据封装,严格遵循包管理规范。
  1. 掌握继承语法,理解向上转型与动态绑定机制。
  1. 能够准确区分抽象类与接口,并根据实际场景选择合适的设计方案。

实战练习

  1. 学生管理系统:定义Student类,封装姓名、成绩等信息,Teacher类继承Person类,并实现多态方法evaluate(),用于对学生进行评价。
  1. 图形计算程序:利用抽象类Shape和接口Printable,实现圆形、矩形等图形的面积计算与打印功能。

系列预告

下一篇将深入探讨异常处理常用类库(如String、集合框架),结合实际案例提升代码的健壮性和开发效率。关注浩南,获取完整学习路径!

互动思考

在实际开发中,你认为 “封装” 和 “继承” 最容易被滥用的场景有哪些?如何依据设计原则来规避这些问题?欢迎在评论区分享你的见解!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值