Skip to content

第7章:面向对象(上)——类与对象

本章开始深入 Java 最核心的概念。学完它,你就能真正用 Java "造东西"了。

📌 代码说明:标注"可复制运行"的代码块包含完整的 main 方法,存为对应文件名后可直接编译运行。没标注的是概念示意片段,帮助你理解,但不能直接复制运行。

你将学会
类和对象·构造器·封装·static
前置知识
第6章 数组
预计时间
40 分钟
需要准备
JDK + 编辑器
完成标志
定义自己的类并用对象组织数据

一个故事开头

你过去 6 章的程序是这样的:数据散落在变量里,方法到处穿插。

java
String name = "小明";
int age = 20;
double score = 85.5;

void printInfo(String name, int age, double score) {
    System.out.println(name + ", " + age + "岁, 分数" + score);
}

现在来了第二个学生小红,你复制粘贴一遍。第三个小张,再复制一遍。

如果每个学生还有地址、课程、成绩列表呢?方法的参数会变成一场灾难。

面向对象(OOP)的解决方式:把"一个学生"的所有属性和行为打包成一个整体——叫做对象。

java
public class Student {
    String name;       // 数据(字段)
    int age;
    
    void printInfo() {  // 行为(方法)
        System.out.println(name + ", " + age + "岁");
    }
}

// 创建对象
Student s1 = new Student();
s1.name = "小明";
s1.age = 20;
s1.printInfo();  // 小明, 20岁

不再是传一大堆松散的数据——你跟一个对象说话,对象自己知道怎么做。


1. 从数组到对象的演进

💥 拆了它:想想没有 OOP 的麻烦

概念示意,不可直接运行

java
// 三个学生,每科成绩
String[] names = {"小明", "小红", "小张"};
int[] ages = {20, 21, 19};
int[][] scores = {{85, 90}, {92, 88}, {78, 95}};

// 要打印小明的信息
System.out.println(names[0] + " " + ages[0] + "岁");
// 有什么问题?——索引对应关系完全靠人脑记,加一个人要改三个数组

如果有对象:

java
class Student {
    String name;
    int age;
    int[] scores;
    
    void printInfo() {
        System.out.println(name + ", " + age + "岁");
    }
}

数据和方法在一起——这是 OOP 的核心思想。


2. 类和对象——设计图和实物

java
// 可复制运行,保存为 MilkTea.java
public class MilkTea {
    String flavor;    // 字段(属性)
    double price;
    
    void display() {  // 方法(行为)
        System.out.println(flavor + " — " + price + "元");
    }
    
    public static void main(String[] args) {
        MilkTea tea1 = new MilkTea();  // new 关键字造对象
        tea1.flavor = "波波奶茶";
        tea1.price = 15.0;
        
        MilkTea tea2 = new MilkTea();  // 同一张设计图,造不同的实物
        tea2.flavor = "杨枝甘露";
        tea2.price = 18.0;
        
        tea1.display();  // 波波奶茶 — 15.0元
        tea2.display();  // 杨枝甘露 — 18.0元
    }
}

一个类可以创建无数个对象,每个对象有自己的字段值。


3. 构造方法:一造出来就设好值

上面那种创建方式有点麻烦——先 new MilkTea(),再一个个设字段。

构造方法(Constructor)让你在 new 的同时直接传值:

java
public class MilkTea {
    String flavor;
    double price;
    
    // 构造方法:名字和类名一样,没有返回值
    MilkTea(String flavor, double price) {
        this.flavor = flavor;  // this.flavor = 对象的 flavor 字段
        this.price = price;    // = 右边的 flavor 是参数
    }
    
    void display() {
        System.out.println(flavor + " — " + price + "元");
    }
}

// 使用:创建的时候直接传参数
MilkTea t1 = new MilkTea("波波奶茶", 15.0);
MilkTea t2 = new MilkTea("杨枝甘露", 18.0);

this 指"当前对象"。 当参数名和字段名冲突时,this.字段 表示对象的字段,不加 this 的是参数。

💥 拆了它:构造方法最容易踩的三个坑

坑1:写了带参构造器,无参构造器就消失了

