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

36 阅读13分钟

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

王铁牛怀揣着紧张又期待的心情,坐在了互联网大厂的面试会议室里,对面是表情严肃的面试官,一场决定命运的面试即将拉开帷幕。

第一轮面试 面试官:“我们先从 Java 核心知识开始。Java 中基本数据类型有哪些,它们的包装类分别是什么?” 王铁牛:“基本数据类型有 byte、short、int、long、float、double、char、boolean。对应的包装类分别是 Byte、Short、Integer、Long、Float、Double、Character、Boolean。” 面试官:“回答得不错。那在 Java 里,什么是自动装箱和自动拆箱,能举个例子吗?” 王铁牛:“自动装箱就是把基本数据类型自动转换为对应的包装类,自动拆箱则相反。比如 Integer i = 10 这就是自动装箱,int j = i 这就是自动拆箱。” 面试官:“很好,理解得很清晰。那说说 Java 中的多态是如何实现的?” 王铁牛:“多态主要通过继承、接口和方法重写来实现。子类可以重写父类的方法,然后通过父类的引用指向子类的对象,调用重写后的方法就可以体现多态。” 面试官:“非常棒,基础很扎实。那 final 关键字在 Java 中有什么作用?” 王铁牛:“final 可以修饰类、方法和变量。修饰类时,这个类不能被继承;修饰方法时,方法不能被重写;修饰变量时,变量就变成常量,一旦赋值就不能再改变。”

第二轮面试 面试官:“接下来聊聊 JUC 和多线程。什么是线程安全,如何保证线程安全?” 王铁牛:“线程安全就是在多线程环境下,程序的运行结果和单线程环境下是一样的。可以通过加锁,比如使用 synchronized 关键字或者 Lock 接口来保证线程安全。” 面试官:“不错。那说说 synchronized 和 Lock 的区别。” 王铁牛:“synchronized 是 Java 的关键字,是隐式锁,会自动释放锁;Lock 是一个接口,需要手动加锁和释放锁,而且 Lock 有更多的功能,比如可以实现公平锁。” 面试官:“有一定的了解。那线程池的作用是什么,Java 中提供了哪些创建线程池的方法?” 王铁牛:“线程池的作用是减少线程创建和销毁的开销,提高系统的性能。Java 中可以通过 Executors 工具类创建固定大小线程池、单线程线程池、缓存线程池等。” 面试官:“基本答对了。那你知道线程池的核心参数有哪些吗?” 王铁牛:“这个……好像有最大线程数、核心线程数,其他的我有点记不清了。”

第三轮面试 面试官:“现在来谈谈一些框架和中间件。Spring 框架的核心特性有哪些?” 王铁牛:“Spring 的核心特性有 IoC(控制反转)和 AOP(面向切面编程)。IoC 就是把对象的创建和依赖关系的管理交给 Spring 容器,AOP 可以在不修改原有代码的基础上增强功能。” 面试官:“回答得挺好。那 Spring Boot 和 Spring 有什么区别?” 王铁牛:“Spring Boot 是基于 Spring 的,它简化了 Spring 的配置,提供了自动配置的功能,能让开发者更快速地搭建项目。” 面试官:“不错。那 MyBatis 中 #{} 和 {} 的区别是什么?” **王铁牛**:“这个……我记得 #{} 是预编译处理,能防止 SQL 注入,{} 是直接替换,可能存在 SQL 注入风险,但具体的我有点说不清楚了。” 面试官:“Dubbo 是一个高性能的分布式服务框架,说说它的主要功能有哪些?” 王铁牛:“Dubbo 好像可以实现服务的注册和发现,还有负载均衡,其他的我不太确定了。” 面试官:“RabbitMQ 是常用的消息队列,它的工作模式有哪些?” 王铁牛:“我只知道有简单模式,其他的不太清楚。”

