《互联网大厂 Java 面试:核心知识、并发、框架与中间件大考验》

26 阅读8分钟

互联网大厂 Java 面试:核心知识、并发、框架与中间件大考验

在互联网大厂的一间安静的面试室内,严肃的面试官正对面坐着紧张的求职者王铁牛,一场激烈的 Java 技术面试即将拉开帷幕。

第一轮面试开始

  • 面试官:首先问几个 Java 核心知识的问题。Java 中基本数据类型有哪些?
  • 王铁牛:Java 的基本数据类型有 byte、short、int、long、float、double、char、boolean。
  • 面试官:回答得不错。那说说面向对象的四大特性是什么?
  • 王铁牛:面向对象的四大特性是封装、继承、多态和抽象。
  • 面试官:很好。在 Java 里,什么是自动装箱和拆箱?
  • 王铁牛:自动装箱是将基本数据类型自动转换为对应的包装类对象,比如把 int 类型转换为 Integer 类型;拆箱则相反,是将包装类对象转换为基本数据类型。

第二轮面试开始

  • 面试官:进入 JUC 和多线程的问题。什么是线程安全?
  • 王铁牛:线程安全就是在多线程环境下,对共享资源的访问不会产生数据不一致或者其他异常问题。
  • 面试官:不错。那说说 synchronized 关键字的使用方式有哪些?
  • 王铁牛:synchronized 可以修饰实例方法,这样对该实例的方法访问会同步;也可以修饰静态方法,对类的所有实例的该静态方法访问会同步;还可以使用同步代码块,指定要同步的对象。
  • 面试官:那线程池有哪些创建方式?
  • 王铁牛:呃……有通过 Executors 工具类创建,像创建固定大小线程池、单线程线程池这些。还有……还有就是自己手动配置参数创建吧。

第三轮面试开始

  • 面试官:现在问一些框架和中间件的问题。Spring 框架的核心特性有哪些?
  • 王铁牛:Spring 的核心特性有 IoC(控制反转)和 AOP(面向切面编程)。
  • 面试官:那 Spring Boot 是如何实现自动配置的?
  • 王铁牛:这个……好像是通过一些配置文件和注解,然后它自己根据类路径下的依赖自动配置。具体的我有点说不太清楚。
  • 面试官:MyBatis 中 #{} 和 ${} 的区别是什么?
  • 王铁牛:呃……#{} 好像是预编译处理,${} 是直接替换,但是具体在业务里怎么用我不太能说清。

面试接近尾声,面试官推了推眼镜,看着王铁牛说:“今天的面试就到这里了,你先回去等通知吧。我们后续会综合评估,有结果会及时通知你。”

问题答案详细解析

  1. Java 基本数据类型
    • Java 有 8 种基本数据类型,分为 4 类。整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节);浮点类型:float(4 字节)、double(8 字节);字符类型:char(2 字节);布尔类型:boolean(理论上 1 位,但在 Java 里通常作为 1 字节处理)。这些基本数据类型是 Java 编程的基础,用于存储不同类型的数据。
  2. 面向对象的四大特性
    • 封装:将数据和操作数据的方法绑定在一起,隐藏对象的内部实现细节,只对外提供必要的接口。例如,一个类的属性可以设置为 private,通过 public 的 getter 和 setter 方法来访问和修改属性,这样可以保证数据的安全性和一致性。
    • 继承:允许一个类继承另一个类的属性和方法,被继承的类称为父类(超类),继承的类称为子类。子类可以重写父类的方法,也可以添加自己的属性和方法,实现代码的复用和扩展。
    • 多态:同一个行为具有多个不同表现形式或形态的能力。多态通过继承和接口实现,主要有方法重载和方法重写两种形式。方法重载是指在一个类中可以有多个方法名相同但参数列表不同的方法;方法重写是指子类重写父类的方法,运行时根据对象的实际类型调用相应的方法。
    • 抽象:抽象是指将一类对象的共同特征总结出来,形成抽象类或接口。抽象类不能实例化,它可以包含抽象方法,抽象方法只有声明没有实现,子类必须实现这些抽象方法。接口是一种特殊的抽象类型,它只包含常量和抽象方法,一个类可以实现多个接口。
  3. 自动装箱和拆箱
    • 自动装箱是 Java 编译器在基本数据类型和对应的包装类之间进行的自动转换。例如,当把一个 int 类型的值赋给一个 Integer 对象时,编译器会自动调用 Integer.valueOf() 方法进行装箱操作。
    • 自动拆箱则相反,当把一个 Integer 对象赋给一个 int 类型的变量时,编译器会自动调用 Integer.intValue() 方法进行拆箱操作。自动装箱和拆箱方便了基本数据类型和包装类之间的使用,但在使用时要注意空指针异常等问题。
  4. 线程安全
    • 在多线程环境下,当多个线程同时访问共享资源时,如果不进行适当的同步控制,可能会导致数据不一致、脏读、幻读等问题。线程安全就是要保证在多线程环境下对共享资源的访问是正确的,不会出现上述问题。实现线程安全的方式有很多,比如使用 synchronized 关键字、Lock 接口、原子类等。
  5. synchronized 关键字的使用方式
    • 修饰实例方法:当一个实例方法被 synchronized 修饰时,该方法在同一时刻只能被一个线程访问。例如:
public class SynchronizedExample {
    public synchronized void method() {
        // 方法体
    }
}
- 修饰静态方法:静态方法属于类,而不是实例。当一个静态方法被 synchronized 修饰时,该类的所有实例的该静态方法在同一时刻只能被一个线程访问。例如:
public class SynchronizedExample {
    public static synchronized void staticMethod() {
        // 方法体
    }
}
- 同步代码块:可以指定要同步的对象,当一个线程进入同步代码块时,会获取该对象的锁,其他线程必须等待该线程释放锁才能进入。例如:
public class SynchronizedExample {
    private Object lock = new Object();
    public void method() {
        synchronized (lock) {
            // 同步代码块
        }
    }
}
  1. 线程池的创建方式
    • 通过 Executors 工具类创建:
      • Executors.newFixedThreadPool(int nThreads):创建一个固定大小的线程池,线程池中的线程数量始终保持为 nThreads。
      • Executors.newSingleThreadExecutor():创建一个单线程的线程池,只有一个线程在工作,保证任务按顺序执行。
      • Executors.newCachedThreadPool():创建一个可缓存的线程池,如果线程池中的线程数量超过了处理任务所需的数量,多余的线程会在一段时间后被回收;如果有新的任务提交,线程池会创建新的线程来处理。
    • 手动配置参数创建:使用 ThreadPoolExecutor 类手动配置线程池的参数,如核心线程数、最大线程数、线程空闲时间、任务队列等。例如:
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolExample {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2, // 核心线程数
                5, // 最大线程数
                60, // 线程空闲时间
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10) // 任务队列
        );
        // 执行任务
        executor.execute(() -> {
            // 任务逻辑
        });
        // 关闭线程池
        executor.shutdown();
    }
}
  1. Spring 框架的核心特性
    • IoC(控制反转):也称为依赖注入(DI),是指将对象的创建和依赖关系的管理从代码中转移到 Spring 容器中。Spring 容器负责创建对象,并将对象之间的依赖关系注入到对象中。这样可以降低代码的耦合度,提高代码的可维护性和可测试性。
    • AOP(面向切面编程):是一种编程范式,它允许开发者在不修改原有代码的基础上,对程序的某些功能进行增强。AOP 通过将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,形成独立的切面,然后在合适的时机将切面织入到目标对象中。
  2. Spring Boot 实现自动配置的原理
    • Spring Boot 基于 Spring 框架,通过自动配置功能简化了 Spring 应用的开发。其实现原理主要基于以下几点:
      • @EnableAutoConfiguration 注解:该注解是 Spring Boot 自动配置的入口,它会触发 Spring Boot 的自动配置机制。
      • SpringFactoriesLoader:Spring Boot 会使用 SpringFactoriesLoader 类从 classpath 下的 META - INF/spring.factories 文件中加载自动配置类。这些自动配置类是一些带有 @Configuration 注解的类,它们会根据类路径下的依赖和配置文件中的属性来自动配置 Spring 应用。
      • 条件注解:Spring Boot 中的自动配置类使用了大量的条件注解,如 @ConditionalOnClass、@ConditionalOnMissingBean 等。这些注解会根据类路径下是否存在某个类、某个 Bean 是否已经存在等条件来决定是否应用该自动配置。
  3. MyBatis 中 #{} 和 ${} 的区别
    • #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为 ? 占位符,然后使用 PreparedStatement 来执行 SQL 语句。这样可以防止 SQL 注入攻击,因为 PreparedStatement 会对输入的参数进行预编译和转义处理。例如:
<select id="getUserById" parameterType="int" resultType="User">
    SELECT * FROM users WHERE id = #{id}
</select>
- **${}**:是直接替换,MyBatis 在处理 ${} 时,会将 ${} 直接替换为传入的参数值。这种方式可能会导致 SQL 注入攻击,因为如果传入的参数包含恶意的 SQL 代码,会直接拼接到 SQL 语句中。例如:
<select id="getUserByTableName" parameterType="String" resultType="User">
    SELECT * FROM ${tableName}
</select>
一般情况下,建议尽量使用 #{},只有在需要动态传入表名、列名等场景下才使用 ${},并且要对传入的参数进行严格的验证和过滤。