JAVA基础面试题(持续更新)

96 阅读11分钟

Java 中的序列化和[反序列化]是什么?

Java 中的序列化和反序列化
序列化和反序列化是 Java 中用于对象持久化和网络传输的重要机制。

序列化 (Serialization)

序列化是将 Java 对象转换为字节序列的过程,以便可以:
将对象保存到文件中
通过[网络传输]对象
将对象存储在内存缓存中

反序列化 (Deserialization)

反序列化是将字节序列重新转换为 Java 对象的过程,是序列化的逆过程。

实现方式
要使一个类可序列化,需要实现 java.io.Serializable 接口(这是一个标记接口,没有方法):

import java.io.Serializable;

public class Person implements Serializable {
    private String name;
    private int age;
    
    // 构造方法、getter和setter
}

AI写代码java
运行
12345678

序列化示例

// 序列化对象到文件
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
    Person person = new Person("张三", 25);
    oos.writeObject(person);
} catch (IOException e) {
    e.printStackTrace();
}

AI写代码java
运行
1234567

反序列化示例

// 从文件反序列化对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {
    Person person = (Person) ois.readObject();
    System.out.println(person.getName()); // 输出: 张三
} catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
}

AI写代码java
运行
1234567

这份小册是从基础到高级涵盖了足足30个技术栈的,包含了JAVA基础,JAVA集合,JAVA并发,Spring,微服务,Netty,计算机网络,MQ,Zookeeper,Redis,MySQL,数据结构与算法以及设计模式等等,足足200余页,由于掘金篇幅限制我在这里就只展示部分内容了,扫一扫免费获取

image.png

重要特性

transient 关键字:标记不被序列化的字段

private transient String password; // 不会被序列化

AI写代码java
运行
1

serialVersionUID:用于版本控制

private static final long serialVersionUID = 1L;

AI写代码java
运行
1

自定义序列化:通过实现 writeObject 和 readObject 方法

使用场景

对象持久化到文件或数据库
远程方法调用(RMI)
分布式计算
缓存对象

什么是 Java 中的不可变类?

不可变类是指其实例在创建后状态不能被修改的类。一旦创建,对象的所有字段值就固定不变。

不可变类的特点

状态不可变:对象创建后,其所有字段值不能被修改
线程安全:天然线程安全,无需同步
可自由共享:可以被多个线程安全地共享
适合作为缓存键:因为状态不变,哈希值也不会变

如何创建不可变类

将类声明为 final:防止被子类修改行为

public final class ImmutablePerson {}

AI写代码java
运行
1

所有字段设为 private final:

private final String name;
private final int age;

AI写代码java
运行
12

不提供 setter 方法:只提供 getter 方法

public String getName() {
    return name;
}

AI写代码java
运行
123

通过构造方法初始化所有字段:

public ImmutablePerson(String name, int age) {
    this.name = name;
    this.age = age;
}

AI写代码java
运行
1234

对可变对象进行防御性拷贝:

// 如果字段是可变对象(如Date、集合等)
private final Date birthDate;

public ImmutablePerson(Date birthDate) {
    this.birthDate = new Date(birthDate.getTime()); // 防御性拷贝
}

public Date getBirthDate() {
    return new Date(birthDate.getTime()); // 返回拷贝而非原始引用
}

AI写代码java
运行
12345678910

示例:完整的不可变类

public final class ImmutablePerson {
    private final String name;
    private final int age;
    private final List<String> hobbies;
    
    public ImmutablePerson(String name, int age, List<String> hobbies) {
        this.name = name;
        this.age = age;
        this.hobbies = new ArrayList<>(hobbies); // 防御性拷贝
    }
    
    public String getName() {
        return name;
    }
    
    public int getAge() {
        return age;
    }
    
    public List<String> getHobbies() {
        return new ArrayList<>(hobbies); // 返回拷贝
    }
}

AI写代码java
运行
1234567891011121314151617181920212223

Java 中的不可变类示例

String
基本类型的包装类(Integer, Long, Double 等)
BigInteger 和 BigDecimal
LocalDate, LocalTime, LocalDateTime 等 java.time 类

不可变类的优点

线程安全:无需同步,天然线程安全
简化代码:无需考虑对象状态变化
适合缓存:哈希值不变,适合作为 Map 的键
减少错误:防止意外修改对象状态

不可变类的缺点

可能产生大量临时对象:每次"修改"都需要创建新对象
不适合频繁变更的场景:如需要频繁修改状态的对象
不可变类是函数式编程的重要概念,也是设计安全、可靠系统的重要工具。

Java 中 Exception 和 Error 有什么区别?

Exception 和 Error 都是 Throwable 类的子类,但它们代表了不同性质的异常情况。

主要区别