面试官:“好了,今天的面试就到这里。你的基础知识掌握得还可以,对于一些简单的问题回答得比较准确,展现出了你对 Java 核心知识有一定的理解。不过在面对一些稍微复杂的问题时,你显得有些力不从心,回答不够完整和深入,特别是对于线程池核心参数、MyBatis 具体细节、Dubbo 功能和 RabbitMQ 工作模式等内容,还需要进一步加强学习。你先回家等通知吧,我们后续会综合评估后给你答复。”

问题答案

第一轮问题答案

  1. Java 中基本数据类型有哪些,它们的包装类分别是什么?
    • Java 有 8 种基本数据类型,分别是:
      • 整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节)。
      • 浮点类型:float(4 字节)、double(8 字节)。
      • 字符类型:char(2 字节)。
      • 布尔类型:boolean。
    • 对应的包装类分别是 Byte、Short、Integer、Long、Float、Double、Character、Boolean。包装类的作用是将基本数据类型转换为对象,方便在一些需要对象的场景中使用,比如集合类只能存储对象。
  2. 在 Java 里,什么是自动装箱和自动拆箱,能举个例子吗?
    • 自动装箱是指 Java 编译器在基本数据类型和对应的包装类之间进行的自动转换。例如,将 int 类型的值赋给 Integer 类型的变量时,编译器会自动将 int 类型的值转换为 Integer 对象,这就是自动装箱。
    • 自动拆箱则相反,是将包装类对象转换为基本数据类型。例如,将 Integer 对象赋给 int 类型的变量时,编译器会自动将 Integer 对象转换为 int 类型的值。
    • 示例代码:
Integer i = 10; // 自动装箱,相当于 Integer i = Integer.valueOf(10);
int j = i; // 自动拆箱,相当于 int j = i.intValue();
  1. 说说 Java 中的多态是如何实现的?
    • 多态是指同一个行为具有多个不同表现形式或形态的能力。在 Java 中,多态主要通过以下几种方式实现:
      • 继承:子类继承父类,并重写父类的方法。
      • 接口:类实现接口,并重写接口中的方法。
      • 方法重写:子类重写父类的方法,在运行时根据实际对象的类型来调用相应的方法。
    • 示例代码:
class Animal {
    public void sound() {
        System.out.println("动物发出声音");
    }
}

class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("汪汪汪");
    }
}

public class PolymorphismExample {
    public static void main(String[] args) {
        Animal animal = new Dog();
        animal.sound(); // 输出:汪汪汪
    }
}
  1. final 关键字在 Java 中有什么作用?
    • 修饰类:当一个类被 final 修饰时,该类不能被继承,即不能有子类。例如,final class FinalClass {},任何尝试继承 FinalClass 的类都会导致编译错误。
    • 修饰方法:当一个方法被 final 修饰时,该方法不能被重写。例如,在父类中定义 final void finalMethod() {},子类不能重写这个方法。
    • 修饰变量:当一个变量被 final 修饰时,该变量就变成了常量,一旦赋值就不能再改变。如果是基本数据类型,其值不能改变;如果是引用类型,其引用不能改变,但对象的内容可以改变。例如,final int NUM = 10;,之后不能再对 NUM 进行赋值。

