互联网大厂 Java 面试:核心知识、框架与中间件大考验
王铁牛怀揣着对互联网大厂的向往,走进了面试室。严肃的面试官早已坐在那里,一场考验 Java 核心能力的面试即将拉开帷幕。
第一轮面试 面试官:“首先问几个 Java 核心知识的问题。Java 中基本数据类型有哪些?” 王铁牛:“有 byte、short、int、long、float、double、char、boolean。” 面试官:“回答得不错。那 String 是基本数据类型吗?” 王铁牛:“不是,String 是引用数据类型。” 面试官:“很好。那说说 Java 中多态的实现方式有哪些?” 王铁牛:“主要有继承和接口两种方式。通过继承父类,子类可以重写父类的方法实现多态;通过实现接口,不同的类可以实现相同的接口方法,也能体现多态。” 面试官:“非常棒,看来你对 Java 核心知识掌握得很扎实。”
第二轮面试 面试官:“接下来聊聊 JUC、JVM 和多线程相关的。JUC 包是什么?有什么作用?” 王铁牛:“JUC 是 java.util.concurrent 包,它提供了在并发编程中常用的工具类,能帮助我们更方便地进行多线程编程。” 面试官:“不错。那 JVM 的内存区域是如何划分的?” 王铁牛:“主要分为堆、栈、方法区、程序计数器和本地方法栈。堆是存放对象实例的地方;栈主要存储局部变量和方法调用信息;方法区存储类的信息、常量、静态变量等;程序计数器记录当前线程执行的字节码行号;本地方法栈为本地方法服务。” 面试官:“回答得挺全面。那在多线程编程中,如何保证线程安全?” 王铁牛:“可以使用 synchronized 关键字和 Lock 接口来实现同步,也可以使用线程安全的类,比如 ConcurrentHashMap 等。” 面试官:“很好,对这些知识理解得很清晰。”
第三轮面试 面试官:“现在谈谈一些框架和中间件。Spring 框架的核心特性有哪些?” 王铁牛:“Spring 的核心特性有 IoC(控制反转)和 AOP(面向切面编程)。IoC 是将对象的创建和依赖关系的管理交给 Spring 容器;AOP 可以在不修改原有代码的情况下,对程序进行增强。” 面试官:“不错。那 Spring Boot 相对于 Spring 有什么优势?” 王铁牛:“Spring Boot 简化了 Spring 应用的开发,它提供了自动配置,能快速搭建项目,减少了大量的配置文件。” 面试官:“那 MyBatis 是如何实现数据库操作的?” 王铁牛:“MyBatis 通过 XML 文件或者注解来配置 SQL 语句,然后通过 Mapper 接口来调用这些 SQL 语句,实现对数据库的增删改查操作。” 面试官:“看来你对框架方面也有一定的了解。那 Dubbo、RabbitMq、xxl - job 和 Redis 你能简单说一下它们的用途吗?” 王铁牛:“Dubbo 是一个分布式服务框架,用于实现服务的注册、发现和调用;RabbitMq 是消息队列,用于实现异步通信和系统解耦;xxl - job 是分布式任务调度平台,能方便地进行任务调度;Redis 是一个高性能的键值对数据库,常用于缓存、分布式锁等场景。” 面试官:“整体回答得还可以。不过在一些细节上还可以再深入研究。你先回家等通知吧,后续有消息我们会及时联系你。”
答案详解
- Java 基本数据类型:Java 中有 8 种基本数据类型,byte 占 1 个字节,范围是 - 128 到 127;short 占 2 个字节;int 占 4 个字节;long 占 8 个字节;float 占 4 个字节,用于表示单精度浮点数;double 占 8 个字节,用于表示双精度浮点数;char 占 2 个字节,用于表示单个字符;boolean 只有两个值 true 和 false,在 JVM 中没有明确规定其占用空间大小。
- String 不是基本数据类型:基本数据类型是 Java 语言中最基础的数据类型,而 String 是一个类,属于引用数据类型。它存储的是对象的引用,而不是具体的值。
- Java 多态的实现方式
- 继承:子类继承父类后,可以重写父类的方法。当通过父类引用指向子类对象时,调用重写的方法会执行子类的实现,从而实现多态。例如:
class Animal {
public void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void sound() {
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Dog();
animal.sound(); // 输出 Dog barks
}
}
- **接口**:一个类可以实现多个接口,不同的类实现相同的接口方法可以有不同的实现逻辑。例如:
interface Shape {
double area();
}
class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
class Rectangle implements Shape {
private double length;
private double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
public double area() {
return length * width;
}
}
public class Main {
public static void main(String[] args) {
Shape circle = new Circle(5);
Shape rectangle = new Rectangle(3, 4);
System.out.println(circle.area());
System.out.println(rectangle.area());
}
}
- JUC 包:java.util.concurrent 包是 Java 为了方便开发者进行并发编程而提供的工具包。它包含了很多实用的类和接口,比如线程池相关的类(ThreadPoolExecutor、Executors 等)、锁相关的类(ReentrantLock、ReadWriteLock 等)、并发集合类(ConcurrentHashMap、CopyOnWriteArrayList 等),可以帮助开发者更高效地实现多线程程序。
- JVM 内存区域划分
- 堆:是 JVM 中最大的一块内存区域,所有的对象实例和数组都在这里分配内存。堆是线程共享的,会进行垃圾回收。
- 栈:每个线程都有自己的栈,栈中存储局部变量、方法调用信息等。每个方法在执行时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
- 方法区:存储类的信息、常量、静态变量等。在 JDK 1.8 之前,方法区也被称为永久代,JDK 1.8 之后使用元空间代替了永久代。
- 程序计数器:是一个较小的内存区域,它记录当前线程执行的字节码行号。每个线程都有自己独立的程序计数器,是线程私有的。
- 本地方法栈:与栈类似,不过它是为本地方法服务的,本地方法是使用非 Java 语言(如 C、C++)实现的方法。
- 多线程编程中保证线程安全的方法
- synchronized 关键字:可以修饰方法或代码块。当修饰方法时,整个方法在同一时间只能被一个线程访问;当修饰代码块时,只有该代码块在同一时间只能被一个线程访问。例如:
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
- **Lock 接口**:常用的实现类是 ReentrantLock。它提供了比 synchronized 更灵活的锁机制,例如可以实现公平锁、可中断锁等。例如:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Counter {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}
- **使用线程安全的类**:例如 ConcurrentHashMap 是线程安全的哈希表,CopyOnWriteArrayList 是线程安全的列表,它们内部使用了各种并发控制机制来保证线程安全。
7. Spring 框架的核心特性 - IoC(控制反转):传统的程序中,对象的创建和依赖关系的管理由程序本身负责,而在 Spring 中,这些工作交给了 Spring 容器。Spring 容器通过 XML 配置文件或者注解来创建和管理对象,将对象之间的依赖关系注入到对象中。例如:
public class UserService {
private UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public void addUser() {
userDao.addUser();
}
}
public class UserDao {
public void addUser() {
System.out.println("Add user to database");
}
}
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.addUser();
}
}
- **AOP(面向切面编程)**:AOP 可以在不修改原有代码的情况下,对程序进行增强。例如,可以在方法执行前后添加日志记录、事务管理等功能。Spring AOP 主要基于代理模式实现。例如:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature().getName());
}
@After("execution(* com.example.service.*.*(..))")
public void afterAdvice(JoinPoint joinPoint) {
System.out.println("After method: " + joinPoint.getSignature().getName());
}
}
- Spring Boot 相对于 Spring 的优势
- 自动配置:Spring Boot 提供了大量的自动配置类,根据项目中引入的依赖自动进行配置,减少了开发者手动配置的工作量。例如,引入 Spring Boot 的 Web 依赖后,它会自动配置嵌入式的 Tomcat 服务器和 Spring MVC。
- 起步依赖:Spring Boot 提供了一系列的起步依赖,开发者只需要添加相应的依赖,就可以快速搭建项目。例如,添加 spring - boot - starter - data - jpa 依赖,就可以快速使用 JPA 进行数据库操作。
- 内置服务器:Spring Boot 内置了 Tomcat、Jetty 等服务器,不需要单独部署服务器,直接运行项目即可。
- MyBatis 实现数据库操作的方式
- XML 配置方式:编写 XML 文件,在文件中配置 SQL 语句。例如:
<mapper namespace="com.example.dao.UserDao">
<select id="getUserById" parameterType="int" resultType="com.example.entity.User">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>
- **注解方式**:在 Mapper 接口的方法上使用注解来配置 SQL 语句。例如:
import org.apache.ibatis.annotations.Select;
public interface UserDao {
@Select("SELECT * FROM users WHERE id = #{id}")
User getUserById(int id);
}
- **Mapper 接口调用**:通过 MyBatis 的 SqlSession 或者 Spring 集成的方式获取 Mapper 接口的实例,然后调用接口中的方法来执行 SQL 语句。例如:
SqlSession session = sqlSessionFactory.openSession();
UserDao userDao = session.getMapper(UserDao.class);
User user = userDao.getUserById(1);
- Dubbo、RabbitMq、xxl - job 和 Redis 的用途
- Dubbo:是一个分布式服务框架,用于实现服务的注册、发现和调用。在分布式系统中,各个服务之间可以通过 Dubbo 进行远程调用,提高系统的可扩展性和可维护性。Dubbo 支持多种注册中心(如 Zookeeper、Nacos 等),可以方便地管理服务的注册和发现。
- RabbitMq:是一个消息队列,用于实现异步通信和系统解耦。生产者将消息发送到 RabbitMq 的队列中,消费者从队列中获取消息进行处理。这样可以提高系统的性能和可靠性,例如在电商系统中,用户下单后可以将订单消息发送到队列中,由其他服务异步处理订单。
- xxl - job:是一个分布式任务调度平台,用于实现任务的定时执行和分布式调度。可以在不同的服务器上部署任务,通过 xxl - job 进行统一管理和调度,方便开发者进行任务的监控和管理。
- Redis:是一个高性能的键值对数据库,常用于缓存、分布式锁、消息队列等场景。由于 Redis 数据存储在内存中,读写速度非常快,所以可以作为缓存来减轻数据库的压力。同时,Redis 提供了原子操作和分布式锁的实现,可以用于解决分布式系统中的并发问题。例如,在电商系统中,可以使用 Redis 缓存商品信息,提高系统的响应速度。