互联网大厂面试:Java核心知识、框架与中间件大考验
严肃的面试官坐在办公桌后,面前放着王铁牛的简历。王铁牛则略显紧张地坐在对面,双手不自觉地攥着衣角。
第一轮提问
- 面试官:首先问几个Java核心知识的问题。Java中基本数据类型有哪些?
- 王铁牛:这个我知道,有byte、short、int、long、float、double、char、boolean。
- 面试官:回答得不错。那说说Java中多态的实现方式有哪些?
- 王铁牛:多态的实现方式主要有继承和接口。通过子类重写父类的方法,或者实现接口中的方法,就可以实现多态。
- 面试官:很好。再问一个,Java中的访问修饰符有哪些,分别有什么作用?
- 王铁牛:访问修饰符有public、protected、default(默认,不写修饰符)和private。public可以被任何类访问;protected可以被同一个包内的类以及不同包内的子类访问;default只能被同一个包内的类访问;private只能在本类中访问。
- 面试官:非常棒,基础很扎实。
第二轮提问
- 面试官:接下来进入JUC和多线程的部分。说说线程的几种状态。
- 王铁牛:线程有新建、就绪、运行、阻塞和死亡这几种状态。新建就是刚创建线程对象,就绪是线程准备好可以执行,运行就是正在执行,阻塞是线程暂时停止执行,死亡就是线程执行完毕或者异常终止。
- 面试官:不错。那线程池有哪些常用的创建方式?
- 王铁牛:可以通过Executors工具类创建,比如newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor 。
- 面试官:那使用线程池有什么好处呢?
- 王铁牛:能降低资源消耗,提高响应速度,还可以方便管理线程。
- 面试官:回答得挺好。那再问一个,JUC中的CountDownLatch是怎么用的?
- 王铁牛:嗯……这个嘛,好像是用来控制线程的,具体怎么用我有点不太清楚了。
第三轮提问
- 面试官:现在问一些框架和中间件的问题。Spring的IoC和AOP是什么?
- 王铁牛:IoC是控制反转,把对象的创建和管理交给Spring容器。AOP是面向切面编程,通过切面来增强功能,比如日志记录、事务管理等。
- 面试官:很好。那Spring Boot的自动配置原理是什么?
- 王铁牛:这个……好像是根据classpath下的依赖和配置文件自动配置Bean,具体细节我不太能说清楚。
- 面试官:MyBatis是如何实现数据库操作的?
- 王铁牛:它通过映射文件或者注解把SQL语句和Java方法关联起来,然后执行SQL操作数据库。
- 面试官:那Dubbo的负载均衡策略有哪些?
- 王铁牛:这个我就不太了解了,好像有几种,但具体我记不清了。
面试总结 面试官扶了扶眼镜,看着王铁牛说:“通过这几轮的提问,能看出来你对Java的一些基础知识掌握得还不错,像Java核心知识里的基本数据类型、多态实现方式、访问修饰符,以及多线程的线程状态、线程池的创建和好处等都回答得很好,说明你有一定的基础。但是在一些更深入的知识点上,比如JUC里的CountDownLatch的使用,Spring Boot的自动配置原理,Dubbo的负载均衡策略等,你回答得不够清晰或者直接表示不太了解。这些知识点在实际的项目开发中是比较重要的,尤其是在我们这种互联网大厂的复杂业务场景下。我们需要员工不仅有扎实的基础,还能对这些高级特性有深入的理解和应用能力。目前你的表现有亮点,但也存在明显的不足。你先回家等通知吧,后续如果有进一步的消息,我们会及时联系你。”
问题答案详细解析
- Java中基本数据类型有哪些?
- Java有8种基本数据类型,分为4类:
- 整数类型:byte(1字节,-128 到 127)、short(2字节,-32768 到 32767)、int(4字节,-2147483648 到 2147483647)、long(8字节,范围更大)。
- 浮点类型:float(4字节,单精度浮点数)、double(8字节,双精度浮点数)。
- 字符类型:char(2字节,用于表示单个字符,采用Unicode编码)。
- 布尔类型:boolean(只有两个值,true和false)。
- Java有8种基本数据类型,分为4类:
- Java中多态的实现方式有哪些?
- 多态是指同一个行为具有多个不同表现形式或形态的能力。实现方式主要有两种:
- 继承:子类继承父类,并重写父类的方法。当通过父类引用指向子类对象时,调用该方法会执行子类重写后的方法。例如:
- 多态是指同一个行为具有多个不同表现形式或形态的能力。实现方式主要有两种:
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");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Dog();
animal.sound(); // 输出 "Dog barks"
}
}
- 接口:一个类实现一个或多个接口,不同的类可以实现同一个接口并提供不同的实现。例如:
interface Shape {
double area();
}
class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
class Rectangle implements Shape {
private double length;
private double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
public double area() {
return length * width;
}
}
public class Main {
public static void main(String[] args) {
Shape circle = new Circle(5);
Shape rectangle = new Rectangle(3, 4);
System.out.println(circle.area());
System.out.println(rectangle.area());
}
}
- Java中的访问修饰符有哪些,分别有什么作用?
- public:被public修饰的类、方法、变量等可以被任何类访问,无论这些类是否在同一个包中。
- protected:可以被同一个包内的类访问,也可以被不同包内的子类访问。例如,在一个包中有一个父类,其某个方法被protected修饰,那么在同一个包中的其他类可以访问这个方法,同时,在不同包中的子类也可以访问这个方法。
- default(默认,不写修饰符):只能被同一个包内的类访问。如果一个类、方法或变量没有指定访问修饰符,那么它就是默认访问权限。
- private:只能在本类中访问。被private修饰的成员,其他类无法直接访问,通常需要通过getter和setter方法来间接访问。
- 线程的几种状态
- 新建(New):当创建一个Thread对象时,线程处于新建状态,此时线程还没有开始执行。例如:
Thread thread = new Thread(); - 就绪(Runnable):调用线程的start()方法后,线程进入就绪状态,此时线程已经准备好执行,但还没有获得CPU时间片。
- 运行(Running):当处于就绪状态的线程获得CPU时间片后,开始执行run()方法,此时线程处于运行状态。
- 阻塞(Blocked):线程在某些情况下会进入阻塞状态,暂时停止执行。常见的阻塞情况有:等待I/O操作完成、等待锁、调用Thread.sleep()方法等。
- 死亡(Terminated):线程执行完run()方法或者因为异常终止,就会进入死亡状态,此时线程生命周期结束。
- 新建(New):当创建一个Thread对象时,线程处于新建状态,此时线程还没有开始执行。例如:
- 线程池有哪些常用的创建方式?
- 通过Executors工具类创建:
- newFixedThreadPool:创建一个固定大小的线程池,线程池中的线程数量是固定的。当有新任务提交时,如果线程池中有空闲线程,则立即执行任务;如果没有空闲线程,则任务会被放入任务队列中等待。例如:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5); - newCachedThreadPool:创建一个可缓存的线程池,线程池中的线程数量可以动态调整。如果有新任务提交,且线程池中有空闲线程,则立即执行任务;如果没有空闲线程,则创建新线程执行任务。当线程空闲时间过长时,会被自动回收。例如:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); - newSingleThreadExecutor:创建一个单线程的线程池,线程池中只有一个线程。所有任务会按照提交的顺序依次执行。例如:
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
- newFixedThreadPool:创建一个固定大小的线程池,线程池中的线程数量是固定的。当有新任务提交时,如果线程池中有空闲线程,则立即执行任务;如果没有空闲线程,则任务会被放入任务队列中等待。例如:
- 通过Executors工具类创建:
- 使用线程池有什么好处呢?
- 降低资源消耗:线程的创建和销毁需要消耗系统资源,使用线程池可以避免频繁创建和销毁线程,降低资源消耗。
- 提高响应速度:当有新任务提交时,线程池中有空闲线程可以立即执行任务,无需等待线程的创建,提高了响应速度。
- 方便管理线程:线程池可以对线程进行统一的管理,例如设置线程的数量、监控线程的状态等。
- JUC中的CountDownLatch是怎么用的?
- CountDownLatch是一个同步工具类,它允许一个或多个线程等待其他线程完成操作。CountDownLatch通过一个计数器来实现,计数器的初始值为需要等待的线程数量。当一个线程完成操作后,计数器的值减1,当计数器的值为0时,等待的线程可以继续执行。例如:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int threadCount = 3;
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " is working");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown(); // 计数器减1
}
}).start();
}
latch.await(); // 等待计数器为0
System.out.println("All threads have finished working");
}
}
- Spring的IoC和AOP是什么?
- IoC(Inversion of Control,控制反转):传统的开发中,对象的创建和管理由程序员负责,而在Spring中,对象的创建和管理交给了Spring容器。Spring容器会根据配置文件或注解来创建和管理对象,程序员只需要从容器中获取对象即可。例如,通过XML配置文件:
<bean id="userService" class="com.example.UserService">
<property name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="com.example.UserDao"/>
- AOP(Aspect-Oriented Programming,面向切面编程):AOP是一种编程范式,它允许开发者在不修改原有代码的情况下,对程序进行增强。AOP通过切面(Aspect)来实现,切面包含了通知(Advice)和切点(Pointcut)。通知定义了在何时执行增强逻辑,切点定义了在哪些方法上执行增强逻辑。例如,使用Spring AOP实现日志记录:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
@After("execution(* com.example.service.*.*(..))")
public void logAfterMethod(JoinPoint joinPoint) {
System.out.println("Method " + joinPoint.getSignature().getName() + " has been executed");
}
}
- Spring Boot的自动配置原理是什么?
- Spring Boot的自动配置是基于条件注解实现的。当Spring Boot应用启动时,会自动扫描classpath下的依赖和配置文件。在
spring-boot-autoconfigure模块中,有很多自动配置类,这些类使用了@Configuration注解,并且使用了@ConditionalOnClass、@ConditionalOnMissingBean等条件注解。例如,当classpath下存在DataSource类时,DataSourceAutoConfiguration类会自动配置数据源。Spring Boot会根据这些条件注解来判断是否需要进行自动配置,如果条件满足,则会自动创建相应的Bean。
- Spring Boot的自动配置是基于条件注解实现的。当Spring Boot应用启动时,会自动扫描classpath下的依赖和配置文件。在
- MyBatis是如何实现数据库操作的?
- MyBatis通过映射文件或者注解把SQL语句和Java方法关联起来。
- 映射文件方式:创建一个XML映射文件,在文件中定义SQL语句和Java方法的映射关系。例如:
<mapper namespace="com.example.UserMapper">
<select id="getUserById" parameterType="int" resultType="com.example.User">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>
- 注解方式:在Mapper接口的方法上使用注解来定义SQL语句。例如:
import org.apache.ibatis.annotations.Select;
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User getUserById(int id);
}
- 当调用Mapper接口的方法时,MyBatis会根据映射关系执行相应的SQL语句,并将结果映射到Java对象中。
11. Dubbo的负载均衡策略有哪些? - Dubbo提供了多种负载均衡策略: - Random LoadBalance(随机负载均衡):随机选择一个服务提供者,默认的负载均衡策略。它根据权重随机选择,权重越大,被选中的概率越高。 - RoundRobin LoadBalance(轮询负载均衡):按照顺序依次选择服务提供者。同样会考虑权重,权重高的服务提供者会被更多地选择。 - LeastActive LoadBalance(最少活跃调用数负载均衡):选择活跃调用数最少的服务提供者。如果有多个服务提供者的活跃调用数相同,则根据权重随机选择。 - ConsistentHash LoadBalance(一致性哈希负载均衡):根据请求的参数计算哈希值,将请求路由到固定的服务提供者。这种策略可以保证相同参数的请求总是路由到同一个服务提供者。