Skip to content

Java 对象与类

Java 本质上是一种面向对象编程(OOP)语言。OOP 基于现实世界的对象来建模软件设计。Java 支持的关键 OOP 概念包括:

  • 封装(Encapsulation): 将数据(字段)和操作数据的方法捆绑在一个单元(类)中,并控制对内部状态的访问。
  • 继承(Inheritance): 允许一个新类(子类)继承现有类(超类)的属性和方法,促进代码重用。
  • 多态(Polymorphism): 使对象能够呈现多种形式;通常允许将子类对象视为其超类的对象,并通过重写方法实现特定行为。
  • 抽象(Abstraction): 隐藏复杂的实现细节,只暴露对象或系统的基本功能。

本章重点介绍 Java 中 OOP 的构建块:类(Classes)和对象(Objects)。

  • 类(Class): 用于创建对象的蓝图或模板。它定义了该类的所有对象将拥有的共同属性(字段或特性)和行为(方法)。例如:一个 Car 类定义了 color、make、model 等属性以及 startEngine()、accelerate()、brake() 等行为。
  • 对象(Object): 类的一个实例。它代表从类蓝图创建的特定实体,拥有自己的状态(字段的具体值)。例如:myBlueToyotaCamry 是 Car 类的一个对象(一个实例),其 color 设置为“blue”,make 设置为“Toyota”,model 设置为“Camry”。

思考现实世界中的对象:您的手机、一张桌子、一只宠物。每一个都有:

  • 状态(State): 特性或属性(例如,一只狗的名字、品种、颜色、年龄)。
  • 行为(Behavior): 它可以执行的动作(例如,一只狗可以叫唤、摇尾巴、吃东西、睡觉)。

Java 中的软件对象反映了这一点。它们的状态存储在字段(fields)(也称为实例变量)中,它们的行为通过**方法(methods)**实现。

方法操作对象的内部状态(其字段),并且是对象之间相互交互的主要方式。

类是蓝图。它定义了从它创建的所有对象将是什么样子以及它们将如何行为。

基本类结构:

// 访问修饰符 class 类名 {
//
// // 字段(实例变量) - 定义状态
// 数据类型 字段名1;
// 数据类型 字段名2;
// // ...
//
// // 构造方法 - 创建对象的特殊方法
// 类名(参数) {
// // 初始化代码
// }
//
// // 方法 - 定义行为
// 返回值类型 方法名1(参数) {
// // 方法体(代码)
// }
// // ...
//}

示例:一个简单的 Dog 类:

public class Dog {
// 字段(实例变量)
String breed;
int age;
String color;
String name;
// 构造方法
public Dog(String dogName, String dogBreed, int dogAge, String dogColor) {
// 'this' 关键字指向当前正在创建的对象
this.name = dogName;
this.breed = dogBreed;
this.age = dogAge;
this.color = dogColor;
System.out.println("创建了 Dog 对象: " + this.name);
}
// 方法(行为)
void bark() {
System.out.println(name + " 说: 汪!");
}
void celebrateBirthday() {
this.age++; // 增加这个特定 Dog 对象的年龄
System.out.println("生日快乐," + name + "!你现在 " + age + " 岁了。");
}
}

类内部的变量类型:

  • 实例变量(Fields): 在类中声明,但在任何方法外部。类的每个对象(实例)都有这些变量的自己的副本。它们代表对象的状态(例如 Dog 类中的 breed、age、color、name)。
  • 局部变量(Local Variables): 在方法、构造方法或块中声明。它们只在代码块执行期间存在,不是对象状态的一部分。
  • 类变量(静态字段,Static Fields): 使用 static 关键字声明。静态变量只有一个副本,由类的所有对象共享。常用于常量或计数器(例如 static final int MAX_SPEED = 100; 或 static int dogCount = 0;)。

构造方法是用于初始化新对象的特殊方法。它们的名称与类名相同,没有返回类型(甚至不是 void)。

  • 每个类至少有一个构造方法。
  • 如果您没有显式定义任何构造方法,Java 编译器会提供一个默认的无参构造方法(例如 public Dog() {})。
  • 如果您定义了任何构造方法(就像我们的 Dog 示例中那样),默认的无参构造方法将不会自动提供。
  • 构造方法在使用 new 关键字创建对象时被调用。
  • 一个类可以有多个参数列表不同的构造方法(构造方法重载)。

