跳到正文
Java 入门到面试指南
返回

02-面向对象与核心类

阶段:入门篇 | 建议时长:6 天 | 前置:01-环境搭建与基础语法.md

学习目标

  • 能用「封装、继承、多态」设计一个 3 层以上继承体系并解释每一处多态调用。
  • 能重写 equals/hashCode/toString,并说清两者的约定关系。
  • 掌握 String 常量池、Integer 缓存、BigDecimal 精度三大经典坑,能预测代码输出结果。
  • 会用枚举表达固定状态集合,并写出线程安全的枚举单例。
  • 会用 java.time 完成日期格式化、加减与间隔计算。

核心知识点

类、对象、构造方法与 this

  • 类是模板,对象是实例:new 在堆中分配内存,字段有默认值,局部变量必须手动初始化。
  • 构造方法与类同名、无返回值;未手写时编译器提供无参构造;this(...) 可在构造方法间复用(必须放第一行)。
  • this 指当前对象,用于区分成员变量与参数(this.name = name)。
  • JavaBean 规范:字段 private、提供 getter/setter、无参构造。

封装、继承、多态

面试官想听:不只是背定义,而是能各举一个真实设计场景说明「为什么需要」。

  • 封装:字段 private + 提供 public 访问器,在 setter 中做校验,隔离变化。
  • 继承:class Dog extends Animal,子类复用并扩展父类;Java 单继承(避免菱形冲突),所有类默认继承 Object。
  • 多态:父类引用指向子类对象 Animal a = new Dog();,调用 a.sound() 执行的是子类重写后的方法(运行时绑定);成员变量没有多态,静态方法不参与多态。
  • super 调用父类构造(必须第一行)或父类成员;instanceof 判断真实类型后再向下转型(Java 16+ 可写 if (a instanceof Dog d))。

重写 vs 重载

面试官想听:一句话点破——重写是运行时多态(纵向),重载是编译时多态(横向)。

维度重写 Override重载 Overload
范围父子类之间同一个类中
参数列表必须相同必须不同
返回值相同或为其子类可以不同
访问权限不能小于父类无要求
异常不能抛更宽的受检异常无要求
绑定时机运行时编译时
  • @Override 注解让编译器帮你校验是否真的是重写,建议永远加上。
  • private、static、final 方法不能被重写。

抽象类 vs 接口

面试官想听:抽象类是「是什么」(模板复用),接口是「能做什么」(能力契约),能按场景选择。

维度抽象类 abstract class接口 interface
构造方法有(供子类调用)无
成员变量任意只能 public static final 常量
方法可含具体实现Java 8 前只有抽象方法;8+ 允许 default/static 方法
继承/实现单继承多实现
设计语义“is-a”,代码复用“can-do”,能力声明
  • 典型组合:抽象类 AbstractList 提供骨架实现,接口 List 定义契约——模板方法模式。

static 与 final

  • static:属于类而非实例。静态变量所有实例共享;静态方法不能访问实例成员、不能用 this;静态代码块在类加载时执行一次。
  • final:修饰类(不能被继承,如 String)、修饰方法(不能被重写)、修饰变量(基本类型值不变,引用类型指向不变但内容可变)。
  • 常量命名:static final int MAX_SIZE = 10;

代码块的执行顺序(面试常给继承场景问输出):

  • 静态代码块(static { }):随类初始化执行,只执行一次;多个静态块按书写顺序执行。
  • 实例代码块({ },也叫构造代码块):每次 new 都执行,且在构造方法体之前;它会被编译器插进每个构造方法的最前面。
  • 顺序:父类静态块 → 子类静态块 → 父类实例块 → 父类构造方法 → 子类实例块 → 子类构造方法。
  • 记忆口诀:静态只一次(先父后子),实例每次 new(先父后子,块在构造前)。类初始化的触发时机与静态块的细节见《02-JVM核心》。

内部类与访问权限

  • 成员内部类(持有外部类引用)、静态嵌套类(不持有,推荐静态场景用)、局部内部类、匿名内部类(Lambda 的前身,实现监听器/临时逻辑)。
  • 访问权限从小到大:private(本类)→ 默认/包级(同包)→ protected(同包+子类)→ public(全公开)。
  • 包的作用:命名空间 + 访问控制;import 引入其他包的类。

Object 核心方法:equals / hashCode / toString

面试官想听:知道默认 equals 是 ==,重写后必须同时重写 hashCode,并能说出约定的具体内容。

  • toString() 默认输出 类名@十六进制hash,打印对象会被自动调用,建议重写。
  • equals() 默认等价于 ==(比较地址);重写模板(IDEA 自动生成即可):
