七:面向对象(上)——类与对象

一、回顾与本篇目标

前面六篇,你学会了 Java 的基础语法——变量、运算符、条件判断、循环、数组。你写的程序已经能处理数据、做判断、重复执行了。

但到目前为止,我们写的代码都是面向过程的:数据是数据,操作是操作,它们分散在各处。比如上一篇的学生成绩统计——学生姓名存在一个数组里,成绩存在另一个数组里,统计逻辑写在 main 方法中。如果项目越来越大,这种方式会让代码越来越难维护——数据和操作数据的方法散落在各处,同一个名字的函数可能冲突,修改一个功能要翻遍整个文件。

面向对象编程提供了一种更自然的代码组织方式。它把数据操作数据的方法打包在一起,形成对象。比如一个“学生”对象,既有姓名、成绩这些数据,也有计算等级、打印信息这些方法。这种方式让代码更直观、更易维护、更可复用。

面向对象是 Java 最核心的编程思想。Java 中的一切代码都必须写在类里面——从第一篇的 HelloWorld 开始,你其实已经在用类了。这一篇,我们正式学习类和对象的完整概念。

本篇的目标:

  1. 理解什么是类、什么是对象
  2. 学会定义一个类——成员变量和成员方法
  3. 学会创建和使用对象
  4. 理解构造方法——对象创建时自动执行
  5. 理解 this 关键字——指向当前对象

二、什么是类和对象——用现实生活来理解

在写代码之前,先用一个生活中的例子来理解这两个核心概念。

类是设计图纸,对象是造出来的实物

想象你要造一辆汽车。首先你需要一张设计图纸——上面画了汽车的结构、零件的尺寸、各个部件怎么连接。这张图纸不是真正的汽车,但它描述了汽车应该是什么样子。

有了图纸之后,你就可以按照图纸造出真正的汽车。一张图纸可以造很多辆车——它们结构相同,但颜色不同、车牌号不同、车主不同。

翻译成 Java 术语:

  • :就是那张设计图纸。它定义了这种“东西”拥有哪些属性(比如颜色、品牌、速度)和哪些行为(比如加速、刹车、鸣笛)。
  • 对象:就是按照图纸造出来的具体的一辆车。它是实际存在的,占用内存,你可以使用它。
  • 属性:对应 Java 中的成员变量
  • 行为:对应 Java 中的成员方法

在 Java 中,类就是数据类型,对象就是具体的值。就像 int 是类型,28 是值一样——Car 是类(类型),myCar 是对象(具体的值)。

三、定义你的第一个类

现在来定义一个简单的类——学生。一个学生有姓名、年龄、成绩这些属性,也有介绍自己、判断是否及格这些行为。

// Student.java —— 定义一个学生类

public class Student {
    // 成员变量(属性)——描述"有什么"
    String name;      // 姓名
    int age;          // 年龄
    double score;     // 成绩

    // 成员方法(行为)——描述"能做什么"
    public void introduce() {
        System.out.println("我叫" + name + ",今年" + age + "岁,成绩是" + score + "分。");
    }

    public boolean isPass() {
        return score >= 60;   // 成绩大于等于 60 就是及格
    }
}

逐行解释:

  • public class Student:定义一个名为 Student 的类。public 表示这个类是公开的,其他代码可以访问它。类名习惯用大驼峰命名法(每个单词首字母大写)。
  • String name;:声明一个成员变量。这个变量属于 Student 类的每个对象。每个学生对象都有自己独立的 nameagescore
  • public void introduce():定义一个成员方法。这个方法打印学生的自我介绍。void 表示没有返回值。
  • public boolean isPass():定义一个返回 boolean 值的方法,用来判断学生是否及格。
  • 在方法内部,nameagescore 直接写变量名就能访问——它们就是当前对象的属性。

四、创建对象并使用

类只是定义,不能直接使用。你需要用类来创建对象,然后使用这个对象。

// Main.java —— 使用 Student 类