带有多个构造方法的示例:

public class Rectangle {
double width;
double height;
// 构造方法 1: 无参数 (创建一个 1x1 的矩形)
public Rectangle() {
this.width = 1.0;
this.height = 1.0;
}
// 构造方法 2: 接受宽度和高度
public Rectangle(double w, double h) {
this.width = w;
this.height = h;
}
// 构造方法 3: 接受一个值用于正方形
public Rectangle(double side) {
this.width = side;
this.height = side;
}
// 计算面积的方法
public double getArea() {
return this.width * this.height;
}
}

创建对象通常包含三个步骤,这些步骤通常合并在一个语句中:

  1. 声明(Declaration): 声明一个类类型的变量(Dog myDog;)。
  2. 实例化(Instantiation): 使用 new 关键字为对象分配内存(new)。
  3. 初始化(Initialization): 调用构造方法设置对象的初始状态(Dog(“Buddy”, …))。

示例:

// 声明、实例化和初始化合并
Dog myDog = new Dog("Buddy", "Golden Retriever", 3, "Golden");
Rectangle square = new Rectangle(5.0); // 调用第三个构造方法

您可以使用点(.)运算符通过对象引用变量访问对象的实例变量(字段)和方法。

// 1. 创建一个对象
ClassName objectReference = new ClassName(parameters);
// 2. 访问字段(通常不鼓励直接访问 public 字段,更倾向于使用方法)
// value = objectReference.fieldName;
// objectReference.fieldName = newValue;
// 3. 调用方法
// objectReference.methodName(arguments);

使用 Dog 类的示例:

public class DogPark {
public static void main(String[] args) {
// 创建两个 Dog 对象
Dog dog1 = new Dog("Buddy", "Golden Retriever", 3, "Golden");
Dog dog2 = new Dog("Lucy", "Poodle", 5, "White");
// 访问字段(此处直接访问,但通常通过 Getter/Setter 等方法完成)
System.out.println(dog1.name + " 的年龄: " + dog1.age);
// 调用方法
dog1.bark(); // 输出: Buddy 说: 汪!
dog2.bark(); // 输出: Lucy 说: 汪!
dog1.celebrateBirthday(); // 输出: 生日快乐,Buddy!你现在 4 岁了。
System.out.println(dog1.name + " 的新年龄: " + dog1.age); // 输出: Buddy 的新年龄: 4
}
}

在 Java 中,为了强制实现封装,通常将字段声明为 private。这意味着它们只能通过类内部定义的方法(如 Getter 和 Setter 方法)进行访问或修改。这可以防止外部代码使对象处于无效状态。

带有 private 字段和 public 方法(Getter/Setter)的示例:

public class SecureBankAccount {
private String accountNumber;
private double balance;
public SecureBankAccount(String accNumber, double initialBalance) {
this.accountNumber = accNumber;
// 为余额添加验证
if (initialBalance >= 0) {
this.balance = initialBalance;
}
else {
this.balance = 0;
System.err.println("初始余额不能为负数。设置为 0。");
}
}
// 余额的 Getter 方法 (允许读取)
public double getBalance() {
return balance;
}
// 账户号的 Getter 方法
public String getAccountNumber() {
return accountNumber;
}
// 存款方法 (允许受控修改)
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
} else {
System.err.println("存款金额必须为正数。");
}
}
// 取款方法 (允许受控修改)
public boolean withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return true; // 成功
} else {
System.err.println("取款失败:无效金额或余额不足。");
return false; // 失败
}
}
}

访问修饰符(public、private、protected、默认)控制类、字段和方法的可见性。它们对于封装和 API 设计至关重要。

组织 Java 代码时请记住这些规则:

  • 每个源文件(.java 文件)只能包含一个 public 类。
  • 源文件的名称必须与其中包含的 public 类的名称完全匹配(例如,Dog 类必须位于名为 Dog.java 的文件中)。
  • 一个源文件可以包含多个非 public(包私有)的类。
  • 如果类属于某个包,则 package 声明必须是文件中的第一条语句。
  • import 语句(如果存在)必须出现在 package 语句(如果存在)和类声明之间。
  • package 和 import 语句适用于源文件中的所有类。