@Override
public boolean equals(Object o) {
    if (this == o) return true;                      // 自反优化
    if (o == null || getClass() != o.getClass()) return false;
    return age == p.age && Objects.equals(name, p.name);
}
@Override
public int hashCode() { return Objects.hash(name, age); }
  • 约定:equals 相等 ⇒ hashCode 必须相等;hashCode 相等 ⇏ equals 相等(哈希冲突)。违反约定会导致 HashSet/HashMap 存取异常。

String 不可变原理与常量池

面试官想听:说出「private final + 不提供修改方法 + 类被 final 修饰」三层防护和安全/池化/线程安全三大好处。

  • 不可变的实现:String 内部 private final char[] value(Java 9+ 改为 byte[]),数组引用不可变且不外泄,String 类本身被 final 修饰不能被子类破坏。
  • 常量池:字面量先查池,存在则复用引用,所以 "a" == "a" 为 true;new String("a") 一定在堆上创建新对象,== 为 false,必须用 equals。
  • 编译期可确定的拼接 "he" + "llo" 会被优化进常量池;含变量的拼接在运行时生成新对象,应用 StringBuilder。
  • String.intern() 可把字符串放入/复用常量池。
  • StringBuilder(非线程安全、快)vs StringBuffer(线程安全、慢);StringJoiner 处理 a, b, c 这类带分隔符拼接。

包装类型与自动装箱、Integer 缓存

面试官想听:能准确答出缓存范围 -128~127,并解释包装类型存在的意义(泛型与集合需要对象)。

Integer a = 127, b = 127;      // true:取自 IntegerCache 缓存数组
Integer c = 128, d = 128;      // false:超出缓存,new 新对象
c == d;        // false
c.equals(d);   // true:包装类型比较永远用 equals
  • 自动装箱调用 Integer.valueOf(),拆箱调用 intValue();装箱发生在集合/泛型场景。
  • 经典坑:Integer 与 int 混合比较会自动拆箱;拆箱 null 抛 NullPointerException;三目运算符两侧类型不统一会触发拆箱。
  • Byte/Short/Long 缓存同为 -128127,Character 缓存 0127,Float/Double 无缓存。

枚举与枚举单例

public enum OrderStatus {
    WAIT_PAY("待支付"), PAID("已支付"), CLOSED("已关闭");
    private final String desc;
    OrderStatus(String desc) { this.desc = desc; }
    public String getDesc() { return desc; }
}
  • 枚举天然继承 java.lang.Enum,有 values()、valueOf()、name()、ordinal();可用 == 比较(实例唯一)。
  • 可定义字段、方法和构造方法;配合 switch 使用最直观。
  • 枚举单例是《Effective Java》推荐写法:防止反射创建实例(构造方法反射调用会直接抛异常)且天然线程安全、可序列化:
public enum Singleton { INSTANCE;
    public void doSomething() { /* 业务方法 */ } }

BigDecimal 与精度问题

面试官想听:知道 new BigDecimal(0.1) 会带入二进制误差,必须用字符串构造或 valueOf。

System.out.println(0.1 + 0.2);                       // 0.30000000000000004
BigDecimal a = new BigDecimal("0.1");                // 正确:字符串构造
BigDecimal b = BigDecimal.valueOf(0.1);              // 等效:内部走 Double.toString
System.out.println(a.add(b));                        // 0.2(精确)
new BigDecimal(0.1).doubleValue();                   // 坑:0.1000000000000000055511151231257827
  • 除不尽时必须指定舍入模式:divide(b, 2, RoundingMode.HALF_UP),否则抛 ArithmeticException。
  • equals 比较值和精度(1.0 不等于 1),比较数值用 compareTo。

java.time 新日期 API

  • LocalDate(日期)、LocalTime(时间)、LocalDateTime(日期时间)不可变且线程安全,取代线程不安全的 SimpleDateFormat。
LocalDateTime now = LocalDateTime.now();
LocalDateTime d = now.plusDays(3).minusHours(2);
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String s = now.format(f);
long days = Duration.between(t1, t2).toDays();       // Period 用于日期差
  • Instant 对应时间戳,ZoneId 处理时区,格式化线程安全可复用。

动手实践

任务 1:Shape 继承体系

  • 要求:抽象类 Shape(抽象方法 double area()、double perimeter(),实现 toString 输出面积周长);接口 Drawable(void draw());子类 Circle、Rectangle 实现三者;用 Shape[] shapes 数组循环打印,体现多态。
  • 验收标准:main 中不出现 Circle 具体类型的 new(统一用 Shape s = new Circle(2) 声明);新增 Triangle 子类时循环代码零改动。

