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

26 阅读2分钟

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

王铁牛,一位怀揣着进入互联网大厂梦想的 Java 程序员,走进了这场决定他职业走向的面试。严肃的面试官坐在桌前,一场技术的较量即将开始。

第一轮面试 面试官:首先问几个 Java 核心知识的问题。Java 中基本数据类型有哪些? 王铁牛:这个我知道,Java 基本数据类型有 byte、short、int、long、float、double、char、boolean。 面试官:不错,回答得很准确。那 Java 中多态的实现方式有哪些? 王铁牛:多态主要通过继承、接口实现和方法重写来实现。子类继承父类,重写父类的方法,就可以实现多态。实现接口并重写接口方法也能实现多态。 面试官:回答得很好。那在 Java 中,String 类为什么是不可变的? 王铁牛:因为 String 类被 final 修饰,它的成员变量也是 final 的,所以它是不可变的。 面试官:非常棒,你的基础很扎实。

第二轮面试 面试官:接下来聊聊 JUC 和多线程的问题。什么是线程池,使用线程池有什么好处? 王铁牛:线程池就是管理线程的一个池子,使用线程池可以减少线程创建和销毁的开销,提高性能,还能控制线程的数量,避免资源过度使用。 面试官:很好。那在 JUC 中,CountDownLatch 和 CyclicBarrier 有什么区别? 王铁牛:嗯……这个嘛,好像……它们都是用来协调线程的,具体区别我有点记不太清了。 面试官:没关系,接着问,在多线程环境下,如何保证数据的一致性? 王铁牛:可以使用 synchronized 关键字或者 Lock 接口来加锁,保证同一时间只有一个线程能访问共享数据。 面试官:前面那个问题回答得不太准确,但后面这个回答得还行。

第三轮面试 面试官:现在谈谈一些框架和中间件。Spring 的 IOC 和 AOP 是什么? 王铁牛:IOC 是控制反转,把对象的创建和依赖关系的管理交给 Spring 容器。AOP 是面向切面编程,用于在不修改原有代码的情况下,对程序进行增强。 面试官:回答得不错。那 Spring Boot 相对于 Spring 有什么优势? 王铁牛:Spring Boot 简化了 Spring 的配置,它有自动配置功能,能快速搭建项目,减少了很多样板代码。 面试官:很好。那 MyBatis 中 #{} 和 ${} 的区别是什么? 王铁牛:这个……好像一个是预编译,一个不是,具体哪个对应哪个我有点乱了。 面试官:整体表现有好有坏吧。你先回家等通知吧,后续如果有消息会及时联系你。

问题答案

  1. Java 中基本数据类型有哪些? Java 的基本数据类型分为四类八种:
  • 整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节)。
  • 浮点类型:float(4 字节)、double(8 字节)。
  • 字符类型:char(2 字节)。
  • 布尔类型:boolean(理论上 1 位,但实际实现中通常为 1 字节)。
  1. Java 中多态的实现方式有哪些?
  • 继承:子类继承父类,重写父类的方法。在调用方法时,根据实际对象的类型来决定调用哪个类的方法。例如:
class Animal {
    public void sound() {
        System.out.println("Animal sound");
    }
}
class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("Woof");
    }
}
// 使用
Animal animal = new Dog();
animal.sound(); // 输出 Woof
  • 接口实现:类实现接口并重写接口中的方法。不同的类实现同一个接口,可以有不同的实现逻辑。例如:
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;
    }
}
  • 方法重写:子类对父类中具有相同方法名、参数列表和返回类型的方法进行重新实现。
  1. 在 Java 中,String 类为什么是不可变的?
  • String 类被 final 修饰,这意味着它不能被继承。
  • String 类内部的字符数组 value 也是被 final 修饰的,并且没有提供修改字符数组内容的方法。一旦创建了 String 对象,其内部的字符序列就不能被改变。例如:
String str = "hello";
str = str + " world"; // 这里实际上是创建了一个新的 String 对象

不可变的好处包括线程安全、可以作为 HashMap 的键、提高性能(字符串常量池的使用)等。

  1. 什么是线程池,使用线程池有什么好处? 线程池是一种管理线程的机制,它预先创建一定数量的线程,当有任务提交时,从线程池中获取线程来执行任务,任务执行完后线程不会销毁,而是返回线程池等待下一个任务。 使用线程池的好处:
  • 减少线程创建和销毁的开销:线程的创建和销毁需要消耗系统资源,使用线程池可以避免频繁创建和销毁线程,提高性能。
  • 提高响应速度:任务提交后可以立即从线程池中获取线程执行,不需要等待线程创建。
  • 控制线程数量:可以避免创建过多的线程导致系统资源耗尽。
  • 方便线程管理:可以对线程进行统一的监控和管理。
  1. 在 JUC 中,CountDownLatch 和 CyclicBarrier 有什么区别?
  • CountDownLatch:是一个同步辅助类,它允许一个或多个线程等待其他线程完成操作。它有一个计数器,当计数器的值减为 0 时,等待的线程会被唤醒。计数器一旦减为 0 就不能再重置。例如:
import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " finished");
                latch.countDown();
            }).start();
        }
        latch.await();
        System.out.println("All threads finished");
    }
}
  • CyclicBarrier:也是一个同步辅助类,它允许一组线程相互等待,直到所有线程都到达一个屏障点,然后所有线程继续执行。它的计数器可以重置,因此可以重复使用。例如:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("All threads reached the barrier"));
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " is waiting at the barrier");
                    barrier.await();
                    System.out.println(Thread.currentThread().getName() + " passed the barrier");
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}
  1. 在多线程环境下,如何保证数据的一致性?
  • 使用 synchronized 关键字:可以修饰方法或代码块,保证同一时间只有一个线程能访问被修饰的方法或代码块。例如:
public class SynchronizedExample {
    private int count = 0;
    public synchronized void increment() {
        count++;
    }
}
  • 使用 Lock 接口:例如 ReentrantLock。相比于 synchronized,Lock 提供了更灵活的锁机制,如可中断锁、公平锁等。例如:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockExample {
    private int count = 0;
    private Lock lock = new ReentrantLock();
    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}
  • 使用原子类:如 AtomicInteger、AtomicLong 等,它们基于 CAS(Compare-And-Swap)操作,保证对变量的原子性操作。例如:
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicExample {
    private AtomicInteger count = new AtomicInteger(0);
    public void increment() {
        count.incrementAndGet();
    }
}
  1. Spring 的 IOC 和 AOP 是什么?
  • IOC(Inversion of Control,控制反转):是一种设计原则,把对象的创建和依赖关系的管理从代码中移除,交给 Spring 容器来完成。通过依赖注入(Dependency Injection)的方式,将对象的依赖关系注入到对象中。例如:
// 接口
interface UserService {
    void addUser();
}
// 实现类
class UserServiceImpl implements UserService {
    @Override
    public void addUser() {
        System.out.println("Add user");
    }
}
// 使用 Spring 配置文件或注解注入
  • AOP(Aspect-Oriented Programming,面向切面编程):是一种编程范式,用于在不修改原有代码的情况下,对程序进行增强。它将一些通用的功能(如日志记录、事务管理等)从业务逻辑中分离出来,形成一个个切面,在特定的连接点(如方法调用前后)插入这些切面代码。例如:
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}

    @Before("serviceMethods()")
    public void beforeServiceMethod() {
        System.out.println("Before service method");
    }

    @After("serviceMethods()")
    public void afterServiceMethod() {
        System.out.println("After service method");
    }
}
  1. Spring Boot 相对于 Spring 有什么优势?
  • 简化配置:Spring Boot 提供了自动配置功能,根据项目中引入的依赖自动配置 Spring 应用,减少了大量的 XML 配置文件和 Java 配置代码。
  • 快速搭建项目:通过 Spring Initializr 可以快速生成一个基本的 Spring Boot 项目骨架,包含所需的依赖和基本配置。
  • 嵌入式服务器:Spring Boot 内置了 Tomcat、Jetty 等嵌入式服务器,无需额外部署服务器,直接运行项目即可。
  • 生产级特性:提供了如监控、健康检查、配置管理等生产级特性,方便应用的运维和管理。
  • 依赖管理:Spring Boot 的 Starter 依赖可以自动管理项目的依赖,避免了依赖冲突。
  1. MyBatis 中 #{} 和 ${} 的区别是什么?
  • #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为占位符 ?,然后使用 PreparedStatement 进行预编译,能有效防止 SQL 注入攻击。例如:
<select id="getUserById" parameterType="int" resultType="User">
    SELECT * FROM users WHERE id = #{id}
</select>
  • :是字符串替换,MyBatis在处理{}:是字符串替换,MyBatis 在处理 {} 时,会将 ${} 直接替换为传入的参数值。这种方式可能会导致 SQL 注入攻击,一般用于动态表名、列名等情况。例如:
<select id="getUserByColumn" parameterType="map" resultType="User">
    SELECT * FROM users WHERE ${column} = #{value}
</select>