首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java基础知识经典面试题(包含详细解析和代码示例)

Java基础知识经典面试题(包含详细解析和代码示例)

原创
作者头像
xiaokai丶
修改2025-08-17 17:22:34
修改2025-08-17 17:22:34
1590
举报

本文围绕 Java 基础核心知识点,整理了多道面试题并作出详细解析。内容涵盖基本数据类型与引用类型的区别、== 和 equals () 的差异、String 相关类的特点、面向对象三大特性、接口与抽象类的不同、final 和 static 关键字的用法、重载与重写的区别、异常体系、反射、泛型、集合框架、多线程实现方式以及 sleep () 和 wait () 的区别等核心内容。

Tips:文章中大部分知识点也是笔者当初面试初级Java时被问到的八股文。

1. Java中的基本数据类型有哪些?与引用类型有什么区别?

Java中的基本数据类型共8种,分为四大类:

  • 整数型:byte(1字节)、short(2字节)、int(4字节)、long(8字节)
  • 浮点型:float(4字节)、double(8字节)
  • 字符型:char(2字节,存储Unicode字符)
  • 布尔型:boolean(1字节,值为truefalse)

基本数据类型与引用类型的核心区别:

  • 存储方式:基本类型直接存储值;引用类型存储对象的内存地址(引用)。
  • 默认值:基本类型有默认值(如int默认0);引用类型默认值为null
  • 内存区域:基本类型存于栈内存;引用类型的引用存于栈,对象存于堆。

示例:

代码语言:java
复制
public class DataTypeDemo {
    public static void main(String[] args) {
        // 基本类型:直接存储值
        int a = 10;
        int b = a; // 复制值,a和b相互独立
        b = 20;
        System.out.println(a); // 输出10(a不受b影响)

        // 引用类型:存储对象地址
        StringBuffer sb1 = new StringBuffer("hello");
        StringBuffer sb2 = sb1; // 复制引用,指向同一对象
        sb2.append(" world");
        System.out.println(sb1); // 输出"hello world"(sb1受sb2影响)
    }
}

2. ==equals()的区别是什么?

  • ==:用于比较值是否相等。

<!---->

  • 比较基本类型时,直接比较值(如10 == 10true)。
  • 比较引用类型时,比较对象的内存地址(是否为同一对象)。

<!---->

  • equals():是Object类的方法,默认实现与==一致(比较地址),但可被重写用于比较对象内容。

<!---->

    • String类重写了equals(),比较字符串内容是否相同;
    • 自定义类需手动重写equals()以实现内容比较。

示例:

代码语言:java
复制
public class EqualsDemo {
    public static void main(String[] args) {
        // 基本类型比较
        int x = 5;
        int y = 5;
        System.out.println(x == y); // true(值相等)

        // 引用类型比较
        String s1 = new String("java");
        String s2 = new String("java");
        System.out.println(s1 == s2); // false(地址不同)
        System.out.println(s1.equals(s2)); // true(String重写equals,内容相同)

        // 自定义类(未重写equals)
        Person p1 = new Person("张三");
        Person p2 = new Person("张三");
        System.out.println(p1.equals(p2)); // false(默认比较地址)
    }

    static class Person {
        String name;
        Person(String name) { this.name = name; }
        // 若重写equals:
        // @Override
        // public boolean equals(Object o) {
        //     if (this == o) return true;
        //     if (o == null || getClass() != o.getClass()) return false;
        //     Person person = (Person) o;
        //     return Objects.equals(name, person.name);
        // }
    }
}

3. String、StringBuffer和StringBuilder的区别?

三者均用于处理字符串,核心区别在于可变性和线程安全性:

  • String:不可变(底层是final char[]),每次修改都会创建新对象,效率低,线程安全(不可变天然安全)。
  • StringBuffer:可变,底层是char[],修改时直接操作原数组,效率高;线程安全(方法加synchronized),但多线程场景下性能有损耗。
  • StringBuilder:可变(同StringBuffer),线程不安全(无同步锁),单线程下效率高于StringBuffer(推荐使用)。

示例:

代码语言:java
复制
public class StringDemo {
    public static void main(String[] args) {
        // String:不可变,每次拼接创建新对象
        String s = "a";
        s += "b"; // 实际创建新String对象,原对象被回收
        System.out.println(s); // "ab"

        // StringBuffer:线程安全,适合多线程
        StringBuffer sb = new StringBuffer("a");
        sb.append("b"); // 直接修改原数组
        System.out.println(sb); // "ab"

        // StringBuilder:非线程安全,单线程效率更高
        StringBuilder sd = new StringBuilder("a");
        sd.append("b");
        System.out.println(sd); // "ab"
    }
}

