互联网大厂 Java 面试:核心知识、框架与中间件大考验
在互联网大厂的一间安静的面试室内,严肃的面试官正对面坐着略显紧张的程序员王铁牛,一场关于 Java 核心知识的面试即将拉开帷幕。
第一轮提问 面试官:“首先,我问你几个基础的 Java 核心知识问题。Java 中基本数据类型有哪些?” 王铁牛:“Java 中的基本数据类型有 byte、short、int、long、float、double、char、boolean。” 面试官:“回答正确,很不错。那 String 是基本数据类型吗?” 王铁牛:“不是,String 是引用数据类型,它是一个类。” 面试官:“很好,理解得很清晰。那 Java 中多态的实现方式有哪些?” 王铁牛:“Java 中多态的实现方式主要有方法重载和方法重写。方法重载是在同一个类中,方法名相同但参数列表不同;方法重写是子类重写父类的方法。” 面试官:“回答得非常准确,基础很扎实。”
第二轮提问 面试官:“接下来,我们聊聊 JUC 和多线程相关的问题。什么是线程安全?” 王铁牛:“线程安全就是在多线程环境下,程序的执行结果和单线程环境下是一样的,不会出现数据不一致等问题。” 面试官:“回答得可以。那说说线程池有什么作用?” 王铁牛:“线程池可以避免频繁创建和销毁线程带来的开销,提高系统的性能,还可以对线程进行统一的管理和监控。” 面试官:“不错。那在 Java 中,如何创建一个线程池?” 王铁牛:“可以使用 Executors 工具类来创建不同类型的线程池,比如 newFixedThreadPool、newCachedThreadPool 等,也可以使用 ThreadPoolExecutor 来手动创建线程池。” 面试官:“回答得挺好,对线程池有一定的了解。”
第三轮提问 面试官:“现在来谈谈一些框架和中间件的问题。Spring 的核心特性有哪些?” 王铁牛:“Spring 的核心特性有 IoC(控制反转)和 AOP(面向切面编程)。IoC 是将对象的创建和依赖关系的管理交给 Spring 容器,AOP 是在不修改原有代码的基础上,对程序进行增强。” 面试官:“可以。那 Spring Boot 相比 Spring 有什么优势?” 王铁牛:“Spring Boot 简化了 Spring 项目的配置,有自动配置功能,还可以快速搭建项目,减少了开发时间。” 面试官:“嗯,有一定认识。那 MyBatis 中 #{} 和 {} 的区别是什么?” **王铁牛**:“呃……这个……好像 #{} 是预编译处理,{} 是直接替换,具体的我有点不太清楚了。” 面试官:“这里回答得不够清晰。再问你,Dubbo 是什么,有什么作用?” 王铁牛:“Dubbo 是一个分布式服务框架,能实现服务的注册和发现,但是具体的细节我不太能说清楚了。” 面试官:“看来你对这些框架的理解还不够深入。”
面试接近尾声,面试官看着王铁牛说:“今天的面试就到这里了。你在一些基础的 Java 核心知识和简单的多线程、线程池问题上回答得不错,展现出了一定的基础。但在一些框架和中间件的深入理解上还有所欠缺,比如 MyBatis 和 Dubbo 的相关问题回答得不够准确和详细。我们需要综合评估后再做决定,你先回家等通知吧。”
问题答案详细解析
- Java 中基本数据类型有哪些?
- Java 中有 8 种基本数据类型,可分为 4 类:
- 整数类型:byte(1 字节,范围 -128 到 127)、short(2 字节,范围 -32768 到 32767)、int(4 字节,范围 -2147483648 到 2147483647)、long(8 字节,范围 -9223372036854775808 到 9223372036854775807)。
- 浮点类型:float(4 字节,单精度浮点数)、double(8 字节,双精度浮点数)。
- 字符类型:char(2 字节,用于表示单个字符)。
- 布尔类型:boolean(只有两个值,true 和 false)。
- Java 中有 8 种基本数据类型,可分为 4 类:
- String 是基本数据类型吗?
- String 不是基本数据类型,而是引用数据类型。它是一个类,位于 java.lang 包中。基本数据类型是 Java 语言内置的,存储的是具体的值;而引用数据类型存储的是对象的引用地址,String 对象存储的是字符序列。
- Java 中多态的实现方式有哪些?
- 方法重载(Overloading):在同一个类中,方法名相同但参数列表不同(参数的类型、个数或顺序不同)。方法重载与返回值类型无关。例如:
public class OverloadingExample {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
}
- **方法重写(Overriding)**:子类重写父类的方法,要求方法名、参数列表和返回值类型都相同(返回值类型可以是父类方法返回值类型的子类,这称为协变返回类型)。重写的方法不能比父类的方法有更严格的访问权限。例如:
class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}
- 什么是线程安全?
- 在多线程环境下,如果多个线程同时访问一个共享资源,可能会导致数据不一致或其他错误。线程安全的代码能够保证在多线程环境下,程序的执行结果和单线程环境下是一样的,不会出现数据竞争等问题。例如,一个线程在读取一个共享变量的同时,另一个线程可能在修改这个变量,这就可能导致读取到的数据是不一致的。线程安全的实现方式有很多,比如使用同步机制(如 synchronized 关键字、Lock 接口)来保证同一时间只有一个线程可以访问共享资源。
- 说说线程池有什么作用?
- 减少线程创建和销毁的开销:线程的创建和销毁是比较消耗系统资源的操作。线程池可以复用已经创建好的线程,避免了频繁创建和销毁线程带来的性能损耗。
- 提高系统响应速度:当有任务提交到线程池时,如果线程池中有空闲线程,就可以立即执行任务,而不需要等待线程的创建。
- 统一管理和监控线程:线程池可以对线程进行统一的管理,比如设置线程的数量、线程的优先级等。还可以对线程的执行情况进行监控,方便排查问题。
- 在 Java 中,如何创建一个线程池?
- 使用 Executors 工具类:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建一个固定大小的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
// 创建一个可缓存的线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 创建一个单线程的线程池
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
}
}
- **使用 ThreadPoolExecutor 手动创建**:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ManualThreadPoolExample {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
60, // 线程空闲时间
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10) // 任务队列
);
}
}
- Spring 的核心特性有哪些?
- IoC(控制反转):也称为依赖注入(DI),是将对象的创建和依赖关系的管理交给 Spring 容器。在传统的编程中,对象的创建和依赖关系是由对象自身来管理的,而在 Spring 中,对象的创建和依赖关系的注入是由 Spring 容器完成的。这样可以降低代码的耦合度,提高代码的可维护性和可测试性。例如:
public class UserService {
private UserDao userDao;
// 通过构造函数注入依赖
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
- **AOP(面向切面编程)**:是在不修改原有代码的基础上,对程序进行增强。AOP 主要用于处理一些横切关注点,如日志记录、事务管理、权限验证等。在 Spring 中,AOP 可以通过 AspectJ 等实现。例如,使用 AOP 实现日志记录:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
@After("execution(* com.example.service.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
System.out.println("Method " + joinPoint.getSignature().getName() + " executed");
}
}
- Spring Boot 相比 Spring 有什么优势?
- 简化配置:Spring Boot 提供了自动配置功能,它会根据项目中引入的依赖自动配置 Spring 应用。相比 Spring,减少了大量的 XML 配置文件和 Java 配置类,开发人员可以更专注于业务逻辑的实现。
- 快速搭建项目:Spring Boot 提供了 Spring Initializr 等工具,可以快速生成项目骨架,包含了基本的依赖和配置,大大缩短了项目的搭建时间。
- 内嵌服务器:Spring Boot 内嵌了 Tomcat、Jetty 等服务器,不需要额外部署服务器,直接运行项目的主类就可以启动应用,方便开发和测试。
- 生产级特性:Spring Boot 提供了一些生产级的特性,如健康检查、指标监控、外部化配置等,方便对应用进行管理和维护。
- MyBatis 中 #{} 和 ${} 的区别是什么?
- #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为 ? 占位符,然后使用 PreparedStatement 来执行 SQL 语句。这样可以防止 SQL 注入攻击,因为 PreparedStatement 会对输入的参数进行自动转义。例如:
<select id="getUserById" parameterType="int" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
- **${}**:是直接替换,MyBatis 在处理 ${} 时,会将 ${} 直接替换为传入的参数值。这种方式可能会导致 SQL 注入攻击,因为它不会对输入的参数进行转义。例如:
<select id="getUserByUsername" parameterType="String" resultType="User">
SELECT * FROM users WHERE username = '${username}'
</select>
一般情况下,推荐使用 #{} 来防止 SQL 注入。只有在一些特殊情况下,如需要动态传入表名、列名时,才使用 ${}。 10. Dubbo 是什么,有什么作用? - Dubbo 是一个分布式服务框架:它由阿里巴巴开源,用于解决分布式系统中服务之间的调用问题。Dubbo 提供了服务注册与发现、远程调用、集群容错、负载均衡等功能。 - 作用: - 服务注册与发现:Dubbo 可以将服务提供者的服务信息注册到注册中心(如 Zookeeper、Nacos 等),服务消费者可以从注册中心获取服务提供者的地址信息,从而实现服务的调用。 - 远程调用:Dubbo 支持多种远程调用协议,如 Dubbo 协议、HTTP 协议等,方便不同服务之间进行远程调用。 - 集群容错:当服务提供者出现故障时,Dubbo 可以通过集群容错机制选择其他可用的服务提供者,保证服务的高可用性。 - 负载均衡:Dubbo 提供了多种负载均衡算法,如随机、轮询、最少活跃调用数等,可以根据不同的场景选择合适的负载均衡算法,将请求均匀地分发到多个服务提供者上。