任务 2:订单状态枚举

  • 要求:把上面的 OrderStatus 补全——每个状态带一个 nextStatus() 返回下一个合法状态(终态返回自身),main 中模拟 WAIT_PAY -> PAID 流转。
  • 验收标准:switch/方法内不含魔法字符串;对 CLOSED.nextStatus() 返回自身有单测或打印验证;说出「为什么不用 int 常量 0/1/2 表达状态」。

任务 3:BigDecimal 揭穿 double 的坑

  • 验收标准:写程序分别用 double 和 BigDecimal 计算 100 个 0.1 累加;输出 double 版本结果 9.99999999999998(或相近值)与 BigDecimal 版本 10.0;再加一段「金额 0.1 元乘以 3」演示四舍五入保留 2 位的正确写法。

高频面试题

Q:面向对象三大特性是什么?⭐

  • 封装:隐藏内部细节,通过公开方法受控访问,降低耦合。
  • 继承:子类复用父类代码并扩展,Java 单继承,用 super 访问父类成员。
  • 多态:父类引用指向子类对象,运行时绑定调用子类重写方法,提高扩展性。
  • 每个特性最好跟一个自己项目/练习里的例子。

Q:接口和抽象类的区别?怎么选?⭐

  • 抽象类:单继承、可有构造方法与成员变量、可含具体实现,表达 is-a、做模板复用。
  • 接口:多实现、成员全是 public static final 常量、Java 8 后可有 default/static 方法,表达 can-do、定义契约。
  • 选择:共享代码骨架用抽象类;定义能力/规范(尤其需要多继承效果)用接口;实践中常组合使用。

Q:== 和 equals 的区别?⭐

  • ==:基本类型比值,引用类型比地址。
  • equals:Object 默认实现就是 ==;String、Integer 等类重写后比较内容。
  • 引用类型判相等一律用 equals;常量放前可防 NPE("abc".equals(s))。

Q:重写 equals 时为什么必须重写 hashCode?⭐

  • 约定:equals 相等的两个对象 hashCode 必须相等。
  • HashSet/HashMap 先用 hashCode 定位桶,再用 equals 判重;只重写 equals 会导致两个「相等」对象被放进不同的桶,出现重复元素或 get 不到。
  • 反之 hashCode 相同不代表 equals 相等(允许哈希冲突)。

Q:String 为什么设计成不可变?⭐

  • 常量池复用:相同字面量共享一个对象,节省内存。
  • 安全性:String 广泛用于类加载名、网络地址、文件路径等,不可变防止被恶意篡改。
  • 线程安全:不可变对象天然线程安全,可放心在多线程间共享。
  • hashCode 可缓存:String 重写了 hashCode 并缓存结果,适合做 HashMap 的 key。

Q:Integer a = 127; Integer b = 127; a == b 和 128 的版本结果分别是什么?⭐

  • 127 为 true:Integer.valueOf 对 -128~127 返回缓存的同一对象。
  • 128 为 false:超出缓存范围 new 了新对象,地址不同。
  • 结论:包装类型比较一律用 equals,或先拆箱成 int 再比较。

Q:重写和重载的区别?

  • 重写发生在父子类,方法签名相同,运行时决定调用哪个(多态基础);访问权限不能更小、不能抛更宽的受检异常。
  • 重载发生在同一个类,方法名相同参数列表不同,编译期按实参静态匹配;与返回值无关。
  • 记忆:重写是同一个方法的不同实现,重载是同名方法的不同入口。

Q:BigDecimal 有哪些使用注意点?

  • 构造用字符串或 valueOf,禁止传 double。
  • 除法指定 scale 和 RoundingMode,防止除不尽抛异常。
  • 比较数值用 compareTo 而非 equals(equals 连精度一起比)。

推荐资料

资料说明链接
廖雪峰 Java 面向对象从类到包的完整 OOP 主线教程https://liaoxuefeng.com/books/java/oop/index.html
pdai Java 基础-面向对象三大特性、抽象类接口的体系化讲解https://pdai.tech/md/java/basic/java-basic-oop.html
廖雪峰 日期与时间java.time 详细用法与陷阱https://liaoxuefeng.com/books/java/datetime/index.html
JavaGuide 基础面试题本章面试题的标准答案与延伸https://javaguide.cn/java/basis/java-basic-questions-01.html
慕课网《Java入门第一季》面向对象章节配套免费视频https://www.imooc.com/learn/85