阶段:入门篇 | 建议时长: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(非线程安全、快)vsStringBuffer(线程安全、慢);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 缓存同为 -128
127,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 |