互联网大厂面试:Java 核心、框架与中间件知识大考验
在互联网大厂的一间安静的面试室内,严肃的面试官正襟危坐,对面坐着略显紧张的求职者王铁牛。一场关于 Java 核心知识的面试即将拉开帷幕。
第一轮面试开始 面试官:“首先问你几个基础的 Java 核心知识问题。Java 中基本数据类型有哪些?” 王铁牛:“有 byte、short、int、long、float、double、char、boolean 这八种。” 面试官:“回答得不错。那 String 类为什么是不可变的呢?” 王铁牛:“因为 String 类被 final 修饰,它的成员变量也是 final 的,所以一旦创建就不能修改。” 面试官:“很好。那在 Java 里,什么是多态?” 王铁牛:“多态就是同一个行为具有多个不同表现形式或形态的能力,比如父类引用指向子类对象。” 面试官:“非常棒,基础很扎实。”
第二轮面试开始 面试官:“接下来我问你一些关于 JUC 和多线程的问题。什么是线程安全?” 王铁牛:“线程安全就是在多线程环境下,程序的执行结果和单线程环境下是一样的,不会出现数据不一致的情况。” 面试官:“不错。那说说 CountDownLatch 和 CyclicBarrier 的区别。” 王铁牛:“呃……好像一个可以重用,一个不能重用吧。”(回答得不够清晰) 面试官:“再详细说说。那线程池有哪些创建方式?” 王铁牛:“可以通过 Executors 工具类创建,还有自定义创建。” 面试官:“回答得还行,但第二个问题还需要加强理解。”
第三轮面试开始 面试官:“现在问你关于框架和中间件的问题。Spring 的核心特性有哪些?” 王铁牛:“有依赖注入和面向切面编程。” 面试官:“很好。那 Spring Boot 的自动配置原理是什么?” 王铁牛:“就是根据类路径下的依赖自动配置一些 Bean。”(回答不完整) 面试官:“再深入说说。那 MyBatis 中 #{} 和 ${} 的区别是什么?” 王铁牛:“呃……一个安全,一个不安全?”(回答不清晰) 面试官:“你对一些概念有基本的了解,但在深入理解上还有所欠缺。”
面试结束,面试官整理了下手中的资料,说道:“今天的面试就到这里,你回去等通知吧。后续如果有进一步的消息,我们会及时联系你。”
答案详解
- Java 基本数据类型:
- Java 中有 8 种基本数据类型,可分为 4 类。整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节);浮点类型:float(4 字节)、double(8 字节);字符类型:char(2 字节);布尔类型:boolean(1 位)。基本数据类型存储的是具体的值,而不是引用。
- String 类不可变的原因:
- String 类被 final 修饰,这意味着它不能被继承。其内部的字符数组 value 也是 final 的,这使得一旦 String 对象被创建,它所包含的字符序列就不能被改变。如果需要修改字符串内容,实际上是创建了一个新的 String 对象。不可变的好处包括线程安全、可以作为 HashMap 的键、提高缓存效率等。
- 多态:
- 多态是面向对象编程的重要特性之一,它允许不同的对象对同一消息做出不同的响应。实现多态的方式主要有两种:方法重载和方法重写。方法重载是指在同一个类中,有多个方法具有相同的名称,但参数列表不同;方法重写是指子类重写父类的方法。另外,父类引用指向子类对象也是多态的一种体现,在运行时会根据实际对象的类型调用相应的方法。
- 线程安全:
- 在多线程环境下,如果多个线程同时访问共享资源,可能会导致数据不一致或其他异常情况。线程安全的代码能够保证在多线程环境下,程序的执行结果和单线程环境下是一样的。实现线程安全的方式有很多,比如使用 synchronized 关键字、Lock 接口、原子类等。
- CountDownLatch 和 CyclicBarrier 的区别:
- CountDownLatch 是一个计数器,它的作用是让一个或多个线程等待其他线程完成操作。计数器的初始值在创建时指定,每个线程完成任务后调用 countDown() 方法将计数器减 1,当计数器的值为 0 时,等待的线程可以继续执行。CountDownLatch 不能重用。
- CyclicBarrier 是一个循环屏障,它允许一组线程相互等待,直到所有线程都到达屏障点,然后所有线程可以继续执行。CyclicBarrier 可以重用,当所有线程都到达屏障点后,它会自动重置,可以再次使用。
- 线程池的创建方式:
- 通过 Executors 工具类创建:
- newFixedThreadPool:创建一个固定大小的线程池,线程池中的线程数量是固定的,当有新任务提交时,如果线程池中有空闲线程,则立即执行;如果没有空闲线程,则任务会被放入任务队列中等待。
- newCachedThreadPool:创建一个可缓存的线程池,线程池中的线程数量是可变的。如果有新任务提交,线程池会优先使用空闲线程;如果没有空闲线程,则会创建新的线程。当线程空闲时间超过 60 秒时,会被回收。
- newSingleThreadExecutor:创建一个单线程的线程池,线程池中只有一个线程,任务会按照提交的顺序依次执行。
- newScheduledThreadPool:创建一个定时任务线程池,可用于执行定时任务或周期性任务。
- 自定义创建:通过 ThreadPoolExecutor 类手动创建线程池,可以更灵活地配置线程池的参数,如核心线程数、最大线程数、任务队列、线程工厂、拒绝策略等。
- 通过 Executors 工具类创建:
- Spring 的核心特性:
- 依赖注入(DI):是指将对象的依赖关系的定义和实现分离,由 Spring 容器负责创建和注入对象的依赖。通过依赖注入,可以降低代码的耦合度,提高代码的可维护性和可测试性。
- 面向切面编程(AOP):是一种编程范式,它允许开发者在不修改原有代码的情况下,对程序的某些功能进行增强。AOP 通过将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,提高了代码的模块化程度。
- Spring Boot 的自动配置原理:
- Spring Boot 的自动配置是基于条件注解实现的。当 Spring Boot 应用启动时,会自动扫描 classpath 下的依赖,根据这些依赖的存在与否,结合条件注解来判断是否需要自动配置某些 Bean。具体来说,Spring Boot 会加载 META - INF/spring.factories 文件,该文件中定义了一系列的自动配置类。这些自动配置类会根据条件注解(如 @ConditionalOnClass、@ConditionalOnMissingBean 等)来决定是否生效。
- MyBatis 中 #{} 和 ${} 的区别:
- #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为 ? 占位符,然后使用 PreparedStatement 进行预编译,这样可以防止 SQL 注入攻击。
- **{} 时,会将 ${} 直接替换为变量的值。由于是直接替换,可能会导致 SQL 注入问题,因此一般用于动态表名、动态列名等情况。