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

44 阅读9分钟

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

严肃的面试官坐在桌前,面前放着王铁牛的简历,王铁牛则略显紧张地坐在对面。面试开始了。

第一轮面试 面试官:首先问几个 Java 核心知识的问题。Java 中基本数据类型有哪些? 王铁牛:有 byte、short、int、long、float、double、char、boolean。 面试官:回答得不错。那 Java 里的自动装箱和拆箱是什么意思? 王铁牛:自动装箱就是把基本数据类型自动转换为对应的包装类,拆箱就是把包装类自动转换为基本数据类型,比如 Integer i = 10 就是自动装箱,int j = i 就是拆箱。 面试官:很好。那说说 Java 中继承和接口实现的区别。 王铁牛:继承是一个类继承另一个类的属性和方法,只能单继承;接口实现是一个类实现接口里的抽象方法,可以实现多个接口。

第二轮面试 面试官:接下来聊聊 JUC 和多线程。什么是线程安全? 王铁牛:就是在多线程环境下,对共享资源的操作不会出现数据不一致等问题。 面试官:那 JUC 里的 CountDownLatch 是怎么用的? 王铁牛:呃……它好像是用来做线程同步的,具体咋用我有点记不清了。 面试官:那讲讲线程池的核心参数有哪些。 王铁牛:有核心线程数、最大线程数,还有……还有其他的,我一下子想不全了。 面试官:那再问一个,如何避免死锁? 王铁牛:这个嘛,好像要注意加锁顺序啥的,具体我也不太确定。

第三轮面试 面试官:现在问一些框架和中间件的问题。Spring 中的 IOC 和 AOP 是什么? 王铁牛:IOC 是控制反转,把对象的创建和管理交给 Spring 容器;AOP 是面向切面编程,能在不修改原有代码的情况下增加额外功能。 面试官:那 Spring Boot 的自动配置原理是什么? 王铁牛:就是它能自动根据依赖和配置来配置 Spring 应用,具体咋实现的我不太清楚。 面试官:MyBatis 中 #{} 和 {} 的区别是什么? **王铁牛**:好像 #{} 是预编译的,能防止 SQL 注入,{} 就是直接替换,有 SQL 注入风险,不过具体细节我也没太搞明白。 面试官:Dubbo 是做什么的,它的负载均衡策略有哪些? 王铁牛:Dubbo 是分布式服务框架,负载均衡策略嘛……我只知道有几种,具体名字说不上来。

面试官:今天的面试就到这里,你先回家等通知吧。我们后续会综合评估你的表现,再给你答复。

答案详解

第一轮

  1. Java 中基本数据类型有哪些: Java 中有 8 种基本数据类型,可分为 4 类。
    • 整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节)。
    • 浮点类型:float(4 字节)、double(8 字节)。
    • 字符类型:char(2 字节)。
    • 布尔类型:boolean(理论上 1 位,但通常 JVM 实现按 1 字节处理)。
  2. Java 里的自动装箱和拆箱是什么意思: 自动装箱是 Java 编译器在基本数据类型和对应的包装类之间做的一个自动转换。例如,把 int 类型自动转换为 Integer 类型。代码示例:
int num = 10;
Integer integerNum = num; // 自动装箱
拆箱则相反,是把包装类自动转换为基本数据类型。示例:
Integer integerNum = 10;
int num = integerNum; // 自动拆箱
  1. Java 中继承和接口实现的区别
    • 继承是指一个类可以继承另一个类的属性和方法,被继承的类称为父类,继承的类称为子类。Java 中类只能单继承,即一个子类只能有一个父类。继承主要用于代码复用和建立类之间的层次关系。示例:
class Animal {
    void eat() {
        System.out.println("Animal is eating");
    }
}
class Dog extends Animal {
    void bark() {
        System.out.println("Dog is barking");
    }
}
- 接口实现是一个类实现接口中定义的抽象方法。一个类可以实现多个接口,接口主要用于定义一组规范,让不同的类按照这个规范去实现功能。示例:
interface Flyable {
    void fly();
}
class Bird implements Flyable {
    @Override
    public void fly() {
        System.out.println("Bird is flying");
    }
}

第二轮

  1. 什么是线程安全: 线程安全是指在多线程环境下,对共享资源的访问和操作不会出现数据不一致、数据损坏等问题。当多个线程同时访问一个共享资源时,如果没有合适的同步机制,可能会导致数据错误。例如,多个线程同时对一个计数器进行自增操作,如果没有同步,就可能出现计数器值不准确的情况。
  2. JUC 里的 CountDownLatch 是怎么用的: CountDownLatch 是一个同步工具类,它允许一个或多个线程等待其他线程完成操作。它通过一个计数器来实现,初始化时设置计数器的值,每当一个线程完成任务后,计数器减 1,当计数器的值为 0 时,等待的线程就会被唤醒继续执行。示例代码:
import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        int threadCount = 3;
        CountDownLatch latch = new CountDownLatch(threadCount);

        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " is working");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown();
                }
            }).start();
        }

        latch.await();
        System.out.println("All threads have finished their work");
    }
}
  1. 线程池的核心参数有哪些: 线程池的核心参数有 7 个,在创建 ThreadPoolExecutor 时需要指定。
    • corePoolSize(核心线程数):线程池中的核心线程数量,当提交的任务数小于核心线程数时,线程池会创建新的线程来执行任务。
    • maximumPoolSize(最大线程数):线程池允许创建的最大线程数量。
    • keepAliveTime(线程存活时间):当线程池中的线程数量超过核心线程数时,多余的空闲线程在等待新任务的最长时间,超过这个时间就会被销毁。
    • unit(时间单位):keepAliveTime 的时间单位,如 TimeUnit.SECONDS 等。
    • workQueue(工作队列):用于存储等待执行的任务的队列,常见的有 ArrayBlockingQueueLinkedBlockingQueue 等。
    • threadFactory(线程工厂):用于创建线程的工厂,可以自定义线程的名称、优先级等。
    • handler(拒绝策略):当工作队列已满且线程池中的线程数量达到最大线程数时,新提交的任务的处理策略,常见的有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(让调用者线程执行任务)等。
  2. 如何避免死锁: 死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。可以通过以下方法避免死锁:
    • 破坏互斥条件:但有些资源本身就是互斥的,所以这种方法不太实际。
    • 破坏占有并等待条件:可以要求线程一次性获取所有需要的资源,而不是逐步获取。
    • 破坏不可抢占条件:允许线程在必要时抢占其他线程占有的资源。
    • 破坏循环等待条件:可以对资源进行排序,线程按照一定的顺序获取资源,避免形成循环等待。

第三轮

  1. Spring 中的 IOC 和 AOP 是什么
    • IOC(控制反转):是一种设计原则,在 Spring 中,对象的创建、管理和依赖关系的注入都由 Spring 容器负责,而不是由对象本身来控制。这样可以降低代码的耦合度,提高代码的可维护性和可测试性。例如,一个类依赖另一个类的对象,以前是在类内部创建这个对象,现在由 Spring 容器创建并注入。示例:
public class UserService {
    private UserDao userDao;

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }
    // 其他方法
}
- AOP(面向切面编程):是一种编程范式,它允许在不修改原有代码的情况下,对程序的功能进行增强。AOP 通过切面(Aspect)来定义一些通用的功能,如日志记录、事务管理等,然后在特定的切入点(Pointcut)插入这些功能。示例:
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
public class LoggingAspect {
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}

    @Before("serviceMethods()")
    public void beforeServiceMethod() {
        System.out.println("Before service method execution");
    }
}
  1. Spring Boot 的自动配置原理是什么: Spring Boot 的自动配置是基于条件注解和类路径扫描实现的。当 Spring Boot 应用启动时,会加载 spring.factories 文件,该文件中定义了一系列的自动配置类。这些自动配置类会根据类路径中是否存在特定的类、配置文件中是否有相关配置等条件来决定是否生效。例如,如果类路径中存在 DataSource 类,并且没有手动配置数据源,Spring Boot 会自动配置一个数据源。具体步骤如下:
    • Spring Boot 启动时,会通过 SpringApplication 类加载 META - INF/spring.factories 文件中的自动配置类。
    • 自动配置类使用 @Conditional 系列注解来判断是否满足条件,如 @ConditionalOnClass 表示只有当类路径中存在指定的类时才生效。
    • 如果条件满足,自动配置类会创建相应的 Bean 并注入到 Spring 容器中。
  2. MyBatis 中 #{} 和 ${} 的区别是什么
    • #{} :是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为占位符 ?,然后使用 PreparedStatement 来执行 SQL 语句,这样可以防止 SQL 注入。例如:
<select id="getUserById" parameterType="int" resultType="User">
    SELECT * FROM users WHERE id = #{id}
</select>
- `${} `:是字符串替换,MyBatis 在处理 `${} `时,会直接将 `${} `中的内容替换到 SQL 语句中。因此,使用 `${} `可能会导致 SQL 注入问题。例如:
<select id="getUserByTableName" parameterType="String" resultType="User">
    SELECT * FROM ${tableName}
</select>
  1. Dubbo 是做什么的,它的负载均衡策略有哪些
    • Dubbo 是阿里巴巴开源的一个高性能、轻量级的分布式服务框架,用于实现服务的远程调用、服务注册与发现、负载均衡等功能。它可以帮助开发者构建分布式系统,将不同的服务进行拆分和部署,提高系统的可扩展性和可维护性。
    • Dubbo 的负载均衡策略有以下几种:
      • RandomLoadBalance(随机负载均衡):随机选择一个服务提供者。
      • RoundRobinLoadBalance(轮询负载均衡):按照顺序依次选择服务提供者。
      • LeastActiveLoadBalance(最少活跃调用数负载均衡):选择当前活跃调用数最少的服务提供者。
      • ConsistentHashLoadBalance(一致性哈希负载均衡):根据请求的某些特征,将请求映射到一个哈希环上,然后选择距离请求最近的服务提供者。