《互联网大厂 Java 面试:核心知识、框架与中间件大考验》

35 阅读11分钟

互联网大厂 Java 面试:核心知识、框架与中间件大考验

王铁牛怀揣着对互联网大厂的憧憬,坐在了面试官面前,一场紧张的面试拉开了帷幕。

第一轮提问 面试官:我们先从 Java 核心知识开始。你能说一下 Java 中基本数据类型有哪些吗? 王铁牛:当然可以,Java 基本数据类型有 byte、short、int、long、float、double、char、boolean。 面试官:回答得不错。那你说说 Java 中重载和重写的区别是什么? 王铁牛:重载是在同一个类中,方法名相同但参数列表不同;重写是子类对父类中方法的实现进行重新编写,方法名、参数列表和返回值类型都要相同。 面试官:很好,理解得很清晰。那 Java 中的多态是如何实现的? 王铁牛:多态主要通过继承、接口实现和方法重写来实现。父类引用指向子类对象,调用重写方法时会根据实际对象类型来执行相应的方法。 面试官:非常棒,看来你对 Java 核心基础掌握得很扎实。

第二轮提问 面试官:接下来我们聊聊 JUC 和多线程。什么是线程安全? 王铁牛:线程安全就是在多线程环境下,对共享资源的访问不会出现数据不一致等问题。 面试官:那你说一下 Java 中创建线程的几种方式? 王铁牛:有三种方式,继承 Thread 类、实现 Runnable 接口、实现 Callable 接口并结合 FutureTask。 面试官:不错。那线程池有什么作用呢? 王铁牛:线程池可以避免频繁创建和销毁线程带来的性能开销,提高系统的响应速度和资源利用率。 面试官:很好。那你说说线程池的核心参数有哪些? 王铁牛:有 corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(线程空闲存活时间)、TimeUnit(时间单位)、workQueue(任务队列)、threadFactory(线程工厂)和 handler(拒绝策略)。 面试官:回答得很全面,这部分知识掌握得很好。

第三轮提问 面试官:现在来谈谈一些框架和中间件。Spring 框架的核心特性有哪些? 王铁牛:Spring 核心特性有 IoC(控制反转)和 AOP(面向切面编程)。 面试官:那 Spring Boot 有什么优势呢? 王铁牛:Spring Boot 可以快速搭建项目,简化配置,内置服务器,提高开发效率。 面试官:MyBatis 中 #{} 和 {} 的区别是什么? **王铁牛**:#{} 是预编译处理,能防止 SQL 注入;{} 是字符串替换,可能存在 SQL 注入风险。 面试官:那 Dubbo 是什么,它的作用是什么? 王铁牛:Dubbo 是一个高性能的分布式服务框架,用于实现服务的远程调用和服务治理。 面试官:看来你对这些框架有一定的了解。RabbitMQ 有哪些工作模式? 王铁牛:这个……嗯……好像有简单模式、工作队列模式,其他的我有点记不太清了。 面试官:没关系,整体表现还可以。xxl - job 你了解多少? 王铁牛:xxl - job 是一个分布式任务调度平台,但是具体的原理和使用细节我不是很清楚。 面试官:Redis 有哪些数据类型呢? 王铁牛:有字符串、哈希、列表、集合、有序集合。不过关于它们的具体应用场景我有点说不明白。

面试接近尾声,面试官总结道:“王铁牛,通过这次面试,我能看出你对 Java 核心知识、多线程和一些基础框架有一定的掌握,在前面两轮的回答中表现得很不错,对于基本概念和常见问题都能准确回答。但是在第三轮关于一些中间件和框架的深入问题上,你有些回答不够完善,对于部分知识的理解和掌握还不够深入和全面。我们后续会综合评估所有面试者的情况,你先回家等通知吧。”

答案详解

  1. Java 基本数据类型
    • byte:8 位,有符号,范围 -128 到 127。
    • short:16 位,有符号,范围 -32768 到 32767。
    • int:32 位,有符号,范围 -2147483648 到 2147483647。
    • long:64 位,有符号,范围 -9223372036854775808 到 9223372036854775807,定义时需在数字后加 L。
    • float:32 位,单精度浮点数,定义时需在数字后加 F。
    • double:64 位,双精度浮点数。
    • char:16 位,无符号,用于表示单个字符,用单引号括起来。
    • boolean:只有两个值 true 和 false。
  2. 重载和重写的区别
    • 重载:在同一个类中,方法名相同但参数列表不同(参数类型、个数、顺序不同),与返回值类型无关。编译器根据调用时传入的参数来决定调用哪个重载方法。
    • 重写:子类对父类中方法的实现进行重新编写,方法名、参数列表和返回值类型都要相同(子类返回值类型可以是父类返回值类型的子类)。重写遵循“两同两小一大”原则,即方法名相同、参数列表相同;子类返回值类型小于等于父类返回值类型,子类抛出的异常小于等于父类抛出的异常;子类的访问权限大于等于父类的访问权限。
  3. Java 中多态的实现
    • 继承:子类继承父类,并重写父类的方法。
    • 接口实现:类实现接口,实现接口中的方法。
    • 方法重写:在运行时,根据实际对象的类型来调用相应的重写方法。例如:
class Animal {
    public void eat() {
        System.out.println("Animal is eating");
    }
}
class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("Dog is eating");
    }
}
public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog();
        animal.eat(); // 输出 Dog is eating
    }
}
  1. 线程安全:在多线程环境下,多个线程同时访问共享资源时,不会出现数据不一致、脏读、幻读等问题。例如,多个线程同时对一个计数器进行自增操作,如果没有进行同步控制,可能会导致计数器的值不准确,这就是线程不安全的情况。可以通过加锁(如 synchronized、Lock)、使用线程安全的类(如 ConcurrentHashMap)等方式来保证线程安全。
  2. 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 接口并结合 FutureTask:创建一个类实现 Callable 接口,实现 call 方法,该方法有返回值。将该类的对象作为参数传递给 FutureTask 类的构造函数,再将 FutureTask 对象作为参数传递给 Thread 类的构造函数,最后调用 start 方法启动线程。可以通过 FutureTask 的 get 方法获取线程执行的结果。
