×

还记得我们学过的流程控制吗?ifforwhile……这些都是面向过程的思维方式——一个步骤接着一个步骤,用代码描述”怎么做”。但现实世界远比一个流程复杂:一台手机有品牌、颜色、价格,还能打电话、拍照、玩游戏。手机是一个对象,它有属性(品牌、颜色),也有行为(打电话、拍照)。把现实世界的事物抽象成”对象”,让程序用对象之间的交互来解决问题——这就是面向对象编程(OOP)的核心思想。

一、什么是面向对象?

1.1 面向过程 vs 面向对象

想象你要做一道番茄炒蛋:

面向过程的写法:

1. 洗番茄 → 2. 切番茄 → 3. 打蛋 → 4. 热油 → 5. 炒蛋 → 6. 加番茄炒 → 7. 出锅

面向对象的写法:

番茄 = new 食材("番茄", 2个);
鸡蛋 = new 食材("鸡蛋", 3个);
锅 = new 厨具("炒锅");
厨师.洗(番茄).切(番茄);
厨师.打(鸡蛋);
锅.热油().炒(鸡蛋).加(番茄).翻炒().出锅();

看到区别了吗?面向过程关心步骤,面向对象关心对象以及它们之间的交互。面向对象的代码更接近人类的思维方式——我们不是在写机器指令,而是在描述”谁做了什么事”。

1.2 面向对象的三大特征

Java 的面向对象有三大基本特征,这也是面试中最高频的概念之一:

特征说明一句话记住
封装(Encapsulation)将数据和操作数据的方法包装在类中,对外隐藏内部细节“我只管用,不用管怎么实现”
继承(Inheritance)子类自动拥有父类的属性和方法,并可以扩展“子承父业,还能自己创新”
多态(Polymorphism)同一方法在不同对象上表现出不同的行为“同一个接口,不同实现”

本章我们先深入讲解封装,继承和多态将在后续章节展开。

二、类与对象

2.1 什么是类?什么是对象?

(Class)是对象的模板蓝图对象(Object)是类的实例

打个比方:

  • = 一张建筑设计图(规定了房子要有几间房、门窗位置)
  • 对象 = 按这张图纸盖出来的真实房子(有具体地址、住着人)

一张图纸可以盖出无数栋房子,每个房子都是独立的——这就是”类可以创建多个实例”。

2.2 定义一个类

在 Java 中,使用关键字 class 来定义类:

// 定义一个"手机"类
public class Phone {
    // 属性(也叫成员变量、字段)
    String brand;      // 品牌
    String color;      // 颜色
    double price;      // 价格
    
    // 方法(也叫成员方法)
    public void call(String name) {
        System.out.println("正在给 " + name + " 打电话...");
    }
    
    public void playGame() {
        System.out.println("正在玩手机游戏...");
    }
}

2.3 创建对象(实例化)

使用 new 关键字来创建对象:

public class TestPhone {
    public static void main(String[] args) {
        // 创建一个手机对象
        Phone myPhone = new Phone();
        
        // 给对象的属性赋值
        myPhone.brand = "华为";
        myPhone.color = "极光蓝";
        myPhone.price = 5999.0;
        
        // 调用对象的方法
        myPhone.call("小明");
        myPhone.playGame();
        
        // 输出属性
        System.out.println("我买了一台 " + myPhone.brand + ",颜色是 " + myPhone.color + ",价格 " + myPhone.price + " 元");
    }
}

输出:

正在给 小明 打电话...
正在玩手机游戏...
我买了一台 华为,颜色是 极光蓝,价格 5999.0 元

2.4 对象的创建过程

