《互联网大厂面试:全方位考察 Java 核心、框架与中间件知识》
在互联网大厂的一间安静的面试室内,严肃的面试官正对面坐着略显紧张的王铁牛,一场激烈的技术面试即将拉开帷幕。
第一轮面试开始 面试官:我们先从 Java 核心知识问起。Java 中基本数据类型有哪些? 王铁牛:Java 基本数据类型有 byte、short、int、long、float、double、char、boolean。 面试官:回答得不错。那你说说 String 类为什么是不可变的? 王铁牛:因为 String 类是用 final 修饰的,它的底层是用字符数组存储的,一旦创建就不能改变。 面试官:很好,基础很扎实。那在 Java 里,== 和 equals 方法有什么区别? 王铁牛:== 比较的是两个对象的引用是否相等,也就是是否指向同一个内存地址。而 equals 方法在 Object 类里和 == 一样,但很多类重写了 equals 方法,比如 String 类,重写后比较的是内容是否相等。 面试官:非常棒,看来你对 Java 核心基础掌握得很牢固。
第二轮面试开始 面试官:接下来聊聊 JUC 和多线程。线程有哪些状态? 王铁牛:线程有新建、就绪、运行、阻塞、死亡这几种状态。 面试官:那如何创建一个线程池? 王铁牛:可以通过 Executors 工具类创建,比如 newFixedThreadPool、newCachedThreadPool 等,也可以通过 ThreadPoolExecutor 类手动创建。 面试官:说说 ThreadLocal 的作用。 王铁牛:ThreadLocal 可以为每个使用它的线程都创建一个独立的变量副本,每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。 面试官:很不错,对多线程和 JUC 有一定的了解。
第三轮面试开始 面试官:现在谈谈框架相关的。Spring 中的 IOC 和 AOP 是什么? 王铁牛:IOC 是控制反转,把对象的创建和依赖关系的管理交给 Spring 容器,而不是由对象本身来控制。AOP 是面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护。 面试官:Spring Boot 有什么优点? 王铁牛:Spring Boot 可以快速搭建项目,它有自动配置,能简化开发过程,还能减少配置文件。 面试官:MyBatis 中 #{} 和 {} 的区别是什么? **王铁牛**:#{} 是预编译处理,能防止 SQL 注入,它会把参数部分用占位符 ? 代替。{} 是直接替换,会有 SQL 注入风险。 面试官:那 Dubbo 的服务注册与发现机制是怎样的? 王铁牛:嗯……这个……好像是通过 Zookeeper 之类的,具体我有点不太清楚了。 面试官:看来你对前面的框架知识掌握得还行,但对于 Dubbo 的细节了解不够。
面试接近尾声,面试官整理了下手中的资料,说道:“王铁牛,今天的面试就到这里。你的基础知识掌握得还不错,对于一些常见的问题回答得比较准确,但在某些框架和中间件的深入理解上还有所欠缺。我们后续会综合评估所有面试者的情况,你先回家等通知吧。”
问题答案详解
- Java 基本数据类型:Java 中有 8 种基本数据类型,分为 4 类。整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节);浮点类型:float(4 字节)、double(8 字节);字符类型:char(2 字节);布尔类型:boolean(只有 true 和 false 两个值)。基本数据类型存储的是具体的值,而不是引用。
- String 类为什么是不可变的:String 类被 final 修饰,意味着它不能被继承。其底层是一个用 final 修饰的字符数组 private final char value[],这就使得数组一旦被初始化,其引用就不能再改变。而且 String 类没有提供修改字符数组内容的公共方法,所以 String 对象一旦创建,其内容就不能被改变。不可变的好处包括提高性能(可以缓存哈希码)、线程安全等。
- == 和 equals 方法的区别:== 是一个比较运算符,对于基本数据类型,比较的是它们的值是否相等;对于引用数据类型,比较的是它们的引用是否指向同一个对象,也就是是否指向同一个内存地址。equals 方法是 Object 类的一个方法,在 Object 类中,equals 方法的实现就是使用 == 来比较引用,所以和 == 效果一样。但很多类重写了 equals 方法,比如 String 类,它重写后的 equals 方法会比较字符串的内容是否相等。
- 线程的状态:
- 新建(New):线程对象被创建后,就处于新建状态,此时线程还没有开始执行。
- 就绪(Runnable):当调用线程的 start() 方法后,线程进入就绪状态,此时线程已经做好了准备,等待获取 CPU 时间片。
- 运行(Running):当线程获得 CPU 时间片后,就进入运行状态,开始执行线程的代码。
- 阻塞(Blocked):线程在某些情况下会进入阻塞状态,比如等待 I/O 操作、等待获取锁等。阻塞状态的线程暂时不参与 CPU 调度,直到满足某些条件后才会重新进入就绪状态。
- 死亡(Terminated):线程执行完所有代码或者因为异常终止后,就进入死亡状态,此时线程的生命周期结束。
- 创建线程池的方式:
- 使用 Executors 工具类:这是一种简单的创建线程池的方式,它提供了一些静态方法来创建不同类型的线程池,例如:
- newFixedThreadPool(int nThreads):创建一个固定大小的线程池,线程池中的线程数量是固定的。
- newCachedThreadPool():创建一个可缓存的线程池,如果线程池中的线程数量超过了处理任务所需的数量,空闲线程会在一段时间后被回收;如果有新的任务提交,线程池会创建新的线程来处理。
- newSingleThreadExecutor():创建一个单线程的线程池,它只会用一个线程来执行任务,保证任务按照提交的顺序依次执行。
- 使用 ThreadPoolExecutor 类手动创建:这种方式更加灵活,可以根据具体需求来配置线程池的参数,例如核心线程数、最大线程数、线程空闲时间、任务队列等。ThreadPoolExecutor 的构造函数如下:
- 使用 Executors 工具类:这是一种简单的创建线程池的方式,它提供了一些静态方法来创建不同类型的线程池,例如:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- ThreadLocal 的作用:ThreadLocal 提供了一种线程局部变量的机制,它为每个使用该变量的线程都创建一个独立的变量副本,每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。ThreadLocal 的实现原理是通过在每个线程内部维护一个 ThreadLocalMap,ThreadLocal 作为键,变量副本作为值存储在这个 Map 中。当线程访问 ThreadLocal 变量时,实际上是从自己的 ThreadLocalMap 中获取对应的副本。ThreadLocal 常用于解决多线程环境下的变量共享问题,例如在 Web 应用中,每个线程处理一个请求,我们可以使用 ThreadLocal 来存储当前请求的用户信息、事务信息等。
- Spring 中的 IOC 和 AOP:
- IOC(Inversion of Control,控制反转):传统的对象创建和依赖关系管理是由对象本身来控制的,而在 Spring 中,IOC 把对象的创建和依赖关系的管理交给了 Spring 容器。容器负责创建对象、管理对象的生命周期以及对象之间的依赖关系。这样可以降低代码的耦合度,提高代码的可维护性和可测试性。IOC 的实现方式主要有依赖注入(Dependency Injection,DI),包括构造函数注入、属性注入和接口注入等。
- AOP(Aspect-Oriented Programming,面向切面编程):AOP 是一种编程范式,它通过预编译方式和运行期动态代理实现程序功能的统一维护。在软件开发中,有些功能(如日志记录、事务管理、权限验证等)会分散在多个模块中,这些功能被称为横切关注点。AOP 可以把这些横切关注点从业务逻辑中分离出来,形成独立的切面,在不修改原有业务逻辑的基础上,对程序进行增强。AOP 的实现主要基于代理模式,Spring 支持两种代理方式:JDK 动态代理和 CGLIB 代理。
- Spring Boot 的优点:
- 快速搭建项目:Spring Boot 提供了大量的 Starter 依赖,通过引入这些 Starter 依赖,可以快速集成各种功能,减少了手动配置的工作量。
- 自动配置:Spring Boot 会根据项目中引入的依赖和配置,自动进行一些默认的配置,例如数据库连接、日志配置等。开发人员可以通过简单的配置文件(如 application.properties 或 application.yml)来覆盖默认配置。
- 简化开发过程:Spring Boot 内置了嵌入式服务器(如 Tomcat、Jetty 等),可以直接将应用打包成可执行的 JAR 文件,无需部署到外部服务器。同时,Spring Boot 还提供了 Actuator 模块,方便对应用进行监控和管理。
- 减少配置文件:相比传统的 Spring 项目,Spring Boot 减少了大量的 XML 配置文件,使用 Java 注解和简单的配置文件来完成配置,提高了开发效率。
- MyBatis 中 #{} 和 ${} 的区别:
- #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为占位符 ?,然后使用 PreparedStatement 来执行 SQL 语句。这样可以防止 SQL 注入攻击,因为参数会经过预编译处理,不会直接拼接到 SQL 语句中。
- {} 时,会将 {} 用于动态传入表名、列名等,在使用时需要确保参数的安全性。
- Dubbo 的服务注册与发现机制:Dubbo 是一个高性能的分布式服务框架,它的服务注册与发现机制主要依赖于注册中心。常见的注册中心有 Zookeeper、Redis 等,以 Zookeeper 为例,其服务注册与发现的过程如下:
- 服务提供者(Provider):在启动时,会将自己提供的服务信息(如服务接口、服务地址等)注册到 Zookeeper 上,在 Zookeeper 中创建对应的临时节点。
- 服务消费者(Consumer):在启动时,会从 Zookeeper 上获取服务提供者的信息,并订阅服务提供者的变更。当服务提供者的信息发生变化时,Zookeeper 会通知服务消费者。
- 注册中心(Zookeeper):负责存储服务提供者的信息,并提供服务的发现和订阅功能。它可以保证服务信息的一致性和高可用性。
通过服务注册与发现机制,Dubbo 实现了服务的动态发现和负载均衡,提高了分布式系统的灵活性和可扩展性。