import java.util.concurrent.*;

class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return 1 + 2;
    }
}
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);
    }
}
  1. 线程池的作用
    • 减少线程创建和销毁的开销:频繁创建和销毁线程会消耗大量的系统资源,线程池可以复用线程,避免了这种开销。
    • 提高系统响应速度:当有任务提交时,线程池中有空闲线程可以立即执行任务,而不需要等待新线程的创建。
    • 便于线程管理:可以对线程的数量、生命周期等进行管理,例如设置核心线程数、最大线程数等。
  2. 线程池的核心参数
    • corePoolSize:核心线程数,线程池在初始化时会创建的线程数量。当有任务提交时,会优先使用核心线程来执行任务。
    • maximumPoolSize:最大线程数,线程池允许创建的最大线程数量。当任务队列满了,并且核心线程都在执行任务时,会创建新的线程直到达到最大线程数。
    • keepAliveTime:线程空闲存活时间,当线程空闲时间超过该时间,且线程数量大于核心线程数时,多余的线程会被销毁。
    • TimeUnit:时间单位,用于指定 keepAliveTime 的时间单位,如 TimeUnit.SECONDS 表示秒。
    • workQueue:任务队列,用于存储提交的任务。常见的任务队列有 ArrayBlockingQueue、LinkedBlockingQueue 等。
    • threadFactory:线程工厂,用于创建线程,可以自定义线程的名称、优先级等。
    • handler:拒绝策略,当任务队列满了,并且线程数量达到最大线程数时,新提交的任务会触发拒绝策略。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(让提交任务的线程来执行任务)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务)。
  3. Spring 框架的核心特性
    • IoC(控制反转):将对象的创建和依赖关系的管理从代码中转移到 Spring 容器中。通过配置文件或注解的方式,Spring 容器负责创建和管理对象,并将对象之间的依赖关系注入到对象中。例如,使用 @Autowired 注解可以自动注入依赖的对象。
    • AOP(面向切面编程):通过将横切关注点(如日志记录、事务管理等)与业务逻辑分离,提高代码的可维护性和可复用性。Spring AOP 基于代理模式实现,可以在方法执行前后、异常抛出等时机插入额外的代码。
  4. Spring Boot 的优势
    • 快速搭建项目:提供了 Spring Initializr 等工具,可以快速生成项目骨架,减少了繁琐的配置过程。
    • 简化配置:通过自动配置机制,Spring Boot 会根据项目中引入的依赖自动进行配置,减少了手动配置的工作量。
    • 内置服务器:内置了 Tomcat、Jetty 等服务器,无需单独部署服务器,直接运行项目即可。
    • 提高开发效率:提供了丰富的 Starter 依赖,只需要引入相应的 Starter 就可以快速集成各种功能。
  5. MyBatis 中 #{} 和 ${} 的区别
    • #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为占位符?,然后使用 PreparedStatement 进行参数设置,能有效防止 SQL 注入。
    • :是字符串替换,MyBatis在处理{}:是字符串替换,MyBatis 在处理 {} 时,会直接将 替换为传入的参数值,可能会导致SQL注入问题。一般在需要动态传入表名、列名等情况时使用{} 替换为传入的参数值,可能会导致 SQL 注入问题。一般在需要动态传入表名、列名等情况时使用 {},但要注意对传入的参数进行严格的验证。
  6. Dubbo:是一个高性能的分布式服务框架,用于实现服务的远程调用和服务治理。它提供了服务注册与发现、远程调用、集群容错、负载均衡等功能。服务提供者将自己的服务注册到注册中心,服务消费者从注册中心获取服务提供者的地址信息,然后通过远程调用的方式调用服务提供者的服务。
  7. RabbitMQ 的工作模式
    • 简单模式:一个生产者对应一个消费者,生产者将消息发送到队列,消费者从队列中获取消息。
    • 工作队列模式:一个生产者对应多个消费者,多个消费者竞争处理队列中的消息,可以提高消息处理的效率。
    • 发布 - 订阅模式:生产者将消息发送到交换机,交换机将消息广播到所有绑定的队列,每个队列对应一个消费者。
    • 路由模式:生产者将消息发送到交换机,交换机根据消息的路由键将消息发送到对应的队列。
    • 主题模式:与路由模式类似,但路由键可以使用通配符,更加灵活。
  8. xxl - job:是一个分布式任务调度平台,支持分布式任务的调度和管理。它采用 Master - Slave 架构,Master 负责任务的调度和分配,Slave 负责执行任务。可以通过 Web 界面进行任务的配置、管理和监控,支持任务的定时执行、任务依赖、失败重试等功能。
  9. Redis 数据类型
    • 字符串(String):最基本的数据类型,可以存储字符串、数字等。常见操作有 set、get 等,可用于缓存、计数器等场景。
    • 哈希(Hash):类似于 Java 中的 HashMap,存储键值对集合。适合存储对象信息,如用户信息。
    • 列表(List):是一个双向链表,可以在列表的两端进行插入和删除操作。可用于实现消息队列、栈等。
    • 集合(Set):无序且唯一的元素集合,支持交集、并集、差集等操作。可用于去重、共同好友等场景。
    • 有序集合(Sorted Set):与集合类似,但每个元素有一个分数,根据分数进行排序。可用于排行榜等场景。