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

75 阅读12分钟

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

在互联网大厂宽敞明亮的面试室内,严肃的面试官坐在桌前,对面坐着略显紧张的求职者王铁牛。一场对 Java 核心知识、JUC、JVM 等多方面技术的面试即将展开。

第一轮面试 面试官:首先问你几个基础的 Java 核心知识问题。Java 中的基本数据类型有哪些? 王铁牛:Java 的基本数据类型有 byte、short、int、long、float、double、char、boolean。 面试官:回答得不错。那你说说 String 类为什么是不可变的? 王铁牛:因为 String 类被 final 修饰,它的成员变量也是 final 的,所以它是不可变的。 面试官:很好。那在 Java 中,继承和组合有什么区别呢? 王铁牛:继承是子类继承父类的属性和方法,是一种“is - a”的关系;组合是一个类包含另一个类的对象,是“has - a”的关系。 面试官:非常棒,基础很扎实。下面我们进入第二轮。

第二轮面试 面试官:现在我们聊聊 JUC 和多线程相关的。什么是线程安全? 王铁牛:线程安全就是在多线程环境下,对共享资源的访问不会出现数据不一致等问题。 面试官:那你说说 CountDownLatch 的作用是什么? 王铁牛:CountDownLatch 可以让一个或多个线程等待其他线程完成操作后再继续执行。 面试官:那线程池的核心参数有哪些呢? 王铁牛:线程池的核心参数有 corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(线程存活时间)、TimeUnit(时间单位)、workQueue(工作队列)、threadFactory(线程工厂)、handler(拒绝策略)。 面试官:回答得很全面。不过接下来的问题会有点难度,准备好进入第三轮。

第三轮面试 面试官:我们来谈谈一些框架和中间件。Spring 中的 AOP 是如何实现的? 王铁牛:这个……好像是用代理模式,具体我有点说不清楚。 面试官:那 Spring Boot 自动配置的原理是什么呢? 王铁牛:就是自动配置一些东西吧,具体的细节我不太记得了。 面试官:MyBatis 是如何实现 SQL 语句的映射的? 王铁牛:好像是用 XML 文件或者注解,但是具体怎么弄我不太明白。 面试官:看来你对这些框架的理解还不够深入。Dubbo 是做什么的,它的工作原理是什么? 王铁牛:Dubbo 好像是个分布式服务框架,原理我不太清楚。

面试结束后,面试官表情严肃地说:“王铁牛,从面试情况来看,你对 Java 基础核心知识掌握得还不错,在多线程和线程池方面也有一定的了解。但是对于 Spring、Spring Boot、MyBatis 以及 Dubbo 这些框架和中间件的理解不够深入,回答不够清晰准确。我们会综合考虑这次面试的情况,你先回家等通知吧。”