java
public class Student {
    String name;
    Student(String name) { this.name = name; }
}

Student s = new Student();  // ❌ 编译错误!Student 没有无参构造器了

Java 在你没有写任何构造器时,会自动提供一个无参构造器。但一旦你写了一个带参的,无参的那个就不存在了。要么显式加一个无参构造器:

java
public class Student {
    String name;
    Student() { }                 // 显式的无参构造器
    Student(String name) {        // 带参构造器
        this.name = name;
    }
}

坑2:构造器不是方法——它没有返回类型,连 void 都不能写

java
public class Student {
    String name;
    void Student(String name) {   // ❌ 加了 void——这是普通方法,不是构造器
        this.name = name;
    }
}

加了 void 之后 Java 把它当普通方法处理,new Student("小明") 仍然会调无参构造器,name 没被赋值。

坑3:构造器里不要调可能会被重写的方法

这个坑要到第8章学完重写才能真正理解,先打预防针。如果构造器里调了一个被子类重写的方法,会执行子类的版本——而此时子类可能还没初始化完,结果会很奇怪。


4. static——属于类的,不属于对象的

前面所有例子里的字段和方法都属于对象——每个对象有自己的副本。

但有些东西不属于某个具体的对象,而属于类本身——那就是 static

java
public class Student {
    String name;              // 实例字段——每个学生有自己的名字
    static int totalCount;    // 静态字段——所有学生共享一个计数器
    
    Student(String name) {
        this.name = name;
        totalCount++;  // 每创建一个学生,总人数+1
    }
    
    void sayHi() {
        System.out.println("我是" + name);
    }
    
    static void printTotal() {  // 静态方法—不需要对象就能调用
        System.out.println("总人数:" + totalCount);
        // System.out.println(name);  // ❌ 静态方法不能访问实例字段!
    }
}

// 使用
Student s1 = new Student("小明");
Student s2 = new Student("小红");
Student s3 = new Student("小张");

Student.printTotal();  // 总人数:3(通过类名调用)
s1.sayHi();            // 我是小明(通过对象调用)

static 在 main 里的意义

main 方法必须是 static——因为 JVM 启动时还没有任何对象,必须能直接调用 main。这带来了一个重要规则:在静态方法(比如 main)里,不能直接调用实例方法(非 static 的方法)——因为没有对象让你调。

java
public class Demo {
    void sayHello() {       // 实例方法——需要对象才能调
        System.out.println("你好");
    }
    
    public static void main(String[] args) {
        // sayHello();      // ❌ 编译错误!静态方法里不能直接调实例方法
        Demo d = new Demo();
        d.sayHello();       // ✅ 先创建对象,通过对象调
    }
}

5. 封装——把东西藏起来

你开奶茶店,总不可能把配方单贴在大街上让所有人都看到吧?

封装(Encapsulation)就是 OOP 里"保护数据"的机制——把字段藏起来(private),只通过公开的方法(public)来访问。

java
// ❌ 不封装——谁都能改,甚至改成不合理的值
public class Person {
    public int age;
}
Person p = new Person();
p.age = -100;  // 合法——但年龄怎么能是负数?

// ✅ 封装——用 private 隐藏字段,用方法控制访问
public class Person {
    private int age;  // private:只有本类的方法能访问
    
    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        if (age < 0 || age > 150) {
            System.out.println("非法年龄:" + age);
            return;
        }
        this.age = age;
    }
}

Person p = new Person();
// p.age = -100;   // ❌ 编译错误!private 不让外部直接访问
p.setAge(20);       // ✅ 通过方法设置——可以加校验
p.setAge(-100);     // 非法年龄:-100(被拦截了)

关于 getter/setter:字段通常设为 private。但不是说每个字段都要配一对 getter/setter——需要外部读取的才加 getter,需要外部修改的才加 setter。比如学生 ID 只需要 getter(不允许修改),年龄需要 getter + setter(带校验)。IDE 里的"Generate Getters and Setters"功能很方便,但想清楚哪些字段真的需要暴露再生成。


6. 引用与垃圾回收

引用 vs 对象

