1. Java基础03:从零开始掌握面向对象编程
刚接触Java的新手常会遇到一个分水岭——当学到第三个基础阶段时,面向对象的概念突然变得抽象起来。我记得自己最初看到"类"和"对象"这些术语时,就像在听天书。但后来发现,只要用生活中的例子来理解,这些概念就会变得非常直观。今天我们就用最接地气的方式,彻底搞懂Java面向对象的核心要点。
面向对象编程(OOP)是Java的立身之本,它让代码像乐高积木一样可复用、可扩展。不同于前两章的基础语法,这部分内容将教你如何用Java的思维方式构建程序。我们会从最基本的类和对象开始,逐步深入到封装、继承和多态这三大特性,最后通过一个完整的超市商品管理系统案例,把理论知识转化为实际编码能力。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 类与对象:Java世界的蓝图与实体
2.1 类:对象的模板设计
想象你要建造一批玩具车。首先需要什么?没错,是一张设计图纸。在Java中,类就是这样的图纸。它定义了对象应有的属性和行为。比如我们要描述超市商品:
java复制public class Product {
// 属性(成员变量)
String name;
double price;
int stock;
// 方法(行为)
void displayInfo() {
System.out.println("商品:" + name + " 价格:" + price + " 库存:" + stock);
}
}
这里有几个关键点需要注意:
- 类名使用大驼峰命名法(Product而非product)
- 成员变量声明在类内部、方法外部
- 方法不需要static修饰(与之前学的main方法不同)
经验:初学者常犯的错误是把类当成"工具集合"来用,实际上类应该代表现实世界中的实体。好的类设计应该让属性私有化(后面会讲封装),并通过方法暴露必要操作。
2.2 对象:类的具体实例
有了设计图,现在可以生产具体的玩具车了。在Java中,我们使用new关键字创建对象:
java复制public class Main {
public static void main(String[] args) {
// 创建Product类的实例
Product apple = new Product();
apple.name = "红富士苹果";
apple.price = 8.9;
apple.stock = 100;
Product milk = new Product();
milk.name = "纯牛奶";
milk.price = 3.5;
milk.stock = 50;
// 调用对象方法
apple.displayInfo();
milk.displayInfo();
}
}
每个对象都在内存中独立存在,修改apple不会影响milk。这就是面向对象的核心优势——我们可以创建多个相似但独立的对象。
3. 封装:保护数据的保险箱
3.1 为什么要封装
直接操作对象的属性(如apple.price = -100)存在严重问题:无法控制数据的有效性。封装就像给数据加了保险箱,只允许通过特定方式访问:
java复制public class Product {
// 私有化属性
private String name;
private double price;
private int stock;
// 提供公开的getter/setter
public String getName() {
return name;
}
public void setName(String name) {
if(name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("商品名不能为空");
}
this.name = name;
}
public void setPrice(double price) {
if(price <= 0) {
throw new IllegalArgumentException("价格必须大于0");
}
this.price = price;
}
// 其他getter/setter...
}
3.2 封装的优势
- 数据验证:在setter中可以添加业务规则
- 灵活性:可以随时修改内部实现而不影响外部代码
- 安全性:防止外部代码随意修改敏感数据
避坑指南:很多新手会觉得写getter/setter很麻烦,直接用public属性。这在小型项目中可能没问题,但随着项目扩大,会带来难以调试的问题。建议从一开始就养成封装的好习惯。
4. 继承:代码复用的利器
4.1 继承的基本使用
假设我们的超市有普通商品和特价商品,后者需要记录折扣信息。使用继承可以避免重复代码:
java复制public class DiscountProduct extends Product {
private double discount; // 折扣率
public double getDiscount() {
return discount;
}
public void setDiscount(double discount) {
if(discount <= 0 || discount >= 1) {
throw new IllegalArgumentException("折扣率应在0-1之间");
}
this.discount = discount;
}
@Override
public void displayInfo() {
super.displayInfo(); // 调用父类方法
System.out.println("折扣率:" + (discount*100) + "%");
}
}
4.2 继承的注意事项
- Java是单继承,一个类只能继承一个父类
- 子类拥有父类所有非private属性和方法
- 可以使用@Override注解明确表示方法重写
- 构造方法不会被继承,需要通过super()调用
常见错误:过度使用继承会导致类层次过深。记住"组合优于继承"原则,当不确定时,优先考虑使用组合(将类作为另一个类的属性)。
5. 多态:同一接口的不同表现
5.1 多态的实现方式
多态允许我们以统一的方式处理不同类型的对象。Java通过方法重写和接口实现多态:
java复制public class Test {
public static void main(String[] args) {
Product p1 = new Product();
Product p2 = new DiscountProduct(); // 向上转型
p1.displayInfo(); // 调用Product的方法
p2.displayInfo(); // 调用DiscountProduct重写的方法
}
}
5.2 多态的应用场景
- 方法参数多态:可以接受父类类型参数
- 集合存储多态:List
可以存储所有子类对象 - 工厂模式:根据条件返回不同类型的对象
性能提示:多态会带来轻微的性能开销(虚方法表查找),但在现代JVM上几乎可以忽略不计。不要因为担心性能而放弃使用多态。
6. 综合案例:超市商品管理系统
现在我们把所有概念整合到一个实际案例中:
java复制import java.util.ArrayList;
import java.util.List;
public class SupermarketSystem {
private List<Product> products = new ArrayList<>();
public void addProduct(Product p) {
products.add(p);
}
public void displayAll() {
for(Product p : products) {
p.displayInfo();
System.out.println("------------");
}
}
public static void main(String[] args) {
SupermarketSystem system = new SupermarketSystem();
Product apple = new Product();
apple.setName("苹果");
apple.setPrice(5.5);
apple.setStock(200);
DiscountProduct milk = new DiscountProduct();
milk.setName("牛奶");
milk.setPrice(3.0);
milk.setStock(100);
milk.setDiscount(0.8); // 8折
system.addProduct(apple);
system.addProduct(milk);
system.displayAll();
}
}
这个案例展示了:
- 使用List集合管理商品
- 多态在addProduct方法中的应用
- 封装确保数据有效性
- 继承扩展商品类型
7. 常见问题与解决方案
7.1 对象创建问题
问题: 创建对象时报NullPointerException
原因: 忘记用new初始化对象
解决:
java复制Product p; // 错误:只是声明引用
p = new Product(); // 正确:实例化对象
7.2 继承中的构造方法
问题: 子类构造方法报错
原因: 父类没有无参构造方法
解决:
java复制public class Parent {
public Parent(int x) { ... }
}
public class Child extends Parent {
public Child() {
super(10); // 必须显式调用父类构造方法
}
}
7.3 封装与访问控制
问题: 无法访问私有属性
原因: 直接访问private成员
解决: 使用getter/setter方法
java复制Product p = new Product();
p.name = "test"; // 错误
p.setName("test"); // 正确
8. 进阶技巧与最佳实践
- toString()方法: 总是重写toString()以便调试:
java复制@Override
public String toString() {
return "Product[name=" + name + ", price=" + price + "]";
}
-
equals()和hashCode(): 当需要比较对象内容时,应该重写这两个方法
-
静态工厂方法: 替代构造方法的更灵活方式:
java复制public static Product createProduct(String name, double price) {
Product p = new Product();
p.setName(name);
p.setPrice(price);
return p;
}
- 不可变对象: 对于值对象,考虑设计为不可变:
java复制public final class ImmutableProduct {
private final String name;
private final double price;
public ImmutableProduct(String name, double price) {
this.name = name;
this.price = price;
}
// 只提供getter,不提供setter
}
掌握这些面向对象基础后,你会发现自己写Java代码的思路完全不同了。记住,好的面向对象设计就像组织一个高效的团队——每个类都应该有明确的职责,对象之间通过定义良好的接口协作。
