我完蛋了,根本预习不完。。。
但秉持开源精神,整理了一份考前突击资料,大家有需自取吧
1. JVM 内存区域划分与存储位置
核心区域划分及存储内容
- 栈(Stack / 线程栈):每个线程私有。存放局部变量表(基本数据类型、对象引用
reference)、操作数栈、方法出口等。方法执行完自动释放。 - 堆(Heap):所有线程共享。存放所有的对象实例(无论是谁创建的)以及数组。是垃圾回收(GC)的主战场。
- 方法区(Method Area / 元空间 Metaspace):所有线程共享。存放类信息(结构、字段、方法字节码)、静态变量(
static)、常量池(包括运行时常量池)。
常见高频面试/考试辨析
基本数据类型存哪?
如果作为局部变量(在方法中声明):存在栈的局部变量表中。
如果作为成员变量(在类中声明):作为对象的一部分,存在堆中。
对象存哪?
对象实例永远在堆中。栈里存的只是指向该对象的引用(指针/地址)。
字符串(String)存哪?
字面量(如
String s = "abc"):存在方法区(具体在字符串常量池)中。new出来的字符串(如new String("abc")):在堆中创建新对象。
2. & 3. 初始化顺序与分派绑定原则
初始化顺序(口诀:父类静态 $\rightarrow$ 子类静态 $\rightarrow$ 父类构造 $\rightarrow$ 子类构造)
当创建一个子类对象时,严格执行以下顺序(同一级别的按代码先后顺序执行):
- 父类静态变量初始化 / 静态代码块
- 子类静态变量初始化 / 静态代码块
- 父类实例变量初始化 / 普通代码块
- 父类构造方法
- 子类实例变量初始化 / 普通代码块
- 子类构造方法
注意: 静态代码块和静态变量在类加载(Class Loading)时执行,且只执行一次。普通代码块和构造方法在每次
new对象时都会执行。
Overloading(重载)的多分派顺序
- 编译期决定。如果有多个重载方法,编译器会按照“参数类型匹配度”从高到低寻找(精确匹配 $\rightarrow$ 基本类型自动转换升型 $\rightarrow$ 自动装箱 $\rightarrow$ 父类/接口 $\rightarrow$ 变长参数
...)。
分派与绑定原则(重点:软工一必考)
静态绑定(Static Binding / 早期绑定):在编译期就能确定具体调用哪个方法。依据的是声明类型(静态类型)。
适用于:
static方法、private方法、final方法、构造器、以及 Overloading(重载)。动态绑定(Dynamic Binding / 晚期绑定):在运行期根据对象的实际类型(Runtime Type)来决定调用哪个方法。
适用于:Overriding(重写)(即虚方法调用)。
4. 编译器与 JVM 如何决定运行哪个方法
编译器的处理(编译期)
- 检查接收者的声明类型(静态类型)。
- 根据方法名和填入的参数,通过静态绑定(Overloading 分派逻辑)在类中找到匹配的方法签名(Method Signature)。
- 将其编译为对应的字节码指令,并把方法符号引用写入常量池。
JVM 的处理(运行期)
- JVM 拿到字节码指令。如果是虚方法,它会先获取运行时对象的实际类型。
- 在实际类型的方法表(Virtual Method Table / vtable)中查找与符号引用一致的方法。
- 如果找到则执行,找不到则顺着继承树往上(父类)找。
5. 基础字节码含义与真题解析
核心概念
- 操作数栈(Operand Stack):用于存放计算过程中的临时操作数和中间结果,后进先出。
- 局部变量表(Local Variable Table):存放方法参数和方法内部定义的局部变量(
slot 0通常是this引用)。 - PC 寄存器(Program Counter):记录当前线程正在执行的字节码指令地址。
四大调用指令
invokestatic:调用静态方法。invokespecial:调用私有方法、构造方法(<init>)、父类方法(super.method())。invokevirtual:调用所有虚方法(公开/保护的普通实例方法),涉及动态绑定。invokeinterface:调用接口方法,运行期会再次检查实现类是否实现了该接口。
📝 真题解析
题目:说明
a.foo(100)和a.foo("helloworld")方法在编译器编译成字节码后,可能是哪两条指令?
正确答案:
1 | invokevirtual #24 |
理由:
foo是普通实例方法(未指明是静态或私有),属于虚方法,因此必须使用invokevirtual。- 这两个方法属于 Overloading(重载),参数类型不同(一个是
int,一个是String)。在编译期,编译器已经根据参数类型将它们分派到了不同的方法签名上,因此在常量池中对应两个不同的编号(如#24和#25)。
6. 测试与调试(Test vs Debug)
| 特征/维度 | 测试 (Testing) | 调试 (Debugging) |
|---|---|---|
| 目的 | 发现程序中是否存在错误(证明有错)。 | 定位并修复已知的错误(找出原因并解决)。 |
| 触发时机 | 贯穿开发全生命周期,看到现象。 | 发现 Bug 之后才进行。 |
| 执行者 | 通常由测试人员或开发人员(单元测试)执行。 | 必须由开发人员执行(需要看代码)。 |
| 思维方式 | 破坏性的、找茬的。 | 推理性的、解谜的。 |
7. 面向对象三特征一句话概括
- 封装 (Encapsulation):隐藏对象的属性和实现细节,仅对外公开接口(通过访问控制符
private/protected/public),保证数据安全与高内聚。 - 继承 (Inheritance):子类复用父类的属性和方法,并通过扩展实现代码重用,体现 “is-a” 关系。
- 多态 (Polymorphism):允许同一引用类型在运行时指向不同的实现对象,执行不同的行为,解耦了“做什么”和“怎么做”。
补充重要概念:
- 抽象 (Abstraction):忽略与当前目标无关的细节,只关注实体的核心特征。
- 高内聚低耦合 (High Cohesion, Low Coupling):软件设计的终极目标。模块内部职责单一且紧密,模块之间依赖尽可能少。
8. 用例图四要素与绘制规范
用例图 4 要素
- 参与者 (Actor):在系统之外,与系统进行交互的人或外部系统。
- 用例 (Use Case):系统为参与者提供的一项完整的功能/服务(椭圆表示)。
- 系统边界 (System Boundary):区分系统内部和外部的方框。
- 关系 (Relationship):
- 关联(Actor 与 Use Case 连线)
- 包含 (
<<include>>:基础用例必选子用例) - 扩展 (
<<extend>>:基础用例可选/特定条件下触发的子用例,箭头指向基础用例) - 泛化(继承关系)
💡 判错标准提示
如果试卷给出用例图让你挑错,90% 的坑在:
- Actor 连了 Actor(错!Actor 之间只能有泛化关系,不能直接关联)。
- Use Case 连了 Use Case(错!它们之间只能是
<<include>>、<<extend>>或泛化,不能用普通实线)。 <<extend>>箭头画反了(记住:扩展用例指向基础用例)。- Actor 放到了系统方框内部(错!Actor 必须在边界外面)。
9. 类图要素、规范、设计方法(核心大题大分点)
静态关系速查(考试画线千万别画错!)
- 依赖 (Dependency):局部变量、方法参数。
A ------> B(虚线箭头)。 - 关联 (Association):成员变量。
A ——————> B(实线箭头)。 - 聚合 (Aggregation):整体与部分,可分离(如学校和老师)。
A ◇——————> B(空心菱形实线)。 - 组合 (Composition):整体与部分,不可分离,同生共死(如鸟和翅膀)。
A ◆——————> B(实心菱形实线)。 - 继承/实现 (Inheritance/Realization):
——————▷(空心三角实线为继承,虚线为实现)。
协作对象角色 (GRASP / 职责驱动设计)
- Client:发起请求者。
- Server:提供核心服务者。
- Manager:管理、创建、协调多个对象。
- Delegate:受托人,替别人干活以实现解耦。
📝 真题解析 1:销售合计职责归谁?
题目:Sale 有多个 SalesLineItem,每个 SalesLineItem 关联一个 Product(包含单价)。求此次销售的合计是哪个类的职责?说明原因。
正确答案:
应归属于 Sale 类。
原因(依据信息专家模式 Information Expert):
- 信息专家模式规定:职责应该分配给拥有完成该职责所需信息的类。
- 要计算总额,需要遍历所有的
SalesLineItem并累加它们的小计。 Sale对象作为“整体”,拥有(或组合了)所有的SalesLineItem列表(“部分”),它是最拥有完整信息的类,因此计算合计是Sale的天然职责。
(注:SalesLineItem负责计算自身的小计 =Product.getPrice() * quantity,然后Sale负责调用并汇总。)
📝 真题解析 2:鸟与狼的类设计与类图
题目:实现 Animal、Bird、Wolf,包含 beat(), fly(), run()。
Java 属性与方法定义伪代码
1 | public abstract class Animal { |
类图绘制规范提示(脑补标准 UML)
- 画三个类框,分为三层:类名、属性、方法。
Animal(如果是抽象类,类名用斜体或加<<abstract>>)。Bird和Wolf各画一条**实线+空心三角箭头——————▷**指向Animal。- 方法前加符号:
+表示public,-表示private。
10. & 11. 面向接口编程与对象职责
面向接口编程的基本概念
- 核心思想:开闭原则(OCP)的基石。上层模块不应该依赖底层模块的具象实现,而应该依赖抽象的接口。这样当底层实现改变时,上层代码完全不需要修改。
常考题(接口定义和实现的伪代码)
1 | // 1. 定义接口 |
既然目标是 90+,针对大题(设计纠错、用例图、类图),阅卷老师手里都是有标准采分点(Rubric)的。为了让你明天答题稳准狠,下面直接为你模块化拆解这三个核心大题的通关秘籍与标准作答模板。
12. 设计纠错大题:通关策略与黄金答题模板
这类题目通常会给一段“烂代码”或一个“糟糕的设计类图”,让你指出问题并给出重构建议。
🚨 核心审查清单(拿着这个清单去代码里挑刺)
- 违背单一职责原则 (SRP):一个类干了两个完全不相关的事(例如:
Order类既算价格,又负责拼 SQL 存数据库,还负责发短信)。 - 硬编码依赖 / 违背依赖倒置原则 (DIP):类内部直接
new了一个具体的实现类(例如:private MySQLDriver driver = new MySQLDriver();)。这导致无法切换底层实现。 - 大段代码重复 (Code Duplication):几个方法里除了两三行不一样,其余长得一模一样。应该抽象成公共方法或父类方法。
- 死板的控制结构 / 缺少多态:代码里出现大量的
if-else或switch-case来判断对象的类型(例如:if(bird.type == "Pigeon") ... else if(bird.type == "Ostrich"))。这是最明显的期末考点:必须用多态(Polymorphism)重构! - 不合理的封装:字段全用
public;或者滥用全局静态变量(public static)传递状态,导致系统混乱。
📝 拿满分的标准答题模板(按这个格式写,老师最喜欢)
考试时,切忌一句话概括。每指出一个错误,都要按照“现状 $\rightarrow$ 危害 $\rightarrow$ 改进方案”三步走:
[第 $X$ 处错误极其重构方案]
- 问题(现状):类
XXX的methodA方法中使用了大量的if-else结构来判断对象的类型。- 违背的原则:违背了**开闭原则(OCP)**和面向对象的多态思想。
- 危害:当系统需要增加新的类型时,必须修改该方法的源代码,导致系统脆弱、不易扩展且容易引入新 Bug。
- 重构方案:将具体的类型抽象为接口或父类,定义统一的抽象方法。让不同的子类各自实现该方法,在调用时通过**动态绑定(Dynamic Binding)**实现多态调用,消除
if-else。
13. 用例图:绘制规范与手把手方法指导
用例图本质上是从用户视角定义系统的边界与功能。
📐 规范要素细节(一分不扣)
- 系统边界 (System Boundary):必须画一个巨大的矩形框,框顶写上系统名称。
- 参与者 (Actor):画在系统边界的外面(左侧通常是主 Actor,如用户;右侧通常是次 Actor,如外部系统/数据库)。
- 用例 (Use Case):画在系统边界的里面。必须用椭圆,名称必须是动宾短语(如
处理订单、修改密码),绝对不能只有名词。 - 关系符号(极其严格):
Actor ——> Use Case:直线(或带箭头的实线),代表交互。<<include>>(包含):基础用例 $\dashrightarrow$ 行为用例(虚线箭头,箭头指向被包含的子用例)。<<extend>>(扩展):扩展用例 $\dashrightarrow$ 基础用例(虚线箭头,箭头指向基础用例,别画反了!)。
🛠️ 具体的建模与绘制五步法
- 第一步:圈定边界。先在纸上画一个大方框,写上系统名字。
- 第二步:搜寻参与者 (Actors):谁来用这个系统?(如:管理员、顾客、定时任务触发器)。把他们画在方框外。
- 第三步:提取主用例 (Core Use Cases):这些人要用系统干什么核心的事?把它们变成动宾短语的椭圆,扔进方框里,并用实线与 Actor 连起来。
- 第四步:做加法(关系梳理):
- 这些用例有没有共同的、必须的前置步骤或必选子流?(如:
生成订单和查看账单都包含登录-> 用<<include>>)。 - 有没有特殊情况下才会触发的流程?(如:
提交订单时,如果积分足够可以使用积分抵扣-> 用<<extend>>,箭头指向提交订单)。
- 第五步:检查纠错:检查有没有框外的 Actor 互相连线?有没有框内的用例互相直接连实线?(有就赶紧擦掉,这是零分点)。
14. 类图:绘制规范与面向对象(OOP)设计方法
类图是面向对象静态结构的核心,大题通常要求“根据伪代码画类图”或“根据业务场景设计类图”。
📐 规范要素细节(严格执行)
- 三层矩形框:
- 第一层:类名。如果是抽象类/接口,类名用斜体,或者在上方加
<<interface>>或<<abstract>>。 - 第二层:属性(字段)。格式:
访问修饰符 属性名: 类型(如:- name: String)。 - 第三层:方法(行为)。格式:
访问修饰符 方法名(参数名: 参数类型): 返回值类型(如:+ getName(): String)。
- 访问修饰符:
+(public)、-(private)、#(protected)。
🔗 六大关系的画法与选择(丢分重灾区)
在设计类图时,如何根据 OOP 思想选择正确的连线?
| 关系类型 | 语义 | UML 线条形状 | 什么时候用?(代码特征) |
|---|---|---|---|
| 泛化 (Generalization) | is-a (继承) | ——————▷ (实线+空心三角) |
class A extends B |
| 实现 (Realization) | 实现接口 | ------▷ (虚线+空心三角) |
class A implements B |
| 组合 (Composition) | 强整体与部分 (同生共死) | ◆——————> (实心菱形+实线) |
A 包含 B,且 A 死了 B 也不能活。通常在 A 的构造函数里 new B()。 |
| 聚合 (Aggregation) | 弱整体与部分 (可分离) | ◇——————> (空心菱形+实线) |
A 包含 B,但 B 可以独立存在。通常通过 setB(B b) 或构造函数传参传进来。 |
| 关联 (Association) | 拥有关系 (常态化的依赖) | ——————> (普通实线箭头) |
A 类中有一个成员变量是 B 类型 (private B b;),但非整体部分关系。 |
| 依赖 (Dependency) | 使用关系 (临时性) | ------> (普通虚线箭头) |
B 作为 A 类中某个方法的参数、局部变量或者返回类型。 |
🏗️ 面向 OOP 思想的设计方法指导(怎么从零构建类图?)
当你拿到一段业务描述(如“图书管理系统”、“在线购物”),遵循以下 GRASP/面向对象建模 思路:
- 文本分析法(找名词与动词):
- 读题,把所有的名词圈出来 -> 它们是潜在的类/属性(如:订单、商品、用户、购物车)。
- 把所有的动词圈出来 -> 它们是潜在的方法/职责(如:添加商品、计算总价、支付)。
- 分配职责(信息专家模式):
- 不要把所有的方法都写在一个
Manager类里。谁拥有数据,谁就负责实现行为。 - 例子:计算订单项的小计,需要商品单价和数量,这两者都在
OrderItem里,所以calculateSubtotal()是OrderItem的方法。而计算订单总价需要遍历所有订单项,所以calculateTotal()是Order的方法。
- 引入抽象(面向接口编程):
- 观察是否有相似的行为但实现不同的主体?
- 例子:系统支持“微信支付”和“支付宝支付”。不要在
Order类里写if(type=="WeChat")。立刻抽象出一个PaymentStrategy接口,里面有一个pay()方法。然后派生出WeChatPay和AliPay两个实现类。Order只关联PaymentStrategy接口。
- 精细化连线:
- 检查类之间的关系。如果是包含关系,分清是组合(如
Order和OrderItem,订单没了订单项也没意义 $\rightarrow$ 组合 ◆)还是聚合(如Course和Student,课程取消了学生还在 $\rightarrow$ 聚合 ◇)。 - 仅仅是方法里用了一下某个工具类?画依赖 ——>。
画类图一定要确保:
- 访问控制符(
+/-)没漏; - 箭头的形状(空心、实心、三角、燕尾)精准无误。