public class Main {
    public static void main(String[] args) {
        // 创建对象:用 new 关键字
        Student s1 = new Student();
        Student s2 = new Student();

        // 给对象的属性赋值
        s1.name = "张三";
        s1.age = 20;
        s1.score = 85.5;

        s2.name = "李四";
        s2.age = 21;
        s2.score = 92.0;

        // 调用对象的方法
        s1.introduce();  // 我叫张三,今年20岁,成绩是85.5分。
        s2.introduce();  // 我叫李四,今年21岁,成绩是92.0分。

        // 使用方法的返回值
        System.out.println("张三及格了吗?" + s1.isPass());  // true
        System.out.println("李四及格了吗?" + s2.isPass());  // true
    }
}

逐行解释:

  • Student s1 = new Student();:用 new 关键字创建一个 Student 对象。类比:按照设计图纸造出一辆真实的汽车。这个对象现在存储在内存中,s1 是一个引用,指向这个对象。
  • s1.name = "张三";:通过点号 . 访问对象的属性并赋值。
  • s1.introduce();:通过点号调用对象的方法。
  • s1s2 是两个独立的对象,它们的属性互不影响——s1 的 name 是张三,s2 的 name 是李四。

默认值:如果创建对象后不给属性赋值,它们会有默认值——int0double0.0String 等引用类型是 null

五、构造方法——对象创建时自动执行

上面的例子中,创建对象后需要逐行给属性赋值,有点麻烦。如果能在创建对象的同时就设置好属性的值,那该多好。

构造方法就是做这件事的。它是一个特殊的方法,在对象被创建时自动调用,通常用来给属性赋初值。

构造方法有两条规则:

  1. 方法名必须和类名完全一样
  2. 没有返回类型(连 void 都不写)。
// Student.java —— 带构造方法的版本

public class Student {
    String name;
    int age;
    double score;

    // 构造方法:创建对象时自动调用
    public Student(String n, int a, double s) {
        name = n;
        age = a;
        score = s;
        System.out.println("一个学生对象被创建了!");
    }

    public void introduce() {
        System.out.println("我叫" + name + ",今年" + age + "岁,成绩是" + score + "分。");
    }

    public boolean isPass() {
        return score >= 60;
    }
}

现在创建对象时可以直接传入参数:

public class Main {
    public static void main(String[] args) {
        // 创建对象的同时设置属性值
        Student s1 = new Student("张三", 20, 85.5);
        Student s2 = new Student("李四", 21, 92.0);

        s1.introduce();  // 我叫张三,今年20岁,成绩是85.5分。
        s2.introduce();  // 我叫李四,今年21岁,成绩是92.0分。
    }
}

输出会先显示两行“一个学生对象被创建了!”,然后是两句自我介绍。构造方法在 new 执行时自动调用

默认构造方法:如果你没有写任何构造方法,Java 会自动提供一个无参数的默认构造方法——所以之前的 new Student() 才能正常工作。但一旦你自己写了构造方法,默认构造方法就会消失。如果你还需要无参数创建对象的方式,需要手动写一个无参数的构造方法。

六、this 关键字——指向当前对象

上面构造方法中的参数名 nas 不太直观——看名字不知道它们代表什么。更自然的写法是参数名和属性名一致。但这样就出现了一个问题:

public Student(String name, int age, double score) {
    name = name;     // 哪个是参数?哪个是属性?
    age = age;       // 这样写是把参数赋给了自己!
    score = score;   // 属性根本没有被赋值
}

这里 name = name 会把参数 name 的值赋给参数自己,对象的属性并没有被修改。这时需要用 this 关键字来区分——this 代表当前对象this.name 代表当前对象的属性,name 代表参数。

// 正确的写法:用 this 区分属性和参数
public Student(String name, int age, double score) {
    this.name = name;    // this.name 是属性,name 是参数
    this.age = age;
    this.score = score;
}