4. 什么是封装、继承、多态?请举例说明。

三者是面向对象的三大特性:

  • 封装:隐藏对象内部实现细节,仅通过公共方法暴露接口,提高安全性和可维护性(如类的private属性通过get/set方法访问)。
  • 继承:子类继承父类的属性和方法,实现代码复用;子类可扩展父类功能(extends关键字),Java单继承(一个类只能有一个父类)。
  • 多态:同一行为在不同对象上表现不同,需满足3个条件:继承关系、方法重写、父类引用指向子类对象。

示例:

代码语言:java
复制
// 封装:Person类隐藏name,通过方法暴露
class Person {
    private String name; // 私有属性,封装细节

    // 公共方法暴露接口
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

// 继承:Student继承Person,复用name属性和方法
class Student extends Person {
    private int score;

    // 扩展父类功能
    public void setScore(int score) { this.score = score; }
}

// 多态:父类引用指向子类对象,调用重写方法
class Animal {
    public void sound() { System.out.println("动物叫"); }
}

class Dog extends Animal {
    @Override // 重写父类方法
    public void sound() { System.out.println("汪汪叫"); }
}

public class OOPDemo {
    public static void main(String[] args) {
        // 封装使用
        Person p = new Person();
        p.setName("张三");
        System.out.println(p.getName()); // 张三

        // 继承使用
        Student s = new Student();
        s.setName("李四"); // 继承父类方法
        s.setScore(90);

        // 多态使用
        Animal animal = new Dog(); // 父类引用指向子类对象
        animal.sound(); // 输出"汪汪叫"(调用子类重写的方法)
    }
}

5. 接口和抽象类的区别?

两者均用于抽象设计,但核心区别如下:

特性

接口(Interface)

抽象类(Abstract Class)

继承方式

多实现(implements,一个类可实现多个接口)

单继承(extends,一个类只能继承一个抽象类)

方法

默认为public abstract(Java 8后可有默认方法default和静态方法)

可包含抽象方法和具体方法

属性

只能是public static final常量

可包含任意修饰符的属性(普通变量)

构造器

有(供子类调用)

设计目的

定义行为规范(“是什么”)

代码复用(“是什么+怎么做”)

示例:

代码语言:java
复制
// 接口:定义规范
interface Flyable {
    int MAX_HEIGHT = 1000; // 隐式为public static final
    void fly(); // 隐式为public abstract

    // Java 8+默认方法
    default void land() {
        System.out.println("降落");
    }
}

// 抽象类:包含部分实现
abstract class Bird {
    String name;
    Bird(String name) { this.name = name; } // 构造器

    abstract void sing(); // 抽象方法

    // 具体方法
    void eat() {
        System.out.println(name + "吃东西");
    }
}

// 类可实现多个接口,继承一个抽象类
class Eagle extends Bird implements Flyable {
    Eagle(String name) { super(name); }

    @Override
    public void fly() {
        System.out.println(name + "在" + MAX_HEIGHT + "米高空飞");
    }

    @Override
    void sing() {
        System.out.println(name + "鸣叫");
    }
}

6. final关键字的用法?

final可修饰类、方法、变量,含义不同:

  • 修饰类:类不可被继承(如String类)。
  • 修饰方法:方法不可被重写(子类不能覆盖该方法)。
  • 修饰变量:变量为常量,初始化后不可修改。

<!---->

    • 基本类型变量:值不可变;
    • 引用类型变量:引用地址不可变(但对象内容可修改)。

示例:

代码语言:java
复制
// final类:不可被继承
final class FinalClass { }
// class SubClass extends FinalClass {} // 编译错误:无法继承final类

class Parent {
    // final方法:不可被重写
    public final void finalMethod() {
        System.out.println("父类final方法");
    }
}

class Child extends Parent {
    // @Override // 编译错误:无法重写final方法
    // public void finalMethod() {}
}

public class FinalDemo {
    public static void main(String[] args) {
        // final基本类型变量:值不可变
        final int a = 10;
        // a = 20; // 编译错误

        // final引用类型变量:地址不可变,内容可改
        final StringBuilder sb = new StringBuilder("hello");
        sb.append(" world"); // 合法:修改对象内容
        // sb = new StringBuilder("new"); // 编译错误:修改引用地址
    }
}

7. static关键字的作用?

static用于修饰类的成员(变量、方法、代码块),表示属于类而非实例:

  • 静态变量:类的所有实例共享同一变量,直接通过类名.变量名访问,内存中仅一份。
  • 静态方法:属于类,可直接通过类名.方法名调用,不能访问非静态成员(无this引用)。
  • 静态代码块:类加载时执行(仅一次),用于初始化静态资源,执行顺序早于构造器。
  • 静态内部类:不依赖外部类实例,可直接创建,不能访问外部类非静态成员。

示例:

代码语言:java
复制
public class StaticDemo {
    // 静态变量:所有实例共享
    static int count;
    // 非静态变量:每个实例独立
    int id;

    // 静态代码块:类加载时执行
    static {
        count = 0;
        System.out.println("静态代码块执行");
    }

    public StaticDemo() {
        id = ++count; // 每次创建实例,id递增
    }

    // 静态方法:可直接通过类名调用
    static void showCount() {
        System.out.println("总实例数:" + count);
        // System.out.println(id); // 编译错误:静态方法不能访问非静态变量
    }

    // 静态内部类
    static class StaticInner {
        void print() {
            System.out.println("静态内部类");
            // System.out.println(id); // 编译错误:不能访问外部类非静态成员
        }
    }

    public static void main(String[] args) {
        StaticDemo.showCount(); // 直接调用静态方法:总实例数:0

        new StaticDemo();
        new StaticDemo();
        StaticDemo.showCount(); // 总实例数:2

        // 静态内部类使用
        StaticInner inner = new StaticInner();
        inner.print();
    }
}

8. 重载(Overload)和重写(Override)的区别?

  • 重载(Overload):同一类中,方法名相同,参数列表(类型、个数、顺序)不同,与返回值和修饰符无关,是编译时多态。
  • 重写(Override):子类继承父类后,方法名、参数列表、返回值完全相同(或子类返回值是父类返回值的子类),子类方法访问权限不能严于父类,是运行时多态。

示例:

代码语言:java
复制
class OverloadDemo {
    // 重载:同一类中方法名相同,参数不同
    public int add(int a, int b) {
        return a + b;
    }

    public double add(double a, double b) { // 参数类型不同
        return a + b;
    }

    public int add(int a, int b, int c) { // 参数个数不同
        return a + b + c;
    }
}

class Parent {
    // 父类方法
    public void doSomething() {
        System.out.println("父类方法");
    }

    protected String getInfo() {
        return "父类信息";
    }
}

class Child extends Parent {
    // 重写:子类方法与父类方法签名一致
    @Override
    public void doSomething() {
        System.out.println("子类方法");
    }

    // 重写:返回值是父类返回值的子类(String的子类仍是String)
    @Override
    public String getInfo() { // 访问权限不能严于父类(protected→public合法)
        return "子类信息";
    }
}

9. Java中的异常体系是什么?try-catch-finally的执行顺序?

Java异常体系以Throwable为根类,分为两类:

  • Error:严重错误(如OutOfMemoryError),程序无法处理,通常由JVM抛出。
  • Exception:可处理的异常,分两类:

<!---->

    • 受检异常(Checked Exception):编译时必须处理(如IOException),需try-catchthrows声明。
    • 非受检异常(Unchecked Exception):运行时异常(如NullPointerException),编译时无需处理。

try-catch-finally执行顺序:

  1. try:执行可能抛出异常的代码。
  2. 若异常发生:匹配catch块处理,再执行finally
  3. 若异常未发生:直接执行finally
  4. finally:无论是否发生异常,只要JVM未退出就一定会执行(常用于释放资源)。

示例:

代码语言:java
复制
import java.io.FileNotFoundException;
import java.io.FileReader;

public class ExceptionDemo {
    public static void main(String[] args) {
        FileReader reader = null;
        try {
            // 可能抛出受检异常FileNotFoundException
            reader = new FileReader("test.txt");
            System.out.println("文件打开成功"); // 若文件不存在,此行不执行
        } catch (FileNotFoundException e) { // 处理受检异常
            System.out.println("异常:文件未找到");
        } finally { // 无论是否异常,均执行
            if (reader != null) {
                try {
                    reader.close(); // 释放资源
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            System.out.println("finally执行");
        }

        // 非受检异常(运行时异常)
        String s = null;
        try {
            System.out.println(s.length()); // 抛出NullPointerException
        } catch (NullPointerException e) {
            System.out.println("处理空指针异常");
        }
    }
}

10. 什么是反射?有什么应用场景?

反射是Java的动态特性,允许程序在运行时获取类的信息(属性、方法、构造器等),并动态操作类或对象(如创建实例、调用方法)。核心类位于java.lang.reflect包(ClassMethodField等)。

应用场景:

  • 框架开发(如Spring的IOC容器,通过反射创建对象);
  • 注解解析(如JUnit的@Test注解,通过反射识别测试方法);
  • 动态代理(如AOP实现)。

示例:

代码语言:java
复制
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

class User {
    private String name;
    public User() {}
    public User(String name) { this.name = name; }
    private void sayHello() {
        System.out.println("Hello, " + name);
    }
}

public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        // 1. 获取Class对象(三种方式)
        Class<?> clazz = User.class;
        // Class<?> clazz = new User().getClass();
        // Class<?> clazz = Class.forName("User");

        // 2. 反射创建对象(调用无参构造)
        User user = (User) clazz.newInstance();

        // 3. 反射调用有参构造
        Constructor<?> constructor = clazz.getConstructor(String.class);
        User user2 = (User) constructor.newInstance("张三");

        // 4. 反射调用私有方法
        Method method = clazz.getDeclaredMethod("sayHello");
        method.setAccessible(true); // 突破私有访问限制
        method.invoke(user2); // 输出"Hello, 张三"
    }
}

11. 什么是泛型?有什么作用?

泛型(Generic)是JDK 5引入的特性,允许在定义类、接口、方法时指定类型参数(如List<String>),编译时检查类型安全性,避免运行时类型转换异常。

作用:

  • 类型安全:编译时限制集合中元素类型,避免ClassCastException
  • 代码复用:同一类/方法可支持多种数据类型。
  • 可读性:明确集合中元素类型,代码更清晰。

泛型擦除:编译后泛型信息被擦除(替换为Object或上限类型),是“伪泛型”。

示例:

代码语言:java
复制
import java.util.ArrayList;
import java.util.List;

// 自定义泛型类
class Box<T> {
    private T value;
    public T getValue() { return value; }
    public void setValue(T value) { this.value = value; }
}

// 泛型方法
class GenericMethod {
    public static <E> void printArray(E[] array) {
        for (E e : array) {
            System.out.print(e + " ");
        }
        System.out.println();
    }
}

public class GenericDemo {
    public static void main(String[] args) {
        // 泛型集合:编译时检查类型
        List<String> strList = new ArrayList<>();
        strList.add("java");
        // strList.add(123); // 编译错误:类型不匹配
        String s = strList.get(0); // 无需强制转换

        // 自定义泛型类使用
        Box<Integer> intBox = new Box<>();
        intBox.setValue(100);
        int num = intBox.getValue(); // 自动转型

        // 泛型方法使用
        Integer[] intArray = {1, 2, 3};
        String[] strArray = {"a", "b", "c"};
        GenericMethod.printArray(intArray); // 1 2 3
        GenericMethod.printArray(strArray); // a b c
    }
}

12. Java集合框架中List、Set、Map的区别?

Java集合框架主要分为Collection(单值集合)和Map(键值对集合),ListSet属于Collection

特性

List

Set

Map

存储结构

有序集合(插入顺序)

无序集合(无索引)

键值对映射(key→value)

元素重复性

允许重复元素

不允许重复元素(依赖equals

key不重复,value可重复

常用实现类

ArrayList(数组)、LinkedList(链表)

HashSet(哈希表)、TreeSet(红黑树)

HashMap(哈希表)、TreeMap(红黑树)

索引访问

支持(get(index)

不支持

不支持(通过key访问value)

示例:

代码语言:java
复制
import java.util.*;

public class CollectionDemo {
    public static void main(String[] args) {
        // List:有序、可重复
        List<String> list = new ArrayList<>();
        list.add("apple");
        list.add("banana");
        list.add("apple"); // 允许重复
        System.out.println(list); // [apple, banana, apple](保持插入顺序)
        System.out.println(list.get(0)); // apple(通过索引访问)

        // Set:无序、不可重复
        Set<String> set = new HashSet<>();
        set.add("apple");
        set.add("banana");
        set.add("apple"); // 重复元素不被添加
        System.out.println(set); // [banana, apple](无序)

        // Map:键值对,key唯一
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 10);
        map.put("banana", 20);
        map.put("apple", 15); // 覆盖原key的值
        System.out.println(map.get("apple")); // 15(通过key获取value)
        System.out.println(map.keySet()); // [banana, apple](key集合)
    }
}

13. HashMap和Hashtable的区别?

两者均实现Map接口,用于存储键值对,核心区别:

特性

HashMap

Hashtable

线程安全

非线程安全(效率高)

线程安全(方法加synchronized,效率低)

null值支持

允许keyvaluenull(仅一个null key)

不允许keyvaluenull(抛NullPointerException

继承关系

继承AbstractMap

继承Dictionary

迭代器

快速失败迭代器(fail-fast

枚举器(Enumeration,非快速失败)

初始容量

初始容量16,扩容因子0.75

初始容量11,扩容因子0.75

示例:

代码语言:java
复制
import java.util.HashMap;
import java.util.Hashtable;

public class MapDifference {
    public static void main(String[] args) {
        // HashMap:允许null key和value
        HashMap<String, String> hashMap = new HashMap<>();
        hashMap.put(null, "null key");
        hashMap.put("key1", null);
        System.out.println(hashMap.get(null)); // null key
        System.out.println(hashMap.get("key1")); // null

        // Hashtable:不允许null
        Hashtable<String, String> hashtable = new Hashtable<>();
        try {
            hashtable.put(null, "value"); // 抛NullPointerException
            hashtable.put("key", null); // 抛NullPointerException
        } catch (NullPointerException e) {
            System.out.println("Hashtable不允许null");
        }
    }
}

14. 多线程的实现方式有哪些?

Java中实现多线程的3种核心方式:

  1. 继承Thread类:重写run()方法,调用start()启动线程(start()会调用run(),并创建新线程)。
  2. 实现Runnable接口:重写run()方法,将实例传入Thread构造器,调用start()启动。
  3. 实现Callable接口:重写call()方法(有返回值,可抛异常),结合FutureTask获取结果。

推荐使用接口实现(避免单继承限制,便于多线程共享资源)。

示例:

代码语言:java
复制
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

// 方式1:继承Thread
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread方式:" + Thread.currentThread().getName());
    }
}

// 方式2:实现Runnable
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable方式:" + Thread.currentThread().getName());
    }
}

