《互联网大厂 Java 面试:核心知识、框架与中间件大考验》
王铁牛怀揣着紧张与期待,坐在了互联网大厂的面试会议室里,对面的面试官表情严肃,一场关于 Java 技术的考验即将拉开帷幕。
第一轮提问
- 面试官:首先问你几个 Java 核心知识的问题。Java 中基本数据类型有哪些?
- 王铁牛:这个我知道,有 byte、short、int、long、float、double、char、boolean。
- 面试官:不错,回答得很准确。那 String 是基本数据类型吗?
- 王铁牛:不是,String 是引用数据类型,它是一个类。
- 面试官:很好。那在 Java 中,什么是方法重载和方法重写?
- 王铁牛:方法重载是指在一个类中,有多个方法名相同,但参数列表不同的方法。方法重写是指子类重写父类的方法,方法名、参数列表和返回值类型都要相同。
- 面试官:回答得非常清晰,看来你对 Java 核心知识掌握得很扎实。
第二轮提问
- 面试官:接下来聊聊 JUC 和多线程相关的。什么是线程安全?
- 王铁牛:线程安全就是在多线程环境下,对共享资源的访问不会出现数据不一致等问题。
- 面试官:对的。那你知道 Java 中实现多线程有哪些方式吗?
- 王铁牛:有继承 Thread 类和实现 Runnable 接口,还有实现 Callable 接口。
- 面试官:很好。那线程池的好处有哪些呢?
- 王铁牛:可以减少线程的创建和销毁开销,提高响应速度,还能方便管理线程。
- 面试官:回答得很到位。那你说一下 Executors 提供了哪些创建线程池的方法?
- 王铁牛:有 newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor、newScheduledThreadPool。
第三轮提问
- 面试官:现在问一些框架和中间件的问题。Spring 框架的核心特性有哪些?
- 王铁牛:有依赖注入和面向切面编程。
- 面试官:不错。那 Spring Boot 有什么优势?
- 王铁牛:它可以快速搭建项目,简化配置,内置服务器。
- 面试官:对。那 MyBatis 中 #{} 和 ${} 的区别是什么?
- 王铁牛:这个……嗯……好像一个是预编译,一个不是,具体我有点记不清了。
- 面试官:再问你,Dubbo 是什么,有什么作用?
- 王铁牛:Dubbo 是个……好像是个分布式服务框架,能实现服务的调用和注册,具体细节我不太清楚了。
- 面试官:最后问你,Redis 有哪些数据类型?
- 王铁牛:有字符串、哈希、列表、集合、有序集合,不过它们的应用场景我不太能说清楚。
面试官扶了扶眼镜,说道:“王铁牛,今天的面试就到这里了。你对一些基础的 Java 知识掌握得还不错,在前面两轮的表现让我看到了你有一定的知识储备。但在第三轮关于框架和中间件的复杂问题上,你回答得不够清晰和深入,对一些细节的掌握还有所欠缺。你先回家等通知吧,我们后续会综合评估后给你答复。”
问题答案
- Java 中基本数据类型有哪些?
- Java 中有 8 种基本数据类型,分为 4 类:
- 整数类型:byte(1 字节,范围 -128 到 127)、short(2 字节)、int(4 字节)、long(8 字节)。
- 浮点类型:float(4 字节)、double(8 字节)。
- 字符类型:char(2 字节,用于表示单个字符)。
- 布尔类型:boolean(表示真或假,只有两个值 true 和 false)。
- Java 中有 8 种基本数据类型,分为 4 类:
- String 是基本数据类型吗?
- String 不是基本数据类型,而是引用数据类型。它是 Java 中的一个类,用于表示字符串。基本数据类型是 Java 语言预先定义好的简单数据类型,而引用数据类型是通过类来创建对象使用的。
- 在 Java 中,什么是方法重载和方法重写?
- 方法重载:在一个类中,方法名相同但参数列表不同(参数的类型、个数、顺序不同)的多个方法。方法重载与返回值类型和访问修饰符无关。例如:
public class OverloadExample {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
}
- **方法重写**:子类重写父类的方法,要求方法名、参数列表和返回值类型都相同(返回值类型在 Java 5 之后支持协变返回类型,即子类方法的返回值类型可以是父类方法返回值类型的子类)。重写的方法不能比父类方法有更严格的访问权限。例如:
class Parent {
public void print() {
System.out.println("Parent method");
}
}
class Child extends Parent {
@Override
public void print() {
System.out.println("Child method");
}
}
- 什么是线程安全?
- 线程安全是指在多线程环境下,对共享资源的访问不会出现数据不一致、数据损坏等问题。当多个线程同时访问一个共享资源时,如果没有进行适当的同步控制,可能会导致数据的错误。例如,多个线程同时对一个计数器进行自增操作,如果没有同步机制,可能会出现计数器的值不准确的情况。
- Java 中实现多线程有哪些方式?
- 继承 Thread 类:创建一个类继承自 Thread 类,重写 run 方法,然后创建该类的对象并调用 start 方法启动线程。例如:
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running");
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
- **实现 Runnable 接口**:创建一个类实现 Runnable 接口,实现 run 方法,然后将该类的对象作为参数传递给 Thread 类的构造函数,再调用 start 方法启动线程。例如:
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable is running");
}
}
public class Main {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
- **实现 Callable 接口**:创建一个类实现 Callable 接口,实现 call 方法,该方法有返回值。然后使用 FutureTask 包装 Callable 对象,再将 FutureTask 对象作为参数传递给 Thread 类的构造函数启动线程,最后可以通过 FutureTask 的 get 方法获取返回值。例如:
import java.util.concurrent.*;
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 100;
}
}
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start();
Integer result = futureTask.get();
System.out.println("Result: " + result);
}
}
- 线程池的好处有哪些?
- 减少线程创建和销毁开销:线程的创建和销毁是比较消耗资源的操作,线程池可以复用已有的线程,避免频繁创建和销毁线程,从而提高系统性能。
- 提高响应速度:当有任务提交到线程池时,如果线程池中存在空闲线程,任务可以立即执行,无需等待线程的创建,提高了任务的响应速度。
- 方便管理线程:线程池可以对线程进行统一的管理,例如设置线程的最大数量、最小数量、线程的存活时间等,还可以监控线程的执行状态。
- Executors 提供了哪些创建线程池的方法?
- newFixedThreadPool:创建一个固定大小的线程池,线程池中的线程数量是固定的。当有新任务提交时,如果线程池中有空闲线程,则立即执行任务;如果没有空闲线程,则任务会被放入任务队列中等待。例如:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
- **newCachedThreadPool**:创建一个可缓存的线程池,线程池中的线程数量会根据任务的数量自动调整。如果有新任务提交,且线程池中有空闲线程,则使用空闲线程执行任务;如果没有空闲线程,则创建新的线程执行任务。当线程空闲时间超过一定时间(默认 60 秒),线程会被回收。例如:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
- **newSingleThreadExecutor**:创建一个单线程的线程池,线程池中只有一个线程。所有的任务会按照提交的顺序依次执行。例如:
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
- **newScheduledThreadPool**:创建一个定时任务的线程池,可用于执行定时任务或周期性任务。例如:
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
- Spring 框架的核心特性有哪些?
- 依赖注入(DI):是一种设计模式,通过将对象的依赖关系从对象内部转移到外部,由外部容器来管理对象之间的依赖关系。这样可以降低对象之间的耦合度,提高代码的可维护性和可测试性。例如,在 Spring 中可以通过 XML 配置、注解等方式实现依赖注入。
- 面向切面编程(AOP):是一种编程范式,用于将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,提高代码的模块化程度。Spring AOP 通过代理模式实现,在不修改原有业务代码的情况下,对方法进行增强。
- Spring Boot 有什么优势?
- 快速搭建项目:Spring Boot 提供了大量的 Starter 依赖,通过添加相应的 Starter 依赖,就可以快速集成各种功能,无需手动配置复杂的依赖关系。
- 简化配置:Spring Boot 采用约定大于配置的原则,默认提供了很多合理的配置,减少了开发者的配置工作量。同时,还支持通过 application.properties 或 application.yml 文件进行简单的配置。
- 内置服务器:Spring Boot 内置了 Tomcat、Jetty 等服务器,无需额外配置和部署服务器,直接运行项目即可。
- MyBatis 中 #{} 和 ${} 的区别是什么?
- #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为 ? 占位符,然后使用 PreparedStatement 进行预编译,防止 SQL 注入攻击。例如:
<select id="selectUserById" parameterType="int" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
- **${}**:是字符串替换,MyBatis 在处理 ${} 时,会直接将 ${} 替换为传入的值。这种方式存在 SQL 注入的风险,一般用于动态表名、列名等情况。例如:
<select id="selectUserByColumnName" parameterType="String" resultType="User">
SELECT * FROM users WHERE ${columnName} = 'value'
</select>
- Dubbo 是什么,有什么作用?
- Dubbo 是阿里巴巴开源的一个高性能、轻量级的分布式服务框架,用于解决分布式系统中服务之间的调用和管理问题。
- 作用:
- 服务注册与发现:Dubbo 提供了服务注册中心(如 Zookeeper、Nacos 等),服务提供者将自己的服务注册到注册中心,服务消费者从注册中心获取服务提供者的地址信息,实现服务的自动发现。
- 远程调用:Dubbo 支持多种远程调用协议(如 Dubbo 协议、HTTP 协议等),可以方便地实现不同服务之间的远程调用。
- 负载均衡:Dubbo 提供了多种负载均衡策略(如随机、轮询、最少活跃调用数等),可以根据不同的情况选择合适的负载均衡策略,提高系统的性能和可用性。
- 服务治理:Dubbo 提供了服务降级、服务熔断、服务限流等服务治理功能,保障系统的稳定性。
- Redis 有哪些数据类型?
- 字符串(String):是 Redis 最基本的数据类型,可以存储字符串、整数、浮点数等。常见的操作有 set、get、incr、decr 等。例如:
set key value
get key
- **哈希(Hash)**:是一个键值对的集合,适合存储对象。常见的操作有 hset、hget、hgetall 等。例如:
hset user name "John"
hget user name
- **列表(List)**:是一个有序的字符串列表,可以在列表的两端进行插入和删除操作。常见的操作有 lpush、rpush、lpop、rpop 等。例如:
lpush list value1
rpush list value2
lpop list
- **集合(Set)**:是一个无序且唯一的字符串集合。常见的操作有 sadd、srem、smembers 等。例如:
sadd set value1
srem set value1
smembers set
- **有序集合(Sorted Set)**:是一个有序的字符串集合,每个成员都有一个分数,根据分数进行排序。常见的操作有 zadd、zrem、zrange 等。例如:
zadd sortedSet 10 value1
zrem sortedSet value1
zrange sortedSet 0 -1