问题答案

  1. Java 中的基本数据类型有哪些? Java 中有 8 种基本数据类型,分为 4 类:

    • 整数类型
      • byte:8 位,有符号,取值范围是 -128 到 127。
      • short:16 位,有符号,取值范围是 -32768 到 32767。
      • int:32 位,有符号,取值范围是 -2147483648 到 2147483647。
      • long:64 位,有符号,取值范围是 -9223372036854775808 到 9223372036854775807,使用时需要在数字后面加 L
    • 浮点类型
      • float:32 位,单精度浮点数,使用时需要在数字后面加 F
      • double:64 位,双精度浮点数,是 Java 中默认的浮点类型。
    • 字符类型
      • char:16 位,用于表示单个字符,使用单引号括起来,例如 'A'
    • 布尔类型
      • boolean:只有两个值,truefalse,用于逻辑判断。
  2. String 类为什么是不可变的? String 类被设计为不可变的,主要有以下几个原因:

    • 安全性:在多线程环境下,如果 String 是可变的,一个线程修改了 String 的值,可能会影响其他线程的使用,导致数据不一致。不可变的 String 可以保证线程安全。
    • 缓存:String 类内部有一个字符串常量池,当创建一个 String 对象时,如果常量池中已经存在相同的字符串,就会直接返回常量池中的对象,避免重复创建。如果 String 是可变的,这种缓存机制就无法实现。
    • 作为哈希键:在使用 HashMap、HashSet 等集合时,通常会使用 String 作为键。不可变的 String 可以保证其哈希码在对象创建后不会改变,从而保证哈希表的正确性。
    • 类的设计:String 类被 final 修饰,这意味着它不能被继承。同时,它的成员变量 value 也是 final 的,value 是一个 char 数组,用于存储字符串的字符序列。虽然 final 修饰的数组引用不能改变,但是数组的内容可以改变,不过 String 类没有提供修改数组内容的方法,所以字符串是不可变的。
  3. 在 Java 中,继承和组合有什么区别呢?

    • 继承
      • 概念:继承是指一个子类继承父类的属性和方法,子类可以使用父类的非私有成员,并且可以重写父类的方法。继承体现了“is - a”的关系,例如“猫是一种动物”,可以设计一个 Cat 类继承自 Animal 类。
      • 优点:代码复用性高,子类可以直接使用父类的方法和属性,减少了代码的重复编写。
      • 缺点:子类和父类的耦合度高,如果父类的代码发生变化,可能会影响到子类。而且 Java 只支持单继承,一个子类只能有一个父类。
    • 组合
      • 概念:组合是指一个类包含另一个类的对象,通过这个对象来使用另一个类的功能。组合体现了“has - a”的关系,例如“汽车有一个发动机”,可以在 Car 类中包含一个 Engine 类的对象。
      • 优点:耦合度低,一个类的变化不会影响到另一个类。而且可以灵活地组合不同的类,实现更复杂的功能。
      • 缺点:代码的复杂度可能会增加,需要管理多个对象之间的关系。
  4. 什么是线程安全? 线程安全是指在多线程环境下,对共享资源的访问不会出现数据不一致、脏读、幻读等问题。当多个线程同时访问一个共享资源时,如果没有进行适当的同步控制,可能会导致数据的错误。例如,多个线程同时对一个计数器进行自增操作,如果没有进行同步,可能会出现计数器的值不准确的情况。为了保证线程安全,可以使用同步机制,如 synchronized 关键字、Lock 接口等。

  5. CountDownLatch 的作用是什么? CountDownLatch 是 Java 并发包(JUC)中的一个同步工具类,它可以让一个或多个线程等待其他线程完成操作后再继续执行。CountDownLatch 内部维护了一个计数器,在创建 CountDownLatch 对象时需要指定计数器的初始值。当一个线程完成任务后,可以调用 countDown() 方法将计数器减 1。其他线程可以调用 await() 方法等待计数器的值变为 0。一旦计数器的值变为 0,所有等待的线程都会被唤醒继续执行。例如,在一个多线程的任务中,主线程需要等待所有子线程完成任务后再继续执行,就可以使用 CountDownLatch 来实现。

  6. 线程池的核心参数有哪些? 线程池的核心参数有以下 7 个:

    • corePoolSize(核心线程数):线程池中的核心线程数量,当提交的任务数小于核心线程数时,线程池会创建新的线程来执行任务。
    • maximumPoolSize(最大线程数):线程池允许创建的最大线程数量。当任务数超过核心线程数且工作队列已满时,线程池会创建新的线程,直到达到最大线程数。
    • keepAliveTime(线程存活时间):当线程池中的线程数量超过核心线程数时,多余的空闲线程在等待新任务的最长时间。如果在这个时间内没有新任务,线程就会被销毁。
    • TimeUnit(时间单位)keepAliveTime 的时间单位,例如 TimeUnit.SECONDS 表示秒。
    • workQueue(工作队列):用于存储等待执行的任务的队列。常见的工作队列有 ArrayBlockingQueue(有界队列)、LinkedBlockingQueue(无界队列)、SynchronousQueue(同步队列)等。
    • threadFactory(线程工厂):用于创建线程的工厂,通过线程工厂可以自定义线程的名称、优先级等属性。
    • handler(拒绝策略):当线程池的工作队列已满且线程数量达到最大线程数时,新提交的任务会触发拒绝策略。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(由调用线程执行任务)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务)。
  7. Spring 中的 AOP 是如何实现的? Spring 中的 AOP(面向切面编程)主要通过代理模式来实现,有两种代理方式:

    • JDK 动态代理:JDK 动态代理是基于接口的代理,当目标对象实现了接口时,Spring 会使用 JDK 动态代理。JDK 动态代理通过 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口来实现。Proxy 类可以创建代理对象,InvocationHandler 接口用于定义代理对象的方法调用逻辑。当调用代理对象的方法时,会调用 InvocationHandlerinvoke() 方法,在 invoke() 方法中可以添加额外的逻辑,如日志记录、事务管理等。
    • CGLIB 代理:当目标对象没有实现接口时,Spring 会使用 CGLIB 代理。CGLIB 是一个强大的、高性能的代码生成库,它通过继承目标对象来创建代理对象。CGLIB 会生成一个子类,并重写目标对象的方法,在重写的方法中添加额外的逻辑。
  8. Spring Boot 自动配置的原理是什么? Spring Boot 自动配置的核心原理是基于条件注解和类路径扫描。具体步骤如下:

    • 启动类注解:Spring Boot 应用的启动类上通常会有 @SpringBootApplication 注解,它是一个组合注解,包含了 @SpringBootConfiguration@EnableAutoConfiguration@ComponentScan 注解。
    • 自动配置类加载@EnableAutoConfiguration 注解会触发 Spring Boot 自动配置机制,它会通过 META - INF/spring.factories 文件加载所有的自动配置类。这个文件中定义了一系列的自动配置类,每个自动配置类都是一个 Java 类,用于配置特定的功能。
    • 条件注解筛选:自动配置类中会使用大量的条件注解,如 @ConditionalOnClass@ConditionalOnMissingBean 等。这些条件注解会根据类路径中是否存在某个类、容器中是否存在某个 Bean 等条件来决定是否启用该自动配置类。例如,@ConditionalOnClass 表示只有当类路径中存在指定的类时,该自动配置类才会生效。
    • 配置属性绑定:自动配置类会读取配置文件(如 application.propertiesapplication.yml)中的配置属性,并将其绑定到相应的 Java 对象上。通过这种方式,用户可以自定义自动配置的行为。
  9. MyBatis 是如何实现 SQL 语句的映射的? MyBatis 实现 SQL 语句的映射有两种方式:

    • XML 映射文件
      • 定义映射文件:创建一个 XML 文件,通常以 .xml 结尾,在文件中使用 <mapper> 标签作为根标签,通过 <select><insert><update><delete> 等标签来定义 SQL 语句。例如:
<mapper namespace="com.example.dao.UserDao">
    <select id="getUserById" parameterType="int" resultType="com.example.entity.User">
        SELECT * FROM users WHERE id = #{id}
    </select>
</mapper>
    - **绑定接口方法**:在 Java 接口中定义与 XML 映射文件中 SQL 语句对应的方法,方法名和参数要与 XML 中的 `id``parameterType` 对应。例如:
public interface UserDao {
    User getUserById(int id);
}
- **注解方式**    - **在接口方法上添加注解**:直接在 Java 接口的方法上使用 `@Select``@Insert``@Update``@Delete` 等注解来定义 SQL 语句。例如:
public interface UserDao {
    @Select("SELECT * FROM users WHERE id = #{id}")
    User getUserById(int id);
}

MyBatis 在运行时会根据接口方法和映射关系,将方法调用转换为对应的 SQL 语句执行,并将查询结果映射到 Java 对象上。

  1. Dubbo 是做什么的,它的工作原理是什么?
    • Dubbo 的作用:Dubbo 是阿里巴巴开源的一个高性能、轻量级的分布式服务框架,用于解决分布式系统中服务之间的调用问题。它可以实现服务的注册与发现、远程调用、负载均衡、集群容错等功能,帮助开发者构建分布式应用。
    • Dubbo 的工作原理
      • 服务注册:服务提供者在启动时,会将自己提供的服务信息(如服务接口、服务地址等)注册到注册中心(如 Zookeeper、Nacos 等)。
      • 服务发现:服务消费者在启动时,会从注册中心获取服务提供者的地址信息,并缓存到本地。
      • 远程调用:当服务消费者需要调用服务提供者的服务时,会根据本地缓存的服务提供者地址,通过网络协议(如 Dubbo 协议、HTTP 协议等)发起远程调用。
      • 负载均衡:如果有多个服务提供者提供相同的服务,Dubbo 会根据负载均衡策略(如随机、轮询、最少活跃调用数等)选择一个服务提供者进行调用。
      • 集群容错:当服务调用失败时,Dubbo 会根据集群容错策略(如失败重试、快速失败等)进行处理,确保服务调用的可靠性。