第二轮问题答案

  1. 什么是线程安全,如何保证线程安全?
    • 线程安全是指在多线程环境下,程序的运行结果和单线程环境下是一样的,不会因为多个线程同时访问共享资源而导致数据不一致或其他错误。
    • 保证线程安全的方法有:
      • 加锁:使用 synchronized 关键字或者 Lock 接口。synchronized 可以修饰方法或代码块,当一个线程进入被 synchronized 修饰的方法或代码块时,会获取锁,其他线程需要等待该线程释放锁才能进入。Lock 接口提供了更灵活的锁机制,需要手动加锁和释放锁。
      • 使用线程安全的类:Java 提供了一些线程安全的类,如 Vector、HashTable、ConcurrentHashMap 等,这些类内部实现了线程安全的机制。
      • 使用原子类:Java 的 java.util.concurrent.atomic 包中提供了一些原子类,如 AtomicInteger、AtomicLong 等,这些类使用 CAS(Compare-And-Swap)算法实现了原子操作,保证了线程安全。
  2. 说说 synchronized 和 Lock 的区别。
    • 语法层面:synchronized 是 Java 的关键字,是隐式锁,不需要手动释放锁,当代码块或方法执行完毕后,锁会自动释放。Lock 是一个接口,需要手动调用 lock() 方法加锁,unlock() 方法释放锁,通常需要在 finally 块中释放锁,以确保锁一定会被释放。
    • 锁的获取和释放:synchronized 无法判断锁的状态,而 Lock 可以通过 tryLock() 方法尝试获取锁,并返回是否获取成功的布尔值,还可以设置超时时间。
    • 锁的类型:synchronized 是非公平锁,而 Lock 可以实现公平锁和非公平锁,通过 ReentrantLock 的构造函数传入 true 可以创建公平锁。
    • 性能方面:在竞争不激烈的情况下,synchronized 的性能和 Lock 差不多;在竞争激烈的情况下,Lock 的性能可能会更好。
  3. 线程池的作用是什么,Java 中提供了哪些创建线程池的方法?
    • 线程池的作用
      • 减少线程创建和销毁的开销,提高系统的性能。因为线程的创建和销毁需要消耗一定的资源,使用线程池可以复用线程,避免频繁创建和销毁线程。
      • 控制并发线程的数量,避免过多的线程导致系统资源耗尽。
    • Java 中创建线程池的方法
      • Executors 工具类
        • Executors.newFixedThreadPool(int nThreads):创建一个固定大小的线程池,线程池中的线程数量始终保持为 nThreads。
        • Executors.newSingleThreadExecutor():创建一个单线程的线程池,线程池中只有一个线程。
        • Executors.newCachedThreadPool():创建一个可缓存的线程池,线程池中的线程数量可以根据需要动态调整。
        • Executors.newScheduledThreadPool(int corePoolSize):创建一个定时任务的线程池,可用于执行定时任务和周期性任务。
      • ThreadPoolExecutor 类:通过手动创建 ThreadPoolExecutor 对象来创建线程池,可以更灵活地配置线程池的参数。
  4. 线程池的核心参数有哪些?
    • corePoolSize:核心线程数,线程池在初始化时会创建的线程数量。当有新任务提交时,如果线程池中的线程数量小于 corePoolSize,会创建新的线程来执行任务。
    • maximumPoolSize:最大线程数,线程池允许的最大线程数量。当任务队列已满,且线程池中的线程数量小于 maximumPoolSize 时,会创建新的线程来执行任务。
    • keepAliveTime:线程的空闲存活时间,当线程池中的线程数量超过 corePoolSize 时,多余的线程在空闲时间超过 keepAliveTime 后会被销毁。
    • TimeUnit:keepAliveTime 的时间单位,如 TimeUnit.SECONDS 表示秒。
    • workQueue:任务队列,用于存储等待执行的任务。常见的任务队列有 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue 等。
    • threadFactory:线程工厂,用于创建线程。可以通过自定义线程工厂来设置线程的名称、优先级等属性。
    • RejectedExecutionHandler:拒绝策略,当任务队列已满,且线程池中的线程数量达到 maximumPoolSize 时,新提交的任务会被拒绝,此时会调用拒绝策略来处理被拒绝的任务。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(由调用线程来执行任务)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃任务队列中最老的任务)。

