面向对象编程是Java中new、toString()、NullPointerException背后的实际规则;类为模板约束对象生命期与行为,对象为独立实例共享类字节码;static方法不可访问非static成员;封装需校验setter而非仅private字段。

Java 里的面向对象编程不是一套要背的概念清单,而是你写 new、调用 toString()、被 NullPointerException 抓到时,背后实际起作用的那套规则。
类和对象到底谁依赖谁
类是模板,对象是实例——这句话没错,但容易让人忽略关键点:对象的生命期、内存布局、方法绑定全由类定义约束。你声明一个 Student 类,哪怕没 new 出任何对象,JVM 已加载其字节码、静态字段和 static 块;而每个 new Student() 都在堆上分配独立字段空间,但共享同一份方法区中的字节码。
常见误操作:
- 在
static方法里直接访问非static字段或方法(编译报错:non-static variable xxx cannot be referenced from a static context) - 以为
new多次就等于“复制了逻辑”,其实只是复用类中定义的行为,字段值彼此隔离
封装不是“private 一下就完事”
private 是起点,不是终点。真正的封装体现在:是否允许外部绕过你的控制逻辑修改状态?比如一个 BankAccount 类把 balance 设为 private,但如果提供 setBalance(double b) 且不做校验,那和公开字段没本质区别。
立即学习“Java免费学习笔记(深入)”;
实操建议:
- 字段尽量
private,暴露行为(方法)而非数据 - getter/setter 要有边界意识:
getAge()可以算出来,setAge(int a)应校验a > 0 && a - 构造器里别留“半初始化”漏洞:避免在构造过程中调用可被子类重写的方法(
this.xxx()),否则可能触发子类字段未初始化就访问
继承中的方法调用为什么有时不走子类
核心是“静态绑定 vs 动态绑定”。static、private、final 方法在编译期就确定调用目标,不走虚方法表;而普通实例方法在运行期根据实际对象类型查表分派。
典型陷阱:
- 子类重写了父类
public void print(),但父类构造器里调用了this.print()→ 实际执行的是子类版本,而此时子类字段可能还是默认值(如0或null) - 用父类引用指向子类对象(
Animal a = new Dog();),调用a.eat()走多态,但调用a.sleep()(若sleep是static)则只看引用类型Animal,不看实际对象
接口和抽象类选哪个,取决于你要约束什么
接口描述“能做什么”(Runnable、Comparable),抽象类描述“是什么+部分怎么做”(AbstractList 提供了 size()、isEmpty() 的默认实现,但把 get(int) 留给子类)。
现实权衡:
- Java 8+ 接口可以有
default方法,但不能有构造器、不能存实例状态(字段必须是public static final) - 抽象类可含构造器、成员变量、
protected方法,适合构建有共同生命周期或共享状态的类族 - 一个类只能继承一个抽象类,但可实现多个接口——这决定了组合优先于继承的实际落地方式
真正难的不是记住“四大特性”,而是每次写 new、加 override、改 access modifier 时,脑子里得同步跑一遍:这个对象的内存在哪?这个方法调用最终落到哪?这个字段能不能被意外改写?这些细节不显眼,但出问题时往往最耗时间。











