Skip to content

第6课:面向对象基础

🎯 学习目标

  • 理解类与对象的概念
  • 掌握成员变量、成员方法、构造方法
  • 理解封装的思想和实现
  • 掌握 this 关键字的使用

📖 一、面向对象 vs 面向过程

C 语言(面向过程)

c
// C: 数据和函数分离
struct Dog {
    char name[50];
    int age;
};

void bark(struct Dog* dog) {
    printf("%s is barking!\n", dog->name);
}

int main() {
    struct Dog dog;
    strcpy(dog.name, "旺财");
    dog.age = 3;
    bark(&dog);  // 函数在结构体外部
}

Java(面向对象)

java
// Java: 数据和方法封装在类中
class Dog {
    String name;
    int age;
    
    void bark() {  // 方法属于对象
        System.out.println(name + " is barking!");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.name = "旺财";
        dog.age = 3;
        dog.bark();  // 对象调用方法
    }
}

📖 二、类的定义

1. 基本结构

java
public class Person {
    // 成员变量(属性、字段)
    String name;
    int age;
    String gender;
    
    // 成员方法(行为)
    void introduce() {
        System.out.println("我叫" + name + ",今年" + age + "岁");
    }
    
    void birthday() {
        age++;
        System.out.println("生日快乐!现在" + age + "岁了");
    }
}

2. 创建对象

java
// 语法:类名 对象名 = new 类名();
Person person = new Person();

// 访问成员变量
person.name = "张三";
person.age = 25;
person.gender = "男";

// 调用成员方法
person.introduce();  // 输出:我叫张三,今年25岁
person.birthday();   // 输出:生日快乐!现在26岁了

3. 内存模型

java
Person p1 = new Person();
Person p2 = new Person();

p1.name = "Alice";
p2.name = "Bob";

// p1 和 p2 是不同的对象,在堆中有独立的内存空间
System.out.println(p1.name);  // Alice
System.out.println(p2.name);  // Bob

内存图

栈(Stack)           堆(Heap)
┌─────────┐          ┌──────────────┐
│ p1  ───>│────────>│  Person对象   │
└─────────┘          │  name: Alice │
                     │  age: 0      │
┌─────────┐          └──────────────┘
│ p2  ───>│────────>┌──────────────┐
└─────────┘          │  Person对象   │
                     │  name: Bob   │
                     │  age: 0      │
                     └──────────────┘

📖 三、构造方法

1. 默认构造方法

java
public class Student {
    String name;
    int age;
    
    // 如果不写构造方法,编译器自动生成无参构造
    // public Student() { }
}

// 使用
Student s = new Student();

2. 自定义构造方法

java
public class Student {
    String name;
    int age;
    
    // 无参构造
    public Student() {
        System.out.println("创建了一个学生对象");
    }
    
    // 有参构造
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

// 使用
Student s1 = new Student();              // 调用无参构造
Student s2 = new Student("李四", 20);    // 调用有参构造

3. 构造方法重载

java
public class Book {
    String title;
    String author;
    double price;
    
    // 构造方法1:无参
    public Book() {
        this("未知书名", "未知作者", 0.0);
    }
    
    // 构造方法2:只传书名
    public Book(String title) {
        this(title, "未知作者", 0.0);
    }
    
    // 构造方法3:全参数
    public Book(String title, String author, double price) {
        this.title = title;
        this.author = author;
        this.price = price;
    }
}

// 使用
Book b1 = new Book();
Book b2 = new Book("Java编程思想");
Book b3 = new Book("Java编程思想", "Bruce Eckel", 99.0);

⚠️ 注意

  • 构造方法名必须与类名完全相同
  • 构造方法没有返回类型(连 void 都不写)
  • 一旦定义了有参构造,无参构造就不会自动生成(需要手动写)

📖 四、this 关键字

1. this 代表当前对象

java
public class Person {
    String name;
    
    void setName(String name) {
        // this.name 是成员变量
        // name 是参数
        this.name = name;
    }
    
    void introduce() {
        System.out.println("我是" + this.name);
        // this 可以省略
        System.out.println("我是" + name);
    }
}

2. this 调用构造方法

java
public class Student {
    String name;
    int age;
    String major;
    
    public Student() {
        this("未命名", 0, "未定");  // 调用三参数构造
    }
    
    public Student(String name, int age) {
        this(name, age, "计算机");  // 调用三参数构造
    }
    
    public Student(String name, int age, String major) {
        this.name = name;
        this.age = age;
        this.major = major;
    }
}

⚠️ 注意

  • this() 必须是构造方法的第一条语句
  • 不能循环调用(A调用B,B调用A)

3. this 的应用场景

java
public class BankAccount {
    private String accountNumber;
    private double balance;
    