当执行 Phone myPhone = new Phone(); 时,JVM 做了三件事:

  1. 声明:在栈内存中创建一个引用变量 myPhone
  2. 实例化:在堆内存中为 Phone 对象分配空间,所有属性被初始化为默认值(null / 0 / 0.0 / false
  3. 赋值:将堆内存地址赋值给 myPhone

示意图:

栈内存(Stack)               堆内存(Heap)
┌──────────────┐            ┌─────────────────┐
│  myPhone     │ ──────→    │   Phone 对象     │
│  (引用地址)   │            │  brand = "华为"  │
└──────────────┘            │  color = "极光蓝"│
                            │  price = 5999.0  │
                            └─────────────────┘

关键理解myPhone 不是对象本身,而是对象的引用(类似遥控器,不是电视机)。myPhone.call() 就像按下遥控器的”拨号”按钮——实际上操作的是堆中的对象。

三、封装:隐藏细节,暴露接口

3.1 为什么需要封装?

回头再看我们刚才写的 Phone 类——brandcolorprice 这些属性是直接暴露在外面的。这意味着外部代码可以随意修改它们:

Phone p = new Phone();
p.price = -100;  // 价格竟然可以是负数?不合理!

这就是问题所在:直接暴露内部数据,外部可以随意破坏对象的合法性

封装就是:

  • 把类的内部数据(属性)用 private 隐藏起来
  • 对外提供公开的 public 方法(getter/setter)来访问和修改数据
  • 在方法内部做合法性校验,保证数据的有效性

3.2 访问修饰符

Java 提供了四种访问修饰符来控制可见性:

修饰符同一个类同一个包子类任何地方
private
默认(default)
protected
public

封装的核心原则:属性私有(private),方法公开(public)

3.3 动手实践:用封装改造 Phone 类

public class Phone {
    // 属性全部私有
    private String brand;
    private String color;
    private double price;
    
    // 公开的 getter 和 setter 方法
    public String getBrand() {
        return brand;
    }
    
    public void setBrand(String brand) {
        if (brand == null || brand.trim().isEmpty()) {
            System.out.println("品牌不能为空!");
            return;
        }
        this.brand = brand;
    }
    
    public String getColor() {
        return color;
    }
    
    public void setColor(String color) {
        if (color == null || color.trim().isEmpty()) {
            System.out.println("颜色不能为空!");
            return;
        }
        this.color = color;
    }
    
    public double getPrice() {
        return price;
    }
    
    public void setPrice(double price) {
        if (price < 0) {
            System.out.println("价格不能为负数!");
            return;
        }
        this.price = price;
    }
    
    public void call(String name) {
        System.out.println(this.brand + "手机正在给 " + name + " 打电话...");
    }
    
    public void playGame() {
        System.out.println("正在用" + this.color + "的" + this.brand + "手机玩游戏...");
    }
}

测试代码:

public class TestPhone {
    public static void main(String[] args) {
        Phone p = new Phone();
        
        p.setBrand("华为");
        p.setColor("极光蓝");
        p.setPrice(5999.0);
        p.setPrice(-100);  // 输出:价格不能为负数!
        
        System.out.println(p.getBrand());  // 华为
        p.call("小明");                     // 华为手机正在给 小明 打电话...
    }
}

现在,价格不会被错误地设为负数了。setPrice() 方法充当了看门人,只允许合法的值进入。

3.4 封装的好处

  1. 提高安全性:防止外部代码直接修改内部数据
  2. 降低耦合:外部只依赖公开方法,内部实现可以自由修改
  3. 增加可维护性:修改内部实现不影响使用者
  4. 增强灵活性:可以在 setter 中添加校验、日志、缓存等逻辑

四、综合案例:学生信息管理

下面我们综合运用本章知识,设计一个 Student 类:

public class Student {
    private String name;
    private int age;
    private String studentId;
    private double score;
    
    // getter / setter
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        if (name == null || name.trim().isEmpty()) {
            System.out.println("姓名不能为空!");
            return;
        }
        this.name = name;
    }
    
    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        if (age  60) {
            System.out.println("年龄必须在6~60之间!");
            return;
        }
        this.age = age;
    }
    
    public String getStudentId() {
        return studentId;
    }
    
    public void setStudentId(String studentId) {
        if (studentId == null || !studentId.matches("\d{8}")) {
            System.out.println("学号必须为8位数字!");
            return;
        }
        this.studentId = studentId;
    }
    
    public double getScore() {
        return score;
    }
    
    public void setScore(double score) {
        if (score  100) {
            System.out.println("成绩必须在0~100之间!");
            return;
        }
        this.score = score;
    }
    
    // 行为方法
    public void showInfo() {
        System.out.println("===== 学生信息 =====");
        System.out.println("姓名:" + name);
        System.out.println("年龄:" + age);
        System.out.println("学号:" + studentId);
        System.out.println("成绩:" + score);
        System.out.println("等级:" + getGrade());
        System.out.println("===================");
    }
    
    private String getGrade() {
        if (score >= 90) return "优";
        if (score >= 80) return "良";
        if (score >= 70) return "中";
        if (score >= 60) return "及格";
        return "不及格";
    }
}

测试:

public class TestStudent {
    public static void main(String[] args) {
        Student s = new Student();
        
        s.setName("张三");
        s.setAge(20);
        s.setStudentId("20240001");
        s.setScore(85.5);
        
        s.showInfo();
        
        // 测试非法数据
        System.out.println("n--- 测试非法数据 ---");
        s.setAge(200);         // 年龄必须在6~60之间!
        s.setScore(-10);       // 成绩必须在0~100之间!
        s.setStudentId("abc"); // 学号必须为8位数字!
    }
}

输出:

===== 学生信息 =====
姓名:张三
年龄:20
学号:20240001
成绩:85.5
等级:良
===================

--- 测试非法数据 ---
年龄必须在6~60之间!
成绩必须在0~100之间!
学号必须为8位数字!

五、关于 this 关键字(预告)

细心的你可能注意到了,上面的代码中反复出现了一个关键字——this

this.brand = brand;
this.name = name;

this 表示”当前对象的引用”,用来区分成员变量局部变量(参数)。this.brand 是对象的属性,brand 是方法的参数。

this 还有一个更重要的用法——在构造器中调用另一个构造器。这正是我们下一篇文章要深入讲解的内容:构造器

结语

本章我们迈出了面向对象编程的第一步:理解了类与对象的概念,学会了如何定义类和创建对象,掌握了封装的原理与实现,并用综合案例把知识串了起来。

回顾要点:

  • 类是对象的模板,对象是类的实例
  • new 关键字创建对象
  • 封装让属性私有,对外提供公共的 getter/setter
  • this 关键字代表当前对象的引用

下一篇文章,我们将深入学习构造器——这是对象创建时最先执行的特殊方法,也是每个 Java 开发者必须掌握的核心技能。我们还会探讨构造器重载this() 调用的高级用法。敬请期待!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

作者

2248768396@qq.com

相关文章