**包(Packages)**用于将相关的类和接口组织到命名空间中,防止命名冲突并改进代码结构(例如 java.util、com.mycompany.myapp.ui)。

**导入语句(Import statements)**允许您使用其他包中的类,而无需每次都指定它们的完全限定名(包名 + 类名)。例如:import java.util.ArrayList; 允许您使用 ArrayList 而不是 java.util.ArrayList。

我们创建两个类:Employee(蓝图)和 EmployeeTest(用于创建和使用 Employee 对象)。

1. Employee.java:

package com.example.hr; // 包声明示例
import java.time.LocalDate;
import java.time.Period;
public class Employee {
// 使用 private 字段实现封装
private String name;
private String designation;
private double salary;
private LocalDate hireDate;
// 构造方法
public Employee(String name, String designation, double salary, LocalDate hireDate) {
this.name = name;
this.designation = designation;
this.salary = salary; // 考虑添加验证
this.hireDate = hireDate;
}
// Getter 方法
public String getName() {
return name;
}
public String getDesignation() {
return designation;
}
public double getSalary() {
return salary;
}
public LocalDate getHireDate() {
return hireDate;
}
// Setter 方法 (可选,仅在允许修改时提供)
public void setDesignation(String designation) {
this.designation = designation;
}
public void setSalary(double salary) {
if (salary >= 0) { // 基本验证
this.salary = salary;
} else {
System.err.println("薪水不能为负数。");
}
}
// 计算服务年限的方法
public int getYearsOfService() {
return Period.between(hireDate, LocalDate.now()).getYears();
}
// 打印员工详情的方法
public void printEmployeeDetails() {
System.out.println("---------------------------");
System.out.println("姓名: " + name);
System.out.println("职位: " + designation);
System.out.printf("薪水: $%.2f%n", salary); // 格式化输出
System.out.println("入职日期: " + hireDate);
System.out.println("服务年限: " + getYearsOfService());
System.out.println("---------------------------");
}
}

2. EmployeeTest.java(与 Employee 类在同一包中或导入 Employee 类):

package com.example.hr; // 与 Employee 在同一包中
// 或者如果在不同包中:
// import com.example.hr.Employee;
import java.time.LocalDate;
import java.time.Month;
public class EmployeeTest {
public static void main(String[] args) {
// 使用构造方法创建 Employee 对象
Employee empOne = new Employee("James Smith", "Senior Software Engineer", 95000.00, LocalDate.of(2019, Month.MARCH, 15));
Employee empTwo = new Employee("Mary Anne", "Software Engineer", 72000.00, LocalDate.of(2021, Month.JULY, 1));
// 使用方法与对象交互
System.out.println("初始员工详情:");
empOne.printEmployeeDetails();
empTwo.printEmployeeDetails();
// 使用 Setter 方法修改对象
System.out.println("\n晋升 " + empTwo.getName() + "...");
empTwo.setDesignation("Senior Software Engineer");
empTwo.setSalary(90000.00);
System.out.println("\n更新后的员工详情:");
empOne.printEmployeeDetails(); // 未改变
empTwo.printEmployeeDetails(); // 显示晋升后的信息
}
}

编译和运行(假设文件位于正确的包结构中):

# 导航到包含 'com' 文件夹的基础目录
cd path/to/your/project/src # 或类似的基础路径
# 编译两个 Java 文件 (使用 -d 指定输出目录)
mkdir ../bin # 创建一个目录用于存放编译后的类文件 (可选但推荐的做法)
javac -d ../bin com/example/hr/Employee.java com/example/hr/EmployeeTest.java
# 运行 EmployeeTest 类 (指定 classpath)
java -cp ../bin com.example.hr.EmployeeTest

预期输出将显示两名员工的详情,接着是 Mary Anne 晋升后的更新详情。

理解类和对象是基础。接下来的章节将深入探讨相关的概念,如继承、多态以及 Java 中的基本数据类型。