特性ExceptionError
可恢复性通常可以恢复通常不可恢复
处理方式应该被捕获处理通常不捕获处理
来源主要来自应用程序逻辑主要来自JVM或系统底层问题
子类IOException, SQLException等OutOfMemoryError, StackOverflowError等
检查性分为检查型(checked)和非检查型(unchecked)都是非检查型(unchecked)

Exception (异常)

可恢复:通常表示程序可以处理的异常情况
分类:
Checked Exceptions (编译时异常) :必须处理(捕获或声明抛出)

try {
    FileReader file = new FileReader("somefile.txt");
} catch (FileNotFoundException e) {
    // 处理文件未找到的情况
}

AI写代码java
运行
12345

Unchecked Exceptions (运行时异常/RuntimeException) :不强制处理

// NullPointerException, ArrayIndexOutOfBoundsException等
String str = null;
System.out.println(str.length()); // 抛出NullPointerException

AI写代码java
运行
123

Error (错误)

不可恢复:表示严重问题,通常不应尝试捕获
JVM级别问题:如内存不足、栈溢出等
不强制处理:都是Unchecked类型

这份小册是从基础到高级涵盖了足足30个技术栈的,包含了JAVA基础,JAVA集合,JAVA并发,Spring,微服务,Netty,计算机网络,MQ,Zookeeper,Redis,MySQL,数据结构与算法以及设计模式等等,足足200余页,由于掘金篇幅限制我在这里就只展示部分内容了,扫一扫免费获取

image.png

示例:

// 可能导致OutOfMemoryError
List<Object> list = new ArrayList<>();
while(true) {
    list.add(new Object());
}

AI写代码java
运行
12345

继承层次

Throwable
├── Error
│   ├── VirtualMachineError
│   │   ├── OutOfMemoryError
│   │   └── StackOverflowError
│   └── ...
└── Exception
    ├── IOException
    ├── SQLException
    └── RuntimeException
        ├── NullPointerException
        ├── IndexOutOfBoundsException
        └── ...

AI写代码java
运行
12345678910111213

最佳实践

对于Exception:
检查型异常:必须处理或声明抛出
运行时异常:预防为主(如空指针检查)

对于Error:
通常不捕获处理
应该修复导致Error的根本问题

自定义异常:
通常继承Exception或RuntimeException
一般不继承Error

什么是 Java 的多态特性?

多态(Polymorphism)是面向对象编程的三大特性之一(封装、继承、多态),指同一操作作用于不同对象时,可以产生不同的行为

多态的核心概念

同一接口,多种实现:通过统一的接口操作不同的对象
运行时绑定:具体调用哪个方法在运行时决定(动态绑定)
代码扩展性:无需修改现有代码即可添加新功能

Java 中多态的两种主要形式

  1. 编译时多态(静态多态/方法重载)
    通过方法重载(Overload)实现
class Calculator {
    // 方法重载 - 同名方法,不同参数
    int add(int a, int b) {
        return a + b;
    }
    
    double add(double a, double b) {
        return a + b;
    }
    
    String add(String a, String b) {
        return a.concat(b);
    }
}

AI写代码java
运行
1234567891011121314

2. 运行时多态(动态多态/方法重写)
通过方法重写(Override)和继承/接口实现

class Animal {
    void makeSound() {
        System.out.println("动物发出声音");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("汪汪汪");
    }
}

class Cat extends Animal {
    @Override
    void makeSound() {
        System.out.println("喵喵喵");
    }
}

// 使用多态
Animal myAnimal = new Dog();
myAnimal.makeSound(); // 输出"汪汪汪"

myAnimal = new Cat();
myAnimal.makeSound(); // 输出"喵喵喵"

AI写代码java
运行
1234567891011121314151617181920212223242526

多态的实现机制

向上转型(Upcasting):

Animal animal = new Dog(); // 子类转父类

AI写代码java
运行
1

动态绑定:JVM在运行时根据实际对象类型决定调用哪个方法
instanceof 检查:

if (animal instanceof Dog) {
    Dog dog = (Dog) animal; // 向下转型
}

AI写代码java
运行
123

多态的应用场景

接口编程:

List<String> list = new ArrayList<>(); // 接口引用指向实现类
list = new LinkedList<>(); // 可随时更换实现

AI写代码java
运行
12

设计模式:
策略模式
工厂模式
模板方法模式

框架扩展:

// Spring框架中的依赖注入
@Autowired
private UserRepository userRepo; // 可能是MySQL或Oracle实现

AI写代码java
运行
123

多态的优势

可替换性:允许用子类对象替换父类对象
可扩展性:容易添加新类而不影响现有代码
接口性:通过父类/接口定义通用接口
灵活性:简化代码结构,提高代码复用

注意事项

不能重写的情况:
private 方法
final 方法
static 方法(实际上是隐藏,不是重写)

字段没有多态:

class Parent {
    String name = "Parent";
}

class Child extends Parent {
    String name = "Child";
}

Parent obj = new Child();
System.out.println(obj.name); // 输出"Parent"(字段看引用类型)