this 的两大作用

  1. 区分成员变量和局部变量:当参数名和属性名相同时,this.属性名 明确指向对象的属性。
  2. 代表当前对象本身:在方法内部,如果你需要把“这个对象”传给另一个方法,就用 this
public class Student {
    String name;
    int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void printInfo() {
        System.out.println("姓名:" + this.name + ",年龄:" + this.age);
        // this.name 和直接写 name 效果一样(当没有同名的局部变量时)
    }
}

七、完整的示例——一个银行账户类

下面这个示例综合运用了类、对象、成员变量、成员方法、构造方法和 this:

// BankAccount.java —— 银行账户类

public class BankAccount {
    // 成员变量(属性)
    private String owner;     // 账户持有人
    private double balance;   // 余额

    // 构造方法
    public BankAccount(String owner, double initialBalance) {
        this.owner = owner;
        this.balance = initialBalance;
        System.out.println(owner + " 的账户已创建,初始余额:" + balance + " 元");
    }

    // 存钱
    public void deposit(double amount) {
        if (amount > 0) {
            this.balance += amount;
            System.out.println("存入 " + amount + " 元,当前余额:" + this.balance + " 元");
        } else {
            System.out.println("存款金额必须大于 0");
        }
    }

    // 取钱
    public void withdraw(double amount) {
        if (amount > 0 && amount <= this.balance) {
            this.balance -= amount;
            System.out.println("取出 " + amount + " 元,当前余额:" + this.balance + " 元");
        } else if (amount > this.balance) {
            System.out.println("余额不足!当前余额:" + this.balance + " 元");
        } else {
            System.out.println("取款金额必须大于 0");
        }
    }

    // 查询余额
    public double getBalance() {
        return this.balance;
    }

    // 显示账户信息
    public void showInfo() {
        System.out.println("账户持有人:" + this.owner + ",余额:" + this.balance + " 元");
    }
}
// Main.java —— 使用银行账户

public class Main {
    public static void main(String[] args) {
        // 创建两个账户
        BankAccount account1 = new BankAccount("张三", 1000.0);
        BankAccount account2 = new BankAccount("李四", 500.0);

        System.out.println("\n===== 操作演示 =====");

        // 张三存钱
        account1.deposit(500.0);
        // 张三取钱
        account1.withdraw(200.0);
        // 张三尝试取太多钱
        account1.withdraw(2000.0);

        // 李四操作
        account2.deposit(300.0);
        account2.withdraw(100.0);

        System.out.println("\n===== 账户信息 =====");
        account1.showInfo();
        account2.showInfo();
    }
}

输出:

张三 的账户已创建,初始余额:1000.0 元
李四 的账户已创建,初始余额:500.0 元

===== 操作演示 =====
存入 500.0 元,当前余额:1500.0 元
取出 200.0 元,当前余额:1300.0 元
余额不足!当前余额:1300.0 元
存入 300.0 元,当前余额:800.0 元
取出 100.0 元,当前余额:700.0 元

===== 账户信息 =====
账户持有人:张三,余额:1300.0 元
账户持有人:李四,余额:700.0 元

代码设计要点

  • 每个账户对象的数据(持有人、余额)是独立的——张三的存款取款不影响李四的账户。
  • 方法内部做了数据验证——存款金额必须大于 0,取款不能超过余额。这保证了对象的数据始终有效。
  • private 关键字让属性只能在这个类内部访问,外部代码不能直接修改 balance,只能通过 deposit()withdraw() 方法来操作。这就是封装的概念,下一篇会详细讲。

八、一个文件里放多个类

前面的例子中,我们把 StudentMain 放在两个 .java 文件里。实际上,一个 .java 文件里可以放多个类,但只能有一个 public,而且文件名必须和这个 public 类的名字一致。

// 文件名:BankDemo.java(必须和 public 类名一致)

public class BankDemo {
    public static void main(String[] args) {
        Account acc = new Account("张三", 1000.0);
        acc.showInfo();
    }
}

// 非 public 类可以放在同一个文件里
class Account {
    private String owner;
    private double balance;

