1. 引言
类结构和初始化是每个Java程序员必须掌握的基础知识。本文整理了该主题下常见的面试问题及解答,帮助开发者巩固核心概念。
2. Q1: 描述final关键字应用于类、方法、字段或局部变量时的含义
final
关键字在不同场景下有不同作用:
✅ final类:不可被继承
✅ final方法:不可被子类重写
✅ final字段:必须在构造器或初始化块中赋值,且后续不可修改
✅ final局部变量:只能赋值一次,之后不可修改
⚠️ 简单粗暴记忆:final
就是"不可变"的代名词
3. Q2: 什么是默认方法?
Java 8前接口只能包含抽象方法(无方法体)。从Java 8开始,接口方法可以有默认实现:
public interface Iterable<T> {
Iterator<T> iterator();
default void forEach(Consumer<? super T> action) { /* */ }
default Spliterator<T> spliterator() { /* */ }
}
✅ 核心价值:向现有接口添加新方法时,用default
修饰可保持向后兼容
✅ 典型案例:Iterable
接口在Java 8新增forEach
和spliterator
方法,未破坏已有实现
4. Q3: 什么是静态类成员?
静态字段和方法不绑定到特定实例,而是绑定到类本身:
✅ 编译期解析:调用静态方法/访问静态字段在编译时确定
✅ 无实例依赖:无需创建对象即可使用
⚠️ 踩坑点:静态方法中不能直接访问非静态成员
5. Q4: 没有抽象成员的类能否声明为抽象类?目的是什么?
✅ 可以:即使没有抽象方法,类仍可声明为abstract
✅ 典型用途:
- 作为继承体系的基类
- 提供通用工具方法
- 防止直接实例化(如
java.util.Collections
)
6. Q5: 什么是构造器链?
构造器链通过多个构造器相互调用简化对象初始化:
public class Discount {
private int percent;
private int days;
public Discount() {
this(10); // 调用单参数构造器
}
public Discount(int percent) {
this(percent, 2); // 调用双参数构造器
}
public Discount(int percent, int days) {
this.percent = percent;
this.days = days;
}
}
✅ 优势:避免代码重复,提供灵活的初始化方式
⚠️ 注意:this()
调用必须是构造器的第一条语句
7. Q6: 什么是方法重写和重载?区别是什么?
特性 | 重写 (Overriding) | 重载 (Overloading) |
---|---|---|
发生位置 | 子类与父类之间 | 同一个类中 |
方法签名 | 必须完全相同 | 方法名相同,参数列表不同 |
返回类型 | 必须相同或子类型 | 无限制 |
绑定时机 | 运行时动态绑定 | 编译时静态绑定 |
重载示例(来自java.io.Writer
):
public abstract class Writer {
public void write(int c) throws IOException { /* */ }
public void write(char cbuf[]) throws IOException { /* */ }
}
8. Q7: 能否重写静态方法?
❌ 不能!静态方法属于类级别,编译时绑定,而重写依赖运行时动态绑定
⚠️ 踩坑点:子类可定义同名静态方法,但这属于"隐藏"而非重写
9. Q8: 什么是不可变类?如何创建?
不可变类实例创建后状态不可修改。创建规则:
✅ 所有字段声明为private final
✅ 不提供setter方法
✅ 确保所有字段不可变(或返回防御性拷贝)
✅ 类声明为final
或所有方法为final
经典案例:String
、Integer
等包装类
10. Q9: 如何比较枚举值:equals()
还是==
?
✅ **推荐使用==
**:
- 枚举值本质是单例,
==
更高效 - 避免空指针风险(
equals()
需检查null) - 代码更简洁
// 推荐写法
if (status == Status.ACTIVE) { ... }
// 可行但啰嗦
if (status.equals(Status.ACTIVE)) { ... }
11. Q10: 什么是初始化块?静态初始化块?
初始化块:类中的{}
代码块,在创建实例时执行(编译器会将其复制到每个构造器开头)
静态初始化块:带static
修饰的{}
代码块,类加载时执行一次
public class Example {
// 实例初始化块
{
System.out.println("实例初始化");
}
// 静态初始化块
static {
System.out.println("静态初始化");
}
}
✅ 执行顺序:静态初始化块 → 实例初始化块 → 构造器
12. Q11: 什么是标记接口?Java中有哪些典型例子?
标记接口是无任何方法的接口,用于标识特定属性:
✅ 典型例子:
Serializable
:标识类可序列化Cloneable
:允许对象克隆(否则抛CloneNotSupportedException
)Remote
:RMI中标识可远程调用的接口
13. Q12: 什么是单例?Java中如何实现?
单例模式确保类只有一个全局可访问实例:
饿汉式实现(线程安全):
public class SingletonExample {
private static final SingletonExample INSTANCE = new SingletonExample();
private SingletonExample() {}
public static SingletonExample getInstance() {
return INSTANCE;
}
}
⚠️ 懒加载需求?使用双重检查锁定(Double-Checked Locking):
public class SingletonExample {
private static volatile SingletonExample instance;
private SingletonExample() {}
public static SingletonExample getInstance() {
if (instance == null) {
synchronized (SingletonExample.class) {
if (instance == null) {
instance = new SingletonExample();
}
}
}
return instance;
}
}
14. Q13: 什么是可变参数(Var-Arg)?有哪些限制?方法体内如何使用?
可变参数允许方法接受零到多个同类型参数:
✅ 规则:
- 每个方法只能有一个可变参数
- 必须是参数列表的最后一个
- 语法:
Type... name
✅ 方法体内使用:作为数组处理
示例(来自Collections.addAll
):
public static <T> boolean addAll(
Collection<? super T> c, T... elements) {
boolean result = false;
for (T element : elements) { // 当作数组遍历
result |= c.add(element);
}
return result;
}
15. Q14: 能否访问父类被重写的方法?能否访问祖父类被重写的方法?
✅ 访问父类方法:使用super
关键字
❌ 访问祖父类方法:Java不支持直接访问
示例(来自LinkedHashMap
):
public void clear() {
super.clear(); // 调用HashMap的clear()
head = tail = null; // 清理链表引用
}
⚠️ 踩坑点:多层继承时,无法跳过直接父类访问祖父类方法