《互联网大厂面试:揭秘 Java 核心、框架与中间件考察风暴》
在互联网大厂的一间严肃的面试室内,面试官正襟危坐,对面坐着略显紧张的求职者王铁牛。面试开始了。
第一轮面试
面试官:“我们先从 Java 核心知识开始。你能说一下 Java 中多态的实现方式有哪些吗?”
王铁牛:“多态的实现方式主要有两种,一种是方法重载,在同一个类中,多个方法有相同的名字,但参数列表不同;另一种是方法重写,子类重写父类的方法。”
面试官:“回答得不错。那在 Java 中,final 关键字有什么作用?”
王铁牛:“final 关键字可以修饰类、方法和变量。修饰类时,这个类不能被继承;修饰方法时,这个方法不能被重写;修饰变量时,这个变量就变成了常量,一旦赋值就不能再改变。”
面试官:“很好,看来你对基础掌握得挺扎实。那你说说 String、StringBuilder 和 StringBuffer 的区别。”
王铁牛:“String 是不可变的,每次对 String 的操作都会生成一个新的 String 对象。StringBuilder 和 StringBuffer 是可变的,StringBuilder 是非线程安全的,效率较高;StringBuffer 是线程安全的,效率相对较低。”
面试官:“非常棒,你的回答很准确。接下来进入第二轮。”
第二轮面试
面试官:“我们聊聊 JUC(Java 并发工具包)。CountDownLatch 和 CyclicBarrier 有什么区别?”
王铁牛:“嗯……好像都和线程同步有关吧,具体区别我有点不太确定。”
面试官:“没关系,那再问你,ReentrantLock 和 synchronized 有什么不同?”
王铁牛:“我知道它们都能实现线程同步,ReentrantLock 好像更灵活,但具体怎么个灵活法我也说不太清楚。”
面试官:“看来你对 JUC 的掌握还不够深入。那说说线程池,线程池有哪几种创建方式?”
王铁牛:“我记得有 Executors 工具类可以创建,像 newFixedThreadPool、newCachedThreadPool 这些,但具体它们有啥区别我不太懂。”
面试官:“这一轮你回答得不太理想,希望下一轮能有更好的表现。”
第三轮面试 面试官:“现在我们来谈谈框架。Spring 框架中 IOC(控制反转)和 AOP(面向切面编程)的原理是什么?” 王铁牛:“IOC 好像就是把对象的创建和管理交给 Spring 容器,AOP 是可以在不修改原有代码的情况下增加一些功能,但具体原理我讲不明白。” 面试官:“那 Spring Boot 有什么优点,它是如何实现自动配置的?” 王铁牛:“Spring Boot 能简化开发,好像是通过一些注解实现自动配置,但具体细节我不太清楚。” 面试官:“再问你,MyBatis 是如何实现数据库操作的?” 王铁牛:“MyBatis 可以通过 XML 文件或者注解来映射 SQL 语句,然后执行数据库操作,但具体过程我不太懂。” 面试官:“综合这三轮面试来看,你对 Java 核心基础知识掌握得还可以,但在 JUC、线程池以及框架方面的理解还不够深入。我们会综合评估这次面试情况,你先回家等通知吧。”
问题答案
- Java 中多态的实现方式有哪些?
- 方法重载(Overloading):在同一个类中,多个方法可以有相同的名字,但参数列表必须不同(参数的类型、个数或顺序不同)。方法重载是一种编译时多态,编译器根据调用方法时传递的参数类型和数量来决定调用哪个方法。例如:
public class OverloadingExample {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
}
- **方法重写(Overriding)**:子类重写父类的方法,方法名、参数列表和返回类型都要相同。方法重写是一种运行时多态,程序在运行时根据对象的实际类型来决定调用哪个方法。例如:
class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}
- 在 Java 中,
final关键字有什么作用?- 修饰类:当一个类被
final修饰时,这个类不能被继承。例如:
- 修饰类:当一个类被
final class FinalClass {
// 类的内容
}
// 以下代码会编译错误,因为 FinalClass 不能被继承
// class SubClass extends FinalClass {}
- **修饰方法**:当一个方法被 `final` 修饰时,这个方法不能被重写。例如:
class Parent {
public final void finalMethod() {
System.out.println("This is a final method");
}
}
class Child extends Parent {
// 以下代码会编译错误,因为 finalMethod 不能被重写
// @Override
// public void finalMethod() {}
}
- **修饰变量**:当一个变量被 `final` 修饰时,这个变量就变成了常量,一旦赋值就不能再改变。对于基本数据类型,其值不能改变;对于引用数据类型,其引用不能改变,但对象的内容可以改变。例如:
final int CONSTANT = 10;
// 以下代码会编译错误,因为 CONSTANT 是常量,不能再赋值
// CONSTANT = 20;
final StringBuilder sb = new StringBuilder("Hello");
// 可以修改对象的内容
sb.append(" World");
// 但不能改变引用
// sb = new StringBuilder("New"); // 编译错误
String、StringBuilder和StringBuffer的区别String:String类是不可变的,一旦创建,其内容不能被修改。每次对String进行操作(如拼接、替换等),都会创建一个新的String对象。这会导致大量的内存开销,尤其是在频繁进行字符串操作的场景下。例如:
String str = "Hello";
str = str + " World"; // 这里创建了新的 String 对象
- **`StringBuilder`**:`StringBuilder` 是可变的,它是非线程安全的,适用于单线程环境下的字符串拼接等操作。它的操作效率较高,因为不会频繁创建新的对象。例如:
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 直接在原对象上进行操作
- **`StringBuffer`**:`StringBuffer` 也是可变的,它是线程安全的,适用于多线程环境下的字符串操作。由于要保证线程安全,它的操作效率相对较低。例如:
StringBuffer sb = new StringBuffer("Hello");
sb.append(" World"); // 线程安全的操作
CountDownLatch和CyclicBarrier有什么区别?CountDownLatch:CountDownLatch是一个同步辅助类,它允许一个或多个线程等待其他线程完成操作。它有一个计数器,在创建CountDownLatch对象时需要指定计数器的初始值。线程调用countDown()方法会将计数器减 1,当计数器的值变为 0 时,等待的线程会被唤醒继续执行。CountDownLatch的计数器只能使用一次,不能重置。例如:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println("Thread is working");
latch.countDown();
}).start();
}
latch.await();
System.out.println("All threads have finished working");
}
}
- **`CyclicBarrier`**:`CyclicBarrier` 也是一个同步辅助类,它允许一组线程相互等待,直到所有线程都到达一个屏障点,然后所有线程可以继续执行。`CyclicBarrier` 有一个计数器,在创建 `CyclicBarrier` 对象时需要指定参与的线程数量。线程调用 `await()` 方法会将计数器减 1,当计数器的值变为 0 时,所有等待的线程会被唤醒继续执行,并且计数器可以重置,以便下次使用。例如:
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("All threads have reached the barrier");
});
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
System.out.println("Thread is working");
barrier.await();
System.out.println("Thread continues after the barrier");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
ReentrantLock和synchronized有什么不同?- 语法层面:
synchronized是 Java 的关键字,是基于 JVM 实现的,使用起来比较简单,可以修饰方法或代码块。例如:
- 语法层面:
public class SynchronizedExample {
public synchronized void method() {
// 同步方法
}
public void anotherMethod() {
synchronized (this) {
// 同步代码块
}
}
}
- **`ReentrantLock`**:`ReentrantLock` 是一个类,是基于 Java 代码实现的,使用时需要手动加锁和解锁。例如:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void method() {
lock.lock();
try {
// 执行同步操作
} finally {
lock.unlock();
}
}
}
- **灵活性**:`ReentrantLock` 比 `synchronized` 更灵活,它可以实现公平锁(`ReentrantLock(true)`),即线程按照请求锁的顺序获取锁;还可以实现可中断锁,通过 `lockInterruptibly()` 方法,线程在等待锁的过程中可以被中断;另外,`ReentrantLock` 可以尝试获取锁(`tryLock()`),如果锁不可用,线程可以立即返回,而不会一直等待。
- **性能**:在 JDK 1.6 之前,`synchronized` 的性能较差,因为它是重量级锁。但在 JDK 1.6 及以后,JVM 对 `synchronized` 进行了优化,引入了偏向锁、轻量级锁等机制,性能有了很大提升,与 `ReentrantLock` 相差不大。
6. 线程池有哪几种创建方式?
- 使用 Executors 工具类:Executors 是 Java 提供的一个线程池工具类,它提供了几种静态方法来创建不同类型的线程池。
- newFixedThreadPool:创建一个固定大小的线程池,线程池中的线程数量是固定的。例如:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
executor.submit(() -> {
System.out.println("Task is running");
});
}
executor.shutdown();
}
}
- **`newCachedThreadPool`**:创建一个可缓存的线程池,如果线程池中的线程数量超过了处理任务所需的数量,空闲时间超过 60 秒的线程会被回收;如果有新的任务提交,线程池会创建新的线程来处理。例如:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
executor.submit(() -> {
System.out.println("Task is running");
});
}
executor.shutdown();
}
}
- **`newSingleThreadExecutor`**:创建一个单线程的线程池,线程池只有一个线程,任务会按照提交的顺序依次执行。例如:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutorExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
executor.submit(() -> {
System.out.println("Task is running");
});
}
executor.shutdown();
}
}
- **`newScheduledThreadPool`**:创建一个可以定时执行任务的线程池。例如:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolExample {
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
executor.schedule(() -> {
System.out.println("Task is running after 2 seconds");
}, 2, TimeUnit.SECONDS);
executor.shutdown();
}
}
- **使用 `ThreadPoolExecutor` 构造函数**:可以通过 `ThreadPoolExecutor` 的构造函数来创建自定义的线程池,这种方式可以更灵活地配置线程池的参数。例如:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class CustomThreadPoolExample {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
60, // 空闲线程的存活时间
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10) // 任务队列
);
for (int i = 0; i < 5; i++) {
executor.submit(() -> {
System.out.println("Task is running");
});
}
executor.shutdown();
}
}
- Spring 框架中 IOC(控制反转)和 AOP(面向切面编程)的原理是什么?
- IOC(控制反转):IOC 的核心思想是将对象的创建和管理交给 Spring 容器,而不是由对象本身来创建和管理依赖对象。Spring 容器通过配置文件(如 XML 配置文件)或注解(如
@Component、@Autowired等)来管理对象的创建和依赖注入。当一个对象需要依赖另一个对象时,Spring 容器会自动将依赖对象注入到需要的对象中。例如:
- IOC(控制反转):IOC 的核心思想是将对象的创建和管理交给 Spring 容器,而不是由对象本身来创建和管理依赖对象。Spring 容器通过配置文件(如 XML 配置文件)或注解(如
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
class ServiceA {
private ServiceB serviceB;
@Autowired
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
public void doSomething() {
serviceB.doAnotherThing();
}
}
@Component
class ServiceB {
public void doAnotherThing() {
System.out.println("Doing another thing");
}
}
- **AOP(面向切面编程)**:AOP 是一种编程范式,它允许在不修改原有代码的情况下,对程序的某些部分进行增强。AOP 的核心概念包括切面(Aspect)、连接点(Join Point)、切入点(Pointcut)、通知(Advice)和织入(Weaving)。Spring AOP 是基于动态代理实现的,对于实现了接口的类,使用 JDK 动态代理;对于没有实现接口的类,使用 CGLIB 动态代理。例如:
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
class LoggingAspect {
@Pointcut("execution(* com.example.Service.*(..))")
public void serviceMethods() {}
@Before("serviceMethods()")
public void beforeServiceMethod() {
System.out.println("Before service method");
}
@After("serviceMethods()")
public void afterServiceMethod() {
System.out.println("After service method");
}