    public Account(String owner, double balance) {
        this.owner = owner;
        this.balance = balance;
    }

    public void showInfo() {
        System.out.println(owner + " 的余额:" + balance);
    }
}

编译 BankDemo.java 后,会生成两个 .class 文件:BankDemo.classAccount.class

九、对象的内存模型——对象变量存的是地址

这是一个重要的理解。当你写 Student s1 = new Student(...); 时,变量 s1 并不是对象本身——它存储的是对象在内存中的地址(也叫引用)。真正的对象数据存在堆内存中。

类比:s1 是一张写着门牌号的纸条,真正的房子(对象)在门牌号指向的位置。把 s1 赋值给 s2,只是复制了纸条上的门牌号,并没有复制房子——两个纸条指向同一栋房子。

Student s1 = new Student("张三", 20, 85.5);
Student s2 = s1;          // s2 和 s1 指向同一个对象

s2.name = "李四";         // 通过 s2 修改对象
System.out.println(s1.name);  // 输出"李四"!因为 s1 和 s2 指向同一个对象

这和基本类型的行为不同——基本类型存的是值本身,赋值时会复制值:

int a = 10;
int b = a;    // b 得到 a 的副本
b = 20;
System.out.println(a);  // 10 —— a 不受 b 的影响

十、本篇动手练习

练习 1:定义一个图书类

新建 Book.javaBookDemo.java。定义 Book 类,包含属性:书名(title)、作者(author)、价格(price)、页数(pages)。写一个构造方法和一个 displayInfo() 方法打印所有信息。在 main 中创建两本不同的书并显示信息。

练习 2:定义一个矩形类

新建 Rectangle.javaRectangleDemo.java。定义 Rectangle 类,包含属性:宽(width)和高(height)。提供方法:getArea() 计算面积,getPerimeter() 计算周长,isSquare() 判断是否是正方形。创建几个矩形并测试。

练习 3:定义一个温度计类

新建 Thermometer.javaThermometerDemo.java。定义 Thermometer 类,包含属性:摄氏温度(celsius)。提供方法:getFahrenheit() 返回华氏温度(公式:celsius × 9/5 + 32),getKelvin() 返回开尔文温度(公式:celsius + 273.15),isBoiling() 判断是否达到沸点(>= 100)。

练习 4:模拟一个简单计算器

新建 Calculator.javaCalculatorDemo.java。定义 Calculator 类,包含一个属性 result(当前结果,初始为 0)。提供方法:add(double n)(加)、subtract(double n)(减)、multiply(double n)(乘)、divide(double n)(除,注意除数为 0)、clear()(清零)、getResult()(获取当前结果)。模拟一系列计算操作。

十一、本篇小结

这一篇你进入了 Java 面向对象编程的世界:

  • 类和对象:类是设计图纸(定义属性和方法),对象是按图纸造出来的实物(占用内存,实际可用)。类是一种数据类型,对象是具体的值。
  • 成员变量:属于对象的属性。每个对象有自己的副本,互不影响。
  • 成员方法:属于对象的行为。方法内部可以直接访问对象的属性。
  • 构造方法:方法名和类名相同,没有返回类型。在 new 创建对象时自动调用,通常用来初始化属性。如果你不写,Java 会提供一个无参数的默认构造方法。
  • this 关键字:代表当前对象本身。当参数名和属性名相同时,用 this.属性名 来区分。
  • 对象变量存的是地址:把对象变量赋给另一个变量,两个变量指向同一个对象。这和基本类型的复制行为完全不同。

面向对象是 Java 编程的基石。下一篇,我们继续深入学习面向对象的三大特性——封装、继承、多态,这是写出高质量 Java 代码的关键。

下一篇预告

下一篇——《面向对象(下)——封装、继承与多态》:private 封装数据、extends 继承父类、方法重写、super 关键字、多态的含义和用法。这是面向对象编程的核心内容。

Java 零基础入门,每周更新。

© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容