java
Student s1 = new Student("小明");
Student s2 = s1;  // s2 获得了 s1 的引用副本——指向同一个对象

s2.name = "小张";
System.out.println(s1.name);  // "小张"——s1 和 s2 是同一个对象

💥 拆了它:看看引用的效果

java
public class RefDemo {
    public static void main(String[] args) {
        Student s = new Student("小明");
        changeName(s);
        System.out.println(s.name);  // 猜猜是"小明"还是"小张"?
    }
    
    static void changeName(Student stu) {
        stu.name = "小张";  // 改的是 stu 指向的对象,和 s 指向的是同一个
    }
}

结果是"小张"。因为 Java 里引用传递的是引用值的副本——两份副本指向同一个对象。

null 和 GC

java
Student s = new Student("小明");
s = new Student("小红");
// "小明"那个对象没人引用了——变成垃圾
// Java 的垃圾回收器会在某个时候自动回收它
java
Student s = null;  // 引用指向"空"
// s.sayHi();      // ❌ NullPointerException

7. 完整例子:奶茶店员工管理系统

java
public class Employee {
    private String name;
    private String position;
    private double salary;
    private static int totalEmployees = 0;
    
    // 构造方法
    public Employee(String name, String position, double salary) {
        this.name = name;
        this.position = position;
        setSalary(salary);
        totalEmployees++;
    }
    
    // Getter
    public String getName() { return name; }
    public String getPosition() { return position; }
    public double getSalary() { return salary; }
    
    // Setter(带校验)
    public void setSalary(double salary) {
        if (salary < 0) {
            System.out.println("工资不能为负数");
            return;
        }
        this.salary = salary;
    }
    
    // 静态方法
    public static void printTotal() {
        System.out.println("总员工数: " + totalEmployees);
    }
    
    // 普通方法
    public void printInfo() {
        System.out.println(name + " | " + position + " | " + salary + "元/月");
    }
    
    public static void main(String[] args) {
        Employee e1 = new Employee("小明", "店长", 8000);
        Employee e2 = new Employee("小红", "调茶师", 5000);
        Employee e3 = new Employee("小张", "收银员", 4500);
        
        e1.printInfo();
        e2.printInfo();
        e3.printInfo();
        
        Employee.printTotal();  // 总员工数: 3
    }
}

本章小结

  1. 类 = 设计图,对象 = 实物——用 new 创建对象
  2. 字段存数据、方法定义行为——数据和操作在一起
  3. 构造方法——创建对象的同时初始化,名字跟类名一样
  4. this——指当前对象,用来区分参数和字段
  5. static——属于类的,不属于对象,通过类名调用
  6. 封装——private 字段 + public getter/setter,保护数据
  7. 引用——多个引用可以指向同一个对象,null 是"啥也不指"
  8. GC——没被引用的对象会被自动回收

✅ 验收标准

完成本章后,你应该能:

  • [ ] 定义一个类,包含字段、构造器、方法
  • [ ] 用 new 创建对象并调用方法
  • [ ] 区分实例成员和 static 成员
  • [ ] 用 private + getter/setter 实现封装
  • [ ] 理解对象引用赋值和 GC

📌 常见卡点

  • 构造器的名字必须和类名完全一致——大小写都要一样
  • 定义了有参构造器后默认无参构造器消失——需要手动写
  • this 用于区分参数和字段
  • 对象赋值是引用复制——`a = b` 后改 a 也会影响 b

🔜 现在不需要理解

  • 构造器重载的 this() 调用
  • 内部类和匿名类
  • finalize——已被弃用
  • 单例模式等设计模式


🧪 练习

1. 判断题:类和对象谁是设计图?谁是实物?

2. 改一改:给本章的 MilkTea 类增加一个 sweetness 字段(0-10 的整数),在 setter 里做范围校验。

3. 造一个类:定义一个 Book 类,包含 titleauthorisBorrowed(boolean)三个字段。实现 borrow() 方法(如果已借出则提示"已被借出",否则设为已借出)和 returnBook() 方法。在 main 里测试。


下一篇

第8章 面向对象(下):继承与多态

用 ❤️ 构建 | Software Systems Atlas