第三轮问题答案

  1. Spring 框架的核心特性有哪些?
    • IoC(控制反转):也称为依赖注入(DI),是指将对象的创建和依赖关系的管理交给 Spring 容器,而不是由对象本身来创建和管理。通过 IoC,对象之间的耦合度降低,提高了代码的可维护性和可测试性。例如,一个类需要依赖另一个类的实例,不需要在类内部手动创建该实例,而是通过 Spring 容器注入。
    • AOP(面向切面编程):AOP 是一种编程范式,它允许开发者在不修改原有代码的基础上,对程序的某些功能进行增强。AOP 主要用于处理一些横切关注点,如日志记录、事务管理、权限验证等。在 Spring 中,AOP 可以通过代理模式来实现,将横切逻辑封装在切面类中,然后在需要的地方进行切入。
  2. Spring Boot 和 Spring 有什么区别?
    • 配置方面:Spring 需要大量的 XML 配置文件或 Java 配置类来配置各种组件和 Bean,配置过程比较繁琐。而 Spring Boot 采用了约定大于配置的原则,提供了自动配置的功能,大部分情况下不需要手动进行配置,只需要添加相应的依赖,Spring Boot 会自动进行配置。
    • 开发效率:Spring Boot 可以快速搭建项目,减少了开发人员的配置时间,提高了开发效率。Spring Boot 还提供了嵌入式的服务器,如 Tomcat、Jetty 等,不需要手动部署项目到服务器上。
    • 微服务支持:Spring Boot 更适合用于开发微服务架构的应用,它可以与 Spring Cloud 等框架结合使用,实现服务的注册、发现、配置管理等功能。
  3. MyBatis 中 #{} 和 ${} 的区别是什么?
    • #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为占位符 ?,然后使用 PreparedStatement 进行预编译,再将参数设置到占位符中。这样可以防止 SQL 注入攻击,因为参数会被自动进行转义处理。例如,SELECT * FROM users WHERE id = #{id},MyBatis 会将其转换为 SELECT * FROM users WHERE id = ?,然后将参数 id 的值设置到占位符中。
    • **:是直接替换,MyBatis在处理{}**:是直接替换,MyBatis 在处理 {} 时,会将 SQL 中的 直接替换为参数的值,不会进行预编译和转义处理。因此,使用{} 直接替换为参数的值,不会进行预编译和转义处理。因此,使用 {} 可能会存在 SQL 注入风险。例如,SELECT * FROM ${tableName},MyBatis 会将 tableName直接替换为参数的值。一般情况下,{tableName} 直接替换为参数的值。一般情况下,{} 主要用于动态表名、动态列名等场景。
  4. Dubbo 是一个高性能的分布式服务框架,说说它的主要功能有哪些?
    • 服务注册与发现:Dubbo 提供了服务注册中心,服务提供者可以将自己的服务注册到注册中心,服务消费者可以从注册中心获取服务提供者的地址信息,从而实现服务的调用。常见的注册中心有 ZooKeeper、Nacos 等。
    • 远程调用:Dubbo 支持多种远程调用协议,如 Dubbo 协议、HTTP 协议、REST 协议等,通过这些协议可以实现不同服务之间的远程调用。
    • 负载均衡:Dubbo 提供了多种负载均衡策略,如随机、轮询、最少活跃调用数等,根据不同的策略将请求分发到不同的服务提供者上,提高系统的性能和可用性。
    • 集群容错:当服务提供者出现故障时,Dubbo 提供了多种集群容错策略,如失败重试、快速失败、失败安全等,确保服务调用的可靠性。
    • 服务监控:Dubbo 提供了服务监控功能,可以监控服务的调用情况,如调用次数、调用时间、成功率等,方便开发者进行性能优化和问题排查。
  5. RabbitMQ 是常用的消息队列,它的工作模式有哪些?
    • 简单模式(Simple Mode):也称为 Hello World 模式,是最简单的消息队列模式。生产者将消息发送到一个队列中,消费者从该队列中获取消息。一个生产者对应一个消费者。
    • 工作队列模式(Work Queues):也称为任务队列模式,多个消费者可以从同一个队列中获取消息,实现任务的分发和并行处理。这种模式可以提高系统的处理能力。
    • 发布/订阅模式(Publish/Subscribe):生产者将消息发送到一个交换机(Exchange)中,交换机将消息广播到所有绑定到该交换机的队列中,多个消费者可以从不同的队列中获取消息