当前位置: 首页 > ds >正文

Java八股文-java基础面试题

八股文面试篇(Java基础)

一.java语言的特点

  • 跨平台
  • 有自己的内存管理机制
  • 面向对象

二.Java为什么跨平台

Java 之所以能够跨平台,是因为不同操作系统都有对应版本的 Java 虚拟机(JVM)。只要在目标平台上安装相应的 JVM,就能运行 Java 的字节码文件(.class)。

字节码本身是跨平台的,但 JVM 并不能跨平台。不同操作系统需要不同版本的 JVM 来解析 Java 字节码,并将其转换为该平台对应的机器码,以供执行。

三.JVM、JDK、JRE关系

好的,我给你梳理一下 JVM、JDK、JRE 的关系,可以类比成一个“运行和开发环境的套娃结构”:


1. JVM (Java Virtual Machine,Java虚拟机)

  • 定位:运行 Java 字节码的虚拟机。

  • 作用:把 .class 文件(字节码)解释/编译成机器能执行的指令。

  • 关键点

    • 屏蔽了不同操作系统之间的差异,实现了 一次编译,到处运行
    • JVM 本身不包含编译器,只负责运行字节码。
    • 包含垃圾回收(GC)、内存管理、多线程支持等。

2. JRE (Java Runtime Environment,Java运行时环境)

  • 定位:运行 Java 程序所需的环境。

  • 组成

    • JVM
    • Java 核心类库(如 java.lang.*, java.util.* 等)
    • 一些运行支持文件(配置文件、资源等)
  • 关键点

    • JRE = JVM + 核心类库
    • 只适合 运行 Java 程序,不包含开发工具。

3. JDK (Java Development Kit,Java开发工具包)

  • 定位:提供开发 Java 程序所需的完整工具包。

  • 组成

    • JRE(所以 JDK 里包含 JVM)

    • 开发工具

      • javac(Java 编译器,把 .java 转成 .class 字节码)
      • java(运行字节码)
      • jdb(调试器)
      • javadoc(生成文档)等
  • 关键点

    • JDK = JRE + 开发工具
    • 既能开发 Java 程序,也能运行。

4. 关系图(套娃关系)

      JDK (开发工具包)┌──────────────────────────────┐│   开发工具 (javac, jdb...)   ││                              ││   JRE (运行环境)             ││   ┌───────────────────────┐ ││   │   Java核心类库         │ ││   │                       │ ││   │   JVM (虚拟机)        │ ││   └───────────────────────┘ │└──────────────────────────────┘

  • JVM 只负责运行字节码
  • JRE 提供运行环境(JVM + 核心类库)
  • JDK 提供开发环境(JRE + 编译器和调试工具)

四.面向对象

什么是面向对象,什么是封装继承多态?

🔹 一、面向对象(OOP, Object Oriented Programming)

面向对象是一种编程思想,它把现实世界的事物抽象成程序中的对象,通过对象之间的交互来完成需求。
在 Java 中,对象是 类的实例,类定义了对象的 属性(字段)行为(方法)

核心思想可以总结为:
👉 “一切皆对象,通过对象交互解决问题。”

面向对象的三大特征就是:封装、继承、多态


🔹 二、封装(Encapsulation)
  • 定义:把数据(属性)和操作数据的方法(行为)打包到一个类里,并通过 访问修饰符 控制外部对数据的访问。

  • 作用

    1. 隐藏内部实现细节(只暴露必要接口)。
    2. 提高代码安全性和可维护性。
  • 例子

public class Person {private String name;  // 私有属性,外部不能直接访问private int age;// 提供公共方法访问和修改public String getName() {return name;}public void setName(String name) {this.name = name;}
}

🔹 三、继承(Inheritance)
  • 定义:子类可以继承父类的属性和方法,从而实现代码复用。

  • 关键字extends

  • 作用

    1. 代码复用。
    2. 表达类之间的层次关系(is-a 关系)。
  • 例子

