互联网大厂 Java 面试:核心知识、框架与中间件大考验
严肃的面试官坐在桌前,面前放着求职者王铁牛的简历。王铁牛紧张地走进面试房间,坐在面试官对面,一场充满挑战的面试拉开了帷幕。
第一轮提问 面试官:首先问几个 Java 核心知识的问题。Java 中基本数据类型有哪些? 王铁牛:有 byte、short、int、long、float、double、char、boolean。 面试官:回答得不错。那 String 类为什么是不可变的呢? 王铁牛:因为 String 类是用 final 修饰的,它的成员变量也是 final 的,所以不可变。 面试官:很好。那在 Java 里,接口和抽象类有什么区别? 王铁牛:接口里的方法默认是抽象的,而且不能有方法体,抽象类可以有具体的方法。接口的成员变量默认是 public static final 的,抽象类不是。
第二轮提问 面试官:接下来考考你 JUC 和多线程的知识。什么是线程池?为什么要使用线程池? 王铁牛:线程池就是管理线程的一个池子,使用它可以减少线程创建和销毁的开销,提高性能。 面试官:不错。那线程池有哪些创建方式? 王铁牛:可以用 Executors 类的 newFixedThreadPool、newCachedThreadPool 这些方法创建。 面试官:那在多线程环境下,如何保证线程安全呢? 王铁牛:可以用 synchronized 关键字或者 Lock 接口。 面试官:那 synchronized 和 Lock 有什么区别呢? 王铁牛:呃……这个……好像 synchronized 是自动释放锁,Lock 要手动释放,其他的我有点不太清楚了。
第三轮提问 面试官:现在来聊聊一些框架和中间件。Spring 框架的核心特性有哪些? 王铁牛:有 IoC(控制反转)和 AOP(面向切面编程)。 面试官:很好。那 Spring Boot 相比 Spring 有什么优势呢? 王铁牛:Spring Boot 可以快速搭建项目,有很多默认配置,能减少开发时间。 面试官:那 MyBatis 中 #{} 和 ${} 的区别是什么? 王铁牛:嗯……这个……好像一个是预编译,一个不是,具体的我有点说不清楚了。 面试官:那 Dubbo 是做什么的,它的核心组件有哪些? 王铁牛:Dubbo 是分布式服务框架,核心组件……我就记得有注册中心,其他的不太记得了。
面试接近尾声,面试官放下手中的笔,看着王铁牛说:“今天的面试就到这里,你对一些基础问题回答得还可以,展现出了一定的 Java 知识储备。不过对于一些稍微复杂的问题,回答得不够完整和清晰,还需要加强对知识的深入理解。你先回家等通知吧,我们后续会综合评估后给你答复。”
问题答案详细解析
- Java 中基本数据类型有哪些?
- Java 有 8 种基本数据类型,分为 4 类:
- 整数类型:byte(1 字节,范围 -128 到 127)、short(2 字节)、int(4 字节)、long(8 字节)。
- 浮点类型:float(4 字节)、double(8 字节)。
- 字符类型:char(2 字节),用于表示单个字符。
- 布尔类型:boolean(理论上 1 位,但实际实现可能占 1 字节),只有 true 和 false 两个值。
- Java 有 8 种基本数据类型,分为 4 类:
- String 类为什么是不可变的呢?
- String 类被声明为 final,意味着它不能被继承。其内部维护一个 final 的 char 数组 value 来存储字符串内容。
- 不可变的好处有:
- 安全性:在多线程环境下,不可变对象是线程安全的,避免了数据被意外修改。
- 缓存性:字符串常量池的实现依赖于 String 的不可变性,可以节省内存。
- 哈希码缓存:String 的哈希码被缓存,提高了在哈希集合(如 HashMap)中的性能。
- 在 Java 里,接口和抽象类有什么区别?
- 定义层面:
- 接口是一种完全抽象的类型,所有方法默认是 public abstract 的,不能有方法体(Java 8 之前),Java 8 之后可以有默认方法和静态方法。成员变量默认是 public static final 的。
- 抽象类是一种部分抽象的类,可以有抽象方法,也可以有具体方法。成员变量没有特殊的默认修饰。
- 继承与实现:
- 一个类可以实现多个接口,但只能继承一个抽象类。
- 使用场景:
- 接口用于定义一组行为规范,更强调“是什么”。
- 抽象类用于抽取子类的公共代码和行为,更强调“是一个”。
- 定义层面:
- 什么是线程池?为什么要使用线程池?
- 线程池是一种管理线程的机制,它包含一组预先创建的线程,当有任务提交时,线程池会从池中分配一个空闲线程来执行任务。
- 使用线程池的好处:
- 降低资源消耗:减少了线程创建和销毁的开销,因为线程可以重复使用。
- 提高响应速度:任务到达时,不需要等待线程创建就可以立即执行。
- 方便管理:可以对线程进行统一的管理,如设置线程的最大数量、控制任务的排队策略等。
- 线程池有哪些创建方式?
- Java 提供了 Executors 工具类来创建不同类型的线程池:
- newFixedThreadPool:创建一个固定大小的线程池,线程数量固定,当有新任务提交时,如果线程池中有空闲线程则执行,否则任务会在队列中等待。
- newCachedThreadPool:创建一个可缓存的线程池,线程数量不固定,会根据任务的数量动态创建和回收线程。如果线程空闲时间过长,会被回收。
- newSingleThreadExecutor:创建一个单线程的线程池,只有一个线程来执行任务,任务会按照提交的顺序依次执行。
- newScheduledThreadPool:创建一个定时任务的线程池,可用于执行定时任务或周期性任务。
- 不过,从 Java 7 开始,更推荐使用 ThreadPoolExecutor 来手动创建线程池,因为 Executors 创建的线程池可能存在一些潜在的风险,如 newCachedThreadPool 可能会创建大量的线程导致内存溢出。
- Java 提供了 Executors 工具类来创建不同类型的线程池:
- 在多线程环境下,如何保证线程安全呢?
- 同步机制:
- synchronized 关键字:可以修饰方法或代码块,保证同一时刻只有一个线程可以访问被修饰的方法或代码块。
- Lock 接口:如 ReentrantLock,需要手动加锁和解锁,相比 synchronized 更加灵活,可以实现公平锁和非公平锁,还可以实现可中断锁等。
- 原子类:如 AtomicInteger、AtomicLong 等,它们使用 CAS(Compare - And - Swap)算法实现,在多线程环境下可以保证对变量的原子操作。
- 线程安全的集合:如 ConcurrentHashMap、CopyOnWriteArrayList 等,这些集合在设计上考虑了多线程并发访问的情况,内部使用了一些同步机制来保证线程安全。
- 同步机制:
- synchronized 和 Lock 有什么区别呢?
- 语法层面:
- synchronized 是 Java 的关键字,是内置的语言特性,使用时不需要手动释放锁,当代码块或方法执行完毕,锁会自动释放。
- Lock 是一个接口,需要手动调用 lock() 方法加锁,unlock() 方法释放锁,一般需要在 finally 块中释放锁,以确保锁一定会被释放。
- 锁的特性:
- synchronized 是非公平锁,不能实现公平锁。
- Lock 可以实现公平锁和非公平锁,通过构造函数指定。
- 锁的灵活性:
- synchronized 无法判断锁的状态,只能阻塞等待。
- Lock 可以通过 tryLock() 方法尝试获取锁,并返回获取锁的结果,还可以使用 lockInterruptibly() 方法实现可中断锁。
- 语法层面:
- Spring 框架的核心特性有哪些?
- IoC(控制反转):也称为依赖注入(DI),是指将对象的创建和依赖关系的管理从代码中转移到 Spring 容器中。通过 Spring 容器来创建和管理对象,对象之间的依赖关系由容器注入,降低了代码的耦合度。
- AOP(面向切面编程):允许开发者在不修改原有业务逻辑的基础上,对程序进行增强,如日志记录、事务管理等。通过定义切面、切点和通知,将这些横切关注点与业务逻辑分离,提高了代码的可维护性和复用性。
- Spring Boot 相比 Spring 有什么优势呢?
- 快速搭建:Spring Boot 提供了大量的 Starter 依赖,通过简单的配置就可以快速搭建一个项目,减少了繁琐的配置过程。
- 自动配置:Spring Boot 会根据项目中引入的依赖自动进行配置,开发者只需要关注业务逻辑,无需手动配置大量的 Bean。
- 内嵌服务器:Spring Boot 可以内嵌 Tomcat、Jetty 等服务器,不需要单独部署服务器,方便开发和测试。
- 监控和管理:Spring Boot Actuator 提供了丰富的监控和管理功能,如健康检查、性能指标监控等。
- MyBatis 中 #{} 和 ${} 的区别是什么?
- #{} 是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为占位符 ?,然后使用 PreparedStatement 来执行 SQL,这样可以防止 SQL 注入攻击。
- {} 时,会将 ${} 直接替换为变量的值,可能会导致 SQL 注入问题。一般用于动态表名、动态列名等场景。
- Dubbo 是做什么的,它的核心组件有哪些?
- Dubbo 是一个高性能的分布式服务框架,用于解决分布式系统中服务之间的调用问题,提供了服务注册与发现、远程调用、集群容错等功能。
- 核心组件:
- 服务提供者(Provider):暴露服务的一方,将自己的服务注册到注册中心。
- 服务消费者(Consumer):调用服务的一方,从注册中心获取服务提供者的地址,然后调用服务。
- 注册中心(Registry):负责服务的注册和发现,服务提供者将服务信息注册到注册中心,服务消费者从注册中心获取服务提供者的信息。常见的注册中心有 ZooKeeper、Nacos 等。
- 监控中心(Monitor):统计服务的调用次数、调用时间等信息,为服务的性能优化提供依据。
- 调用代理(Proxy):Dubbo 会为服务生成代理对象,服务消费者通过代理对象调用服务,隐藏了远程调用的细节。