    // 场景1:区分成员变量和参数
    public BankAccount(String accountNumber, double balance) {
        this.accountNumber = accountNumber;
        this.balance = balance;
    }
    
    // 场景2:返回当前对象(链式调用)
    public BankAccount deposit(double amount) {
        this.balance += amount;
        return this;  // 返回当前对象
    }
    
    public BankAccount withdraw(double amount) {
        this.balance -= amount;
        return this;
    }
}

// 链式调用
BankAccount account = new BankAccount("001", 1000);
account.deposit(500).withdraw(200).deposit(100);

📖 五、封装

1. 为什么需要封装

java
// ❌ 不封装的问题
public class Person {
    String name;
    int age;  // 任何人都可以随意修改
}

Person p = new Person();
p.age = -100;  // 不合理的数据,但无法阻止

2. 封装的实现

java
public class Person {
    // private:私有,只能在本类中访问
    private String name;
    private int age;
    
    // public getter:提供读取权限
    public String getName() {
        return name;
    }
    
    // public setter:提供修改权限,可以加入校验
    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 < 0 || age > 150) {
            System.out.println("年龄不合理");
            return;
        }
        this.age = age;
    }
}

// 使用
Person p = new Person();
p.setAge(-100);  // 输出:年龄不合理,不会设置
p.setAge(25);    // 正常设置
System.out.println(p.getAge());  // 25

3. 访问修饰符

修饰符本类同包子类任何地方
private
默认(不写)
protected
public
java
public class Example {
    private int a;      // 只能在本类访问
    int b;              // 默认,同包可访问
    protected int c;    // 同包和子类可访问
    public int d;       // 任何地方都可访问
}

4. JavaBean 规范

java
// 标准的 JavaBean
public class User {
    // 1. 成员变量私有
    private String username;
    private String password;
    private int age;
    
    // 2. 无参构造
    public User() {
    }
    
    // 3. 有参构造(可选)
    public User(String username, String password, int age) {
        this.username = username;
        this.password = password;
        this.age = age;
    }
    
    // 4. getter/setter 方法
    public String getUsername() {
        return username;
    }
    
    public void setUsername(String username) {
        this.username = username;
    }
    
    public String getPassword() {
        return password;
    }
    
    public void setPassword(String password) {
        this.password = password;
    }
    
    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        this.age = age;
    }
}

📖 六、对象的创建过程

java
Student s = new Student("张三", 20);

// 执行步骤:
// 1. JVM 在堆中分配内存
// 2. 成员变量初始化为默认值(0, null, false)
// 3. 执行构造方法
// 4. 返回对象的引用给 s

📖 七、对象比较

1. == vs equals()

java
Person p1 = new Person("Alice", 25);
Person p2 = new Person("Alice", 25);
Person p3 = p1;

System.out.println(p1 == p2);   // false(不同对象)
System.out.println(p1 == p3);   // true(同一对象)
System.out.println(p1.equals(p2));  // 需要重写 equals 方法

2. 重写 equals 方法

java
public class Person {
    private String name;
    private int age;
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && name.equals(person.name);
    }
}

⚠️ 常见陷阱

陷阱1:类只是一堆 getter/setter

如果类只有字段和访问器,没有行为,往往说明领域建模还不够。对象应尽量把数据和相关行为放在一起。

陷阱2:构造器没有维护不变量

构造器应该保证对象创建后处于合法状态,不要创建“半初始化对象”。

陷阱3:直接暴露可变字段

把内部 List、数组直接返回给外部,会破坏封装。必要时返回副本或不可变视图。

陷阱4:用 == 比较对象内容

对象内容比较应使用 equals== 比较的是引用是否相同。


🆚 Java vs C 对比

特性C structJava class
数据与行为通常分离字段和方法封装在类中
构造手动初始化函数构造方法
封装靠约定和头文件private/protected/public
内存管理手动 malloc/freenew + GC

对 C 程序员来说,Java 类不是“带函数的 struct”这么简单。它还承担封装、生命周期初始化和行为聚合的职责。


💡 最佳实践

  1. 成员变量私有化,提供 getter/setter
  2. 构造方法中初始化对象
  3. 重写 toString() 方法便于调试
  4. 重写 equals() 和 hashCode() 方法

📝 练习预告

完成 练习/Ex06_OOP_Basic.java

  1. 设计一个学生类
  2. 设计一个银行账户类
  3. 实现图书管理
  4. 对象数组应用
  5. 封装实践
  6. 综合:员工管理系统

🎓 下一步

  • 第7课:继承 - extends、super、方法重写