奇虎360 Java开发工程师 一面
1. 基本数据类型与包装类在内存分配上有何本质差异?
思路
核心区分“栈/堆分配”“值存储/引用存储”,结合常量池补充包装类的特殊情况。
回答示例
基本数据类型和包装类的内存分配核心差异有两点:
- 存储位置与方式:
- 常量池优化:
包装类存在缓存池(如IntegerCache缓存-128~127),范围内的包装类对象会复用常量池中的实例(堆上),超出范围才新建对象;基本类型无此概念。
示例:
int a = 10; // 栈存数值10 Integer b = 10; // 栈存引用,堆复用常量池对象 Integer c = new Integer(10); // 栈存引用,堆新建对象(不走常量池)
2. 接口中定义的静态方法能否被子接口继承?
思路
明确“不能继承”,结合Java语法规则解释原因,区分静态方法与默认方法的继承特性。
回答示例
接口中的静态方法不能被子接口继承,核心原因:
- Java语法规定:接口的静态方法属于当前接口本身,而非接口的实现/子接口,子接口无法继承父接口的静态方法;
- 调用方式:父接口静态方法必须通过“父接口名.方法名()”调用,子接口无法通过自身名称调用父接口静态方法。
示例:
interface Parent {
static void method() { System.out.println("parent"); }
}
interface Child extends Parent {}
// 正确:Parent.method()
// 错误:Child.method()(编译报错,Child未继承method)
补充:接口的default方法可被子接口继承/重写,静态方法则不行,这是静态成员“属于类/接口本身”的特性决定的。
3. finally块中修改返回值,最终返回结果以谁为准?(附代码场景)
思路
分“基本类型”和“引用类型”两种场景,结合代码示例说明核心规则:finally修改返回值时,以finally执行后的结果为准(基本类型),引用类型则看是否修改对象内部值。
回答示例
核心规则:finally块会在return之前执行,若finally中修改返回值,最终返回以finally的修改结果为准(基本类型);引用类型需区分“修改引用”和“修改对象内容”。
场景1:基本类型(finally修改返回值生效)
public static int test() {
int a = 10;
try {
return a; // 先记录返回值10,待finally执行后返回
} finally {
a = 20; // 修改局部变量a
System.out.println("finally: " + a); // 输出20
}
}
// 调用test()返回:10(关键:try的return已记录a的副本,finally修改a不影响返回值)
场景2:finally中显式return(覆盖try的返回值)
public static int test2() {
try {
return 10;
} finally {
return 20; // finally的return会覆盖try的return
}
}
// 调用test2()返回:20
场景3:引用类型(修改对象内容生效,修改引用不生效)
static class User { int age; }
public static User test3() {
User u = new User();
try {
u.age = 10;
return u; // 记录u的引用,指向堆中对象
} finally {
u.age = 20; // 修改对象内容,返回值的age为20
// u = new User(); // 修改引用,不影响返回值(仍指向原对象)
}
}
// 调用test3().age返回:20
核心总结:
- 基本类型:try的return会拷贝值的副本,finally修改原变量不影响返回值;
- finally显式return:直接覆盖所有返回值;
- 引用类型:修改对象内容生效,修改引用本身不生效。
4. TreeSet添加自定义对象时,compareTo与hashCode需如何配合?
思路
明确TreeSet的底层(TreeMap,依赖Comparable/compareTo),说明hashCode的非必要性,解释两者的配合原则。
回答示例
TreeSet底层基于TreeMap实现,核心依赖Comparable接口的compareTo方法(或Comparator比较器),hashCode方法对TreeSet的元素唯一性无影响,但需遵循“compareTo返回0则equals为true”的约定。
配合规则:
- compareTo是核心:
TreeSet通过compareTo判断元素是否重复:若两个对象compareTo返回0,则视为同一元素,添加失败;
- hashCode的配合要求:
虽TreeSet不依赖hashCode,但为了符合Java对象的通用约定(equals相等则hashCode相等),需保证:
示例:
class User implements Comparable<User> {
private int id;
@Override
public int compareTo(User o) { // 按id比较
return Integer.compare(this.id, o.id);
}
@Override
public boolean equals(Object o) { // 与compareTo一致,按id判断相等
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return id == user.id;
}
@Override
public int hashCode() { // 与equals一致,基于id生成
return Objects.hash(id);
}
}
5. HashMap负载因子0.75是理论最优解还是工程折中?依据是什么?
思路
明确“工程折中”,从“时间/空间平衡”“泊松分布”两个维度解释依据。
回答示例
HashMap的负载因子0.75是工程折中方案,而非纯理论最优解,核心依据:
- 时间与空间的平衡:
- 泊松分布的统计依据:
哈希冲突的链表长度遵循泊松分布,负载因子0.75时,链表长度超过8的概率仅为0.00000006(十亿分之六),冲突概率极低,无需为更低的冲突概率牺牲大量内存。
6. ReentrantLock的公平锁与非公平锁在队列调度上有何区别?
思路
从“抢锁逻辑”“队列优先级”“性能”三个维度对比,突出公平锁的“先到先得”和非公平锁的“插队”特性。
回答示例
ReentrantLock的公平锁与非公平锁核心差异在锁竞争的队列调度逻辑:
维度 |
公平锁 |
非公平锁 |
抢锁逻辑 |
先检查等待队列,队列非空则排队 |
先尝试CAS抢锁,抢不到再排队 |
队列优先级 |
严格FIFO,队列头节点优先获取锁 |
新线程可能“插队”(抢在队列头前) |
性能 |
吞吐量低(频繁上下文切换) |
吞吐量高(减少排队开销) |
适用场景 |
要求严格顺序(如金融交易) |
追求高并发(如普通业务系统) |
核心细节:
- 公平锁:线程获取锁时,必须先判断AQS等待队列是否有前驱节点,有则排队,无则抢锁,保证“先到先得”;
- 非公平锁:线程获取锁时,先CAS尝试修改AQS的state变量(抢锁),成功则直接获取,失败再进入队列排队;可能出现“新线程抢过队列头线程”的情况,但减少了队列唤醒的开销。
7. wait()必须在synchronized块内调用的根本原因?
思路
核心讲“线程状态一致性”“防止虚假唤醒”,解释wait()与锁的绑定关系。
回答示例
wait()必须在synchronized块内调用的根本原因是保证线程状态的一致性,防止虚假唤醒:
- 锁与等待的绑定:
wait()
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏在精不在多,内容分为八股文、大厂真实面经,面试通过后将offer和面试题私发给我,可退还专栏的收益部分费用。欢迎大家共建专栏
查看14道真题和解析