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

99 阅读11分钟

互联网大厂面试: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的负载均衡策略等,你回答得不够清晰或者直接表示不太了解。这些知识点在实际的项目开发中是比较重要的,尤其是在我们这种互联网大厂的复杂业务场景下。我们需要员工不仅有扎实的基础,还能对这些高级特性有深入的理解和应用能力。目前你的表现有亮点,但也存在明显的不足。你先回家等通知吧,后续如果有进一步的消息,我们会及时联系你。”

问题答案详细解析

  1. 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)。
  2. 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()); 
    }
}
  1. Java中的访问修饰符有哪些,分别有什么作用?
    • public:被public修饰的类、方法、变量等可以被任何类访问,无论这些类是否在同一个包中。
    • protected:可以被同一个包内的类访问,也可以被不同包内的子类访问。例如,在一个包中有一个父类,其某个方法被protected修饰,那么在同一个包中的其他类可以访问这个方法,同时,在不同包中的子类也可以访问这个方法。
    • default(默认,不写修饰符):只能被同一个包内的类访问。如果一个类、方法或变量没有指定访问修饰符,那么它就是默认访问权限。
    • private:只能在本类中访问。被private修饰的成员,其他类无法直接访问,通常需要通过getter和setter方法来间接访问。
  2. 线程的几种状态
    • 新建(New):当创建一个Thread对象时,线程处于新建状态,此时线程还没有开始执行。例如:Thread thread = new Thread();
    • 就绪(Runnable):调用线程的start()方法后,线程进入就绪状态,此时线程已经准备好执行,但还没有获得CPU时间片。
    • 运行(Running):当处于就绪状态的线程获得CPU时间片后,开始执行run()方法,此时线程处于运行状态。
    • 阻塞(Blocked):线程在某些情况下会进入阻塞状态,暂时停止执行。常见的阻塞情况有:等待I/O操作完成、等待锁、调用Thread.sleep()方法等。
    • 死亡(Terminated):线程执行完run()方法或者因为异常终止,就会进入死亡状态,此时线程生命周期结束。
  3. 线程池有哪些常用的创建方式?
    • 通过Executors工具类创建:
      • newFixedThreadPool:创建一个固定大小的线程池,线程池中的线程数量是固定的。当有新任务提交时,如果线程池中有空闲线程,则立即执行任务;如果没有空闲线程,则任务会被放入任务队列中等待。例如:ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
      • newCachedThreadPool:创建一个可缓存的线程池,线程池中的线程数量可以动态调整。如果有新任务提交,且线程池中有空闲线程,则立即执行任务;如果没有空闲线程,则创建新线程执行任务。当线程空闲时间过长时,会被自动回收。例如:ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
      • newSingleThreadExecutor:创建一个单线程的线程池,线程池中只有一个线程。所有任务会按照提交的顺序依次执行。例如:ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
  4. 使用线程池有什么好处呢?
    • 降低资源消耗:线程的创建和销毁需要消耗系统资源,使用线程池可以避免频繁创建和销毁线程,降低资源消耗。
    • 提高响应速度:当有新任务提交时,线程池中有空闲线程可以立即执行任务,无需等待线程的创建,提高了响应速度。
    • 方便管理线程:线程池可以对线程进行统一的管理,例如设置线程的数量、监控线程的状态等。
  5. 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");
    }
}
  1. 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");
    }
}
  1. Spring Boot的自动配置原理是什么?
    • Spring Boot的自动配置是基于条件注解实现的。当Spring Boot应用启动时,会自动扫描classpath下的依赖和配置文件。在spring-boot-autoconfigure模块中,有很多自动配置类,这些类使用了@Configuration注解,并且使用了@ConditionalOnClass@ConditionalOnMissingBean等条件注解。例如,当classpath下存在DataSource类时,DataSourceAutoConfiguration类会自动配置数据源。Spring Boot会根据这些条件注解来判断是否需要进行自动配置,如果条件满足,则会自动创建相应的Bean。
  2. 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(一致性哈希负载均衡):根据请求的参数计算哈希值,将请求路由到固定的服务提供者。这种策略可以保证相同参数的请求总是路由到同一个服务提供者。