互联网大厂 Java 面试:核心知识、框架与中间件大考验
王铁牛怀着紧张又期待的心情,走进了互联网大厂的面试间。严肃的面试官早已坐在那里,一场对 Java 技术全方位的考验即将开始。
第一轮提问 面试官:我们先从 Java 核心知识开始。你能说一下 Java 中基本数据类型有哪些吗? 王铁牛:这个我知道,Java 基本数据类型有 byte、short、int、long、float、double、char、boolean。 面试官:不错,回答得很准确。那你讲讲自动装箱和拆箱是怎么回事? 王铁牛:自动装箱就是把基本数据类型转换成对应的包装类,拆箱就是把包装类转换成基本数据类型。比如 Integer i = 10 就是自动装箱,int j = i 就是拆箱。 面试官:很好。那在多线程场景下,使用基本数据类型会有什么问题,怎么解决呢? 王铁牛:在多线程场景下,多个线程同时操作基本数据类型可能会出现数据不一致的问题。可以使用同步机制,像 synchronized 关键字来解决。
第二轮提问 面试官:接下来聊聊 JUC 和多线程。你能说一下 Java 中创建线程有几种方式吗? 王铁牛:有三种,继承 Thread 类、实现 Runnable 接口、实现 Callable 接口。 面试官:回答正确。那你讲讲 Thread 类中的 start() 和 run() 方法有什么区别? 王铁牛:start() 方法是启动一个新线程,让线程进入就绪状态,等待操作系统调度执行;run() 方法只是普通的方法调用,不会启动新线程。 面试官:非常好。那线程池你了解吗,为什么要使用线程池呢? 王铁牛:线程池就是管理线程的一个容器。使用线程池可以降低资源消耗,提高响应速度,便于线程管理。 面试官:不错。那说说线程池的核心参数有哪些? 王铁牛:有核心线程数、最大线程数、线程存活时间、时间单位、工作队列、线程工厂和拒绝策略。
第三轮提问 面试官:现在谈谈一些框架和中间件。你对 Spring 框架熟悉吗,说说 Spring 的核心特性有哪些? 王铁牛:Spring 的核心特性有 IoC(控制反转)和 AOP(面向切面编程)。IoC 就是把对象的创建和依赖关系的管理交给 Spring 容器,AOP 是在不修改源代码的情况下,对程序进行增强。 面试官:很好。那 Spring Boot 相比 Spring 有什么优势呢? 王铁牛:Spring Boot 简化了 Spring 项目的配置,提供了嵌入式服务器,还能自动配置,提高了开发效率。 面试官:回答得挺清晰。那 MyBatis 中 #{} 和 ${} 的区别是什么? 王铁牛:#{} 是预编译处理,能防止 SQL 注入,${} 是字符串替换,可能会有 SQL 注入风险。 面试官:不错。那 Redis 你用过吗,说说 Redis 的数据类型有哪些? 王铁牛:Redis 数据类型有字符串、哈希、列表、集合、有序集合。
面试结束,面试官表情严肃地说:“今天的面试就到这里了,你的基础知识掌握得还可以,对于一些问题回答得比较准确,但在某些深入的技术点上还需要进一步加强。你先回家等通知吧,我们后续会综合评估后给你反馈。”
答案详解
- Java 基本数据类型:
- Java 有 8 种基本数据类型,分为 4 类:
- 整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节)。
- 浮点类型:float(4 字节)、double(8 字节)。
- 字符类型:char(2 字节)。
- 布尔类型:boolean(1 位)。
- Java 有 8 种基本数据类型,分为 4 类:
- 自动装箱和拆箱:
- 自动装箱是 Java 编译器在基本数据类型和对应的包装类之间进行的自动转换。例如,当把一个基本数据类型赋值给对应的包装类对象时,编译器会自动调用包装类的 valueOf() 方法进行装箱。拆箱则是当把包装类对象赋值给基本数据类型时,编译器会自动调用包装类的 xxxValue() 方法进行拆箱。
- 多线程场景下基本数据类型问题及解决方法:
- 在多线程场景下,多个线程同时操作基本数据类型可能会出现数据不一致的问题,因为基本数据类型的操作不是原子性的。可以使用同步机制来解决,如 synchronized 关键字,它可以保证同一时刻只有一个线程可以访问被其修饰的代码块或方法,从而保证数据的一致性。也可以使用 JUC 包中的原子类,如 AtomicInteger 等,这些类的操作是原子性的。
- Java 创建线程的方式:
- 继承 Thread 类:创建一个类继承 Thread 类,重写 run() 方法,然后创建该类的对象并调用 start() 方法启动线程。
- 实现 Runnable 接口:创建一个类实现 Runnable 接口,实现 run() 方法,然后将该类的对象作为参数传递给 Thread 类的构造函数,再调用 start() 方法启动线程。
- 实现 Callable 接口:创建一个类实现 Callable 接口,实现 call() 方法,该方法有返回值。需要使用 FutureTask 类来包装 Callable 对象,然后将 FutureTask 对象作为参数传递给 Thread 类的构造函数,再调用 start() 方法启动线程,最后可以通过 FutureTask 的 get() 方法获取线程执行的结果。
- Thread 类中 start() 和 run() 方法的区别:
- start() 方法是启动一个新线程,它会调用本地方法 start0() 来创建一个新的线程,新线程会进入就绪状态,等待操作系统调度执行。
- run() 方法只是一个普通的方法,如果直接调用 run() 方法,并不会启动新线程,而是在当前线程中执行 run() 方法中的代码。
- 线程池及使用原因:
- 线程池是一种线程管理机制,它预先创建一定数量的线程,当有任务提交时,从线程池中获取线程来执行任务,任务执行完后线程不会销毁,而是返回线程池等待下一个任务。
- 使用线程池的原因:
- 降低资源消耗:避免了频繁创建和销毁线程带来的开销。
- 提高响应速度:任务提交后可以立即从线程池中获取线程执行,不需要等待线程创建。
- 便于线程管理:可以对线程的数量、生命周期等进行统一管理。
- 线程池的核心参数:
- 核心线程数:线程池长期维持的线程数量,即使这些线程处于空闲状态,也不会被销毁。
- 最大线程数:线程池允许创建的最大线程数量。
- 线程存活时间:当线程池中的线程数量超过核心线程数时,多余的空闲线程在等待新任务的最长时间,超过这个时间就会被销毁。
- 时间单位:线程存活时间的单位,如秒、毫秒等。
- 工作队列:用于存储等待执行的任务的队列。
- 线程工厂:用于创建线程的工厂类,可以自定义线程的名称、优先级等。
- 拒绝策略:当工作队列已满且线程池中的线程数量达到最大线程数时,对新提交的任务的处理策略,如抛出异常、丢弃任务等。
- Spring 的核心特性:
- IoC(控制反转):将对象的创建和依赖关系的管理从代码中转移到 Spring 容器中,通过配置文件或注解来定义对象之间的依赖关系,降低了代码的耦合度。
- AOP(面向切面编程):在不修改源代码的情况下,对程序进行增强,如日志记录、事务管理等。通过定义切面、切点和通知,将横切关注点(如日志、事务)与业务逻辑分离。
- Spring Boot 相比 Spring 的优势:
- 简化配置:Spring Boot 提供了自动配置功能,根据项目的依赖和配置自动生成 Spring 配置,减少了大量的 XML 配置文件。
- 嵌入式服务器:Spring Boot 内置了 Tomcat、Jetty 等服务器,不需要额外部署服务器,直接运行项目即可。
- 提高开发效率:通过 Starter 依赖简化了依赖管理,开发者只需要引入相应的 Starter 依赖,Spring Boot 会自动处理相关的依赖和配置。
- MyBatis 中 #{} 和 ${} 的区别:
- #{} 是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为 ? 占位符,然后使用 PreparedStatement 进行预编译,能有效防止 SQL 注入。
- ${} 是字符串替换,MyBatis 在处理 ${} 时,会直接将 ${} 替换为相应的值,可能会存在 SQL 注入风险。
- Redis 的数据类型:
- 字符串(String):最基本的数据类型,可以存储字符串、数字等。
- 哈希(Hash):类似于 Java 中的 HashMap,是一个键值对集合,适合存储对象。
- 列表(List):是一个有序的字符串列表,可以从两端进行插入和删除操作,常用于消息队列等场景。
- 集合(Set):是一个无序且唯一的字符串集合,支持交集、并集、差集等操作。
- 有序集合(Sorted Set):与集合类似,但每个元素都有一个分数,根据分数进行排序,常用于排行榜等场景。