Java 面向对象基础初步
面向对象的核心概念概览
面向对象的核心目标是 把数据和操作封装在一起(对象),并通过抽象、继承与多态组织程序。简而言之,我们总是没法回避程序设计的四个话题:
- 封装(Encapsulation):隐藏内部状态,仅通过方法暴露行为。
- 继承(Inheritance):子类复用父类代码并扩展行为。
- 多态(Polymorphism):通过父类型引用调用子类实现,实现运行时多种行为。
- 抽象(Abstraction):只暴露必要接口,忽略实现细节。
类与对象:声明、字段、方法、构造器
最基本的类
public class Person {// 字段(成员变量)private String name;private int age;// 构造器public Person(String name, int age) {this.name = name;this.age = age;}// 方法(行为)public String getName() { return name; }public int getAge() { return age; }public void setAge(int age) {if (age < 0) throw new IllegalArgumentException("age must be >= 0");this.age = age;}public void sayHello() {System.out.println("Hi, I'm " + name + ", " + age + " years old.");}
}
创建对象(实例化)
Person p = new Person("Alice", 30);
p.sayHello(); // Hi, I'm Alice, 30 years old.
字段类型
- 实例字段(每个对象一份)
- 类字段(
static
,类共享一份) - 局部变量(方法内部)
上述讨论内容,我们基本上就确定了一个类。
封装与访问控制
封装的实践主要靠访问修饰符与方法(getter/setter)来实现。
访问修饰符
private
:仅类内可见(最严格)- (package-private)(默认): 同包可见
protected
:同包或子类可见public
:全局可见
最佳实践:默认 private
,只对外暴露必要的 public
/protected
API。保留最小可见性以降低耦合与错误使用风险。
不可变对象
不可变类(immutable)能降低并发复杂度和调试难度。常见做法:
- 所有字段
private final
- 不提供 setter
- 若包含可变对象,返回防御性拷贝或不可变视图
示例不可变类:
public final class Point {private final int x;private final int y;public Point(int x, int y) { this.x = x; this.y = y; }public int getX() { return x; }public int getY() { return y; }
}
继承与 extends
继承让子类获得父类(超类)的字段和方法。Java 支持单继承(一个类只能有一个直接父类),但允许实现多个接口。
public class Employee extends Person {private String id;public Employee(String name, int age, String id) {super(name, age); // 调用父类构造器this.id = id;}public String getId() { return id; }
}
方法重写(Override)
用 @Override
注解明确你在重写:
@Override
public void sayHello() {System.out.println("Employee " + getName() + " (id=" + id + ")");
}
super
关键字
super(...)
调用父类构造器(必须放在构造器首行)super.method()
调用父类方法实现
小心滥用继承
继承表示“is-a”关系(例如 Employee
is-a Person
)。如果只是为了代码复用而继承往往是错误的,优先考虑组合(has-a)。
多态与动态绑定
多态允许你用父类型引用指向子类型对象,并在运行时调用子类实现。
Person p = new Employee("Bob", 28, "E001");
p.sayHello(); // 调用 Employee 中的覆盖版本(动态绑定)
关键点:
- 决定调用哪个方法的,是运行时对象的实际类型,而不是引用类型(动态绑定)。
- 编译时只能访问引用类型可见的方法/字段;不能访问子类特有的方法除非进行类型转换(cast)。
类型转换示例:
if (p instanceof Employee) {Employee e = (Employee) p;System.out.println(e.getId());
}
抽象类与接口
抽象类 (abstract
)
- 可以包含抽象方法(没有实现)和具体方法(有实现)。
- 适合需要共享实现与状态的场景。
public abstract class Animal {public abstract void sound();public void breathe() { System.out.println("breathing..."); }
}
接口 (interface
)
- 定义行为契约。Java 8+ 接口可以包含
default
方法与static
方法,Java 9+ 有私有方法。 - 接口更倾向于描述能力(capability),支持多重实现(多态性更强)。
public interface Flyable {void fly();default void land() { System.out.println("landing"); }
}
何时用抽象类 vs 接口
- 需要字段或构造器时用抽象类。
- 希望多个不相关类实现同一行为时用接口。
- 优先接口(更灵活),必要时用抽象类提供共享实现。
组合(Composition)与委托(Delegation)
组合优先于继承(composition over inheritance)。将功能拆成小组件并组合能降低耦合。
public class Engine { void start() { System.out.println("engine on"); } }public class Car {private final Engine engine;public Car(Engine engine) { this.engine = engine; }public void start() { engine.start(); } // 委托给 Engine
}
组合的优点:
- 更灵活(运行时替换组件)
- 避免子类层次膨胀
- 更易单元测试(mock 组件)
static
, final
, 常量与工具类
static
- 静态字段/方法属于类,不属于实例。
- 常用于工具方法(
Math
)、单例模式中的单例引用或缓存。
public class Utils {public static int max(int a, int b) { return a > b ? a : b; }
}
final
- 修饰变量:值不可变(对于引用类型,引用不可变但对象可变)。
- 修饰方法:禁止子类重写。
- 修饰类:禁止继承(如
String
)。
equals
, hashCode
, toString
的正确实现
规范摘要
equals
应满足自反、对称、传递、一致性和对null
返回false
。- 若重写
equals
,必须同时重写hashCode
(相等对象必须有相同的 hashCode)。 toString
用于调试输出,应简洁明了。
使用示例:IDE / Objects
帮助生成
public class Point {private final int x, y;public Point(int x, int y) { this.x = x; this.y = y; }@Overridepublic boolean equals(Object o) {if (this == o) return true;if (!(o instanceof Point)) return false;Point p = (Point) o;return x == p.x && y == p.y;}@Overridepublic int hashCode() {return 31 * x + y; // 简单组合}@Overridepublic String toString() {return "Point(" + x + "," + y + ")";}
}
常见错误
- 在
equals
中用getClass()
校验时会阻止子类与父类比较(这可能是期望行为,也可能不是)。使用instanceof
更通用,但需注意 LSP(里氏替换)。 - 把可变字段用于
hashCode
:若对象作为HashSet
key,修改这些字段会破坏 HashMap 的语义。
对象创建、初始化顺序与垃圾回收简介
创建与初始化顺序(类/实例)
- 类加载时静态字段和静态初始化块按出现顺序执行。
- 每次创建实例时:父类构造器先被调用,字段初始化与实例初始化块然后按顺序执行,最后子类构造器体执行。
示例顺序(伪):
- 父类静态初始化(类第一次加载)
- 子类静态初始化
- 创建实例:父类实例字段初始化 -> 父类构造器 -> 子类实例字段初始化 -> 子类构造器
垃圾回收(GC)
Java 使用自动垃圾回收(JVM 实现多样)。要点:
- 不要依赖 finalizer (
finalize()
已被废弃,不推荐使用)。 - 优先控制对象生命周期、减少临时分配以降低 GC 压力。
- 使用内存分析工具(VisualVM、jmap、jstat、YourKit)诊断内存泄漏/对象分配热点。