互联网大厂 Java 面试:核心知识、框架与中间件大考验
在互联网大厂宽敞明亮的面试室内,严肃的面试官正襟危坐,对面坐着略显紧张的求职者王铁牛。一场关于 Java 核心知识的面试即将拉开帷幕。
第一轮提问 面试官:首先,我问几个基础的 Java 核心知识问题。Java 中的基本数据类型有哪些? 王铁牛:Java 的基本数据类型有 byte、short、int、long、float、double、char、boolean。 面试官:回答得不错。那你说说 Java 中重写和重载的区别是什么? 王铁牛:重写是子类对父类中已有方法的重新实现,方法名、参数列表和返回值类型都要一样,访问修饰符不能比父类的更严格。重载是在同一个类中,方法名相同但参数列表不同,和返回值类型无关。 面试官:非常好。那 final、finally、finalize 有什么区别? 王铁牛:final 可以修饰类、方法和变量,修饰类时类不能被继承,修饰方法时方法不能被重写,修饰变量时变量变成常量。finally 是在异常处理中,无论是否发生异常,finally 块中的代码都会执行。finalize 是 Object 类的一个方法,在对象被垃圾回收之前会调用这个方法。 面试官:你的基础很扎实,继续保持。
第二轮提问 面试官:接下来聊聊 JUC 和多线程相关的。什么是线程池,使用线程池有什么好处? 王铁牛:线程池就是预先创建好一定数量的线程,当有任务提交时,从线程池中获取线程来执行任务。使用线程池可以减少线程创建和销毁的开销,提高系统的响应速度,还能更好地管理线程数量,避免创建过多线程导致系统资源耗尽。 面试官:不错。那你说说线程池的几种创建方式有哪些? 王铁牛:可以通过 Executors 工具类创建,比如 newFixedThreadPool 可以创建固定大小的线程池,newCachedThreadPool 可以创建可缓存的线程池,newSingleThreadExecutor 可以创建单线程的线程池。 面试官:那在使用线程池时,如何避免线程池出现 OOM(OutOfMemoryError)问题? 王铁牛:呃……这个嘛,我觉得就是不要让线程池里的任务太多吧,具体的我不太确定。 面试官:这部分回答得不够清晰,你后续还需要深入学习。我们接着下一个问题,说说 synchronized 和 ReentrantLock 的区别。 王铁牛:synchronized 是 Java 的关键字,是隐式锁,会自动释放锁。ReentrantLock 是一个类,是显式锁,需要手动释放锁。
第三轮提问 面试官:现在来谈谈一些框架和中间件。Spring 框架的核心特性有哪些? 王铁牛:Spring 框架的核心特性有 IoC(控制反转)和 AOP(面向切面编程)。IoC 是将对象的创建和依赖关系的管理交给 Spring 容器,AOP 是在不修改原有代码的基础上,对程序进行增强。 面试官:很好。那 Spring Boot 相对于 Spring 有什么优势? 王铁牛:Spring Boot 简化了 Spring 项目的配置,它有自动配置功能,能快速搭建项目,还内置了嵌入式服务器,方便开发和部署。 面试官:说说 MyBatis 的工作原理是什么? 王铁牛:呃……MyBatis 嘛,就是和数据库交互的,具体原理我有点说不清楚。 面试官:那 Dubbo 是做什么的,它的核心组件有哪些? 王铁牛:Dubbo 是一个分布式服务框架,组件嘛,我就记得有服务提供者和服务消费者,其他的不太记得了。
面试接近尾声,面试官放下手中的笔,表情严肃地说:“今天的面试就到这里,你在基础的 Java 核心知识方面回答得不错,对线程池和 Spring 框架也有一定的了解,但在一些复杂问题上,比如避免线程池 OOM 问题、MyBatis 工作原理和 Dubbo 核心组件等方面,回答得不够清晰和完整。我们会综合考虑这次面试的情况,你回家等通知吧。”
答案详解
第一轮问题答案
- Java 中的基本数据类型:
- 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 位 Unicode 字符,用于表示单个字符,用单引号括起来。
- boolean:只有两个值,true 和 false,用于逻辑判断。
- 重写和重载的区别:
- 重写(Override):发生在子类和父类之间,子类重写父类的方法。方法名、参数列表和返回值类型必须相同(返回值类型在 Java 5 及以后支持协变返回类型),访问修饰符不能比父类的更严格,抛出的异常不能比父类的更宽泛。重写是实现多态的一种方式。
- 重载(Overload):发生在同一个类中,方法名相同但参数列表不同(参数的类型、个数或顺序不同),和返回值类型无关。重载可以提高代码的可读性和可维护性。
- final、finally、finalize 的区别:
- final:
- 修饰类:类不能被继承,如 String 类就是用 final 修饰的。
- 修饰方法:方法不能被重写。
- 修饰变量:变量变成常量,一旦赋值就不能再修改。如果是引用类型的变量,其引用不能改变,但对象的内容可以改变。
- finally:在异常处理中,无论 try 块中是否发生异常,finally 块中的代码都会执行。常用于释放资源,如关闭文件、数据库连接等。
- finalize:是 Object 类的一个方法,当对象被垃圾回收器回收之前会调用这个方法。不过由于垃圾回收的不确定性,不建议依赖 finalize 方法来释放资源。
- final:
第二轮问题答案
- 线程池的概念和好处:
- 概念:线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池中的线程可以反复使用,避免了频繁创建和销毁线程的开销。
- 好处:
- 降低资源消耗:减少了线程创建和销毁的开销,提高了系统的性能。
- 提高响应速度:当有任务提交时,线程池中已经有线程可以立即执行任务,无需等待线程创建。
- 便于线程管理:可以控制线程池中的线程数量,避免创建过多线程导致系统资源耗尽。
- 线程池的创建方式:
- newFixedThreadPool:创建一个固定大小的线程池,线程数量固定,当有新任务提交时,如果线程池中有空闲线程,则立即执行任务,否则将任务放入队列中等待。
- newCachedThreadPool:创建一个可缓存的线程池,如果线程池中的线程数量超过了处理任务所需的线程数量,空闲时间超过 60 秒的线程会被回收。当有新任务提交时,如果有空闲线程则使用空闲线程,否则创建新线程执行任务。
- newSingleThreadExecutor:创建一个单线程的线程池,线程池中只有一个线程,所有任务按照提交的顺序依次执行。
- 除了使用 Executors 工具类,还可以通过 ThreadPoolExecutor 类来创建线程池,这种方式可以更灵活地配置线程池的参数。
- 避免线程池出现 OOM 问题:
- 合理配置线程池参数:包括核心线程数、最大线程数、队列容量等。核心线程数是线程池始终保持的线程数量,最大线程数是线程池允许的最大线程数量,队列用于存储等待执行的任务。
- 选择合适的队列:不同的队列有不同的特性,如 ArrayBlockingQueue 是有界队列,LinkedBlockingQueue 可以是有界或无界队列。使用有界队列可以避免队列无限增长导致内存溢出。
- 监控线程池状态:通过监控线程池的线程数量、队列长度等指标,及时发现线程池的异常情况并进行调整。
- synchronized 和 ReentrantLock 的区别:
- 语法层面:synchronized 是 Java 的关键字,是隐式锁,在方法或代码块前加上 synchronized 关键字即可使用。ReentrantLock 是一个类,需要手动创建对象并调用 lock() 和 unlock() 方法来加锁和解锁。
- 锁的释放:synchronized 会在代码块或方法执行完毕后自动释放锁,而 ReentrantLock 需要手动调用 unlock() 方法释放锁,通常在 finally 块中调用,以确保锁一定会被释放。
- 锁的特性:ReentrantLock 提供了更多的特性,如可中断锁、公平锁等。可中断锁可以在等待锁的过程中被中断,公平锁可以保证线程按照请求锁的顺序获取锁。
第三轮问题答案
- Spring 框架的核心特性:
- IoC(控制反转):也称为依赖注入(DI),是指将对象的创建和依赖关系的管理交给 Spring 容器。在传统的编程中,对象的创建和依赖关系是由对象本身来管理的,而在 Spring 中,对象的创建和依赖关系由 Spring 容器负责。通过 IoC,可以降低对象之间的耦合度,提高代码的可维护性和可测试性。
- AOP(面向切面编程):是一种编程范式,它允许在不修改原有代码的基础上,对程序进行增强。AOP 主要用于处理一些横切关注点,如日志记录、事务管理、权限验证等。在 Spring 中,AOP 是通过代理模式实现的,可以使用 JDK 动态代理或 CGLIB 代理。
- Spring Boot 相对于 Spring 的优势:
- 简化配置:Spring Boot 提供了自动配置功能,根据项目的依赖和配置,自动为项目配置 Spring 框架的各种组件,减少了大量的 XML 配置文件和 Java 配置类。
- 快速搭建项目:Spring Boot 提供了 Spring Initializr 工具,可以快速生成项目骨架,包含了项目所需的依赖和配置文件,开发者可以直接开始编写业务代码。
- 内置嵌入式服务器:Spring Boot 内置了 Tomcat、Jetty 等嵌入式服务器,无需额外部署服务器,方便开发和部署。
- MyBatis 的工作原理:
- 读取配置文件:MyBatis 首先读取配置文件,包括数据库连接信息、Mapper 映射文件等。
- 创建 SqlSessionFactory:根据配置文件创建 SqlSessionFactory,它是 MyBatis 的核心对象,用于创建 SqlSession。
- 创建 SqlSession:SqlSession 是 MyBatis 提供的一个会话对象,用于执行 SQL 语句。通过 SqlSession 可以获取 Mapper 接口的代理对象。
- 执行 SQL 语句:通过 Mapper 接口的代理对象执行 SQL 语句,MyBatis 会根据 Mapper 映射文件中的 SQL 语句和参数,与数据库进行交互,并将查询结果封装成 Java 对象返回。
- Dubbo 的作用和核心组件:
- 作用:Dubbo 是一个高性能的分布式服务框架,用于解决分布式系统中服务之间的调用问题。它提供了服务注册与发现、远程调用、负载均衡、集群容错等功能,帮助开发者快速构建分布式系统。
- 核心组件:
- 服务提供者(Provider):暴露服务的一方,将自己的服务注册到注册中心。
- 服务消费者(Consumer):调用服务的一方,从注册中心获取服务提供者的地址信息,并调用服务。
- 注册中心(Registry):负责服务的注册和发现,服务提供者将服务信息注册到注册中心,服务消费者从注册中心获取服务信息。
- 监控中心(Monitor):负责收集服务的调用信息,如调用次数、调用时间等,用于监控服务的性能和健康状况。
- 容器(Container):负责启动、加载和运行服务提供者。