1. 从现实世界理解类和对象的概念
想象你走进一家汽车4S店,展厅里停放着各种型号的汽车。这些汽车虽然外观、配置各不相同,但都有共同的特征:方向盘、发动机、轮胎等。在这个场景中:
-
**类(Class)**就像是汽车的设计图纸,它定义了:
- 属性(字段):颜色、排量、座位数等
- 行为(方法):加速、刹车、鸣笛等
-
**对象(Object)**则是根据这张图纸制造出来的具体汽车,比如:
- 一辆红色2.0T的SUV(属性实例化)
- 正在以60km/h行驶(方法调用)
java复制// 汽车类的Java定义
public class Car {
// 属性定义
String color;
double engineDisplacement;
int seats;
// 方法定义
void accelerate() {
System.out.println("正在加速...");
}
void brake() {
System.out.println("正在制动...");
}
}
// 创建具体对象
Car myCar = new Car();
myCar.color = "红色";
myCar.engineDisplacement = 2.0;
myCar.seats = 5;
myCar.accelerate();
2. 类的组成要素详解
2.1 成员变量 vs 局部变量
在类中定义变量时有三种类型需要区分:
| 变量类型 | 声明位置 | 生命周期 | 初始化要求 | 访问权限 |
|---|---|---|---|---|
| 成员变量 | 类内部,方法外部 | 与对象共存亡 | 有默认值(int为0,对象为null等) | 整个类可见 |
| 局部变量 | 方法或代码块内部 | 方法/块执行期间 | 必须显式初始化 | 仅在声明块内可见 |
| 类变量 | 类内部,用static修饰 | 程序运行期间 | 有默认值 | 全局可访问 |
java复制public class VariableDemo {
// 成员变量
int memberVar;
// 类变量(静态变量)
static int classVar;
void demoMethod() {
// 局部变量
int localVar = 10; // 必须初始化
System.out.println(localVar);
}
}
2.2 方法的定义与使用
方法是对象行为的载体,定义时需注意:
- 方法签名:由方法名+参数列表组成,如
calculate(int a, int b) - 返回值:无返回值用void,有返回值需明确类型
- 参数传递:Java是值传递,对象传递的是引用副本
java复制public class Calculator {
// 方法重载示例(同名不同参)
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
// 可变参数方法
int sum(int... numbers) {
int total = 0;
for(int num : numbers) {
total += num;
}
return total;
}
}
3. 对象的创建与使用
3.1 构造方法深度解析
构造方法是对象诞生的"接生婆",有几个关键特性:
- 默认构造器:当类没有显式定义构造方法时,编译器会自动提供无参构造器
- 构造器重载:可以定义多个参数不同的构造器
- this调用:在一个构造器中可以用this()调用其他构造器(必须首行)
java复制public class Student {
String name;
int age;
// 无参构造器
public Student() {
this("匿名", 18); // 调用另一个构造器
}
// 全参构造器
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// 拷贝构造器
public Student(Student other) {
this(other.name, other.age);
}
}
3.2 对象初始化流程
当执行new Student()时,JVM会:
- 加载Student.class到方法区
- 在堆中分配内存空间
- 执行成员变量默认初始化(基本类型0,对象null等)
- 执行显式初始化(如
int age = 18;) - 执行构造器代码
- 返回对象引用
内存提示:对象存储在堆内存,引用变量存储在栈内存。一个引用可以指向多个对象(重新赋值),但一个对象可以有多个引用(如集合中的元素)。
4. 面向对象特性实现
4.1 封装的实际应用
封装不只是简单的private+getter/setter,好的封装应该:
- 隐藏实现细节:比如集合类的内部数据结构
- 验证数据合法性:在setter中添加业务校验
- 控制访问粒度:部分方法设为protected供子类使用
java复制public class BankAccount {
private String accountId;
private double balance;
// 封装取款操作
public boolean withdraw(double amount) {
if(amount <= 0 || amount > balance) {
return false;
}
balance -= amount;
return true;
}
// 替代简单getter的业务方法
public String getDisplayBalance() {
return String.format("¥%.2f", balance);
}
}
4.2 static关键字的正确使用
static修饰的成员属于类而非对象,使用时需注意:
- 类变量:用于类级别的数据共享(如计数器)
- 类方法:工具类方法常用(如Math.sqrt())
- 静态块:类加载时执行,用于初始化静态资源
java复制public class AppConfig {
// 静态常量(全大写命名)
public static final String VERSION = "1.0.0";
// 静态块初始化复杂静态变量
private static Properties config;
static {
config = new Properties();
try {
config.load(AppConfig.class.getResourceAsStream("/app.properties"));
} catch (IOException e) {
throw new RuntimeException("加载配置文件失败", e);
}
}
// 静态工具方法
public static String getConfig(String key) {
return config.getProperty(key);
}
}
5. 开发中的常见误区
5.1 对象比较的陷阱
java复制String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false,比较的是引用地址
System.out.println(s1.equals(s2)); // true,比较内容
// 解决方法:
// 1. 重写equals()方法时必须同时重写hashCode()
// 2. 对于枚举类型可以直接用==比较
5.2 可变对象的安全共享
java复制public class UnsafeExample {
private static List<String> sharedList = new ArrayList<>();
public static void addItem(String item) {
// 多线程环境下会出问题
sharedList.add(item);
}
// 安全方案1:返回不可变视图
public static List<String> getItems() {
return Collections.unmodifiableList(sharedList);
}
// 安全方案2:防御性拷贝
public static void addAllItems(List<String> items) {
sharedList.addAll(new ArrayList<>(items));
}
}
6. 工程实践建议
6.1 合理的类设计原则
- 单一职责:一个类只做一件事(如UserService只处理用户相关)
- 高内聚低耦合:类内部紧密相关,类之间依赖接口而非实现
- 优先组合而非继承:has-a关系比is-a关系更灵活
java复制// 不好的继承设计
class Rectangle {
int width, height;
// getters/setters
}
class Square extends Rectangle {
// 破坏里氏替换原则
void setWidth(int w) {
super.setWidth(w);
super.setHeight(w);
}
}
// 更好的设计:使用组合
interface Shape {
double area();
}
class Square implements Shape {
private Rectangle rectangle;
Square(int size) {
this.rectangle = new Rectangle(size, size);
}
@Override
public double area() {
return rectangle.getWidth() * rectangle.getHeight();
}
}
6.2 Lombok的明智使用
虽然Lombok能简化代码,但要注意:
- 谨慎使用@Builder:会生成全参构造器可能破坏不变性
- 避免@Data:默认实现equals/hashCode可能不符合业务需求
- 与JPA实体配合问题:需要额外配置如
@EqualsAndHashCode(exclude = "orders")
java复制// 推荐的Lombok用法
@Value // 生成不可变类
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
public class OrderItem {
@NonNull
Long productId;
@NonNull
@Builder.Default
Integer quantity = 1;
@NonNull
BigDecimal unitPrice;
}
在实际项目中,理解类和对象的关系是写出优质Java代码的基础。建议从简单的领域模型开始练习,逐步掌握面向对象设计的精髓。