AI写代码java
运行
12345678910

JAVA注解

注解的本质

注解是一种特殊接口:所有注解类型都隐式继承自 java.lang.annotation.Annotation 接口

编译时处理:注解本身不会改变代码逻辑,但可以通过以下方式发挥作用:
编译时处理(如  @Override
运行时反射处理(如 Spring 的  @Autowired
编译时生成代码(如 Lombok

注解的实现原理

元注解

Java 提供了 5 种元注解(用于注解其他注解):

@Target - 指定注解可以应用的目标(类、方法、字段等)
@Retention - 指定注解的保留策略(SOURCE/CLASS/RUNTIME)
@Documented - 标记是否包含在 Javadoc 中
@Inherited - 标记是否允许子类继承父类的注解
@Repeatable - 标记注解是否可以重复应用于同一目标

AI写代码java
运行
12345

保留策略@Retention

SOURCE:仅存在于源码中,编译时丢弃(如 @Override)
CLASS:保留到 class 文件,但 JVM 不加载(默认行为)
RUNTIME:保留到运行时,可通过反射读取(如 Spring 的注解)

运行时处理机制

运行时注解通过反射 API 处理:

// 获取类注解
Annotation[] annotations = MyClass.class.getAnnotations();

// 获取方法注解
Method method = MyClass.class.getMethod("myMethod");
Annotation[] methodAnnotations = method.getAnnotations();

// 获取字段注解
Field field = MyClass.class.getField("myField");
Annotation[] fieldAnnotations = field.getAnnotations();

AI写代码java
运行
12345678910

编译时处理机制

通过注解处理器(Annotation Processor)实现:

继承 AbstractProcessor 类
重写 process 方法
在 META-INF/services/javax.annotation.processing.Processor 中注册处理器

注解的底层实现

JVM 并不直接支持注解,编译器会将注解转换为:
一个继承 Annotation 的接口
一个实现该接口的代理类(运行时动态生成)
注解信息存储在 class 文件的属性表中

示例:自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
    String value() default "";
    int count() default 0;
}

AI写代码java
运行
123456

使用示例:

public class MyClass {
    @MyAnnotation(value = "test", count = 5)
    public void myMethod() {
        // ...
    }
}

AI写代码java
运行
123456

处理示例:

Method method = MyClass.class.getMethod("myMethod");
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
System.out.println(annotation.value()); // 输出 "test"
System.out.println(annotation.count()); // 输出 5

AI写代码java
运行
1234

性能考虑

运行时注解处理依赖于反射,可能影响性能。在性能敏感的场景中,可以考虑:
缓存反射结果
使用编译时注解处理生成代码(如 ButterKnife)
使用 AspectJ 等编译时织入技术

JAVA 反射

反射核心 API

反射主要通过 java.lang.reflect 包和 Class 类实现:

获取 Class 对象:

Class<?> clazz1 = Class.forName("完整类名");  // 最常用方式
Class<?> clazz2 = MyClass.class;            // 类字面常量
Class<?> clazz3 = obj.getClass();           // 对象实例

AI写代码java
运行
123

创建实例:

Object obj = clazz1.newInstance();  // 调用无参构造器(已过时)
Constructor<?> constructor = clazz1.getConstructor(String.class);
Object obj = constructor.newInstance("参数");  // 推荐方式

AI写代码java
运行
123

实际应用场景

动态加载和调用方法

// 加载类
Class<?> clazz = Class.forName("com.example.MyClass");

// 创建实例
Object instance = clazz.newInstance();

// 获取方法
Method method = clazz.getMethod("methodName", String.class, int.class);

// 调用方法
Object result = method.invoke(instance, "参数1", 123);

AI写代码java
运行
1234567891011

访问和修改字段

// 获取字段(包括私有字段)
Field field = clazz.getDeclaredField("fieldName");

// 对于私有字段需要设置可访问
field.setAccessible(true);

// 获取字段值
Object value = field.get(instance);

// 设置字段值
field.set(instance, "新值");

AI写代码java
运行
1234567891011

注解处理

// 获取类上的注解
Annotation[] annotations = clazz.getAnnotations();

// 获取方法注解
Method method = clazz.getMethod("methodName");
MyAnnotation anno = method.getAnnotation(MyAnnotation.class);

AI写代码java
运行
123456

动态代理

InvocationHandler handler = new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        // 前置处理
        Object result = method.invoke(target, args);  // 调用实际方法
        // 后置处理
        return result;
    }
};

MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
    MyInterface.class.getClassLoader(),
    new Class[]{MyInterface.class},
    handler
);

AI写代码java
运行
123456789101112131415

反射的优缺点

优点:
极大的灵活性
支持动态编程
是许多框架的基础

缺点:
性能开销(比直接调用慢)
安全限制(需要运行时权限)
破坏封装性(可以访问私有成员)
调试困难