互联网大厂 Java 面试:核心知识、框架与组件大考验
第一轮面试开始 面试官:首先问你几个 Java 核心知识的问题。Java 中基本数据类型有哪些? 王铁牛:这个我知道,Java 基本数据类型有 byte、short、int、long、float、double、char、boolean。 面试官:不错。那说说面向对象的四大特性是什么? 王铁牛:是封装、继承、多态和抽象。 面试官:很好。在 Java 中,String 类为什么是不可变的? 王铁牛:因为 String 类内部使用了一个 final 修饰的字符数组来存储字符串内容,所以它是不可变的。 面试官:回答得非常好,基础很扎实。
第二轮面试开始 面试官:进入 JUC 和多线程相关的问题。JUC 包是什么? 王铁牛:JUC 就是 java.util.concurrent 包,它提供了很多用于多线程编程的工具类。 面试官:那创建线程有几种方式? 王铁牛:有四种,继承 Thread 类、实现 Runnable 接口、实现 Callable 接口、使用线程池。 面试官:当多个线程访问一个共享资源时,会出现什么问题,怎么解决? 王铁牛:会出现线程安全问题,比如数据不一致。可以使用 synchronized 关键字或者 Lock 接口来解决。 面试官:非常棒,对多线程这块掌握得很清晰。
第三轮面试开始 面试官:现在考考你关于框架和中间件的知识。Spring 框架的核心是什么? 王铁牛:Spring 核心是 IoC(控制反转)和 AOP(面向切面编程)。 面试官:Spring Boot 有什么优点? 王铁牛:它可以快速搭建项目,简化配置,内嵌服务器,还能自动配置。 面试官:MyBatis 中 #{} 和 ${} 的区别是什么? 王铁牛:呃……这个,好像……一个是预编译,一个不是预编译。具体的我有点说不清楚了。 面试官:这里还是要理解清楚的。Dubbo 是什么,有什么作用? 王铁牛:Dubbo 是个……好像是个分布式服务框架,能做服务调用啥的,但具体原理我不太明白。 面试官:好吧,那 RabbitMQ 有哪些使用场景? 王铁牛:我知道能做消息队列,但具体场景我也说不太好。
面试结束,面试官总结道:“王铁牛,从前面几轮的回答来看,你对 Java 核心知识、JUC 和多线程部分掌握得比较扎实,回答得都很不错。不过在框架和中间件这一块,对于一些细节和原理的理解还不够深入,像 MyBatis、Dubbo、RabbitMQ 等方面的问题回答得不够清晰准确。我们后续会综合评估这次面试情况,你先回家等通知吧。”
问题答案详细解析
- 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。
- 面向对象的四大特性:
- 封装:将数据和操作数据的方法绑定在一起,隐藏对象的内部实现细节,只对外提供必要的接口。例如,一个类中的属性可以使用 private 修饰,通过 public 的 getter 和 setter 方法来访问和修改属性。
- 继承:一个类可以继承另一个类的属性和方法,被继承的类称为父类(基类),继承的类称为子类(派生类)。子类可以重写父类的方法,实现代码的复用和扩展。
- 多态:同一个行为具有多个不同表现形式或形态的能力。多态通过继承和方法重写以及接口实现来实现,在运行时根据对象的实际类型来调用相应的方法。
- 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象类和接口是实现抽象的两种方式,抽象类中可以有抽象方法和具体方法,而接口中只能有抽象方法(Java 8 及以后可以有默认方法和静态方法)。
- String 类不可变的原因:
- String 类内部使用
private final char value[];来存储字符串内容。final修饰的数组意味着引用不可变,即不能再指向其他数组对象。同时,String 类没有提供修改字符数组内容的公共方法,所以一旦 String 对象创建,其内容就不能被改变。这种不可变性带来了很多好处,比如线程安全、可以作为 HashMap 的键等。
- String 类内部使用
- JUC 包:
- java.util.concurrent 包是 Java 提供的用于多线程编程的工具包,包含了很多实用的类和接口。例如,
ReentrantLock可重入锁、CountDownLatch倒计时器、CyclicBarrier循环栅栏、Semaphore信号量等,这些类可以帮助开发者更方便地实现多线程之间的同步和协作。
- java.util.concurrent 包是 Java 提供的用于多线程编程的工具包,包含了很多实用的类和接口。例如,
- 创建线程的四种方式:
- 继承 Thread 类:创建一个类继承自
Thread类,重写run()方法,在run()方法中定义线程要执行的任务。然后创建该类的对象,调用start()方法启动线程。 - 实现 Runnable 接口:创建一个类实现
Runnable接口,实现run()方法。然后将该类的对象作为参数传递给Thread类的构造函数,创建Thread对象并调用start()方法启动线程。这种方式避免了单继承的局限性。 - 实现 Callable 接口:创建一个类实现
Callable接口,实现call()方法,call()方法可以有返回值。通过FutureTask类来包装Callable对象,将FutureTask对象作为参数传递给Thread类的构造函数,创建Thread对象并调用start()方法启动线程。可以通过FutureTask的get()方法获取线程执行的结果。 - 使用线程池:通过
Executors类提供的静态方法创建线程池,如Executors.newFixedThreadPool(int nThreads)创建固定大小的线程池。将实现了Runnable或Callable接口的任务提交给线程池执行。线程池可以复用线程,减少线程创建和销毁的开销,提高性能。
- 继承 Thread 类:创建一个类继承自
- 多线程访问共享资源的问题及解决方法:
- 问题:当多个线程同时访问一个共享资源时,可能会出现数据不一致的问题,比如一个线程正在修改共享资源,而另一个线程同时读取该资源,就可能读到不一致的数据。这种问题称为线程安全问题。
- 解决方法:
- synchronized 关键字:可以修饰方法或代码块。当修饰方法时,该方法在同一时间只能被一个线程访问;当修饰代码块时,需要指定一个对象作为锁,同一时间只有获得该锁的线程才能执行该代码块。
- Lock 接口:
ReentrantLock是Lock接口的一个实现类,使用lock()方法获取锁,使用unlock()方法释放锁。与synchronized相比,ReentrantLock提供了更灵活的锁机制,比如可以实现公平锁、可中断锁等。
- Spring 框架的核心:
- IoC(控制反转):也称为依赖注入(DI),是一种将对象的创建和依赖关系的管理从代码中转移到外部容器的设计思想。Spring 容器负责创建对象,并将对象之间的依赖关系注入到对象中。例如,一个类需要依赖另一个类的对象,传统方式是在类内部创建该对象,而在 Spring 中,通过配置文件或注解告诉 Spring 容器该类需要依赖哪个对象,Spring 容器会自动创建并注入该对象。
- AOP(面向切面编程):是一种在不修改原有代码的基础上,对程序进行增强的编程思想。AOP 将程序中的一些通用功能(如日志记录、事务管理等)提取出来,形成一个独立的模块(切面),在程序运行时将这些切面动态地织入到目标方法中。Spring AOP 主要基于代理模式实现,有 JDK 动态代理和 CGLIB 代理两种方式。
- Spring Boot 的优点:
- 快速搭建项目:Spring Boot 提供了很多 Starter 依赖,只需要在项目中添加相应的 Starter 依赖,就可以快速集成各种功能,减少了繁琐的配置。
- 简化配置:Spring Boot 采用了约定大于配置的原则,很多配置都有默认值,开发者只需要根据实际需求进行少量的配置即可。
- 内嵌服务器:Spring Boot 内嵌了 Tomcat、Jetty 等服务器,不需要额外部署服务器,直接运行项目的主类就可以启动应用。
- 自动配置:Spring Boot 会根据项目中引入的依赖自动进行配置,比如引入了 Spring Data JPA 依赖,Spring Boot 会自动配置数据源和 JPA 相关的配置。
- MyBatis 中 #{} 和 ${} 的区别:
- #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为占位符
?,然后使用PreparedStatement来执行 SQL 语句,这样可以防止 SQL 注入攻击。例如,SELECT * FROM user WHERE id = #{id},MyBatis 会将其转换为SELECT * FROM user WHERE id = ?,然后将参数值安全地设置到占位符中。 - {} 时,会直接将 {tableName}
,如果参数tableName的值是user,则最终的 SQL 语句为SELECT * FROM user`。由于是直接替换,所以使用 ${} 可能会存在 SQL 注入的风险,一般用于表名、列名等动态替换的场景。
- #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为占位符
- Dubbo 是什么及作用:
- Dubbo 是阿里巴巴开源的一个高性能、轻量级的分布式服务框架,用于解决分布式系统中服务之间的调用问题。
- 作用:
- 服务注册与发现:Dubbo 提供了服务注册中心(如 Zookeeper、Nacos 等),服务提供者将自己的服务注册到注册中心,服务消费者从注册中心获取服务提供者的地址信息,实现服务的自动发现。
- 远程调用:Dubbo 支持多种远程调用协议,如 Dubbo 协议、HTTP 协议等,服务消费者可以通过这些协议远程调用服务提供者的服务,就像调用本地方法一样。
- 负载均衡:Dubbo 提供了多种负载均衡策略,如随机、轮询、最少活跃调用数等,根据不同的策略将请求分发到不同的服务提供者上,提高系统的性能和可用性。
- 集群容错:当服务提供者出现故障时,Dubbo 提供了多种集群容错机制,如失败重试、快速失败、集群容错等,保证服务的高可用性。
- RabbitMQ 的使用场景:
- 异步处理:将一些耗时的操作(如发送邮件、短信等)通过消息队列异步处理,提高系统的响应速度。例如,用户注册时,将发送注册成功邮件的任务放入 RabbitMQ 队列中,主线程可以继续处理其他业务,而邮件发送任务由专门的消费者线程从队列中取出并执行。
- 应用解耦:不同的应用之间通过消息队列进行通信,降低了应用之间的耦合度。例如,一个电商系统中的订单服务和库存服务,订单服务在创建订单时将消息发送到 RabbitMQ 队列,库存服务从队列中获取消息并更新库存,这样订单服务和库存服务可以独立开发和部署。
- 流量削峰:在高并发场景下,将请求放入消息队列中,通过控制消费者的消费速度来平滑处理请求,避免系统因瞬间高流量而崩溃。例如,在电商的秒杀活动中,将用户的秒杀请求放入 RabbitMQ 队列,系统按照一定的速度从队列中取出请求进行处理。