《互联网大厂 Java 求职者面试:从核心知识到框架的全面考察》

50 阅读6分钟

以下是面试对话:

第一轮: 面试官:请你简单介绍一下 Java 的核心知识有哪些? 王铁牛:Java 的核心知识包括数据类型、控制流程、面向对象编程等。 面试官:不错,那你说说面向对象编程的三大特性是什么? 王铁牛:封装、继承、多态。 面试官:很好,那在面向对象编程中,抽象类和接口有什么区别? 王铁牛:抽象类可以有具体方法和抽象方法,接口只能有抽象方法。抽象类可以被继承,接口可以被实现。

答案:

  • 数据类型:Java 有基本数据类型(如 int、float、char 等)和引用数据类型(如类、数组、接口等)。基本数据类型直接存储值,引用数据类型存储对象的引用。
  • 控制流程:包括条件判断(如 if-else、switch 等)和循环结构(如 for、while、do-while 等),用于控制程序的执行流程。
  • 面向对象编程的三大特性:
    • 封装:将数据和操作封装在类中,对外提供公共的接口,隐藏内部实现细节,提高代码的安全性和可维护性。
    • 继承:子类继承父类的属性和方法,实现代码的复用和扩展。子类可以重写父类的方法,以满足不同的需求。
    • 多态:同一操作作用于不同的对象可以有不同的表现形式。通过父类引用指向子类对象,在调用方法时根据对象的实际类型来确定调用的具体方法。
  • 抽象类和接口的区别:
    • 抽象类可以包含具体方法和抽象方法,接口只能包含抽象方法。抽象类的抽象方法可以有默认的实现,接口的抽象方法必须由实现类实现。
    • 抽象类可以被继承,一个类只能继承一个抽象类;接口可以被实现,一个类可以实现多个接口。
    • 抽象类用于定义共性的行为和属性,体现is-a 的关系;接口用于定义规范和行为,体现 like-a 的关系。

第二轮: 面试官:谈谈你对 JUC(Java 并发工具包)的了解。 王铁牛:JUC 提供了很多用于并发编程的工具类和框架,比如 Lock、Condition 等。 面试官:嗯,那你说说 Lock 和 synchronized 的区别是什么? 王铁牛:Lock 是显式锁,需要手动获取和释放锁,而 synchronized 是隐式锁,由编译器自动管理。Lock 可以实现公平锁和非公平锁,synchronized 只能是非公平锁。 面试官:很好,那你知道如何使用 ReentrantLock 实现线程同步吗? 王铁牛:(有点犹豫)嗯……这个不太清楚。

答案:

  • JUC 是 Java 提供的用于并发编程的工具包,它包含了很多并发相关的类和接口,如 Lock、Condition、ExecutorService 等。这些工具类和框架可以帮助开发者更方便地实现线程同步、线程安全和并发控制。
  • Lock 和 synchronized 的区别:
    • Lock 是显式锁,需要通过 lock()方法获取锁,通过 unlock()方法释放锁。synchronized 是隐式锁,由编译器自动管理锁的获取和释放。
    • Lock 可以实现公平锁和非公平锁,通过构造函数指定。公平锁保证线程获取锁的顺序按照申请锁的时间顺序,非公平锁则不保证。synchronized 只能是非公平锁。
    • Lock 可以中断线程的等待,通过 lockInterruptibly()方法实现。synchronized 不能中断线程的等待。
    • Lock 可以实现尝试获取锁的功能,通过 tryLock()方法实现。如果锁被其他线程占用,tryLock()方法会立即返回 false,不会像 synchronized 那样一直等待。
  • 使用 ReentrantLock 实现线程同步的步骤:
    • 创建 ReentrantLock 对象。
    • 在需要同步的代码块中调用 lock()方法获取锁。
    • 执行同步代码。
    • 在同步代码块结束后调用 unlock()方法释放锁。

例如:

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private final ReentrantLock lock = new ReentrantLock();

    public void performAction() {
        lock.lock();
        try {
            // 同步代码块
            System.out.println(Thread.currentThread().getName() + " 获取了锁");
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantLockExample example = new ReentrantLockExample();
        Thread t1 = new Thread(example::performAction);
        Thread t2 = new Thread(example::performAction);
        t1.start();
        t2.start();
    }
}

在上述代码中,创建了一个 ReentrantLock 对象,并在 performAction()方法中使用 lock()和 unlock()方法来实现线程同步。在 main()方法中创建了两个线程,分别调用 performAction()方法,由于使用了 ReentrantLock,所以两个线程会按照顺序获取锁并执行同步代码。

第三轮: 面试官:讲讲你对 JVM(Java 虚拟机)的内存结构的理解。 王铁牛:(思考了一会儿)嗯……大概知道有堆、栈、方法区等。 面试官:那你详细说说堆和栈的区别吧。 王铁牛:(有点不确定)堆是用来存储对象的,栈是用来存储局部变量和方法调用的。 面试官:不错,那你知道 Java 中的对象是如何在堆中分配内存的吗? 王铁牛:(挠挠头)不太清楚。

答案:

  • JVM 的内存结构主要包括堆、栈、方法区、本地方法栈和程序计数器。
    • 堆:是 Java 虚拟机管理的最大的一块内存区域,用于存储对象实例和数组。堆是线程共享的,所有线程都可以访问堆中的对象。
    • 栈:每个线程都有一个私有的栈,用于存储局部变量、方法参数和方法调用的栈帧。栈的大小是固定的,当方法调用时,栈帧会被压入栈中,当方法结束时,栈帧会被弹出栈。
    • 方法区:用于存储类的信息、常量、静态变量和编译后的代码等。方法区是线程共享的。
    • 本地方法栈:用于存储本地方法的调用信息。本地方法是用其他语言(如 C、C++)实现的,通过 JNI(Java Native Interface)调用。
    • 程序计数器:用于记录当前线程执行的字节码的行号。每个线程都有一个独立的程序计数器,线程切换时,程序计数器会保存当前线程的执行位置,以便在下次切换回来时继续执行。
  • 堆和栈的区别:
    • 存储内容:堆用于存储对象实例和数组,栈用于存储局部变量、方法参数和方法调用的栈帧。
    • 内存分配:堆的内存分配是动态的,需要在运行时进行分配和回收。栈的内存分配是静态的,在方法定义时就确定了栈的大小。
    • 内存回收:堆的内存回收是自动的,由垃圾收集器进行回收。栈的内存回收是自动的,当方法结束时,栈帧会被弹出栈,栈帧所占用的内存会被自动回收。
    • 访问速度:栈的访问速度比堆快,因为栈的内存是连续的,而堆的内存是不连续的,需要通过指针进行访问。

Java 中的对象是在堆中分配内存的,当创建一个对象时,首先会在堆中分配一块内存空间,然后在栈中创建一个引用变量,指向堆中的对象。当对象不再被引用时,垃圾收集器会自动回收堆中的对象所占用的内存。

面试官:今天的面试就到这里,你回去等通知吧。

希望以上内容对你有所帮助,祝你求职顺利!