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

26 阅读10分钟

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

王铁牛怀揣着紧张又期待的心情,走进了互联网大厂的面试间。严肃的面试官早已坐在那里,一场对 Java 技术深度与广度的考验即将拉开帷幕。

第一轮面试

面试官:“我们先从 Java 核心知识开始。Java 中基本数据类型有哪些?” 王铁牛:“基本数据类型有 byte、short、int、long、float、double、char、boolean。” 面试官:“回答得不错。那说说面向对象的四大特性。” 王铁牛:“面向对象的四大特性是封装、继承、多态和抽象。封装是把数据和操作数据的方法绑定起来,继承是子类继承父类的属性和方法,多态是同一个行为具有多个不同表现形式,抽象是将一类对象的共同特征总结出来构造类。” 面试官:“非常好。那在 Java 中,== 和 equals 方法有什么区别?” 王铁牛:“== 对于基本数据类型比较的是值是否相等,对于引用数据类型比较的是引用是否指向同一个对象。而 equals 方法默认情况下和 == 一样,但很多类会重写 equals 方法来比较对象的内容是否相等,比如 String 类。” 面试官:“你对 Java 核心知识掌握得很扎实。”

第二轮面试

面试官:“接下来聊聊 JUC 和多线程。什么是线程安全?” 王铁牛:“线程安全就是当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。” 面试官:“不错。那说说线程池的好处有哪些?” 王铁牛:“线程池可以降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗;提高响应速度,当任务到达时,任务可以不需要等到线程创建就能立即执行;提高线程的可管理性,线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。” 面试官:“很好。那在 JUC 中,CountDownLatch 是怎么使用的?” 王铁牛:“CountDownLatch 是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。比如有一个主线程需要等待多个子线程完成任务后再继续执行,就可以使用 CountDownLatch。先创建一个 CountDownLatch 对象,设置初始计数,子线程执行完任务后调用 countDown 方法将计数减 1,主线程调用 await 方法等待计数变为 0。” 面试官:“你对 JUC 和多线程的理解很到位。”

第三轮面试

面试官:“现在谈谈框架和中间件。Spring 的 IOC 和 AOP 是什么?” 王铁牛:“IOC 是控制反转,它把对象的创建和依赖关系的管理交给 Spring 容器,而不是由对象本身来创建和管理依赖,这样可以降低代码的耦合度。AOP 是面向切面编程,它可以在不修改原有代码的基础上,对程序进行增强,比如实现日志记录、事务管理等功能。” 面试官:“回答得可以。那 Spring Boot 有什么优点?” 王铁牛:“Spring Boot 可以快速搭建项目,它提供了大量的 Starter 依赖,能自动配置 Spring 框架,减少了繁琐的配置文件;还能独立运行,内置了 Tomcat、Jetty 等 Servlet 容器,方便开发和部署。” 面试官:“还可以。那 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 位)。基本数据类型直接存储值,占用内存空间小,操作效率高。
  2. 面向对象的四大特性
    • 封装:将数据和操作数据的方法绑定在一起,隐藏对象的内部实现细节,只对外提供必要的接口。例如,一个类的属性可以设置为私有,通过公有的 getter 和 setter 方法来访问和修改属性,这样可以保证数据的安全性和完整性。
    • 继承:子类可以继承父类的属性和方法,从而实现代码的复用和扩展。子类可以在父类的基础上添加新的属性和方法,或者重写父类的方法以实现不同的功能。
    • 多态:同一个行为具有多个不同表现形式。多态的实现方式有两种,一种是方法重载(在同一个类中,方法名相同但参数列表不同),另一种是方法重写(子类重写父类的方法)。通过多态,程序可以根据对象的实际类型来调用相应的方法,提高了代码的灵活性和可扩展性。
    • 抽象:抽象是将一类对象的共同特征总结出来构造类的过程。抽象类和接口是实现抽象的两种方式。抽象类中可以包含抽象方法和具体方法,抽象方法只有方法声明没有方法体,子类必须实现抽象方法。接口中所有的方法都是抽象方法,接口主要用于定义行为规范。
  3. == 和 equals 方法的区别
    • ==:对于基本数据类型,比较的是值是否相等;对于引用数据类型,比较的是引用是否指向同一个对象,即两个引用是否指向内存中的同一个地址。
    • equals 方法:Object 类中的 equals 方法默认实现是比较对象的引用是否相等,和 == 一样。但很多类(如 String、Integer 等)会重写 equals 方法来比较对象的内容是否相等。例如,String 类的 equals 方法会比较两个字符串的字符序列是否相同。
  4. 线程安全
    • 当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。例如,String 类是线程安全的,因为它的所有方法都是不可变的,不会被多个线程同时修改。而 ArrayList 不是线程安全的,因为它的 add、remove 等方法在多线程环境下可能会出现数据不一致的问题。
  5. 线程池的好处
    • 降低资源消耗:线程的创建和销毁需要消耗系统资源,线程池可以重复利用已创建的线程,避免了频繁创建和销毁线程带来的开销。
    • 提高响应速度:当任务到达时,如果线程池中有空闲线程,任务可以立即执行,不需要等待线程创建,从而提高了响应速度。
    • 提高线程的可管理性:线程是稀缺资源,如果无限制地创建线程,会消耗大量的系统资源,还可能导致系统崩溃。线程池可以对线程进行统一的分配、调优和监控,提高了系统的稳定性和可维护性。
  6. CountDownLatch 的使用
    • CountDownLatch 是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。使用步骤如下:
      • 创建 CountDownLatch 对象,构造函数接受一个整数参数,表示初始计数。
      • 在需要等待的线程中调用 CountDownLatch 的 await 方法,该方法会阻塞线程,直到计数变为 0。
      • 在其他线程中,当任务执行完后,调用 CountDownLatch 的 countDown 方法,将计数减 1。
    • 例如,有一个主线程需要等待 3 个子线程完成任务后再继续执行,可以这样实现:
import java.util.concurrent.CountDownLatch;

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

        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                try {
                    // 模拟子线程执行任务
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + " 任务完成");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 任务完成,计数减 1
                    latch.countDown();
                }
            }).start();
        }

        // 主线程等待计数变为 0
        latch.await();
        System.out.println("所有子线程任务完成,主线程继续执行");
    }
}
  1. Spring 的 IOC 和 AOP
    • IOC(控制反转):传统的对象创建和依赖关系管理是由对象本身负责,而在 Spring 中,IOC 把对象的创建和依赖关系的管理交给 Spring 容器。Spring 容器通过 XML 配置文件、注解等方式来配置对象和它们之间的依赖关系。当需要使用某个对象时,只需要从 Spring 容器中获取即可。IOC 可以降低代码的耦合度,提高代码的可维护性和可测试性。
    • AOP(面向切面编程):AOP 是在不修改原有代码的基础上,对程序进行增强。在程序执行过程中,AOP 可以在特定的切入点(如方法调用前后、异常抛出时等)插入额外的代码,实现日志记录、事务管理、权限验证等功能。AOP 的实现方式有基于代理的方式(如 JDK 动态代理和 CGLIB 代理)和基于字节码增强的方式。
  2. Spring Boot 的优点
    • 快速搭建项目:Spring Boot 提供了大量的 Starter 依赖,只需要在项目中添加相应的 Starter 依赖,Spring Boot 就会自动配置 Spring 框架,减少了繁琐的配置文件。例如,添加 spring-boot-starter-web 依赖,就可以快速搭建一个 Web 项目。
    • 独立运行:Spring Boot 内置了 Tomcat、Jetty 等 Servlet 容器,项目可以以 Jar 包的形式独立运行,不需要部署到外部的 Servlet 容器中,方便开发和部署。
    • 自动配置:Spring Boot 根据项目的依赖和配置自动进行配置,减少了开发者的配置工作量。例如,如果项目中添加了数据库相关的依赖,Spring Boot 会自动配置数据源和 JPA 等。
  3. MyBatis 中 #{} 和 ${} 的区别
    • #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为 ? 占位符,然后使用 PreparedStatement 进行预编译,再将参数设置到占位符中。这样可以防止 SQL 注入,因为参数会被自动进行转义处理。
    • **:是字符串替换,MyBatis在处理{}**:是字符串替换,MyBatis 在处理 {} 时,会直接将 替换为参数的值。这种方式可能存在SQL注入风险,因为参数不会进行转义处理。例如,当参数为"1;DROPTABLEusers"时,如果使用{} 替换为参数的值。这种方式可能存在 SQL 注入风险,因为参数不会进行转义处理。例如,当参数为 "1; DROP TABLE users" 时,如果使用 {} 进行拼接,可能会导致数据库表被删除。所以,在使用 MyBatis 时,尽量使用 #{} 来避免 SQL 注入。
  4. Dubbo 是做什么的
    • Dubbo 是一个分布式服务框架,主要用于实现服务的远程调用和服务治理。在分布式系统中,不同的服务可能部署在不同的服务器上,Dubbo 可以让这些服务之间方便地进行远程调用,就像调用本地方法一样。Dubbo 提供了服务注册与发现、负载均衡、集群容错、远程通信等功能。服务提供者将自己的服务注册到注册中心,服务消费者从注册中心获取服务提供者的地址,然后通过 Dubbo 的远程调用机制调用服务提供者的服务。Dubbo 可以提高系统的可扩展性和可维护性,使得各个服务可以独立开发、部署和维护。