class Animal {public void eat() {System.out.println("动物吃东西");}
}class Dog extends Animal {  // Dog继承Animalpublic void bark() {System.out.println("狗在汪汪叫");}
}public class Test {public static void main(String[] args) {Dog dog = new Dog();dog.eat();  // 继承自Animaldog.bark(); // 自己的行为}
}

🔹 四、多态(Polymorphism)
  • 定义:同一个行为,在不同对象中表现不同的形态。

  • 实现方式

    1. 方法重写(Override):子类对父类方法进行不同实现。
    2. 接口实现:不同类实现相同接口,表现不同行为。
  • 前提:必须有继承或接口,且方法签名相同。

  • 例子

class Animal {public void makeSound() {System.out.println("动物叫");}
}class Dog extends Animal {@Overridepublic void makeSound() {System.out.println("汪汪汪");}
}class Cat extends Animal {@Overridepublic void makeSound() {System.out.println("喵喵喵");}
}public class Test {public static void main(String[] args) {Animal a1 = new Dog(); // 向上转型Animal a2 = new Cat();a1.makeSound(); // 输出:汪汪汪a2.makeSound(); // 输出:喵喵喵}
}

🔹 五、总结一句话
  • 封装:隐藏细节,只暴露接口(对内保护)。
  • 继承:代码复用,类之间有层次(对上复用)。
  • 多态:同一接口,多种实现(对外灵活)。
  • 面向对象:通过这三大特性,把现实世界的对象抽象成代码里的类和对象,用对象的交互来解决问题。

五.重载重写的区别

重载是对同一命名方法可以拥有不同的参数列表,编译器根据调用时参数类型来自动选择调用哪个方法
重写是子类重写父类同名方法,通过@override来标注

六.抽象类实体类(普通类/具体类) 的区别。


🔹 1. 抽象类(abstract class)

  • 定义:使用 abstract 修饰的类,不能直接创建对象。

  • 作用:用来抽象出一类事物的 共性,为子类提供统一的模板。

  • 特点

    1. 可以包含 抽象方法(没有方法体的,必须由子类实现)。
    2. 也可以包含 具体方法(有方法体的)。
    3. 不能直接 new,必须由子类继承并实现抽象方法后,才能实例化子类对象。
  • 例子

abstract class Animal {// 抽象方法(没有方法体)public abstract void makeSound();// 普通方法public void sleep() {System.out.println("动物在睡觉");}
}class Dog extends Animal {@Overridepublic void makeSound() {System.out.println("汪汪汪");}
}public class Test {public static void main(String[] args) {// Animal a = new Animal(); // ❌ 抽象类不能实例化Animal dog = new Dog();      // ✅ 可以通过子类实例化dog.makeSound();  // 汪汪汪}
}

🔹 2. 实体类(Concrete Class,普通类)

  • 定义:能直接创建对象的类。

  • 作用:用来描述某个具体对象的属性和行为。

  • 特点

    1. 必须实现所有方法(不能有抽象方法)。
    2. 可以直接 new 出对象使用。
  • 例子

class Cat {public void makeSound() {System.out.println("喵喵喵");}
}public class Test {public static void main(String[] args) {Cat cat = new Cat();  // ✅ 普通类可以直接实例化cat.makeSound();      // 喵喵喵}
}

🔹 3. 区别总结表格

特性抽象类 (abstract class)实体类 (Concrete Class)
是否能实例化❌ 不能直接实例化✅ 可以直接实例化
是否有抽象方法✅ 可以有抽象方法,也可以有普通方法❌ 不能有抽象方法,方法必须实现
主要作用提供统一模板,约束子类描述具体对象,实现具体逻辑
关键字abstract无(默认就是实体类)

✅ 总结一句话:

  • 抽象类:抽象的是“共性”,像设计蓝图,不能直接用。
  • 实体类:具体实现了功能,能直接 new 出对象来用。

注意

如果一个类 继承了抽象类,那么它必须 实现父类中所有抽象方法,否则它自己也必须声明为 abstract。

如果子类 不想/不能实现所有抽象方法,那么这个子类本身也可以定义成 抽象类,把责任继续交给它的子类。

七.Java抽象类和接口的区别

🔹 1. 定义不同

  • 抽象类(abstract class)

    • 是一种 ,可以包含 抽象方法具体方法
    • abstract 关键字修饰,不能直接实例化。
  • 接口(interface)

    • 是一种 规范/契约,定义类必须实现哪些方法。
    • 默认所有方法是 public abstract(Java 8 之后支持 defaultstatic,Java 9 之后还支持 private)。

🔹 2. 关键区别对比

对比项抽象类 (abstract class)接口 (interface)
关键字abstract classinterface
继承/实现extends,单继承implements,可多实现
成员变量可以有成员变量(可以是 private/protected/public,也可以有 staticfinal默认是 public static final 常量(即全局常量)
方法可以有抽象方法和普通方法Java 8 以前只有抽象方法;Java 8+ 可以有 defaultstatic 方法,Java 9+ 还能有 private 方法
构造器可以有构造器(但不能直接 new,主要用于子类调用)不能有构造器
继承关系一个类只能继承一个抽象类(单继承)一个类可以实现多个接口(多实现)
设计目的抽取 共性(is-a 关系,模板化设计)规定 规范(can-do 关系,能力扩展)

🔹 3. 例子对比

抽象类
abstract class Animal {String name;// 抽象方法public abstract void makeSound();// 普通方法public void sleep() {System.out.println("动物在睡觉");}
}class Dog extends Animal {@Overridepublic void makeSound() {System.out.println("汪汪汪");}
}
接口
interface Flyable {void fly(); // 默认 public abstract
}interface Swimmable {void swim();
}class Duck implements Flyable, Swimmable {@Overridepublic void fly() {System.out.println("鸭子会飞一小段");}@Overridepublic void swim() {System.out.println("鸭子游泳");}
}

👉 Duck 同时实现了两个接口,但如果 Duck 想继承 Animal 的属性/方法,就必须用 抽象类继承 + 接口实现 结合。


🔹 4. 使用场景

  • 抽象类

    • 当多个类有 相同的属性/方法,且希望代码复用时,用抽象类。
    • 强调 继承关系(is-a)。
    • 例子:AnimalDog / Cat
  • 接口

    • 当多个类需要具有 相同行为能力,但不关心其继承体系时,用接口。
    • 强调 能力扩展(can-do)。
    • 例子:FlyableSwimmable

🔹 5. 总结口诀

  • 抽象类:有“血缘关系”的共性抽取,代码复用(is-a)。

  • 接口:没有血缘关系的“能力扩展”,规定契约(can-do)。

  • 类 → 抽象类 → 接口

    • 类 = 具体实现
    • 抽象类 = 模板
    • 接口 = 规范

八.抽象类有构造器吗?

🔹 为什么抽象类可以有构造器?

  1. 抽象类也是类

    • 虽然不能实例化,但它仍然是类,所以可以有构造器。
    • 构造器的主要作用不是给抽象类自己用,而是 供子类在实例化时调用(通过 super()),完成 父类部分属性的初始化
  2. 继承时的初始化顺序

    • 子类实例化时,会先调用父类构造器,再调用子类构造器。
    • 如果父类是抽象类,它的构造器同样会在子类构造时执行。

🔹 示例代码

abstract class Animal {String name;// 抽象类构造器public Animal(String name) {this.name = name;System.out.println("Animal 的构造器被调用");}public abstract void makeSound();
}class Dog extends Animal {public Dog(String name) {super(name);  // 调用抽象类的构造器System.out.println("Dog 的构造器被调用");}@Overridepublic void makeSound() {System.out.println(name + ":汪汪汪");}
}public class Test {public static void main(String[] args) {Dog dog = new Dog("小黑");dog.makeSound();}
}
输出:
Animal 的构造器被调用
Dog 的构造器被调用
小黑:汪汪汪

👉 可以看到:虽然 Animal 是抽象类,但它的构造器在 Dog 实例化时照常执行。


🔹 总结

  • 抽象类 可以有构造器
  • 构造器作用是:供子类调用,完成父类属性初始化
  • 不能直接 new 抽象类,但子类实例化时一定会先执行抽象类的构造器。
    好问题 👍,这是 Java 面试常考点。我帮你详细梳理一下 浅拷贝 vs 深拷贝

九.深拷贝浅拷贝

🔹 1. 定义

  • 浅拷贝(Shallow Copy)

    • 复制对象时,只复制 对象本身及其基本类型的字段
    • 引用类型的字段仍然指向原对象的地址
    • → “拷贝壳子,里面的引用还是同一个”。
  • 深拷贝(Deep Copy)

    • 复制对象时,不仅复制对象本身,还会 复制其引用类型所指向的对象(递归复制)。
    • → “拷贝整个对象树,原对象和新对象完全独立”。

🔹 2. 内存结构对比

假设有一个 Person 对象,里面有个 Address 对象:

class Address {String city;public Address(String city) {this.city = city;}
}class Person implements Cloneable {String name;Address address;public Person(String name, Address address) {this.name = name;this.address = address;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone(); // 默认浅拷贝}
}

浅拷贝(默认 clone())

Person1 (name="Tom", address -> 地址A)│└─> Address(city="Beijing")  ← 同一份对象
Person2 (name="Tom", address -> 地址A)

👉 两个 Person 对象的 address 指向同一个内存地址。

深拷贝(手动实现)

Person1 (name="Tom", address -> 地址A)│└─> Address(city="Beijing")
Person2 (name="Tom", address -> 地址B)│└─> Address(city="Beijing")

👉 两个 Person 对象的 address 是不同对象,互不影响。


🔹 3. 示例代码

浅拷贝

Person p1 = new Person("Tom", new Address("Beijing"));
Person p2 = (Person) p1.clone();p2.name = "Jerry";
p2.address.city = "Shanghai";System.out.println(p1.name);        // Tom (不影响)
System.out.println(p1.address.city); // Shanghai (被影响了)

👉 address 是引用类型,两个对象共享同一份数据。

深拷贝

class Person implements Cloneable {String name;Address address;public Person(String name, Address address) {this.name = name;this.address = address;}@Overrideprotected Object clone() throws CloneNotSupportedException {Person cloned = (Person) super.clone();cloned.address = new Address(this.address.city); // 手动复制引用对象return cloned;}
}
Person p1 = new Person("Tom", new Address("Beijing"));
Person p2 = (Person) p1.clone();p2.address.city = "Shanghai";System.out.println(p1.address.city); // Beijing (不受影响)

🔹 4. 区别总结表格

特性浅拷贝深拷贝
基本类型拷贝值拷贝值
引用类型拷贝引用(同一对象)拷贝新的对象(完全独立)
内存关系部分共享完全独立
性能快(只复制一层)慢(递归复制所有层级)
应用场景数据只读、对象简单需要完全隔离,防止互相影响

✅ 总结一句话:

  • 浅拷贝:只复制一层,引用对象共享。
  • 深拷贝:递归复制,引用对象独立。

十.浅拷贝中不拷贝的“引用数据类型”具体指哪些?


🔹 Java 里的数据类型分类

在 Java 中,数据类型分为两大类:

  1. 基本数据类型(primitive types,值类型)

    • byteshortintlong
    • floatdouble
    • char
    • boolean
      👉 浅拷贝时,这些会 直接复制值,互不影响。
  2. 引用数据类型(reference types,引用类型)

    • 类(class) → 例如 StringInteger(包装类)、自定义的 PersonAddress
    • 数组(Array) → 例如 int[]String[]
    • 接口(interface)实现类
    • 枚举(enum)
      👉 浅拷贝时,只会复制引用(地址),不会新建对象,所以两个对象指向同一块内存。

🔹 总结

在浅拷贝中:

  • 值类型(基本数据类型) → 复制值 ✅(独立)。
  • 引用类型(类、数组、接口实现、枚举等) → 复制引用 ❌(共享同一对象)。

这个就是 Java 垃圾回收(GC)机制
简单来说:在 Java 中,用 new 出来的对象,会在 JVM 堆内存中分配空间,当它“不再被引用”时,就会被 GC 回收。


十一.Java中new出来的对象什么时候回收

🔹 1. 对象创建

Person p = new Person();
  • new Person() 会在 堆(Heap) 上分配一块内存存储对象。
  • 变量 p 存在 栈(Stack) 上,保存着对这个堆中对象的引用地址。

🔹 2. 对象什么时候会被回收?

JVM 垃圾回收器(GC)会回收 不可达对象

① 不再被任何引用指向时

Person p = new Person();
p = null; // 原来new出来的对象不再有引用 → 可被回收

② 引用被重新赋值时

Person p = new Person();   // 对象1
p = new Person();          // 对象1失去引用 → 可被回收

③ 方法执行结束,局部变量出栈

public void test() {Person p = new Person(); // test结束后,p出栈 → 对象可被回收
}

④ 循环引用不影响回收(Java 用可达性分析,不是引用计数)

class A { B b; }
class B { A a; }A a = new A();
B b = new B();
a.b = b;
b.a = a;a = null;
b = null;  // 两个对象互相引用,但不可达 → GC 会回收

🔹 3. JVM 如何判断对象是否可回收?

JVM 使用 可达性分析(Reachability Analysis)

  • 从 GC Roots(根对象,比如栈中的局部变量、静态变量等)出发,沿着引用链可达的对象,都是“活着”的。
  • 没有被引用链关联到的对象,就是“不可达对象”,会被回收。

🔹 4. 回收的时机

  • 不是立刻回收
    Java 中对象的回收由 GC 负责,开发者不能确定具体回收时间。
  • 手动提示 GC
    可以调用 System.gc()Runtime.getRuntime().gc(),但只是 提示,JVM 不保证立即执行。

🔹 5. 总结一句话

  • new 出来的对象存放在堆中
  • 只要还有引用指向,就不会被回收
  • 一旦不可达(没有任何引用指向它),就会在 GC 下次运行时被回收

在 Java 中,final 是一个非常重要的关键字,用于修饰 变量、方法、类,它的作用在不同的上下文中略有不同。我给你详细梳理一下:


十二.什么是反射,反射的应用场景有哪些?

1. 修饰变量

  • 基本类型

    final int a = 10;
    a = 20; // ❌ 编译报错,不能改变值
    

    作用:一旦赋值后不能修改。保证变量成为常量

  • 引用类型

    final List<String> list = new ArrayList<>();
    list = new ArrayList<>(); // ❌ 报错,引用不可变
    list.add("hello");        // ✅ 可以修改对象内容
    

    作用

    • 引用本身不可改变(不能指向另一个对象)
    • 对象内部状态仍然可以修改(除非对象本身不可变)
  • 注意final 变量必须在声明时或者构造器中初始化,否则编译报错。


2. 修饰方法

class Parent {public final void show() {System.out.println("Hello");}
}class Child extends Parent {public void show() { // ❌ 报错,不能被重写}
}

作用

  • final 修饰的方法不能被子类重写
  • 常用于保证核心方法逻辑不被篡改,提高安全性和稳定性

3. 修饰类

final class MyClass {// 类内容
}class SubClass extends MyClass { // ❌ 报错,不能继承 final 类
}

作用

  • final 修饰的类不能被继承
  • 常用于创建不可变类(比如 String 就是 final 类)
  • 提高安全性,同时 JVM 可以做一些优化(比如方法调用优化)

4. 和其他关键字组合

  • final + static

    public static final double PI = 3.14159;
    
    • 表示常量(类级别共享,值不可改变)
  • final + 参数

    public void test(final int x) {// x = 5; // ❌ 报错
    }
    
    • 表示方法内部不能修改参数值
    • 在匿名类或 lambda 表达式中,也要求捕获的变量是 final 或“实际 final”(值不变)

总结一下,final 的核心思想就是 不可变

  • 变量 → 值或引用不可变
  • 方法 → 不可被重写
  • 类 → 不可被继承

十三.什么是反射,反射的应用场景是什么?

在 Java 中,反射(Reflection) 是指程序在运行时能够 获取类的信息并操作类的属性、方法和构造器 的能力。换句话说,就是在运行时动态地操作类,而不是在编译时确定


1. 反射的核心概念

Java 中主要通过 java.lang.reflect 包来实现反射,包括:

  • Class 对象:代表一个类
  • Field:类的属性
  • Method:类的方法
  • Constructor:类的构造器

获取 Class 对象的方式有三种:

// 1. 通过类名.class
Class<String> c1 = String.class;// 2. 通过对象.getClass()
String s = "hello";
Class<?> c2 = s.getClass();// 3. 通过 Class.forName("完整类名")
Class<?> c3 = Class.forName("java.lang.String");

2. 反射可以做的事情

  1. 获取类的完整信息

    Class<?> clazz = Class.forName("java.util.ArrayList");
    System.out.println(clazz.getName()); // 包名+类名
    System.out.println(clazz.getSuperclass()); // 父类
    
  2. 创建对象

    Class<?> clazz = Class.forName("java.util.ArrayList");
    Object obj = clazz.getDeclaredConstructor().newInstance(); // 调用无参构造器
    
  3. 访问字段(属性)

    Class<Person> clazz = Person.class;
    Field nameField = clazz.getDeclaredField("name");
    nameField.setAccessible(true); // 私有属性也能访问
    Person p = new Person();
    nameField.set(p, "Alice"); // 设置值
    System.out.println(nameField.get(p)); // 获取值
    
  4. 调用方法

    Method method = clazz.getDeclaredMethod("sayHello", String.class);
    method.setAccessible(true);
    method.invoke(p, "Tom"); // 相当于 p.sayHello("Tom")
    
  5. 操作构造器

    Constructor<Person> constructor = clazz.getConstructor(String.class, int.class);
    Person p = constructor.newInstance("Alice", 20);
    

3. 反射的应用场景

  1. 框架开发

    • 依赖注入(DI)、Spring、MyBatis 都广泛使用反射来动态实例化对象和调用方法。
    • ORM 框架根据数据库表映射生成对象。
  2. 工具类

    • JSON 序列化/反序列化(如 Jackson、FastJSON)需要根据对象的字段动态赋值。
    • BeanCopier、PropertyUtils 等工具实现对象拷贝。
  3. 动态代理

    • AOP(面向切面编程)通过反射调用目标方法。
    • JDK 动态代理或 CGLib 都依赖反射。
  4. 运行时动态加载类

    • 插件式架构或模块化系统,可以根据配置加载不同的类实现。
  5. 测试

    • 单元测试中可以访问私有字段和方法,验证内部逻辑。

⚠️ 注意点
  1. 性能开销:反射比普通方法调用慢,因为 JVM 很难做优化。
  2. 安全问题:反射可以访问私有字段,可能破坏封装。
  3. 可维护性差:大量使用反射可能导致代码难以阅读和调试。

十四.注解

四种标准原注解

@Retention
用来定义注解的生命周期,即注解在哪个阶段可用。

RetentionPolicy.SOURCE:注解仅在源代码中存在,编译时会被丢弃。
RetentionPolicy.CLASS:注解在编译后保留在字节码中,但不会在运行时可见(默认策略)。
RetentionPolicy.RUNTIME:注解会保留在字节码中,并且在运行时可通过反射访问。

@Target
用来指定注解可以应用的 Java 元素类型(如类、方法、字段等)。

ElementType.TYPE:注解可用于类、接口或枚举。
ElementType.FIELD:注解可用于字段。
ElementType.METHOD:注解可用于方法。
ElementType.PARAMETER:注解可用于方法参数。
ElementType.CONSTRUCTOR:注解可用于构造函数。
ElementType.LOCAL_VARIABLE:注解可用于局部变量。
ElementType.ANNOTATION_TYPE:注解可用于其他注解类型。
ElementType.PACKAGE:注解可用于包。

@Inherited
用于标记注解是否可以被继承。如果一个注解标记了 @Inherited,则该注解会被应用到该类的子类上(仅适用于类级别的注解)。

@Documented
表示该注解应该包含在 Javadoc 中。也就是说,当生成 Javadoc 时,使用该注解的类、方法等会在文档中显示出来。

好的,我们来详细梳理一下在Java面试中常被问到的“八大常见异常”以及更广泛的异常相关面试题目。

十五.Java中八大常见异常

所谓的“八大常见异常”并非官方定义,而是面试中频繁被提及的、具有代表性的几个异常。它们通常涵盖了运行时异常(Unchecked)和编译时异常(Checked)的主要类别。

  1. NullPointerException (空指针异常)

    • 原因:尝试在null对象上调用实例方法或访问实例字段。
    • 场景:最常见的运行时异常之一。例如:String str = null; str.length();
    • 面试重点:如何避免?(判空、使用Optional、保证对象初始化等)
  2. ArrayIndexOutOfBoundsException (数组下标越界异常)

    • 原因:试图访问数组中不存在的索引位置。
    • 场景:循环遍历时索引超出数组长度或为负数。例如:int[] arr = new int[5]; arr[5] = 10;
    • 面试重点:与StringIndexOutOfBoundsException类似,是IndexOutOfBoundsException的子类。
  3. ClassCastException (类转换异常)

    • 原因:尝试将对象强制转换为不是其实例的子类。
    • 场景:向下转型时类型不匹配。例如:Object obj = new String("hello"); Integer i = (Integer) obj;
    • 面试重点:使用instanceof进行类型检查可以避免。
  4. IllegalArgumentException (非法参数异常)

    • 原因:向方法传递了一个不合法或不正确的参数。
    • 场景:方法内部逻辑检测到参数无效时主动抛出。例如:传入负数给要求非负数的方法。
    • 面试重点NumberFormatException是其子类。
  5. NumberFormatException (数字格式化异常)

    • 原因:尝试将不符合数字格式的字符串转换为数字类型。
    • 场景Integer.parseInt("abc")Double.parseDouble("xyz")
    • 面试重点:是IllegalArgumentException的子类,处理字符串转数字时必须注意。
  6. ArithmeticException (算术异常)

    • 原因:出现异常的算术条件。最常见的是除以零。
    • 场景int result = 10 / 0;
    • 面试重点:注意整数除以零会抛出此异常,而浮点数除以零会得到InfinityNaN,不会抛异常。
  7. FileNotFoundException (文件未找到异常)

    • 原因:试图打开指定路径的文件进行读取,但该文件不存在。
    • 场景new FileInputStream("nonexistent.txt");
    • 面试重点:是IOException的子类,属于编译时异常(受检异常),必须处理(try-catch或throws)。
  8. IOException (输入输出异常)

    • 原因:发生某种I/O异常。这是一个更广泛的类别,FileNotFoundException是其子类。
    • 场景:网络连接中断、磁盘写满、文件读写权限问题等。
    • 面试重点:是编译时异常(受检异常) 的代表,处理文件、网络等I/O操作时必须考虑。

十六.==和equals有什么区别

一、== 运算符

  1. 作用

    • == 是一个运算符
    • 它比较的是两个操作数的值是否相等
  2. 比较内容

    • 对于基本数据类型int, char, boolean, double等):比较的是变量存储的实际数值
      int a = 5;
      int b = 5;
      System.out.println(a == b); // true (数值5等于5)
      
    • 对于引用数据类型(对象、数组、字符串等):比较的是两个引用变量存储的内存地址值是否相同。换句话说,它判断的是两个引用是否指向堆内存中的同一个对象
      String str1 = new String("hello");
      String str2 = new String("hello");
      System.out.println(str1 == str2); // false
      // 因为str1和str2是两个不同的String对象,分别在堆中有自己的内存地址。
      

二、equals() 方法

  1. 作用

    • equals() 是一个方法,定义在 java.lang.Object 类中。
    • 它的设计初衷是比较两个对象的“逻辑内容”或“业务意义上的相等性”
  2. 默认行为

    • Object 类中,equals() 方法的默认实现就是使用 == 进行比较:
      public boolean equals(Object obj) {return (this == obj);
      }
      
    • 这意味着,对于没有重写 equals() 方法的类,equals() 的行为和 == 完全一样,都是比较内存地址。
  3. 重写(Override)

    • 为了让 equals() 能够比较对象的内容而不是地址,许多重要的Java类(如 String, Integer, Date, List 等)都重写了 equals() 方法。
    • 例如 String
      String str1 = new String("hello");
      String str2 = new String("hello");
      System.out.println(str1.equals(str2)); // true
      // String类重写了equals(),它会逐个字符比较两个字符串的内容是否相同。
      

三、核心区别总结

特性==equals()
类型运算符方法
定义位置语言内置java.lang.Object 类中定义
比较对象基本类型:比较
引用类型:比较内存地址
比较对象的内容/逻辑相等性(需重写方法)
可重写性不可重写可以被子类重写
典型用例比较基本类型值
检查对象是否为 null
比较字符串内容
比较集合元素
比较自定义对象的业务逻辑相等性

四、重要示例分析

public class EqualsDemo {public static void main(String[] args) {// 示例1:基本类型int a = 1000;int b = 1000;System.out.println(a == b);     // true (值相等)// System.out.println(a.equals(b)); // 编译错误!基本类型没有方法// 示例2:String (字符串常量池)String s1 = "hello";        // 字面量,放入字符串常量池String s2 = "hello";        // 指向常量池中已有的"hello"System.out.println(s1 == s2);     // true (指向同一个对象)System.out.println(s1.equals(s2)); // true (内容相同)// 示例3:String (new创建)String s3 = new String("hello"); // 在堆中新建对象String s4 = new String("hello"); // 在堆中新建另一个对象System.out.println(s3 == s4);     // false (两个不同的对象,地址不同)System.out.println(s3.equals(s4)); // true (String重写了equals,内容相同)// 示例4:自定义类 (未重写equals)Person p1 = new Person("Alice", 25);Person p2 = new Person("Alice", 25);System.out.println(p1 == p2);     // false (两个不同的对象)System.out.println(p1.equals(p2)); // false (默认用==比较,地址不同)// 如果Person类重写了equals方法来比较name和age,则p1.equals(p2)可能返回true。}
}class Person {String name;int age;public Person(String name, int age) {this.name = name;this.age = age;}// 注意:这里没有重写equals方法,使用的是Object的默认实现。
}

五、面试要点

  • 核心回答== 比较的是(基本类型)或内存地址(引用类型);equals() 默认比较内存地址,但通常被重写用来比较对象的内容
  • String是特例:要特别注意 String 对象使用 ==equals() 的区别,这与字符串常量池有关。
  • 重写equals():当你需要根据对象的属性(如ID、姓名等)来判断两个对象是否“相等”时,必须重写 equals() 方法(通常也需要重写 hashCode() 方法以保证一致性)。
  • null安全:调用 equals() 时,如果左边的对象可能为 null,应避免 null.equals(someObject)(会抛 NullPointerException),而应使用 Objects.equals(obj1, obj2)"literal".equals(variable)

十七.String、StringBuilder和StringBuffer有什么区别和特点

StringStringBuilderStringBuffer 是 Java 中用于处理字符串的三个核心类,它们在可变性、线程安全性、性能等方面有显著区别。理解它们的差异是 Java 基础的重要部分。


一、核心区别概览

特性StringStringBuilderStringBuffer
可变性不可变 (Immutable)可变 (Mutable)可变 (Mutable)
线程安全线程安全 (不可变即安全)非线程安全线程安全
性能频繁修改时低效高效高效,但略低于 StringBuilder
所属包java.langjava.langjava.lang
JDK 版本所有版本JDK 1.5+JDK 1.0+

二、详细解析

1. String - 不可变字符串
  • 核心特点不可变性 (Immutability)

    • 一旦创建,其内容(字符序列)就无法被修改
    • 任何看似“修改” String 的操作(如 concat(), substring(), + 连接),实际上都是创建一个新的 String 对象,并将引用指向新对象,原对象保持不变。
  • 优点

    • 线程安全:因为不可变,多个线程可以安全地共享同一个 String 实例,无需同步。
    • 安全性:防止内容被意外修改,适合用作方法参数、Map的key等。
    • 字符串常量池 (String Pool):JVM 维护一个字符串常量池,相同字面量的 String 可以共享同一个对象,节省内存。例如:
      String s1 = "hello";
      String s2 = "hello";
      System.out.println(s1 == s2); // true (指向常量池中同一个对象)
      
  • 缺点

    • 性能差:在需要频繁修改或拼接字符串的场景下,会产生大量中间的、无用的 String 对象,导致频繁的内存分配和垃圾回收(GC),严重影响性能。
      String result = "";
      for(int i = 0; i < 1000; i++) {result += i; // 每次循环都创建一个新String对象,效率极低
      }
      
2. StringBuilder - 可变、非线程安全
  • 核心特点可变性非线程安全

    • 内部维护一个可变的字符数组 (char[])。当进行追加、插入、删除等操作时,直接修改这个数组,不会创建新对象
    • 非线程安全:它的方法没有同步(synchronized)。如果多个线程同时修改同一个 StringBuilder 实例,可能会导致数据不一致或错误。
  • 优点

    • 性能高:在单线程环境下进行字符串拼接、修改时,性能远优于 StringStringBuffer,因为它避免了对象创建和同步开销。
  • 缺点

    • 非线程安全:不能在多线程环境中安全地共享和修改同一个实例。
  • 使用场景单线程中进行大量的字符串拼接、修改操作(如循环内构建字符串)。

3. StringBuffer - 可变、线程安全
  • 核心特点可变性线程安全

    • 功能和 StringBuilder 几乎完全相同,内部也是可变的字符数组。
    • 线程安全:它的关键方法(如 append(), insert(), delete())都被 synchronized 关键字修饰,保证了在多线程环境下的安全。
  • 优点

    • 线程安全:可以在多线程环境中安全地共享和修改同一个实例。
    • 性能较好:相比 String,在频繁修改时性能好得多。
  • 缺点

    • 性能开销:由于方法加了同步锁,在单线程环境下,性能略低于 StringBuilder,因为存在不必要的同步开销。
  • 使用场景多线程环境中需要共享并修改同一个字符串缓冲区(但这种情况相对较少见)。


三、共同点

  • 继承关系:都实现了 CharSequence 接口。
  • 常用方法:都提供了类似 append(), insert(), delete(), toString(), length(), charAt() 等方法(Stringappend 等是通过 +concat 体现)。
  • 初始容量StringBuilderStringBuffer 内部都有一个容量(capacity)的概念。当内容超过当前容量时,会自动扩容(通常是原容量的2倍+2),并复制原有内容。可以通过构造函数指定初始容量以优化性能。

四、如何选择?

  • 优先使用 String

    • 字符串内容基本不变
    • 需要作为Map的key多线程共享且不修改
    • 进行少量的字符串连接(编译器会优化 + 操作)。
  • 优先使用 StringBuilder

    • 单线程中进行大量的字符串拼接、修改操作。
    • 这是最常见的场景,性能最优。
  • 使用 StringBuffer

    • 多线程环境中,需要多个线程并发修改同一个字符串缓冲区(需谨慎设计,通常有更好的并发方案)。
    • 由于 StringBuilder 是在 JDK 1.5 引入的,在维护非常老的代码(JDK 1.4 及以下)时可能需要使用 StringBuffer

五、面试要点

  • 核心三要素:回答时务必围绕可变性、线程安全性、性能这三点展开。
  • 不可变性是关键:解释 String 的不可变性及其带来的影响(安全、常量池、性能问题)。
  • StringBuilder vs StringBuffer:强调 StringBuilderStringBuffer 的非同步版本,性能更高,是单线程下的首选。
  • 性能对比:明确指出在频繁修改场景下,StringBuilder > StringBuffer >> String
  • 实际应用:给出明确的选择建议,通常推荐 StringBuilder 用于拼接。

总结String 如同“只读文档”,安全但修改成本高;StringBuilder 如同“个人草稿本”,高效但不共享;StringBuffer 如同“带锁的共享文档”,安全共享但访问稍慢。根据场景选择合适的工具。

http://www.xdnf.cn/news/18770.html

相关文章:

  • 9.Shell脚本修炼手册---数值计算实践
  • 使用tensorRT10部署yolov5目标检测模型(2)
  • UE5.3 中键盘按键和操作绑定
  • 青少年机器人技术(六级)等级考试试卷-实操题(2021年12月)
  • 深入理解3x3矩阵
  • 11.Shell脚本修炼手册---IF 条件语句的知识与实践
  • 【数据结构】布隆过滤器的概率模型详解及其 C 代码实现
  • mysql没有mvcc之前遇到了什么问题
  • 2025年AI Agent规模化落地:企业级市场年增超60%,重构商业作业流程新路径
  • Hive中的join优化
  • 基于SpringBoot的招聘系统源码
  • 解决Conda访问官方仓库失败:切换国内镜像源的详细教程
  • STAR-CCM+|K-epsilon湍流模型溯源
  • GEO优化供应商:AI搜索时代的“答案”构建与移山科技的引领,2025高性价比实战指南
  • 基于大模型的对话式推荐系统技术架构设计
  • Linux服务环境搭建指南
  • AI绘画落地难?我用Firefly+Marmoset,将2D概念图“反向工程”为3D游戏资产
  • Deep Unfolding Net(LR到HR)
  • Linux 进程间通信之System V 共享内存
  • react中多个页面,数据相互依赖reducer解决方案
  • 文生3D实战:用[灵龙AI API]玩转AI 3D模型 – 第7篇
  • 青少年机器人技术(四级)等级考试试卷-实操题(2021年12月)
  • Boost.Asio 库中的 async_read_some用法
  • 我从零开始学习C语言(14)- 基本类型 PART1
  • vscode 中自己使用的 launch.json 设置
  • 5.7 生成环境使用cdn加载element ui
  • ASCOMP PDF Conversa:高效精准的PDF转换工具
  • pcie实现虚拟串口
  • 人工智能之数学基础:离散随机变量和连续随机变量
  • Java中如何实现对象的拷贝