// 方式3:实现Callable(有返回值)
class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "Callable方式:" + Thread.currentThread().getName();
    }
}

public class ThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 方式1启动
        new MyThread().start();

        // 方式2启动
        Thread thread2 = new Thread(new MyRunnable());
        thread2.start();

        // 方式3启动
        FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
        new Thread(futureTask).start();
        System.out.println(futureTask.get()); // 获取返回值
    }
}

15. sleep()wait()的区别?

两者均用于线程等待,核心区别:

特性

sleep(long)

wait()/wait(long)

所属类

Thread类的静态方法

Object类的实例方法

锁释放

不释放锁(持有锁休眠)

释放锁(需在synchronized块中调用)

唤醒方式

时间到自动唤醒,或被interrupt()中断

notify()/notifyAll()唤醒,或时间到

使用场景

暂停线程执行一段时间

线程间通信(等待/唤醒机制)

示例:

代码语言:java
复制
public class SleepWaitDemo {
    public static void main(String[] args) {
        Object lock = new Object();

        // 演示sleep:不释放锁
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程1获得锁");
                try {
                    Thread.sleep(2000); // 休眠2秒,不释放锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程1释放锁");
            }
        }).start();

        // 演示wait:释放锁
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程2获得锁");
                try {
                    lock.wait(2000); // 释放锁,等待2秒或被唤醒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程2继续执行");
            }
        }).start();
    }
}

执行结果:

代码语言:txt
复制
线程1获得锁
(2秒后)
线程1释放锁
线程2获得锁
(2秒后或被唤醒)
线程2继续执行

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. Java中的基本数据类型有哪些?与引用类型有什么区别?
  • 2. ==和equals()的区别是什么?
  • 3. String、StringBuffer和StringBuilder的区别?
  • 4. 什么是封装、继承、多态?请举例说明。
  • 5. 接口和抽象类的区别?
  • 6. final关键字的用法?
  • 7. static关键字的作用?
  • 8. 重载(Overload)和重写(Override)的区别?
  • 9. Java中的异常体系是什么?try-catch-finally的执行顺序?
  • 10. 什么是反射?有什么应用场景?
  • 11. 什么是泛型?有什么作用?
  • 12. Java集合框架中List、Set、Map的区别?
  • 13. HashMap和Hashtable的区别?
  • 14. 多线程的实现方式有哪些?
  • 15. sleep()和wait()的区别?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档