Java32 基础测试题及答案
测试时间: 5月30日 (周六) | 问答题: 抽取10道,每题10分 | 机试题: 统一时间录屏答题
一、问答题(56道)
1. Java 语言有哪些主要特点?
答案:
- 面向对象:以对象为基本单位,支持封装、继承、多态
- 跨平台:通过JVM实现”一次编译,到处运行”(Write Once, Run Anywhere)
- 健壮性:强类型检查机制、异常处理体系、自动垃圾回收(Garbage Collection)
- 安全性:无指针操作、字节码校验机制、类加载器安全沙箱
- 多线程:语言级内置多线程支持
- 动态性:支持反射机制、运行时动态类加载
- 高性能:JIT(Just-In-Time)即时编译——JVM在运行时识别热点代码并将其编译为本地机器码,后续调用直接执行编译后的机器码,省去解释执行开销
2. JDK、JRE、JVM 分别是什么?它们之间的关系是怎样的?
答案:
- JVM(Java Virtual Machine):Java虚拟机,负责加载并执行字节码(.class文件),是实现跨平台的核心组件
- JRE(Java Runtime Environment):Java运行环境,包含JVM与核心类库(rt.jar等),用于运行Java程序,面向程序使用者
- JDK(Java Development Kit):Java开发工具包,包含JRE及开发工具(javac编译器、jdb调试器、javadoc文档生成器等),用于开发Java程序,面向开发者
三者关系:JDK ⊇ JRE ⊇ JVM,即JDK包含JRE,JRE包含JVM。
3. Java 中基本数据类型有哪 8 种?分别给出它们的默认值。
答案:
| 类型 | 默认值 | 占用空间 | 取值范围 |
|---|---|---|---|
| byte | 0 | 1字节 | -128 ~ 127 |
| short | 0 | 2字节 | -2¹⁵ ~ 2¹⁵-1 |
| int | 0 | 4字节 | -2³¹ ~ 2³¹-1 |
| long | 0L | 8字节 | -2⁶³ ~ 2⁶³-1 |
| float | 0.0f | 4字节 | IEEE 754单精度 |
| double | 0.0d | 8字节 | IEEE 754双精度 |
| char | ’�‘ | 2字节 | 0 ~ 65535(Unicode) |
| boolean | false | JVM实现相关 | true / false |
声明语法:
数据类型 变量名 [= 初始值];
// 8种基本类型的声明
byte b = 127;
short s = 32767;
int i = 100;
long l = 100L; // 字面量后缀 L 或 l
float f = 3.14f; // 字面量后缀 F 或 f
double d = 3.14; // 默认浮点字面量为double
char c = 'A';
boolean flag = true;4. 什么是自动装箱和自动拆箱?
答案:
自动装箱与自动拆箱是Java 5引入的编译器特性,用于在基本数据类型与其对应包装类之间自动转换。
语法规则:
// 自动装箱(Auto-Boxing):基本类型 → 包装类型
// 编译器调用: 包装类.valueOf(基本类型值)
包装类型 变量 = 基本类型值;
// 自动拆箱(Auto-Unboxing):包装类型 → 基本类型
// 编译器调用: 包装类对象.xxxValue()
基本类型 变量 = 包装类型对象;示例:
// 自动装箱
Integer i = 10; // 编译后: Integer i = Integer.valueOf(10);
Double d = 3.14; // 编译后: Double d = Double.valueOf(3.14);
// 自动拆箱
int n = i; // 编译后: int n = i.intValue();
double x = d; // 编译后: double x = d.doubleValue();
// 混合运算中的隐式装箱/拆箱
Integer result = i + 5;
// 执行过程: i.intValue() → 加法运算 → Integer.valueOf(结果)注意事项:频繁装箱/拆箱会产生额外性能开销;包装类对象可能为null,拆箱时可能触发NullPointerException。
5. == 和 equals() 的区别是什么?
答案:
== 是运算符,equals() 是Object类中定义的方法,二者的默认行为与适用场景不同。
使用规则:
// == 运算符
// 比较基本类型: 比较值是否相等
// 比较引用类型: 比较栈中存储的引用地址是否相同(是否指向同一堆对象)
// equals() 方法
// 默认实现(Object类): 等价于 ==,比较引用地址
// String/Integer等: 已重写,比较对象内容是否相等
// 自定义类: 需按需重写,定义业务上的相等逻辑示例:
// == 比较基本类型
int a = 10, b = 10;
System.out.println(a == b); // true,值相同
// == 比较引用类型
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false,堆中两个不同对象,引用地址不同
// equals() — String类已重写
System.out.println(s1.equals(s2)); // true,String重写了equals,比较字符序列内容
// 自定义类未重写equals时,继承Object实现,等同于==
class Person {
String name;
}
Person p1 = new Person(); p1.name = "张三";
Person p2 = new Person(); p2.name = "张三";
System.out.println(p1.equals(p2)); // false,未重写,比较的是引用地址6. String 类的特点?
答案:
- 不可变性(Immutable):String对象一旦创建,其内部字符序列不可更改。任何修改操作(concat、replace、substring等)均返回新的String对象,原对象不变
- 字符串常量池(String Pool):通过字面量创建的字符串存储于运行时常量池,相同字面量引用同一对象,节省堆内存
- 线程安全:不可变性使得String天然线程安全,多线程并发访问无需同步
- 底层存储:JDK 8及之前使用
char[];JDK 9起改为byte[]+coder标识(Latin-1或UTF-16),即紧凑字符串(Compact Strings)优化 - final类:String类被
final修饰,不可被继承,保证语义一致性
创建方式与区别:
// 字面量:存储于字符串常量池,相同字面量复用同一对象
String s1 = "hello";
// new构造:在堆中强制创建新对象,不经过常量池
String s2 = new String("hello");
// 拼接:产生新的String对象,原对象不变
String s3 = s1 + " world";示例:
String a = "hello";
String b = "hello";
System.out.println(a == b); // true,指向常量池同一对象
String c = new String("hello");
System.out.println(a == c); // false,c是堆中新对象
a.toUpperCase(); // 返回新对象 "HELLO"
System.out.println(a); // "hello",原对象未改变7. 什么是变量的作用域?
答案:
变量的作用域(Scope)指变量在程序中可被合法访问的代码范围,由变量的声明位置决定。
分类与规则:
class 类名 {
// 成员变量(实例变量): 类中方法外声明,作用域为整个类体,
// 随对象创建而初始化,随对象回收而销毁
类型 成员变量;
// 静态变量(类变量): static修饰,类级别,所有实例共享,类加载时初始化
static 类型 静态变量;
public void 方法名(类型 参数名) {
// 方法参数: 作用域为整个方法体
// 局部变量: 从声明处到所在代码块 } 结束
类型 局部变量 = 值;
}
}示例:
public class ScopeDemo {
String name = "张三"; // 成员变量,当前类的所有方法均可访问
static int count = 0; // 静态变量,可通过 ScopeDemo.count 访问
public void sayHello(String who) { // who: 方法参数,sayHello方法体内可用
int age = 18; // 局部变量,sayHello方法体内可用
if (age > 0) {
int year = 2025; // 局部变量,仅在当前if块内可用
System.out.println(year); // ✓ 可访问
}
// System.out.println(year); // ✗ 编译错误,year已超出作用域
}
}8. Java 如何实现跨平台运行?
答案:
Java的跨平台性(“一次编译,到处运行”)依赖以下机制:
- Java源代码(.java)经
javac编译器编译为字节码(.class文件),字节码是独立于特定硬件架构和操作系统的中间表示 - 不同操作系统上安装对应平台的JVM实现(Windows/Linux/macOS各有适配版本)
- JVM负责将字节码解释执行或通过JIT编译器转为本地机器码
- 核心原理:JVM作为中间抽象层,封装了底层操作系统与硬件的差异——Java程序面向JVM编程,而非面向操作系统编程
9. final、finally、finalize() 的区别是什么?
答案:
三者名称相似但含义与用途完全不同。
| 关键字/方法 | 类型 | 作用 |
|---|---|---|
| final | 修饰符 | 修饰类(不可继承)、方法(不可重写)、变量(不可修改) |
| finally | 代码块 | try-catch-finally的一部分,无论是否发生异常均执行 |
| finalize() | 方法 | Object类方法,对象被GC回收前回调,JDK 9起标记为 @Deprecated |
使用模板:
// final
final class 类名 { } // 类不可被继承
final 返回类型 方法名() { } // 方法不可被重写
final 类型 变量名 = 值; // 基本类型值不可变,引用类型引用不可变
// finally
try {
// 可能抛出异常的代码
} catch (异常类型 e) {
// 异常处理
} finally {
// 无论是否异常均执行的清理逻辑(如关闭流、释放锁)
}
// finalize() — 已过时,推荐使用 try-with-resources 或 Cleaner
@Override
protected void finalize() throws Throwable { ... }示例:
final int MAX = 100;
// MAX = 200; // ✗ 编译错误,final变量不可重新赋值
final class Animal { }
// class Dog extends Animal { } // ✗ 编译错误,final类不可被继承
try {
int result = 10 / 0; // 将抛出 ArithmeticException
} catch (ArithmeticException e) {
System.out.println("捕获到除零异常");
} finally {
System.out.println("finally块始终执行"); // 无论是否异常,此行必然执行
}10. break 和 continue 的作用分别是什么?
答案:
| 关键字 | 作用 | 影响范围 |
|---|---|---|
| break | 终止当前循环或switch语句 | 跳出当前层级,执行循环/switch之后的代码 |
| continue | 跳过当前迭代的剩余代码 | 进入当前循环的下一次迭代 |
使用模板:
// break
for / while / switch {
break; // 立即跳出当前循环体或switch块
}
// 带标签的break:跳出指定外层循环
label:
for/while {
break label; // 跳出label标记的那层循环
}
// continue
for/while {
continue; // 跳过本次循环中continue之后的代码,进入下一次迭代
}示例:
// break — 终止循环
for (int i = 1; i <= 10; i++) {
if (i == 5) break;
System.out.print(i + " "); // 输出: 1 2 3 4
}
// 带标签的break — 跳出外层循环
outer:
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (i == 2 && j == 2) break outer;
System.out.println("(" + i + "," + j + ")");
}
}
// 输出: (1,1) (1,2) (1,3) (2,1),遇(2,2)直接跳出outer标记的外层循环
// continue — 跳过本次迭代
for (int i = 1; i <= 5; i++) {
if (i == 3) continue;
System.out.print(i + " "); // 输出: 1 2 4 5
}11. switch 语句支持哪些数据类型?
答案:
| JDK版本 | 支持的表达式类型 |
|---|---|
| JDK 7之前 | byte、short、int、char 及对应包装类;枚举(enum) |
| JDK 7 | 以上 + String |
| JDK 14+(预览) / JDK 17(正式) | 以上 + 模式匹配(Pattern Matching for switch) |
使用模板:
switch (表达式) {
case 常量值1:
// 代码
break; // 终止当前case,防止case穿透(fall-through)
case 常量值2:
// 代码
break;
default:
// 无匹配case时执行的默认分支
}示例:
String day = "MONDAY";
switch (day) {
case "MONDAY":
System.out.println("星期一");
break;
case "FRIDAY":
System.out.println("星期五");
break;
default:
System.out.println("其他日期");
}12. 什么是方法重载?重载的条件是什么?
答案:
方法重载(Overload)是指在同一个类中定义多个同名方法,但参数列表不同,编译器根据调用时传入的实参类型与数量自动匹配合适的方法版本。
构成重载的必要条件:
- 方法名称完全相同
- 参数列表不同(以下至少满足之一):
- 参数个数不同
- 参数类型不同
- 参数顺序不同(仅当类型不同的参数交换顺序时)
- 仅返回值类型不同不能构成重载
- 访问修饰符、抛出异常类型不影响重载判定
使用模板:
返回类型 方法名(类型A 参数) { ... } // 版本1
返回类型 方法名(类型A 参数1, 类型B 参数2) { ... } // 版本2(参数个数不同)
返回类型 方法名(类型C 参数) { ... } // 版本3(参数类型不同)示例:
public class Calculator {
// 参数个数不同
public int add(int a, int b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
// 参数类型不同
public double add(double a, double b) {
return a + b;
}
}
Calculator c = new Calculator();
c.add(1, 2); // 匹配 add(int, int)
c.add(1, 2, 3); // 匹配 add(int, int, int)
c.add(1.5, 2.5); // 匹配 add(double, double)13. 数组和集合(如 ArrayList)的主要区别有哪些?
答案:
| 特性 | 数组 | ArrayList |
|---|---|---|
| 长度 | 创建时确定,不可变 | 动态扩容(默认扩容至1.5倍) |
| 存储元素类型 | 基本数据类型与引用类型均可 | 仅引用类型(基本类型需装箱) |
| 泛型支持 | 不支持 | 支持,编译期类型检查 |
| API丰富度 | 仅 length 属性 | 提供 add()/remove()/get()/size() 等方法 |
| 性能 | 无装箱/拆箱开销,访问更快 | 存在装箱/拆箱开销 |
使用模板:
// 数组
元素类型[] 数组名 = new 元素类型[长度];
元素类型[] 数组名 = {元素1, 元素2, ...};
// ArrayList
List<包装类型> 集合名 = new ArrayList<>();示例:
// 数组 — 长度固定
int[] arr = new int[5];
arr[0] = 10;
System.out.println(arr.length); // 5
// ArrayList — 动态扩容
List<Integer> list = new ArrayList<>();
list.add(10);
list.add(20);
list.add(30);
list.remove(1); // 移除索引1的元素(20)
System.out.println(list.get(0)); // 10
System.out.println(list.size()); // 214. 面向对象的三大基本特征是什么?请简要解释。
答案:
-
封装(Encapsulation)
- 将数据(属性)和操作数据的方法绑定为一个整体
- 通过
private隐藏内部实现,通过public方法对外暴露受控访问接口 - 提高代码安全性与可维护性
-
继承(Inheritance)
- 子类(派生类)继承父类(基类)的属性和方法
- Java采用单继承机制——一个类有且仅有一个直接父类
- 实现代码复用,表达”is-a”关系
-
多态(Polymorphism)
- 同一方法调用作用于不同对象时,表现出不同的行为
- 依赖继承、方法重写、父类引用指向子类对象三个条件
- 提高代码的灵活性与可扩展性
实现模板:
// 封装
class 类名 {
private 类型 属性; // 属性私有化
public 类型 get属性() { return 属性; } // 公有访问器
public void set属性(类型 v) { ... } // 公有修改器(可含校验逻辑)
}
// 继承
class 子类 extends 父类 { }
// 多态
父类型 引用变量 = new 子类型(); // 编译看左,运行看右示例:
// 封装
class Person {
private int age;
public int getAge() { return age; }
public void setAge(int age) {
if (age > 0) this.age = age; // 数据校验
}
}
// 继承
class Animal {
public void eat() { System.out.println("进食"); }
}
class Dog extends Animal { }
// 多态(详见第20题)
Animal a = new Dog(); // 父类引用指向子类对象
a.eat(); // 运行时调用Dog重写后的版本(若Dog重写了eat)15. 什么是构造方法?能否重载?能否被继承?
答案:
- 构造方法:一种与类同名的特殊方法,无返回值类型(连void也没有),在对象创建时由
new关键字自动调用,负责对象初始化 - 可以重载:同一个类可定义多个参数列表不同的构造方法,提供不同的初始化途径
- 不可被继承:构造方法隶属于当前类,子类不继承父类构造方法。但子类构造方法可通过
super(参数)调用父类的指定构造方法,且该调用必须作为子类构造方法的第一条语句
使用模板:
class 类名 {
// 无参构造(若类中未定义任何构造方法,编译器自动生成默认无参构造)
public 类名() { }
// 有参构造(重载)
public 类名(参数1类型 参数1, 参数2类型 参数2) {
// 初始化逻辑
}
}
class 子类名 extends 父类名 {
public 子类名() {
super(参数); // 调用父类构造(必须为第一条语句,省略时默认调用 super())
}
}示例:
class Person {
String name;
int age;
public Person() {
this("未知", 0); // 通过this调用本类有参构造
}
public Person(String name, int age) { // 重载
this.name = name;
this.age = age;
}
}
class Student extends Person {
String school;
public Student(String name, int age, String school) {
super(name, age); // 调用父类Person(String, int)构造
this.school = school;
}
}16. this 和 super 关键字的用法有哪些?
答案:
| 用法 | this | super |
|---|---|---|
| 访问成员变量 | this.变量名 — 本类成员变量 | super.变量名 — 父类成员变量 |
| 调用方法 | this.方法() — 本类方法 | super.方法() — 父类方法 |
| 调用构造方法 | this(参数) — 本类其他构造 | super(参数) — 父类构造 |
使用模板:
class 父类 {
类型 属性;
public 父类(参数) { ... }
public void 方法() { ... }
}
class 子类 extends 父类 {
类型 属性;
public 子类() {
super(参数); // 调父类构造(必须为首条语句)
}
public 子类(类型 属性) {
this(); // 调本类无参构造(必须为首条语句)
this.属性 = 属性; // this.属性: 本类成员; 属性: 局部参数
}
public void 方法() {
super.方法(); // 调用父类被覆盖的方法
System.out.println(this.属性); // 本类成员变量
System.out.println(super.属性); // 父类成员变量
}
}示例:
class Father {
String name = "Father";
public Father(String msg) { System.out.println("Father: " + msg); }
public void show() { System.out.println("Father.show()"); }
}
class Son extends Father {
String name = "Son";
public Son() {
super("init"); // 调父类构造
}
public Son(String name) {
this(); // 调本类无参构造
this.name = name;
}
public void print() {
System.out.println(this.name); // Son
System.out.println(super.name); // Father
super.show(); // 调用父类show方法
}
}17. 什么是代码块?静态代码块和实例代码块的区别是什么?
答案:
| 类型 | 语法 | 执行时机 | 执行次数 |
|---|---|---|---|
| 静态代码块 | static { ... } | 类加载时 | 仅一次 |
| 实例代码块 | { ... } | new创建对象时 | 每次创建对象均执行 |
执行顺序:静态代码块 → 实例代码块 → 构造方法
使用模板:
class 类名 {
// 静态代码块:类加载时执行且仅执行一次,常用于初始化静态资源
static {
// 初始化逻辑
}
// 实例代码块(构造代码块):每次 new 对象时执行,先于构造方法
{
// 多个构造方法的公共初始化逻辑
}
public 类名() {
// 构造方法体 — 在实例代码块之后执行
}
}示例:
public class BlockDemo {
static { System.out.println("静态代码块"); }
{ System.out.println("实例代码块"); }
public BlockDemo() {
System.out.println("构造方法");
}
public static void main(String[] args) {
System.out.println("main方法");
new BlockDemo();
new BlockDemo();
}
}
// 输出:
// 静态代码块
// main方法
// 实例代码块
// 构造方法
// 实例代码块 ← 第二次new,实例代码块再次执行
// 构造方法18. Java 支持多继承吗?如何实现类似多继承的功能?
答案:
Java 不支持类的多继承(一个类同时继承多个父类),这是为了避免菱形继承(Diamond Problem)导致的语义歧义。
实现类似多继承能力的三种途径:
- 实现多个接口(推荐)——一个类可实现多个接口,JDK 8起接口支持
default方法 - 组合模式——将其他类的实例作为当前类的成员变量
- 内部类——通过多个内部类间接继承不同类
使用模板:
// 正确:一个类实现多个接口
class 类名 implements 接口A, 接口B, 接口C { }
// 组合模式
class 类名 {
private A a = new A(); // 持有其他类的引用
private B b = new B();
// 通过 a.method()、b.method() 复用其功能
}示例:
interface Flyable {
default void fly() { System.out.println("飞行"); }
}
interface Swimmable {
default void swim() { System.out.println("游泳"); }
}
class Duck implements Flyable, Swimmable {
// 同时拥有 Flyable 和 Swimmable 的行为能力
}
Duck d = new Duck();
d.fly(); // 输出: 飞行
d.swim(); // 输出: 游泳19. 方法重写(Override)需要遵循哪些规则?
答案:
方法重写指子类重新定义父类中已有的方法,以满足自身的特定行为需求。
必须遵循的规则:
- 方法签名(方法名 + 参数列表)必须与父类完全相同
- 返回值类型:与父类相同,或者是父类返回值类型的子类型(协变返回类型,JDK 5+)
- 访问权限不能比父类更严格(可相同或更宽松),如父类是protected,子类可以是protected或public,不能是private
- 抛出的受检异常范围不能比父类更宽泛(可以不抛,或抛父类异常的子类)
- 构造方法、static方法、final方法、private方法不能被重写
使用模板:
class 父类 {
public 返回类型 方法名(参数列表) throws 异常类型 { ... }
}
class 子类 extends 父类 {
@Override // 编译期校验注解
public 返回类型 [或子类型] 方法名(参数列表必须完全一致) throws 异常子类 {
// 重写后的实现
}
}示例:
class Animal {
public Animal create() throws Exception {
return new Animal();
}
}
class Dog extends Animal {
@Override
public Dog create() throws RuntimeException { // 返回Dog(子类)、异常RuntimeException(子类)
return new Dog();
}
}20. 什么是多态?多态的形成需要满足哪三个条件?
答案:
多态:同一操作作用于不同对象时,可产生不同的执行结果。Java中表现为父类引用指向子类对象,调用重写方法时执行的是子类的版本。
核心原则:编译时看左边(引用类型),运行时看右边(实际对象类型)。
三个必要条件:
- 继承:存在父类-子类关系(或接口-实现类关系)
- 重写:子类重写父类的方法
- 父类引用指向子类对象:
父类 变量 = new 子类();
使用模板:
父类型 引用名 = new 子类型(); // 向上转型
引用名.重写的方法(); // 运行时绑定到子类的重写版本示例:
class Animal {
public void sound() { System.out.println("动物发出声音"); }
}
class Cat extends Animal {
@Override
public void sound() { System.out.println("喵"); }
}
class Dog extends Animal {
@Override
public void sound() { System.out.println("汪"); }
}
Animal a1 = new Cat();
Animal a2 = new Dog();
a1.sound(); // 输出: 喵(编译看Animal,运行看Cat)
a2.sound(); // 输出: 汪(编译看Animal,运行看Dog)
// 多态的最大价值:用统一代码处理不同类型
Animal[] zoo = {new Cat(), new Dog(), new Cat()};
for (Animal a : zoo) {
a.sound(); // 同一行代码,不同对象产生不同行为
}21. 什么是向上转型和向下转型?向下转型时需要注意什么?
答案:
- 向上转型:子类对象赋值给父类引用,由编译器自动完成,安全
- 向下转型:父类引用强制转换为子类引用,不检查则可能抛出ClassCastException
使用模板:
// 向上转型:自动、安全
父类型 引用 = new 子类型(); // 或: 子类型对象直接赋给父类型变量
// 向下转型:必须强转,须先用 instanceof 检查
if (父类型引用 instanceof 目标子类型) {
目标子类型 引用 = (目标子类型) 父类型引用; // 安全
}示例:
class Animal { }
class Cat extends Animal {
public void catchMouse() { System.out.println("捕鼠"); }
}
class Dog extends Animal { }
Animal a = new Cat(); // 向上转型:自动发生
// Cat c = a; // ✗ 编译错误,不可隐式向下转型
// Dog d = (Dog) a; // ✗ 运行时 ClassCastException
if (a instanceof Cat) {
Cat cat = (Cat) a; // ✓ 安全检查后强转
cat.catchMouse(); // 调用Cat特有方法
}22. 抽象类和接口的区别有哪些?
答案:
| 对比维度 | 抽象类(abstract class) | 接口(interface) |
|---|---|---|
| 关键字 | abstract class | interface |
| 构造方法 | 可以有 | 不能有 |
| 成员变量 | 无限制,可为任意类型 | 仅 public static final 常量 |
| 方法 | 抽象方法 + 具体方法 | JDK 8: 抽象方法 + default + static;JDK 9: 增加 private |
| 继承机制 | 单继承(extends) | 多实现(implements),接口间多继承(extends) |
| 访问修饰 | 方法可为任意权限 | 方法默认 public abstract(JDK 9 private除外) |
| 设计意图 | ”is-a” 关系,抽取共性,模板化 | ”can-do” 契约,定义行为规范 |
使用模板:
// 抽象类
abstract class 类名 {
类型 变量名; // 普通成员变量
abstract 返回类型 方法名(参数列表); // 抽象方法(无方法体,由子类实现)
public 返回类型 具体方法(参数列表) { ... } // 具体方法(含方法体,子类可直接继承使用)
}
// 接口
interface 接口名 {
// 常量(默认 public static final)
类型 常量名 = 值;
// 抽象方法(默认 public abstract)
返回类型 方法名(参数列表);
// default 方法(JDK 8+,含方法体,实现类可直接继承或选择性覆盖)
default 返回类型 方法名(参数列表) { ... }
}示例:
abstract class Animal {
String name; // 普通成员变量
public abstract void sound(); // 抽象方法:子类强制实现
public void sleep() { // 具体方法:子类直接复用
System.out.println(name + " 开始睡觉");
}
}
interface Flyable {
int WINGS = 2; // 等价于 public static final
void fly(); // 等价于 public abstract
default void land() { // JDK 8 default 方法
System.out.println("降落");
}
}
class Bird extends Animal implements Flyable {
@Override public void sound() { System.out.println("啾啾"); }
@Override public void fly() { System.out.println("振翅起飞"); }
}23. static 关键字可以修饰哪些内容?有什么共同特点?
答案:
修饰范围及语法:
class 类名 {
static 类型 变量名 = 值; // 静态变量(类变量)
static 返回类型 方法名() { } // 静态方法
static { ... } // 静态代码块
static class 内部类名 { } // 静态内部类
}
// 还支持静态导入:
import static 包名.类名.静态成员名;共同特点:
- 属于类级别而非对象级别,随类加载而初始化
- 被该类的所有实例共享
- 可直接通过
类名.成员名调用,无需创建对象 - 静态方法中不能直接访问实例成员(无
this引用)
示例:
public class MathUtil {
public static final double PI = 3.14159; // 静态常量
public static int invokeCount = 0; // 静态变量:记录调用次数
public static double circleArea(double r) { // 静态方法
invokeCount++; // 只能访问静态成员
return PI * r * r;
}
}
System.out.println(MathUtil.PI); // 3.14159
System.out.println(MathUtil.circleArea(5.0)); // 78.53975
System.out.println(MathUtil.invokeCount); // 1
// 静态导入
import static java.lang.Math.PI;
System.out.println(PI); // 直接使用,无需 Math. 前缀24. 什么是内部类?内部类有哪几种?
答案:
内部类:定义在另一个类或方法内部的类,Java提供四种形式的内部类。
| 类型 | 定义位置 | 修饰符 | 特点 |
|---|---|---|---|
| 成员内部类 | 类中方法外 | 无static | 属于实例,可访问外部类所有成员 |
| 静态内部类 | 类中方法外 | static | 属于类级别,只能访问外部类静态成员 |
| 局部内部类 | 方法/代码块内 | 无 | 作用域局限在方法/代码块内部 |
| 匿名内部类 | 表达式位置 | 无 | 无类名,常配合接口/抽象类简化使用 |
使用模板:
class 外部类 {
class 成员内部类 { } // 1. 成员内部类
static class 静态内部类 { } // 2. 静态内部类
public void 方法() {
class 局部内部类 { } // 3. 局部内部类
接口类型 变量 = new 接口() { // 4. 匿名内部类
// 实现接口的抽象方法
};
}
}示例:
// 1. 成员内部类
class Outer {
private int x = 10;
class Inner {
public void print() { System.out.println(x); } // 可访问外部类私有成员
}
}
Outer.Inner inner = new Outer().new Inner(); // 创建方式
// 2. 静态内部类
class Outer {
static class StaticInner { }
}
Outer.StaticInner si = new Outer.StaticInner(); // 无需外部类实例
// 3. 局部内部类
class Outer {
public void method() {
class Local { void say() { System.out.println("局部"); } }
new Local().say();
}
}
// 4. 匿名内部类(JDK 8+ 函数式接口推荐用Lambda替代)
Runnable r1 = new Runnable() { // 传统匿名内部类
@Override
public void run() { System.out.println("run"); }
};
Runnable r2 = () -> System.out.println("run"); // Lambda(等效,更简洁)25. 什么是包(package)?使用包有什么好处?
答案:
包(package):Java的命名空间机制,将相关类组织在一起,对应文件系统的目录结构。
好处:
- 避免类名冲突(不同包下允许同名类)
- 与访问修饰符
default配合实现包级访问控制 - 按功能模块组织代码结构,便于管理和维护
使用模板:
// 声明包 — 写在源文件第一行
package 组织域名倒写.项目名.模块名;
// 导入包 — 写在 package 和 class 声明之间
import 包名.类名; // 导入单个类
import 包名.*; // 导入包下所有类(不含子包)
import static 包名.类名.成员; // 静态导入示例:
// 文件位置:src/com/demo/util/DateUtils.java
package com.demo.util;
import java.util.Date; // 导入指定类
import java.text.*; // 导入 java.text 下所有类
import static java.lang.Math.PI; // 静态导入
public class DateUtils {
public static Date now() {
return new Date(); // 已import,无需全限定名
}
}26. 四种访问修饰符的可见范围分别是什么?
答案:
| 修饰符 | 本类内部 | 同包下其他类 | 跨包子类 | 全局(任意类) |
|---|---|---|---|---|
| private | ✓ | ✗ | ✗ | ✗ |
| default(无修饰符) | ✓ | ✓ | ✗ | ✗ |
| protected | ✓ | ✓ | ✓ | ✗ |
| public | ✓ | ✓ | ✓ | ✓ |
权限严格程度:private > default > protected > public
27. 什么是单例模式?请写出两种常见的单例实现方式。
答案:
单例模式:确保一个类在整个应用程序生命周期中仅有一个实例,并提供一个全局访问点。
实现要点:
- 构造方法私有化(
private),阻止外部通过new创建 - 类内部维护私有静态实例引用
- 提供公有静态方法返回唯一实例
实现一:饿汉式(类加载时即创建,线程安全,但可能造成资源浪费)
public class SingletonEager {
private static final SingletonEager INSTANCE = new SingletonEager();
private SingletonEager() { }
public static SingletonEager getInstance() {
return INSTANCE;
}
}实现二:静态内部类式(推荐。利用类加载机制实现懒加载与线程安全,无同步开销)
public class Singleton {
private Singleton() { }
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE; // 首次调用时才触发 Holder 类加载
}
}28. Object 类中有哪些常用方法?
答案:
| 方法 | 功能说明 |
|---|---|
toString() | 返回对象的字符串表示,默认格式:类名@十六进制哈希码 |
equals(Object obj) | 判断两个对象是否”逻辑相等”,默认比较引用地址 |
hashCode() | 返回对象的哈希码整数值,用于HashMap/HashSet等哈希容器 |
getClass() | 返回对象的运行时类型(Class对象) |
clone() | 创建并返回对象的浅拷贝(需实现Cloneable接口) |
finalize() | GC回收对象前回调(JDK 9起已标记 @Deprecated,不推荐使用) |
wait() | 当前线程进入等待队列,释放对象锁 |
notify() | 唤醒该对象等待队列中的单个线程 |
notifyAll() | 唤醒该对象等待队列中的所有线程 |
重写 equals 与 hashCode 的标准模板:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
本类名 other = (本类名) o;
return Objects.equals(属性1, other.属性1)
&& Objects.equals(属性2, other.属性2);
}
@Override
public int hashCode() {
return Objects.hash(属性1, 属性2); // 参与equals比较的字段必须参与hashCode计算
}
@Override
public String toString() {
return "类名{" + "属性1=" + 属性1 + ", 属性2=" + 属性2 + '}';
}29. hashCode() 和 equals() 方法之间有什么约定?
答案:
Object类规范定义的三条核心约定:
- 一致性:同一对象多次调用
hashCode()应返回相同值(在参与equals()比较的属性未变化的前提下) - 等价则等哈希:若
a.equals(b) == true,则必须有a.hashCode() == b.hashCode() - 等哈希不一定等价:若
a.hashCode() == b.hashCode(),a.equals(b)未必为true(哈希冲突) - 重写equals必须重写hashCode:否则哈希容器(HashMap、HashSet等)的行为将不符合预期
违反约定的后果:
class Person {
String name;
public Person(String name) { this.name = name; }
@Override
public boolean equals(Object o) { // 重写了equals
if (this == o) return true;
if (!(o instanceof Person)) return false;
return Objects.equals(name, ((Person) o).name);
}
// 未重写 hashCode() — 违反约定
}
Set<Person> set = new HashSet<>();
set.add(new Person("张三"));
set.add(new Person("张三"));
System.out.println(set.size()); // 输出 2 而非预期的 1
// 原因:两个equals相等的对象因hashCode不同,落入HashMap的不同bucket,被视为不同key30. 什么是函数式接口?内置的函数式接口有哪些?
答案:
函数式接口:仅声明了一个抽象方法的接口(可包含多个 default 或 static 方法)。使用 @FunctionalInterface 注解标注可由编译器强制检查。
常用内置函数式接口(java.util.function 包):
| 接口 | 抽象方法签名 | 用途 |
|---|---|---|
Function<T, R> | R apply(T t) | 接收T类型参数,返回R类型结果(转换/映射) |
Consumer<T> | void accept(T t) | 接收T类型参数,无返回值(消费) |
Supplier<T> | T get() | 无参数,返回T类型结果(供给) |
Predicate<T> | boolean test(T t) | 接收T类型参数,返回boolean(断言/筛选) |
UnaryOperator<T> | T apply(T t) | Function的特化,输入输出同类型 |
BiFunction<T, U, R> | R apply(T t, U u) | 接收两个参数,返回一个结果 |
使用模板:
@FunctionalInterface
interface 接口名 {
返回类型 方法名(参数列表); // 唯一抽象方法
}
// 通过Lambda表达式实例化
接口名 变量 = (参数) -> 表达式或代码块;示例:
// 自定义函数式接口
@FunctionalInterface
interface Calculator {
int calc(int a, int b);
}
Calculator add = (a, b) -> a + b;
System.out.println(add.calc(3, 5)); // 8
// 内置函数式接口
Function<String, Integer> toLen = s -> s.length();
System.out.println(toLen.apply("hello")); // 5
Consumer<String> printer = s -> System.out.println(s);
printer.accept("Hello Java"); // 输出: Hello Java
Supplier<Double> randomSupplier = () -> Math.random();
System.out.println(randomSupplier.get()); // 0.0~1.0 的随机数
Predicate<Integer> isPositive = n -> n > 0;
System.out.println(isPositive.test(10)); // true31. 什么是泛型?使用泛型有什么好处?
答案:
泛型(Generics):一种参数化类型(Parameterized Type)机制,允许类、接口、方法在定义时使用类型参数(Type Parameter),使用时再指定具体类型。
主要好处:
- 编译期类型安全:将运行时可能发生的
ClassCastException提前到编译期发现 - 消除强制类型转换:获取元素时无需手动强转
- 代码复用:一套代码适配多种数据类型
使用模板:
// 泛型类
class 类名<T1, T2> { // T1/T2 为类型参数占位符
private T1 属性;
public T1 方法(T1 参数) { ... }
}
// 泛型方法(类型参数声明在返回值之前)
public <T> T 方法名(T 参数) { ... }
// 泛型接口
interface 接口名<T> { }
// 实例化
类名<具体类型> 实例 = new 类名<>(); // JDK 7+ 钻石操作符,右侧可省略类型示例:
class Box<T> {
private T item;
public void put(T item) { this.item = item; }
public T get() { return item; }
}
Box<String> strBox = new Box<>(); // 指定T为String
strBox.put("hello");
// strBox.put(123); // ✗ 编译错误,类型不匹配
String s = strBox.get(); // 无需强转
Box<Integer> intBox = new Box<>(); // 指定T为Integer
intBox.put(123);
// 泛型方法
public static <T> T firstElement(List<T> list) {
return list.get(0);
}32. 什么是通配符?<? extends T> 和 <? super T> 的区别是什么?
答案:
通配符?:用于泛型中表示未知类型,提供比具体类型参数更灵活的类型限定。
| 语法 | 名称 | 读/写能力 | 适用场景(PECS) |
|---|---|---|---|
<? extends T> | 上界通配符 | 只能读,不可写入 | Producer(生产者),从结构中读取数据 |
<? super T> | 下界通配符 | 只能写,读取为Object | Consumer(消费者),向结构中写入数据 |
PECS 原则:Producer Extends, Consumer Super。
使用模板:
// extends 上界通配符:T 或 T 的子类
// 能安全读出(返回至少是T类型),但不能写入(不知具体子类型)
void readOnly(List<? extends T> list) {
T item = list.get(0); // ✓ 安全读出
// list.add(new T()); // ✗ 编译错误
}
// super 下界通配符:T 或 T 的父类
// 能安全写入(容器至少能装T),但读出只知道是Object
void writeOnly(List<? super T> list) {
list.add(new T()); // ✓ 安全写入
// T item = list.get(0); // ✗ 编译错误,get()返回Object
}示例:
class Animal { }
class Dog extends Animal { }
// Producer Extends: 从集合读
public static void printAnimals(List<? extends Animal> animals) {
for (Animal a : animals) { // ✓ 作为Animal安全读取
System.out.println(a);
}
// animals.add(new Dog()); // ✗ 不能写,编译器不知道实际List<Dog>还是List<Cat>
}
// Consumer Super: 向集合写
public static void addDogs(List<? super Dog> dogs) {
dogs.add(new Dog()); // ✓ Dog类型安全写入
// Dog d = dogs.get(0); // ✗ 返回Object,不是Dog
}
List<Animal> animals = new ArrayList<>();
addDogs(animals); // List<Animal> 符合 <? super Dog>
List<Dog> dogs = new ArrayList<>();
printAnimals(dogs); // List<Dog> 符合 <? extends Animal>33. Error 和 Exception 的区别是什么?
答案:
| 对比维度 | Error | Exception |
|---|---|---|
| 来源层级 | JVM级别(虚拟机错误) | 应用程序级别 |
| 典型场景 | 系统资源耗尽、JVM内部错误 | 业务逻辑异常、外部条件异常 |
| 处理策略 | 不应试图捕获和处理 | 应当捕获或以throws声明 |
| 可恢复性 | 绝大多数不可恢复 | 多数可恢复 |
| 典型例子 | OutOfMemoryError、StackOverflowError、NoClassDefFoundError | IOException、SQLException、FileNotFoundException |
处理模板:
try {
// 可能抛出异常的业务代码
} catch (Exception e) { // 捕获并处理Exception
// 异常处理或恢复逻辑
} finally {
// 资源清理
}
// Error 不应捕获 — 应让程序终止并检查JVM/系统环境34. 运行时异常(RuntimeException)与非运行时异常(受检异常)有何区别?
答案:
| 对比维度 | 运行时异常(RuntimeException) | 受检异常(Checked Exception) |
|---|---|---|
| 继承关系 | 继承自 RuntimeException | 继承自 Exception 但非 RuntimeException 子类 |
| 编译器检查 | 不强制检查(Unchecked) | 强制检查(Checked) |
| 处理要求 | 可自由选择处理或不处理 | 必须用 try-catch 捕获或用 throws 声明抛出 |
| 常见示例 | NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException | IOException、SQLException、FileNotFoundException |
35. throw 和 throws 关键字的区别是什么?
答案:
| 对比维度 | throw | throws |
|---|---|---|
| 作用 | 实际抛出一个异常对象 | 声明方法可能抛出的异常类型 |
| 位置 | 方法体内部 | 方法签名参数列表之后 |
| 后接内容 | 异常实例(单个) | 异常类型(多个以逗号分隔) |
| 语义 | 执行到throw时立即抛出,控制流转到上层 | 告知调用者”此方法可能抛出这些异常,请处理” |
使用模板:
// throws:声明异常
修饰符 返回类型 方法名(参数列表) throws 异常类型A, 异常类型B {
// ...
throw new 异常类型A("异常描述"); // throw:手动抛出
}
// 调用方
try {
方法名(实参); // 调用声明了throws的方法
} catch (异常类型A e) {
// 处理该异常
}示例:
public static int divide(int a, int b) throws ArithmeticException {
if (b == 0) {
throw new ArithmeticException("除数不能为零"); // throw 抛出异常
}
return a / b;
}
public static void main(String[] args) {
try {
int result = divide(10, 0); // 调用可能抛异常的方法
} catch (ArithmeticException e) { // 捕获处理
System.out.println("发生异常: " + e.getMessage());
}
}36. 如何自定义一个异常类?
答案:
自定义异常需继承 Exception(受检异常)或 RuntimeException(运行时异常),并提供构造方法。
使用模板:
// 自定义受检异常
public class XxxException extends Exception {
public XxxException() { super(); }
public XxxException(String message) { super(message); }
public XxxException(String message, Throwable cause) { super(message, cause); }
}
// 自定义运行时异常
public class XxxRuntimeException extends RuntimeException {
public XxxRuntimeException(String message) { super(message); }
}示例:
// 自定义受检异常
class InsufficientFundsException extends Exception {
public InsufficientFundsException() { super(); }
public InsufficientFundsException(String message) { super(message); }
}
// 使用
class BankAccount {
private double balance;
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException(
"余额不足,当前余额: " + balance + ",取款金额: " + amount);
}
balance -= amount;
}
}37. 为什么 String 类被设计为不可变的?
答案:
String的不可变性是其设计的核心前提,原因包括:
- 字符串常量池复用:不可变性允许多个引用指向常量池中同一字符串对象,显著节省堆内存
- 安全性:字符串广泛用于类名、文件路径、数据库URL、网络连接参数等场景,不可变性保证其不会被恶意或意外篡改
- 线程安全:不可变对象天然线程安全,多线程环境下无需同步开销
- Hash缓存优化:String内部缓存
hash字段,因内容不变可保证hash值始终有效,使得HashMap的key查找高效 - 类加载机制安全:JVM加载类时使用String表示类名,若String可变则类加载的安全性无法保证
38. ArrayList 和 LinkedList 的区别是什么?分别适用于什么场景?
答案:
| 对比维度 | ArrayList | LinkedList |
|---|---|---|
| 底层数据结构 | Object[] 动态数组 | 双向链表(Node包含prev/next指针) |
| 随机访问 get(index) | O(1) | O(n) |
| 头部插入/删除 | O(n)(需搬移所有元素) | O(1) |
| 尾部追加 add(E) | 均摊 O(1)(扩容时为O(n)) | O(1) |
| 中间插入/删除 | O(n) | O(1)(遍历到位置仍需O(n)) |
| 内存占用 | 连续内存,仅存元素引用 | 每个节点额外存储前驱/后继引用 |
| 实现接口 | List | List + Deque(双端队列接口) |
适用场景:
- ArrayList:频繁按索引随机访问、主要在尾部追加、遍历操作为主的场景
- LinkedList:频繁在头部/中间插入删除、需要双端队列操作(FIFO队列、LIFO栈)的场景
使用模板:
// 面向接口编程,切换实现类仅需改动new的一侧
List<String> list = new ArrayList<>(); // 默认选择,多数场景适用
List<String> list = new LinkedList<>(); // 需要频繁头/中部增删时
// Deque 特有操作(需声明为Deque类型)
Deque<String> deque = new LinkedList<>();
deque.addFirst(元素);
deque.addLast(元素);
deque.removeFirst();
deque.removeLast();39. HashMap 的底层工作原理是什么?
答案:
JDK 8起,HashMap底层采用 数组(Node[])+ 链表 + 红黑树 的复合结构。
核心流程——put(K key, V value):
- 对key计算
hashCode(),经扰动函数(高16位与低16位异或)得到hash值,再与数组长度-1做按位与运算得出桶下标(index) - 若对应桶为空 → 直接存入Node
- 若对应桶非空 → 遍历链表/红黑树,用
equals()逐一比对:- 遇到key相等 → 覆盖旧value
- 遍历完未找到相等key → 在尾部追加新Node
- 链表长度 ≥ 8 且数组长度 ≥ 64 → 链表树化为红黑树(查找由O(n)优化为O(log n))
- 红黑树节点数 ≤ 6 → 退化回链表
- 元素总数 > 容量 × 负载因子 → 触发扩容(容量翻倍,重新计算各元素位置)
默认参数:初始容量 = 16,负载因子 = 0.75,树化阈值 = 8,退化阈值 = 6
40. HashSet 如何保证元素的唯一性?
答案:
HashSet 基于 HashMap 实现:存入的元素作为 HashMap 的 key,所有 key 的 value 指向同一个共享的 Object 常量(PRESENT)。
判重流程:
- 先比较
hashCode()→ 决定该元素应落入哪个bucket - 若bucket中已有元素,再调用
equals()→ 逐一比对 - 若 hashCode 相同且 equals 返回 true → 判定为重复,不加入集合
因此,存入HashSet的元素必须同时正确重写 hashCode() 与 equals() 方法。
使用模板:
// HashSet.add(e) 内部等价于:
// map.put(e, PRESENT) == null 时表示添加成功(新元素)
// map.put(e, PRESENT) != null 时表示已存在(重复元素)
// 自定义元素类必须重写:
@Override public boolean equals(Object o) { ... }
@Override public int hashCode() { ... }示例:
class Person {
String name;
int age;
public Person(String name, int age) { this.name = name; this.age = age; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person p = (Person) o;
return age == p.age && Objects.equals(name, p.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
Set<Person> set = new HashSet<>();
set.add(new Person("张三", 20));
set.add(new Person("张三", 20));
System.out.println(set.size()); // 1,name和age相同视为同一元素41. TreeMap 和 HashMap 的主要区别是什么?
答案:
| 对比维度 | TreeMap | HashMap |
|---|---|---|
| 底层结构 | 红黑树 | 数组 + 链表 + 红黑树 |
| 排序性 | 按键的自然顺序或自定义Comparator排序 | 无序(不保证任何顺序) |
| null键 | 不允许(抛NullPointerException) | 允许一个null键 |
| 时间复杂度 | O(log n) | 平均O(1),最坏O(n)(退化时) |
| 键的约束 | 必须实现 Comparable 或构造时传入 Comparator | 依赖 hashCode() 与 equals() |
| 适用场景 | 需要按key排序 / 范围查找 / 取最大最小值 | 快速单key存取,不关心顺序 |
使用模板:
// HashMap — 无序,常规存取
Map<String, Integer> hashMap = new HashMap<>();
// TreeMap — 按key自然顺序排列(key必须实现 Comparable)
Map<String, Integer> treeMap = new TreeMap<>();
// TreeMap — 自定义排序规则
Map<String, Integer> customTreeMap = new TreeMap<>(自定义Comparator);42. Iterator 是什么?使用 Iterator 与使用 for-each 遍历集合有什么不同?
答案:
Iterator(迭代器):Java集合框架提供的统一遍历接口,通过 hasNext() / next() 方法逐个访问集合元素。所有Collection子类均实现了Iterable接口,可获取对应的Iterator。
| 对比维度 | Iterator | for-each(增强for循环) |
|---|---|---|
| 删除元素 | iterator.remove() 安全删除 | 不可直接删除,会抛ConcurrentModificationException |
| 控制粒度 | 精细控制遍历过程 | 语法简洁,适合只读遍历 |
| 底层本质 | 显式操作 | Iterator的语法糖封装 |
使用模板:
// Iterator 显式遍历(支持安全删除)
Iterator<元素类型> it = 集合.iterator();
while (it.hasNext()) {
元素类型 变量 = it.next();
if (删除条件) {
it.remove(); // 安全删除
}
}
// for-each 遍历(简洁,只读)
for (元素类型 变量 : 集合) {
// 处理元素(不可在遍历期间通过集合本身修改结构)
}示例:
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
// Iterator 安全删除
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if ("B".equals(it.next())) {
it.remove(); // 安全删除当前元素
}
}
System.out.println(list); // [A, C]
// for-each 中删除会导致异常
// for (String s : list) {
// list.remove(s); // ConcurrentModificationException
// }43. java.util.Date 常用操作?
答案:
JDK 8引入
java.time包(LocalDate、LocalDateTime、Instant等),推荐在新项目中使用。Date主要存在于遗留系统中。
操作模板:
// 构造
Date date = new Date(); // 当前系统时间
Date date = new Date(long 毫秒值); // 指定时间戳
// 时间戳
long millis = date.getTime(); // 获取毫秒级时间戳
// 比较
boolean before = date1.before(date2); // date1 在 date2 之前?
boolean after = date1.after(date2); // date1 在 date2 之后?
int cmp = date1.compareTo(date2); // 返回 -1 / 0 / 1
// 格式化与解析(配合 SimpleDateFormat)
SimpleDateFormat sdf = new SimpleDateFormat("格式模式");
String str = sdf.format(date); // Date → String
Date parsed = sdf.parse("日期字符串"); // String → Date(需处理ParseException)示例:
Date now = new Date();
System.out.println(now); // Sat May 30 09:30:00 CST 2026
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(now)); // 2026-05-30 09:30:00
Date parsed = sdf.parse("2026-06-01 12:00:00");
System.out.println(parsed.after(now)); // true44. 什么是包装类?为什么需要包装类?
答案:
包装类:Java为8种基本数据类型提供的对应引用类型封装。
| 基本类型 | byte | short | int | long | float | double | char | boolean |
|---|---|---|---|---|---|---|---|---|
| 包装类 | Byte | Short | Integer | Long | Float | Double | Character | Boolean |
为什么需要包装类:
- 集合框架(List、Set、Map)只能存储对象,不能存储基本类型
- 泛型要求类型参数必须是引用类型
- 提供
parseXxx()、toString()等类型转换工具方法 - 支持
null语义,表示”值不存在” - 提供常量(如
Integer.MAX_VALUE)
常用操作模板:
// 字符串 ↔ 基本类型
基本类型 n = 包装类.parseXxx(String); // 字符串 → 基本类型
String s = 包装类.toString(基本类型值); // 基本类型 → 字符串
// valueOf(推荐:Integer -128~127 有缓存)
包装类 obj = 包装类.valueOf(基本类型值);
包装类 obj = 包装类.valueOf(String);
// 常量
包装类.MAX_VALUE; // 该类型的最大值
包装类.MIN_VALUE; // 该类型的最小值示例:
int n = Integer.parseInt("123"); // String → int
String s = Integer.toString(456); // int → String
Integer a = Integer.valueOf(127);
Integer b = Integer.valueOf(127);
System.out.println(a == b); // true(缓存范围内复用同一对象)
System.out.println(Integer.MAX_VALUE); // 214748364745. Math.round() 方法的舍入规则是什么?
答案:
Math.round(double x) 采用四舍五入规则,其内部实现等价于:
(long) Math.floor(x + 0.5)即:将原值加0.5后向下取整。
示例:
Math.round(11.5) // = Math.floor(11.5 + 0.5) = Math.floor(12.0) = 12
Math.round(11.4) // = Math.floor(11.4 + 0.5) = Math.floor(11.9) = 11
Math.round(-11.5) // = Math.floor(-11.5 + 0.5) = Math.floor(-11.0) = -11
Math.round(-11.6) // = Math.floor(-11.6 + 0.5) = Math.floor(-11.1) = -12特别注意:Math.round(-11.5) 返回 -11 而非 -12。这是因为 -11.5 + 0.5 = -11.0,Math.floor(-11.0) = -11.0。在数轴上,-11 比 -12 更靠近原点,这符合”向正无穷方向舍入”的语义。
46. 字节流和字符流的区别是什么?各自的应用场景是什么?
答案:
| 对比维度 | 字节流(Byte Stream) | 字符流(Character Stream) |
|---|---|---|
| 抽象基类 | InputStream / OutputStream | Reader / Writer |
| 最小处理单元 | 1字节(8 bits) | 1字符(2字节,UTF-16编码的char) |
| 编码处理 | 不处理编码,直接操作原始字节 | 内部以字符为单位,自动进行编解码转换 |
| 缓冲区 | 无内置缓冲(需外部包装BufferedInputStream) | 内部自带缓冲 |
| 适用场景 | 二进制文件:图片、音频、视频、压缩包、序列化文件等 | 文本文件:.txt、.java、.xml、.json、.csv等 |
使用模板:
// 字节流 — 以 Stream 为后缀
InputStream in = new FileInputStream("源文件路径");
OutputStream out = new FileOutputStream("目标文件路径");
// 字符流 — 以 Reader / Writer 为后缀
Reader reader = new FileReader("源文件路径");
Writer writer = new FileWriter("目标文件路径");示例:
// 字节流复制文件(适用于任意类型文件)
try (FileInputStream in = new FileInputStream("source.jpg");
FileOutputStream out = new FileOutputStream("target.jpg")) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
// 字符流读写文本文件
try (FileReader reader = new FileReader("input.txt");
FileWriter writer = new FileWriter("output.txt")) {
char[] buffer = new char[1024];
int charsRead;
while ((charsRead = reader.read(buffer)) != -1) {
writer.write(buffer, 0, charsRead);
}
}47. 什么是缓冲流?使用缓冲流有什么好处?
答案:
缓冲流:在底层节点流之上包装一层内部缓冲区(默认大小8192字节/字符),实现批量I/O操作。包括:
BufferedInputStream/BufferedOutputStreamBufferedReader/BufferedWriter
好处:
- 减少系统调用次数:单次read/write操作从磁盘/网络读取一大块数据到内存缓冲区,后续read直接从内存返回,显著降低I/O频次
- 显著提升性能:将逐字节操作转为批量操作,I/O吞吐量大幅提高
- 提供便捷API:如
BufferedReader.readLine()按行读取文本,BufferedWriter.newLine()写入平台无关的换行符
使用模板:
// 字节缓冲流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("文件"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("文件"));
// 字符缓冲流
BufferedReader br = new BufferedReader(new FileReader("文件"));
BufferedWriter bw = new BufferedWriter(new FileWriter("文件"));
// 便捷方法
String line = br.readLine(); // 读取一行
bw.newLine(); // 写入换行符示例:
// 逐行读取
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
// 高效写入
try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
bw.write("第一行内容");
bw.newLine();
bw.write("第二行内容");
}48. 什么是序列化?如何实现一个类的序列化?
答案:
- 序列化(Serialization):将对象的状态信息转换为可存储或可传输的字节序列的过程
- 反序列化(Deserialization):将字节序列恢复为对象的过程
实现步骤:
// 1. 类实现 Serializable 标记接口
class 类名 implements Serializable {
private static final long serialVersionUID = 1L; // 显式声明版本号
// 如需忽略某字段,添加 transient 修饰符
private transient 类型 字段名;
}
// 2. 序列化 — 对象写入流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("文件"));
oos.writeObject(对象);
// 3. 反序列化 — 从流读取对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("文件"));
类型 对象 = (类型) ois.readObject(); // 需处理 ClassNotFoundException示例:
class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private transient String password; // transient字段不会被序列化
public Person(String name, int age, String password) {
this.name = name; this.age = age; this.password = password;
}
@Override
public String toString() {
return "Person{name=" + name + ", age=" + age + ", password=" + password + '}';
}
}
// 序列化
Person p = new Person("张三", 25, "secret");
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.dat"))) {
oos.writeObject(p);
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.dat"))) {
Person restored = (Person) ois.readObject();
System.out.println(restored); // password=null (transient字段未序列化)
}49. 创建线程有哪几种方式?哪种更好?为什么?
答案:
四种方式及其模板:
// 方式1:继承Thread类
class MyThread extends Thread {
@Override
public void run() { /* 任务逻辑 */ }
}
new MyThread().start();
// 方式2:实现Runnable接口(推荐的基础方式)
class MyTask implements Runnable {
@Override
public void run() { /* 任务逻辑 */ }
}
new Thread(new MyTask()).start();
// 方式3:实现Callable接口(有返回值、可抛出异常)
class MyCallable implements Callable<返回类型> {
@Override
public 返回类型 call() throws Exception {
return 计算结果;
}
}
FutureTask<返回类型> futureTask = new FutureTask<>(new MyCallable());
new Thread(futureTask).start();
返回类型 结果 = futureTask.get(); // 阻塞等待结果
// 方式4:线程池(生产环境推荐)
ExecutorService pool = Executors.newFixedThreadPool(线程数);
pool.execute(Runnable任务); // 提交无返回值任务
Future<类型> future = pool.submit(Callable任务); // 提交有返回值任务
pool.shutdown(); // 任务完成后关闭线程池示例:
// Runnable
class Task implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 执行中");
}
}
new Thread(new Task()).start();
// Callable
Callable<Integer> task = () -> {
Thread.sleep(500);
return 42;
};
FutureTask<Integer> ft = new FutureTask<>(task);
new Thread(ft).start();
System.out.println("结果: " + ft.get());
// 线程池
ExecutorService pool = Executors.newFixedThreadPool(3);
pool.execute(() -> System.out.println("异步任务"));
Future<String> f = pool.submit(() -> "任务结果");
System.out.println(f.get());
pool.shutdown();推荐方式及其原因:
推荐使用 Runnable/Callable + 线程池,理由如下:
- 避免Java单继承限制(实现接口比继承Thread更灵活)
- 任务与线程解耦,同一任务可提交给不同线程执行
- 线程池复用线程,避免频繁创建/销毁的开销
- Callable支持返回值与异常抛出,表达能力更强
50. 线程的生命周期包含哪些状态?
答案:
Java线程共有6种状态,定义于 Thread.State 枚举中:
| 状态 | 说明 | 进入条件 |
|---|---|---|
| NEW | 线程对象已创建,尚未调用 start() | new Thread() |
| RUNNABLE | 就绪或正在运行中(已获得/等待CPU时间片) | 调用 start() 后;从BLOCKED/WAITING被唤醒后 |
| BLOCKED | 被阻塞,等待获取监视器锁 | 进入synchronized块时锁被其他线程持有 |
| WAITING | 无限期等待,直到被显式唤醒 | Object.wait()、Thread.join()、LockSupport.park() |
| TIMED_WAITING | 限时等待,超时自动唤醒 | Thread.sleep(ms)、wait(ms)、join(ms) |
| TERMINATED | 线程执行完毕或异常退出 | run() 方法正常返回或抛出未捕获异常 |
状态流转示意:
NEW ──start()──▶ RUNNABLE ──run()执行完毕──▶ TERMINATED
│ ▲
获取锁失败 │ │ 成功获取锁
▼ │
BLOCKED ──┘
│ ▲
wait() │ │ notify()/notifyAll()
join() │ │ join()的目标线程结束
park() │ │ unpark()
▼ │
WAITING ──────────────────────▶ RUNNABLE
│ ▲
sleep(ms) │ │ 超时到期 / notify()
wait(ms) │ │
join(ms) │ │
parkNanos() │ │
▼ │
TIMED_WAITING ───────────────────▶ RUNNABLE
51. sleep() 方法和 wait() 方法的区别有哪些?
答案:
| 对比维度 | sleep() | wait() |
|---|---|---|
| 所属类 | Thread 类的静态方法 | Object 类的实例方法 |
| 锁释放行为 | 不释放已持有的锁 | 释放当前对象锁,进入等待队列 |
| 唤醒方式 | 指定时间到达后自动唤醒 | 需其他线程调用 notify() / notifyAll(),或带超时的 wait(long) |
| 调用条件 | 任意上下文中均可调用 | 必须在 synchronized 代码块中调用(否则抛 IllegalMonitorStateException) |
| 主要用途 | 让当前线程暂停执行指定时长 | 实现多线程间的协调通信 |
使用模板:
// sleep:线程暂停,持有锁不释放
Thread.sleep(毫秒值);
// wait/notify:线程通信,wait 释放锁
synchronized (锁对象) {
while (条件不满足) {
锁对象.wait(); // 等待并释放锁
}
// 执行临界区代码
锁对象.notify(); // 唤醒等待队列中的一个线程
// 或 锁对象.notifyAll(); // 唤醒等待队列中的所有线程
}示例(sleep不释放锁 vs wait释放锁):
Object lock = new Object();
// 线程A:使用 sleep(),持有锁期间不释放
new Thread(() -> {
synchronized (lock) {
System.out.println("A: 获取锁,开始sleep(2000)");
try { Thread.sleep(2000); } catch (InterruptedException e) { }
System.out.println("A: sleep结束,释放锁");
}
}).start();
// 线程B:尝试获取同一把锁
new Thread(() -> {
try { Thread.sleep(100); } catch (InterruptedException e) { } // 确保A先获取锁
System.out.println("B: 尝试获取锁...");
synchronized (lock) {
System.out.println("B: 成功获取锁"); // A的sleep结束后才能输出此行
}
}).start();
// 输出顺序: A获取锁 → A sleep结束 → B成功获取锁
// 证明:sleep 不释放已持有的锁52. 如何解决多线程环境下的线程安全问题?
答案:
线程安全问题源于多个线程同时访问共享可变数据。Java提供以下同步机制:
7种解决方案及模板:
// 1. synchronized — JVM内置监视器锁
public synchronized void method() { ... }
synchronized (锁对象) { ... }
// 2. Lock 接口 — 显式锁,提供更灵活的控制
Lock lock = new ReentrantLock();
lock.lock();
try { /* 临界区 */ } finally { lock.unlock(); } // 务必在finally中释放
// 3. volatile — 保证变量可见性,禁止指令重排(不保证原子性)
private volatile boolean flag;
// 4. Atomic 原子类 — CAS无锁算法
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet(); // 原子自增
// 5. ThreadLocal — 为每个线程提供独立的变量副本
ThreadLocal<SimpleDateFormat> local = new ThreadLocal<>();
// 6. 并发集合 — 内置线程安全的集合实现
ConcurrentHashMap<K, V> map = new ConcurrentHashMap<>();
CopyOnWriteArrayList<E> list = new CopyOnWriteArrayList<>();
// 7. 不可变对象 — 状态不可修改,天然线程安全
final class ImmutableClass { ... }三种常用方案的对比示例:
// synchronized — 简单直观,JVM层面自动加锁/释放
class SyncCounter {
private int count = 0;
public synchronized void increment() { count++; }
}
// Atomic — 无锁,高并发场景性能更好
class AtomicCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() { count.incrementAndGet(); }
}
// ReentrantLock — 支持公平锁、可中断获取、超时获取
class LockCounter {
private int count = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try { count++; } finally { lock.unlock(); }
}
}53. synchronized 关键字可以修饰哪些地方?各自的锁对象是什么?
答案:
| 修饰位置 | 锁对象 | 互斥范围 |
|---|---|---|
| 实例方法 | this(当前实例对象) | 同一实例的所有synchronized实例方法之间互斥 |
| 静态方法 | 类的Class对象(如 MyClass.class) | 该类的所有synchronized静态方法之间互斥(与实例方法无关) |
| 同步代码块 | 括号中()指定的对象 | 以同一对象为锁的synchronized块之间互斥 |
关键结论:类锁(Class对象)与实例锁(this对象)是两把独立的锁,互不影响。
使用模板:
// 实例方法锁 — 同一实例内互斥
class Demo {
public synchronized void m1() { } // 锁 this
public synchronized void m2() { } // 锁 this,与 m1 互斥
}
// 静态方法锁 — 所有实例共享一把类锁
class Demo {
public static synchronized void s1() { } // 锁 Demo.class
public static synchronized void s2() { } // 锁 Demo.class,与 s1 互斥
}
// 代码块锁 — 灵活指定锁对象
Object lockA = new Object();
Object lockB = new Object();
synchronized (lockA) { /* 以lockA为锁 */ }
synchronized (lockB) { /* 以lockB为锁,与lockA互不影响 */ }54. 集合的体系结构
答案:
Java集合框架分为两大接口体系:Collection(单列集合)和 Map(双列键值对)。
Collection 接口
│
├── List 接口(有序,可重复,有索引)
│ ├── ArrayList — 动态数组,查快(O(1)),增删慢(O(n))
│ ├── LinkedList — 双向链表,增删快(O(1)),查慢(O(n)),也实现了Deque
│ ├── Vector — 线程安全(synchronized),已过时,现用 CopyOnWriteArrayList 替代
│ └── Stack — Vector子类,LIFO栈,已过时,现用 ArrayDeque 替代
│
├── Set 接口(无序,不可重复)
│ ├── HashSet — 基于HashMap,无序,允许一个null元素
│ │ └── LinkedHashSet — 基于LinkedHashMap,维护插入顺序
│ ├── TreeSet — 基于TreeMap,元素自然排序或按Comparator排序
│ └── EnumSet — 专为枚举类型设计的高效Set实现
│
└── Queue / Deque 接口(队列 / 双端队列)
├── LinkedList — 实现了Deque,可作FIFO队列或LIFO栈
├── PriorityQueue — 优先级队列(二叉堆实现),按优先级出队
└── ArrayDeque — 循环数组实现的双端队列,比Stack和LinkedList性能好
Map 接口(独立于Collection体系)
│
├── HashMap — 数组+链表+红黑树,无序,允许一个null键,多个null值
│ └── LinkedHashMap — 继承HashMap,维护插入顺序或访问顺序(LRU缓存)
├── Hashtable — 线程安全(synchronized),不允许null键/值,已过时
├── TreeMap — 红黑树,按key自然排序或自定义Comparator排序
├── ConcurrentHashMap — 线程安全的高性能Map(JDK8: CAS+synchronized),Hashtable的现代替代
└── Properties — Hashtable子类,专用于.properties配置文件读写(key与value均为String)
55. 获取 Stream 对象有几种方式?分别是哪几种?
答案:
共8种获取Stream对象的方式:
模板汇总:
// 1. 从 Collection 获取
集合.stream() // 串行流
集合.parallelStream() // 并行流
// 2. 从数组获取
Arrays.stream(数组)
Stream.of(数组)
// 3. Stream 静态工厂方法
Stream.of(元素1, 元素2, ...) // 可变参数
// 4. 数值范围流(IntStream / LongStream)
IntStream.range(start, end) // 半开区间 [start, end)
IntStream.rangeClosed(start, end) // 闭区间 [start, end]
// 5. 从 BufferedReader
bufferedReader.lines()
// 6. 从文件
Files.lines(Path对象) // 按行读取文件为流
Files.list(Path对象) // 列出目录内容为流
// 7. 无限流
Stream.iterate(种子值, 一元操作符) // 迭代生成
Stream.generate(Supplier供给函数) // 通过供给函数生成
// 8. 从正则表达式
Pattern.compile("分隔符").splitAsStream(字符串)示例:
// 集合 → 流
List<String> list = Arrays.asList("a", "b", "c");
list.stream().forEach(System.out::println);
// 数组 → 流
int[] arr = {1, 2, 3};
int sum = Arrays.stream(arr).sum(); // 6
// 数值范围流
IntStream.range(1, 5).forEach(System.out::print); // 1234
IntStream.rangeClosed(1, 5).forEach(System.out::print); // 12345
// 无限流(配合 limit 截断)
Stream.iterate(1, n -> n + 1).limit(5).forEach(System.out::print); // 12345
Stream.generate(Math::random).limit(3).forEach(System.out::println);56. 什么是线程池?线程池的参数?线程池的执行流程?
答案:
线程池:预创建并维护一组可复用线程的机制。提交任务时从池中分配空闲线程执行,任务完成后线程返回池中等待下次分配,避免反复创建/销毁线程的系统开销。
ThreadPoolExecutor 的7个构造参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| corePoolSize | int | 核心线程数,始终存活(除非设置allowCoreThreadTimeOut) |
| maximumPoolSize | int | 最大线程数上限 |
| keepAliveTime | long | 非核心线程空闲超过此时长则被回收 |
| unit | TimeUnit | keepAliveTime的时间单位 |
| workQueue | BlockingQueue | 任务等待队列(如LinkedBlockingQueue、ArrayBlockingQueue) |
| threadFactory | ThreadFactory | 创建线程的工厂,可自定义线程名 |
| handler | RejectedExecutionHandler | 线程池满载且队列满时的拒绝策略 |
4种内置拒绝策略:
| 策略 | 行为 |
|---|---|
| AbortPolicy(默认) | 直接抛出 RejectedExecutionException |
| CallerRunsPolicy | 由提交任务的调用者线程执行该任务 |
| DiscardPolicy | 静默丢弃新提交的任务 |
| DiscardOldestPolicy | 丢弃队列头部(等待最久)的任务,然后重新提交当前任务 |
任务执行流程:
任务提交
│
├─ 核心线程数未满? ──是──▶ 创建核心线程,直接执行任务
│
└─ 核心线程数已满
│
├─ 工作队列未满? ──是──▶ 任务进入 workQueue 排队等待
│
└─ 工作队列已满
│
├─ 线程数未达 maximumPoolSize? ──是──▶ 创建非核心线程执行任务
│
└─ 线程数已达 maximumPoolSize ──▶ 执行拒绝策略(handler)
便捷创建方式(不推荐生产环境使用,建议显式构造ThreadPoolExecutor):
ExecutorService pool = Executors.newFixedThreadPool(n); // 固定大小线程池
ExecutorService pool = Executors.newCachedThreadPool(); // 可缓存线程池(弹性伸缩)
ExecutorService pool = Executors.newSingleThreadExecutor(); // 单线程线程池二、编程题(五选三)
1. 猜数字游戏
import java.util.Random;
import java.util.Scanner;
public class GuessNumberGame {
public static void main(String[] args) {
int target = new Random().nextInt(100) + 1;
Scanner scanner = new Scanner(System.in);
int guess, count = 0;
System.out.println("猜数字游戏开始!请输入1~100之间的数字:");
while (true) {
System.out.print("你的猜测:");
guess = scanner.nextInt();
count++;
if (guess > target) {
System.out.println("猜大了");
} else if (guess < target) {
System.out.println("猜小了");
} else {
System.out.println("恭喜!共猜测" + count + "次");
break;
}
}
scanner.close();
}
}2. 数组合并与排序
public class MergeAndSort {
public static int[] mergeAndSort(int[] arr1, int[] arr2) {
int[] result = new int[arr1.length + arr2.length];
int i = 0, j = 0, k = 0;
// 双指针归并两个已排序数组
while (i < arr1.length && j < arr2.length) {
result[k++] = (arr1[i] <= arr2[j]) ? arr1[i++] : arr2[j++];
}
// 拷贝剩余元素
while (i < arr1.length) result[k++] = arr1[i++];
while (j < arr2.length) result[k++] = arr2[j++];
return result;
}
public static void main(String[] args) {
int[] a = {1, 3, 5, 7};
int[] b = {2, 4, 6, 8};
int[] merged = mergeAndSort(a, b);
for (int n : merged) System.out.print(n + " ");
}
}3. 字符出现次数统计
import java.util.Map;
import java.util.Scanner;
import java.util.TreeMap;
public class CharCount {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入一行字符串:");
String input = scanner.nextLine().replace(" ", "");
Map<Character, Integer> map = new TreeMap<>();
for (char c : input.toLowerCase().toCharArray()) {
map.put(c, map.getOrDefault(c, 0) + 1);
}
for (Map.Entry<Character, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
scanner.close();
}
}4. 银行账户与异常处理
class InsufficientFundsException extends Exception {
public InsufficientFundsException(String message) {
super(message);
}
}
class BankAccount {
private String account;
private double balance;
public BankAccount(String account, double balance) {
this.account = account;
this.balance = balance;
}
public void deposit(double amount) {
if (amount <= 0) {
System.out.println("存款金额必须大于0");
return;
}
balance += amount;
System.out.println("存款成功,当前余额:" + balance);
}
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException(
"余额不足!账号:" + account + ",余额:" + balance + ",取款金额:" + amount);
}
balance -= amount;
System.out.println("取款成功,当前余额:" + balance);
}
public String getAccount() { return account; }
public double getBalance() { return balance; }
}
public class BankTest {
public static void main(String[] args) {
BankAccount account = new BankAccount("1001", 1000.0);
account.deposit(500.0);
try {
account.withdraw(2000.0);
} catch (InsufficientFundsException e) {
System.out.println("异常:" + e.getMessage());
}
try {
account.withdraw(300.0);
} catch (InsufficientFundsException e) {
System.out.println("异常:" + e.getMessage());
}
}
}5. 学生成绩处理(Stream 流练习)
import java.util.*;
class Student {
private int id;
private String name;
private double score;
public Student(int id, String name, double score) {
this.id = id;
this.name = name;
this.score = score;
}
public String getName() { return name; }
public double getScore() { return score; }
}
public class StudentScoreTest {
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student(1, "张三", 85.5), new Student(2, "李四", 92.0),
new Student(3, "王五", 78.5), new Student(4, "赵六", 88.0),
new Student(5, "钱七", 65.0), new Student(6, "孙八", 95.5),
new Student(7, "周九", 72.0), new Student(8, "吴十", 81.0),
new Student(9, "郑一", 90.0), new Student(10, "冯二", 55.5)
);
students.stream()
.filter(s -> s.getScore() > 80)
.sorted(Comparator.comparingDouble(Student::getScore).reversed())
.forEach(s -> System.out.println(s.getName() + ":" + s.getScore()));
}
}laoren QQ: 1756866565