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

42 阅读11分钟

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

王铁牛怀揣着对互联网大厂的向往,走进了面试房间。严肃的面试官坐在对面,一场关于 Java 技术的考验即将开始。

第一轮提问 面试官:我们先从 Java 核心知识开始。能说一下 Java 中多态的实现方式有哪些吗? 王铁牛:多态的实现方式主要有两种,方法重载和方法重写。方法重载是在一个类中,有多个方法名相同,但参数列表不同;方法重写是子类重写父类的方法。 面试官:回答得不错。那 Java 中基本数据类型有哪些,它们的包装类分别是什么? 王铁牛:Java 基本数据类型有 byte、short、int、long、float、double、char、boolean。对应的包装类分别是 Byte、Short、Integer、Long、Float、Double、Character、Boolean。 面试官:很好。那在 Java 里,String 类为什么是不可变的,有什么好处呢? 王铁牛:String 类不可变是因为它内部使用了一个被 final 修饰的字符数组来存储字符串。好处是可以提高性能,比如作为哈希表的键时,不可变性能保证哈希值的唯一性,还能保证线程安全。

第二轮提问 面试官:接下来聊聊 JUC 和多线程。说说线程的几种状态以及它们之间是如何转换的? 王铁牛:线程有新建、就绪、运行、阻塞、死亡这几种状态。新建状态就是创建了线程对象还没调用 start 方法;调用 start 方法后进入就绪状态,等待 CPU 调度;CPU 调度后进入运行状态;如果遇到阻塞事件,比如等待 I/O 操作,就进入阻塞状态;当线程执行完任务或者出现异常就进入死亡状态。 面试官:回答得挺清晰。那 Java 中的锁机制了解吗,比如 synchronized 和 Lock 有什么区别? 王铁牛:synchronized 是 Java 的关键字,是隐式锁,由 JVM 来管理锁的获取和释放;Lock 是一个接口,是显式锁,需要手动获取和释放锁。synchronized 是可重入、非公平、不可中断的;Lock 可以实现公平锁、可中断锁等。 面试官:不错。那线程池的作用是什么,有哪些创建线程池的方式? 王铁牛:线程池的作用是减少线程创建和销毁的开销,提高性能,还能控制并发线程的数量。创建线程池可以使用 Executors 工具类创建,比如 newFixedThreadPool、newCachedThreadPool 等,也可以直接使用 ThreadPoolExecutor 来创建。

第三轮提问 面试官:现在来谈谈一些框架和中间件。Spring 的核心特性有哪些,说说你对 IoC 和 AOP 的理解? 王铁牛:Spring 的核心特性有 IoC 和 AOP。IoC 就是控制反转,把对象的创建和依赖关系的管理交给 Spring 容器,降低了代码的耦合度。AOP 是面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护,比如可以在不修改原有业务逻辑的情况下进行日志记录、事务管理等。 面试官:理解得还可以。那 Spring Boot 和 Spring 有什么区别,Spring Boot 的优势在哪里? 王铁牛:Spring Boot 是基于 Spring 的,它简化了 Spring 应用的开发过程。Spring Boot 有自动配置功能,减少了大量的配置文件,还内置了嵌入式服务器,方便开发和部署。 面试官:最后问一下,Redis 有哪些数据类型,在实际业务中如何应用? 王铁牛:Redis 有字符串、哈希、列表、集合、有序集合这些数据类型。字符串可以用来缓存数据,哈希可以用来存储对象,列表可以实现消息队列,集合可以用于去重和交集运算,有序集合可以实现排行榜。

面试官:今天的面试就到这里,你先回家等通知吧。我们会综合评估后给你反馈。

答案详细解析

  1. Java 多态的实现方式
    • 方法重载:在同一个类中,方法名相同,但参数列表不同(参数的类型、个数、顺序不同)。编译器会根据调用方法时传入的参数来决定调用哪个具体的方法。例如:
public class OverloadExample {
    public int add(int a, int b) {
        return a + b;
    }
    public double add(double a, double b) {
        return a + b;
    }
}
- **方法重写**:子类继承父类后,重写父类中的方法。重写要求方法名、参数列表、返回值类型都相同,访问修饰符不能比父类的更严格。例如:
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");
    }
}
  1. Java 基本数据类型和包装类
    • 基本数据类型
      • byte:8 位有符号整数,范围是 -128 到 127。
      • short:16 位有符号整数,范围是 -32768 到 32767。
      • int:32 位有符号整数,范围是 -2147483648 到 2147483647。
      • long:64 位有符号整数,范围更大,定义时需要在数字后面加 L。
      • float:32 位单精度浮点数,定义时需要在数字后面加 F。
      • double:64 位双精度浮点数。
      • char:16 位 Unicode 字符。
      • boolean:表示布尔值,只有 true 和 false 两个值。
    • 包装类:基本数据类型没有方法和属性,包装类为基本数据类型提供了方法和属性,方便进行一些操作。例如 Integer 类有 parseInt 方法可以将字符串转换为整数。
  2. String 类不可变的原因和好处
    • 原因:String 类内部使用了一个被 final 修饰的字符数组 private final char value[]; 来存储字符串。final 修饰的数组一旦初始化,其引用不能再指向其他数组,并且 String 类没有提供修改字符数组内容的方法。
    • 好处
      • 提高性能:因为字符串不可变,所以可以在常量池中缓存字符串,当多个地方使用相同的字符串时,可以直接从常量池中获取,避免了重复创建对象,节省了内存。
      • 线程安全:在多线程环境下,不可变对象是线程安全的,因为多个线程可以同时访问同一个字符串对象,而不用担心数据被修改。
      • 作为哈希表的键:不可变性能保证哈希值的唯一性,在哈希表中使用字符串作为键时,能确保哈希表的正确性。
  3. 线程的几种状态及转换
    • 新建状态(New):创建了线程对象,但还没有调用 start 方法。例如 Thread t = new Thread();
    • 就绪状态(Runnable):调用了 start 方法后,线程进入就绪状态,等待 CPU 调度。
    • 运行状态(Running):CPU 调度该线程,线程开始执行 run 方法中的代码。
    • 阻塞状态(Blocked):线程在执行过程中遇到一些阻塞事件,比如等待 I/O 操作、等待锁等,会进入阻塞状态。阻塞解除后,线程会回到就绪状态。
    • 死亡状态(Terminated):线程执行完 run 方法中的代码或者出现异常,线程结束,进入死亡状态。
  4. synchronized 和 Lock 的区别
    • 实现方式
      • synchronized:是 Java 的关键字,由 JVM 来管理锁的获取和释放。它可以修饰方法和代码块。修饰方法时,锁的对象是当前实例对象(实例方法)或类对象(静态方法);修饰代码块时,需要指定锁的对象。
      • Lock:是一个接口,常用的实现类有 ReentrantLock。需要手动调用 lock 方法获取锁,调用 unlock 方法释放锁,通常在 finally 块中释放锁,以确保锁一定会被释放。
    • 特性
      • synchronized:是可重入、非公平、不可中断的。可重入是指同一个线程可以多次获取同一把锁;非公平是指线程获取锁的顺序不是按照请求锁的顺序;不可中断是指线程在等待锁的过程中不能被中断。
      • Lock:可以实现公平锁、可中断锁等。通过构造函数可以指定是否为公平锁;可以使用 lockInterruptibly 方法实现可中断锁。
  5. 线程池的作用和创建方式
    • 作用
      • 减少开销:减少线程创建和销毁的开销,因为线程的创建和销毁是比较消耗资源的。
      • 提高性能:可以复用线程,提高了程序的执行效率。
      • 控制并发数量:可以控制线程池中的线程数量,避免过多线程导致系统资源耗尽。
    • 创建方式
      • 使用 Executors 工具类
        • newFixedThreadPool:创建一个固定大小的线程池,线程数量固定,当有新任务提交时,如果线程池中有空闲线程,则执行任务;如果没有空闲线程,则任务会被放入队列中等待。
        • newCachedThreadPool:创建一个可缓存的线程池,线程数量不固定,如果有新任务提交,且线程池中有空闲线程,则执行任务;如果没有空闲线程,则创建新线程执行任务。当线程空闲时间超过一定时间后,会被回收。
        • newSingleThreadExecutor:创建一个单线程的线程池,只有一个线程执行任务,任务会按照提交的顺序依次执行。
      • 使用 ThreadPoolExecutor:可以自定义线程池的参数,如核心线程数、最大线程数、线程空闲时间、任务队列等。例如:
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    2, // 核心线程数
    5, // 最大线程数
    60, // 线程空闲时间
    TimeUnit.SECONDS,
    new LinkedBlockingQueue<Runnable>() // 任务队列
);
  1. Spring 的核心特性:IoC 和 AOP
    • IoC(控制反转):传统的程序中,对象的创建和依赖关系的管理是由程序自己负责的,而在 Spring 中,把这些工作交给了 Spring 容器。通过 XML 配置文件、注解等方式,Spring 容器会自动创建对象并注入依赖。例如:
public class UserService {
    private UserDao userDao;
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }
    // 其他方法
}

在 Spring 配置文件中配置 UserDao 和 UserService 的关系,Spring 容器会自动创建 UserDao 和 UserService 对象,并将 UserDao 注入到 UserService 中。 - AOP(面向切面编程):AOP 是一种编程范式,通过预编译方式和运行期动态代理实现程序功能的统一维护。在 Spring 中,AOP 可以用来实现日志记录、事务管理等功能。例如,使用 AspectJ 注解实现日志记录:

@Aspect
@Component
public class LoggingAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void beforeAdvice() {
        System.out.println("Before method execution");
    }
}

上面的代码表示在 com.example.service 包下的所有方法执行前,会执行 beforeAdvice 方法。 8. Spring Boot 和 Spring 的区别及 Spring Boot 的优势 - 区别:Spring 是一个轻量级的 Java 开发框架,提供了 IoC、AOP 等核心功能,但需要大量的配置文件来进行配置。Spring Boot 是基于 Spring 的,它简化了 Spring 应用的开发过程。 - 优势: - 自动配置:Spring Boot 有自动配置功能,根据项目中引入的依赖,自动进行配置,减少了大量的配置文件。例如,引入了 Spring Data JPA 和 MySQL 驱动,Spring Boot 会自动配置数据源和 JPA。 - 嵌入式服务器:Spring Boot 内置了嵌入式服务器,如 Tomcat、Jetty 等,不需要单独部署服务器,方便开发和部署。 - 起步依赖:Spring Boot 提供了一系列的起步依赖,通过引入相应的起步依赖,就可以快速搭建项目。例如,引入 spring-boot-starter-web 就可以快速搭建一个 Web 项目。 9. Redis 的数据类型及实际应用 - 字符串(String):最基本的数据类型,可以存储字符串、数字等。可以用于缓存数据,比如缓存用户信息、商品信息等。例如,使用 SET key value 命令存储数据,使用 GET key 命令获取数据。 - 哈希(Hash):类似于 Java 中的 HashMap,用于存储键值对。可以用来存储对象,比如用户对象的各个属性。例如,使用 HSET key field value 命令存储字段和值,使用 HGET key field 命令获取字段的值。 - 列表(List):是一个双向链表,可以在列表的两端进行插入和删除操作。可以实现消息队列,生产者将消息插入到列表的一端,消费者从列表的另一端取出消息。例如,使用 LPUSH key value 命令将值插入到列表的头部,使用 RPOP key 命令从列表的尾部取出值。 - 集合(Set):是一个无序且唯一的数据集合。可以用于去重和交集运算,比如统计网站的独立访客数。例如,使用 SADD key member 命令向集合中添加元素,使用 SINTER key1 key2 命令求两个集合的交集。 - 有序集合(Sorted Set):和集合类似,也是唯一的,但每个元素有一个分数,根据分数进行排序。可以实现排行榜,比如游戏中的玩家排行榜。例如,使用 ZADD key score member 命令向有序集合中添加元素,使用 ZRANGE key start stop 命令获取指定范围的元素。