Java基础面试题总结(题目来源JavaGuide)

问题1:Java 中有哪 8 种基本数据类型?它们的默认值和占用的空间大小知道不? 说说这 8 种基本数据类型对 应的包装类型。

在 Java 中,有 8 种基本数据类型(Primitive Types):

基本数据类型关键字默认值占用空间对应的包装类
整数类型
字节型 (byte)byte01 字节 (8 bit)Byte
短整型 (short)short02 字节 (16 bit)Short
整型 (int)int04 字节 (32 bit)Integer
长整型 (long)long0L8 字节 (64 bit)Long
浮点数类型
单精度浮点型 (float)float0.0f4 字节 (32 bit)Float
双精度浮点型 (double)double0.0d8 字节 (64 bit)Double
字符类型
字符型 (char)char\u0000(空字符)2 字节 (16 bit)Character
布尔类型
布尔型 (boolean)booleanfalseJVM 规范未明确大小(通常 1 bit)Boolean

额外说明:

  1. boolean 的存储大小依赖于 JVM 实现,通常使用 1 bit(但实际存储可能会占据 1 字节)。
  2. char 采用 Unicode 编码,所以它占用 2 字节
  3. 包装类(Wrapper Classes)java.lang 包中,提供了基本类型的对象封装,并支持自动装箱(Autoboxing)和拆箱(Unboxing)。

问题2:包装类型的常量池技术了解么?

1. 什么是包装类型的常量池?

Java 的 ByteShortIntegerLongCharacterBoolean 类在一定范围内会缓存对象,避免重复创建,提高性能


2. 包装类常量池的示例

(1) Integer 缓存池

public class WrapperCacheTest {
    public static void main(String[] args) {
        Integer a = 127;
        Integer b = 127;
        System.out.println(a == b); // true,使用缓存

        Integer c = 128;
        Integer d = 128;
        System.out.println(c == d); // false,超出缓存范围,创建新对象
    }
}

解析:

  • Integer a = 127;Integer b = 127; 指向同一个缓存对象,所以 a == btrue
  • Integer c = 128;Integer d = 128; 超出缓存范围,创建不同对象,c == dfalse

(2) Boolean 常量池

Boolean bool1 = true;
Boolean bool2 = true;
System.out.println(bool1 == bool2); // true

Boolean 只有 TRUEFALSE 两个缓存对象,所以 bool1 == bool2 始终为 true

(3) Character 缓存池

Character char1 = 127;
Character char2 = 127;
System.out.println(char1 == char2); // true

Character char3 = 128;
Character char4 = 128;
System.out.println(char3 == char4); // false

Character 只缓存 0 ~ 127,超出范围会创建新对象。

3. 为什么 FloatDouble 没有缓存池?

Float f1 = 1.0f;
Float f2 = 1.0f;
System.out.println(f1 == f2); // false,每次创建新对象

Double d1 = 1.0;
Double d2 = 1.0;
System.out.println(d1 == d2); // false,每次创建新对象
原因
  • 浮点数范围太大,缓存意义不大。
  • 浮点数计算常常涉及小数误差,缓存可能会导致不稳定的行为。

4. valueOf()new 的区别

(1) 使用 valueOf()

Integer x = Integer.valueOf(127);
Integer y = Integer.valueOf(127);
System.out.println(x == y); // true

valueOf() 方法使用缓存池,所以 x == ytrue

(2) 使用 new Integer()

Integer x = new Integer(127);
Integer y = new Integer(127);
System.out.println(x == y); // false

new Integer() 直接创建新对象,不使用缓存,所以 x == yfalse

最佳实践:推荐使用 valueOf(),避免 new 关键字,以减少内存开销。

5. equals() 比较推荐

由于 == 比较的是对象地址,而 equals() 比较的是,建议用 equals() 进行数值比较:

Integer a = 128;
Integer b = 128;
System.out.println(a.equals(b)); // true,比较值,结果正确

System.out.println(a == b); // false,比较对象地址,超出缓存范围

6. 总结

包装类缓存范围缓存机制
Byte-128 ~ 127使用缓存
Short-128 ~ 127使用缓存
Integer-128 ~ 127(可扩展)使用缓存,可调整 -XX:AutoBoxCacheMax
Long-128 ~ 127使用缓存
Character0 ~ 127使用缓存
Boolean只有 truefalse使用缓存
Float无缓存每次创建新对象
Double无缓存每次创建新对象

✅ 最佳实践:

  1. 使用 valueOf() 代替 new 关键字。
  2. 使用 equals() 而不是 == 进行值比较。
  3. 了解缓存范围,避免意外的 == 结果。

问题3:为什么要有包装类型?

Java 之所以引入 包装类型(Wrapper Classes),主要是为了让基本数据类型(primitive types)具备对象的特性,方便在面向对象编程(OOP)中使用,同时增强泛型、集合框架等的兼容性。

1. 基本数据类型不是对象

Java 中有 8 种基本数据类型intcharbooleanfloat 等),它们的设计目标是提高性能,但它们不是对象:

int a = 10;
a.toString();  // ❌ 编译错误,int 没有方法
  • 不能直接调用方法。
  • 不能存储在**集合(Collection)**中。
  • 不能作为泛型的类型参数。

2. 包装类弥补了基本类型的不足

Java 提供了 对应的包装类型IntegerDoubleBoolean 等),它们是,可以像对象一样使用:

Integer num = 10;
System.out.println(num.toString()); // ✅ 10
  • 允许基本类型调用方法(比如 toString())。
  • 能够存入 泛型集合(如 ArrayList<Integer>)。
  • 支持 自动装箱/拆箱,让基本类型和对象能无缝转换。

3. 适用于 Java 集合框架

Java 集合(如 ArrayListHashMap只能存储对象,不能存储基本类型:

ArrayList<int> list = new ArrayList<>(); // ❌ 编译错误

必须使用包装类

ArrayList<Integer> list = new ArrayList<>();
list.add(10); // ✅ 自动装箱:int → Integer

原因:Java 泛型(Generics)不支持基本类型,但支持对象。

4. 支持泛型(Generics)

泛型不能直接使用基本类型:

public class Box<T> {
    private T value;
    public void set(T value) { this.value = value; }
    public T get() { return value; }
}

Box<int> box = new Box<>(); // ❌ 编译错误

必须使用包装类型

Box<Integer> box = new Box<>();
box.set(100); // ✅ 自动装箱:int → Integer
int num = box.get(); // ✅ 自动拆箱:Integer → int

泛型只能接受对象,所以 int 不能直接用,而 Integer 作为对象可以使用。

5. 具备更多功能

包装类提供了丰富的方法,可以方便地进行类型转换、数学运算等:

String str = "123";
int num = Integer.parseInt(str); // ✅ String → int
double d = Double.parseDouble("3.14"); // ✅ String → double

基本类型无法进行字符串解析,但包装类可以。

6. 适用于多线程中的同步

基本类型是线程不安全的,而包装类(如 AtomicInteger)可以在多线程环境下使用:

AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // ✅ 线程安全的自增

适用于高并发场景

7. 支持 null

基本类型不能存储 null,但包装类型可以:

Integer num = null; // ✅ 合法
int n = null;  // ❌ 编译错误

数据库操作时,某些字段可能为空,包装类更合适。

总结

基本数据类型包装类的作用
不是对象让基本类型具备对象特性
不能存集合支持泛型和集合框架
无方法包装类提供丰富的方法
不支持 null包装类支持 null
非线程安全包装类有线程安全实现

最佳实践

  • 优先使用基本类型(性能更好),只在需要对象时才用包装类。
  • 避免不必要的自动装箱/拆箱,以提高性能。

问题4:什么是自动拆装箱?原理?

自动装箱(Autoboxing)自动拆箱(Unboxing)Java 5 引入的特性,使得基本数据类型(intcharboolean 等)和它们的包装类IntegerCharacterBoolean 等)之间可以自动转换,简化代码编写。

1. 自动装箱(Autoboxing)

把基本数据类型 自动转换成 对应的包装类对象

Integer num = 10;  // 相当于 Integer num = Integer.valueOf(10);
  • 10int 类型,自动转换为 Integer 对象。
  • 底层调用 Integer.valueOf(int) 方法,如果在 -128 ~ 127 之间,会使用缓存池,否则创建新对象。

2. 自动拆箱(Unboxing)

把包装类对象 自动转换成 基本数据类型

Integer num = 10;  // 自动装箱
int a = num;       // 自动拆箱,相当于 int a = num.intValue();
  • numInteger 对象,自动转换成 int 类型。
  • 底层调用 num.intValue() 方法

3. 自动装箱/拆箱的使用示例

public class AutoBoxingDemo {
    public static void main(String[] args) {
        // 自动装箱:基本类型 → 包装类
        Integer a = 100; // 相当于 Integer a = Integer.valueOf(100);
        
        // 自动拆箱:包装类 → 基本类型
        int b = a; // 相当于 int b = a.intValue();

        // 自动装箱 + 计算 + 自动拆箱
        Integer c = 200;
        int d = c + 300; // c 先自动拆箱,再加 300,最后结果赋值给 int 类型的 d

        // 直接存入集合
        ArrayList<Integer> list = new ArrayList<>();
        list.add(10); // 自动装箱

        // 取出时自动拆箱
        int e = list.get(0);

        System.out.println("b = " + b); // 100
        System.out.println("d = " + d); // 500
        System.out.println("e = " + e); // 10
    }
}

问题5:遇到过自动拆箱引发的 NPE 问题吗?

1. 自动拆箱导致 NullPointerException 的示例

(1) null 赋值给基本类型

public class UnboxingNPE {
    public static void main(String[] args) {
        Integer num = null; // num 为空
        int value = num;    // 自动拆箱:num.intValue(),导致 NPE
        System.out.println(value);
    }
}

原因

  • int value = num; 触发自动拆箱,本质上调用了 num.intValue()
  • 由于 numnull,调用 intValue() 抛出 NullPointerException

2. 真实场景中的 NPE

(1) 集合取值时自动拆箱

import java.util.*;

public class UnboxingNPE {
    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<>();
        scores.put("Alice", 95);
        scores.put("Bob", null); // Bob 没有分数

        int bobScore = scores.get("Bob"); // NPE: null 不能拆箱成 int
        System.out.println("Bob's score: " + bobScore);
    }
}

原因

  • scores.get("Bob") 返回 null,然后 int bobScore = null; 触发自动拆箱,抛出 NullPointerException

解决方案

方式 1:手动检查 null

Integer bobScore = scores.get("Bob");
int score = (bobScore != null) ? bobScore : 0; // 避免 NPE

方式 2:使用 getOrDefault()

int bobScore = scores.getOrDefault("Bob", 0); // 直接提供默认值

(2) 数据库查询结果可能为 null

public class UnboxingNPE {
    public static Integer getUserAgeFromDB() {
        return null; // 模拟数据库查询不到数据
    }

    public static void main(String[] args) {
        int age = getUserAgeFromDB(); // NPE
        System.out.println("User age: " + age);
    }
}

解决方案

  • 使用 Optional 处理 null
Optional<Integer> ageOpt = Optional.ofNullable(getUserAgeFromDB());
int age = ageOpt.orElse(0); // 如果为空,默认值 0

3. 避免自动拆箱 NPE 的最佳实践

方法示例优点
手动 null 检查(num != null) ? num : 0直接避免 NPE
使用 getOrDefault()map.getOrDefault("key", 0)适用于 Map
使用 OptionalOptional.ofNullable(val).orElse(0)更优雅的 null 处理
避免包装类用于计算int sum = 0; 代替 Integer sum = 0;避免不必要的拆装箱

总结

  • 自动拆箱会导致 NullPointerException,如果变量可能为 null,一定要做 null 检查!
  • 使用 getOrDefault()Optional 等方法来避免 NPE
  • 避免在计算时使用 Integer 等包装类,尽量使用基本类型。

问题6:String、StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?

1. StringStringBufferStringBuilder 的区别

在 Java 中,StringStringBufferStringBuilder 都是用于表示字符串的类,但它们的可变性、线程安全性和性能不同。

特性String (不可变)StringBuffer (可变 & 线程安全)StringBuilder (可变 & 非线程安全)
可变性不可变 (final char[])可变 (char[] 数组)可变 (char[] 数组)
线程安全性线程安全线程安全 (同步 synchronized)非线程安全
性能(每次修改都会创建新对象)较慢(线程安全的同步开销)最快(无同步机制)
适用场景少量字符串处理(如字符串常量、少量拼接)多线程环境(字符串频繁修改)单线程高性能需求(字符串频繁修改)

2. 为什么 String 是不可变的?

String 在 Java 中是 不可变对象(Immutable),一旦创建就不能修改。这是由于以下几个原因:

(1) String 内部使用 final char[] 存储数据

查看 String 类的源码:

public final class String implements java.io.Serializable, Comparable<String> {
    private final char value[];
}
  • valuefinal 类型的 字符数组 (char[]),所以它的引用不能被修改。
  • 不可变String 类不提供修改 char[] 内容的方法,如 setCharAt(),只能通过创建新对象改变值。

(2) 线程安全

由于 String 不可变,所以它天然是线程安全的,多个线程可以安全地共享同一个 String 对象,而不用加锁

例如:

String str1 = "Hello";
String str2 = str1; // 共享同一个对象

由于 str1 是不可变的,str2 也不会因为 str1 的改变而受到影响。

(3) String 常量池优化

在 Java 中,String 对象会存储在字符串常量池(String Pool)中,避免重复创建:

String s1 = "Hello";
String s2 = "Hello";
System.out.println(s1 == s2); // true, 指向同一个对象
  • s1s2 指向的是同一个字符串常量池对象,而不会新建对象,减少内存占用。

如果 String 是可变的,这个优化就会导致数据混乱

s1.toUpperCase(); // 如果 String 可变,s2 也会被改变,破坏了安全性!

(4) hashCode() 设计

  • String 是不可变的,所以它的 hashCode() 在创建时就计算好并缓存,提高了 Hash 相关操作(如 HashMap)的性能:
public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        for (char val : value) {
            h = 31 * h + val;
        }
        hash = h;
    }
    return h;
}

由于 hashCode 不变,String 可以安全地作为 HashMapkey,不必担心 key 被修改导致哈希值变化。

3. StringBufferStringBuilder 的区别

StringBufferStringBuilder 都是 可变的字符串类,但它们的主要区别是线程安全性

(1) StringBuffer 是线程安全的

  • StringBuffer 方法使用 synchronized 关键字,保证线程安全:
public synchronized StringBuffer append(String str) { ... }
  • 适用于多线程环境,但由于同步锁的存在,性能比 StringBuilder 低。

示例:

StringBuffer sb = new StringBuffer("Hello");
sb.append(" World");
System.out.println(sb); // Hello World

(2) StringBuilder 是非线程安全的

  • StringBuilder 没有同步机制,所以性能更高,适用于单线程环境:
public StringBuilder append(String str) { ... } // 无 synchronized
  • 单线程环境推荐使用 StringBuilder,比 StringBuffer 更快。

示例:

StringBuilder sb = new StringBuilder("Hello");
sb.append(" World");
System.out.println(sb); // Hello World

4. 何时使用 StringStringBufferStringBuilder

需求推荐使用原因
少量字符串拼接String代码简洁,性能影响不大
大量字符串拼接(单线程)StringBuilder最高性能,无同步开销
大量字符串拼接(多线程)StringBuffer线程安全,防止并发问题

5. 关键总结

  1. String 是不可变的,存储在字符串常量池中,适用于少量字符串操作
  2. StringBuffer 是线程安全的,使用 synchronized,适用于多线程环境
  3. StringBuilder 是非线程安全的,但性能最好,适用于单线程高性能场景
  4. 推荐:
    • 少量拼接用 String(简洁)。
    • 单线程高性能用 StringBuilder
    • 多线程环境用 StringBuffer

问题7:重载和重写的区别?

重载(Overloading)重写(Overriding) 是 Java 中**多态(Polymorphism)**的重要表现形式。它们的主要区别如下:

方法重载(Overloading)方法重写(Overriding)
定义同一个类中,方法名相同,参数列表不同(参数个数或类型不同)父类和子类之间,方法名、参数列表都相同,子类对父类的方法进行重新实现
方法名必须相同必须相同
参数列表必须不同(参数类型、数量或顺序)必须相同
返回值可以不同必须相同或是父类返回值的子类(协变返回类型)
访问修饰符可以不同不能更严格,但可以更宽松
抛出异常可以不同不能抛出比父类更大的异常(可以抛出更小的或不抛出异常)
发生范围同一个类内部子类继承父类后
是否依赖继承不需要继承必须有继承关系
调用方式通过方法签名的不同,在编译时决定调用哪个方法(静态绑定,编译期多态通过子类对象调用,运行时决定调用哪个方法(动态绑定,运行期多态

 问题8:== 和 equals() 的区别

在 Java 中,==equals() 都可以用来比较对象,但它们的本质、适用范围和行为有所不同。

比较项==(引用/值比较)equals()(对象内容比较)
比较方式比较内存地址(引用)比较对象的内容(可重写)
适用范围基本数据类型引用类型只能用于对象
默认行为对于对象,默认比较地址Object 类的 equals() 方法)需要重写 equals() 方法以比较内容
适用于基本数据类型的值比较引用是否相同判断两个对象是否逻辑相等

1. == 的行为

(1) 用于基本数据类型

对于 基本数据类型intdoublecharboolean 等),== 直接比较值

int a = 10;
int b = 10;
System.out.println(a == b); // true,值相等

(2) 用于引用类型

对于 引用类型(对象),== 比较的是 对象在内存中的地址(是否指向同一对象):

String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false,不是同一个对象

虽然 s1s2 的内容相同,但它们指向不同的内存地址,所以 == 返回 false

(3) == 在字符串常量池中的行为

Java 的 字符串常量池 机制会让相同的字符串共享内存

String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // true,指向相同的字符串池对象

但如果用 new 关键字创建字符串:

String s1 = new String("hello");
String s2 = "hello";
System.out.println(s1 == s2); // false,s1 在堆中,s2 在字符串池

2. equals() 的行为

(1) Object 类的默认 equals()

Java 中所有类默认继承 Object,其 equals() 方法默认也是比较内存地址

class Person {}
public class Test {
    public static void main(String[] args) {
        Person p1 = new Person();
        Person p2 = new Person();
        System.out.println(p1.equals(p2)); // false,不同对象
    }
}

== 行为相同。

2) String 类重写了 equals()

String 类重写了 equals(),改为比较字符串的内容

String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1.equals(s2)); // true,比较的是内容

尽管 s1s2 指向不同的对象,但 equals() 比较的是字符内容,所以返回 true

3) 自定义类重写 equals()

如果想让 自定义类 按内容比较,需要重写 equals()

class Person {
    String name;

    Person(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true; // 判断是否是同一对象
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return this.name.equals(person.name); // 按 name 比较
    }
}

public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("Alice");
        Person p2 = new Person("Alice");
        System.out.println(p1.equals(p2)); // true,内容相同
    }
}

这里 p1p2 是不同对象,但 equals() 被重写为比较 name,所以返回 true

3. == vs equals() 总结

比较项==equals()
基本数据类型比较值不能用
对象引用比较地址默认比较地址,但可重写
String

比较地址

比较内容(已重写)
可否重写不可重写可重写,按需求自定义逻辑
适用场景判断是否为同一对象判断对象内容是否相等

4. 推荐使用方式

1.基本数据类型用 ==

int a = 100;
int b = 100;
System.out.println(a == b); // true

2.引用类型判断是否为同一个对象用 ==

String s1 = "hello";
String s2 = new String("hello");
System.out.println(s1 == s2); // false,不是同一个对象

3.判断对象内容是否相等用 equals()

String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1.equals(s2)); // true,内容相同

4.对于自定义对象,重写 equals() 方法

class Person {
    String name;
    @Override
    public boolean equals(Object obj) { ... }
}

 问题9:Java 反射?反射有什么优点/缺点?你是怎么理解反射的(为什么框架需要反射)?

Java 反射(Reflection)概述

Java 反射是 Java 提供的一种强大功能,它允许我们在运行时 动态地获取类的信息(如类的方法、字段、构造方法等),并对它们进行操作。通过反射,我们可以 动态地创建对象、调用方法、访问属性,甚至可以在运行时加载类。

反射的基本概念

  1. Class:Java 中所有类的元数据都由 Class 类表示。通过 Class 类,你可以获得类的构造方法、字段、方法等信息。
  2. Method:通过反射可以获取类的所有方法并执行它们。
  3. Field:通过反射可以访问类的字段。
  4. Constructor:通过反射可以创建类的实例。

常用反射操作示例

import java.lang.reflect.*;

class Person {
    private String name;
    private int age;

    public Person() {}

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

    public void sayHello() {
        System.out.println("Hello, my name is " + name);
    }

    private void privateMethod() {
        System.out.println("This is a private method.");
    }
}

public class ReflectionExample {
    public static void main(String[] args) throws Exception {
        // 获取类的 Class 对象
        Class<?> clazz = Class.forName("Person");

        // 获取构造方法并创建实例
        Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
        Object person = constructor.newInstance("Alice", 25);

        // 调用方法
        Method method = clazz.getMethod("sayHello");
        method.invoke(person);

        // 获取私有方法并调用
        Method privateMethod = clazz.getDeclaredMethod("privateMethod");
        privateMethod.setAccessible(true); // 设置可访问
        privateMethod.invoke(person);
    }
}

输出

Hello, my name is Alice
This is a private method.

在这个例子中,我们使用反射:

  • 获取类的 Class 对象
  • 通过构造方法创建对象
  • 调用公开方法 sayHello
  • 调用私有方法 privateMethod,并通过 setAccessible(true) 让私有方法可以被访问。

反射的优点

  1. 动态性

    • 反射允许你在运行时 动态地加载类动态地创建对象,以及 动态地调用方法,这让程序可以非常灵活地应对不同的情况。
    • 例如,Spring 框架使用反射来根据配置文件 自动注入依赖,而不需要在代码中硬编码类。
  2. 灵活性

    • 通过反射,你可以访问类的私有方法和字段,甚至是 访问不存在的类成员,这使得在某些场景下,开发者可以灵活处理一些特殊情况。
  3. 框架和库的开发

    • 框架和库(例如 Hibernate、Spring、JUnit)通过反射来实现灵活的功能。通过反射,框架可以在运行时了解类的信息并做出相应的处理,而无需显式地了解每个类。
  4. 与遗留代码的兼容性

    • 使用反射可以访问没有源代码的类,例如,在 Java 库中使用的第三方库或组件,反射可以帮助在运行时动态地调用和修改类成员。

反射的缺点

  1. 性能开销

    • 反射操作通常比直接调用方法慢得多,因为它会绕过编译时的类型检查。每次反射都会涉及到一些额外的计算(如查找方法、创建实例等),因此 性能开销较大
    • 对于需要频繁调用的代码,反射可能会导致性能瓶颈。
  2. 安全性问题

    • 反射可以访问类的私有成员,这可能会暴露 敏感数据 或者 破坏类的封装性,带来 安全隐患。因此,反射有时会被禁用,尤其是在安全敏感的应用中
  3. 代码可读性和可维护性差

    • 使用反射的代码不如普通的面向对象代码清晰和易于理解。因为你不能通过直接查看代码或接口来确定一个类的行为,反射代码可能会变得难以调试和维护
  4. 错误较难发现

    • 反射的代码通常在编译时无法捕获错误,错误通常会在运行时出现,这使得 调试变得困难
    • 例如,反射可能会尝试调用不存在的方法,或者访问不存在的字段,这些问题通常只有在程序运行时才能被发现。

为什么框架需要反射

许多框架(如 Spring、Hibernate)依赖反射来实现灵活的配置和动态行为。反射为框架提供了以下几方面的优势:

  1. 依赖注入

    • Spring 框架通过反射来实现 依赖注入。当应用启动时,Spring 容器会通过反射获取各个类的构造方法、属性等信息,然后根据配置自动为类注入所需的依赖。
  2. 动态代理

    • 在 AOP(面向切面编程)中,Spring 使用反射技术生成 动态代理类,通过代理对象的反射,拦截目标方法的执行,实现诸如日志记录、事务控制等功能。
  3. ORM(对象关系映射)

    • Hibernate 等 ORM 框架通过反射来将 数据库表映射成 Java 对象,并实现自动的持久化操作。通过反射,Hibernate 可以动态地从类中获取字段信息,将数据持久化到数据库。
  4. 配置和扩展性

    • 反射为框架提供了 高度的扩展性,使得框架可以在运行时动态地加载不同的类或组件,而不需要在编译时知道所有的细节。比如,插件式框架可以通过反射动态加载和调用外部插件。

总结

  • 反射是 Java 提供的一种强大机制,可以在运行时动态地获取类的信息并操作它们。
  • 反射的优点包括 动态性、灵活性,尤其适用于框架开发和与遗留代码的兼容。
  • 然而,反射也有一些缺点,主要是 性能开销、代码可维护性差、潜在的安全隐患
  • 框架需要反射,主要是为了提供 灵活的依赖注入、动态代理、对象关系映射 等功能,以便在运行时根据需求灵活调整。

问题10:谈谈对 Java 注解的理解,解决了什么问题?

Java 注解概述

Java 注解是一种提供元数据的机制,用于向代码中添加额外的信息,通常通过反射等方式进行处理。它本身不直接影响程序执行,但可以提供对代码的附加信息,用于编译检查、代码生成、运行时处理等。

注解解决的问题

  1. 简化代码和配置: 注解帮助减少配置文件或硬编码,提升开发效率。比如在 Spring 中使用 @Autowired 注解自动注入依赖。

  2. 提高可读性: 注解使得代码自文档化,开发者能通过注解清晰地知道代码的意图。例如,@Override 注解标明方法是覆盖父类方法。

  3. 自动化处理: 通过注解和反射,框架能够自动化处理某些功能,如 Spring 框架通过 @RequestMapping 处理 HTTP 请求。

  4. 验证和编译时检查: 使用注解可以进行数据验证或编译时检查,比如 @NotNull 注解确保字段或参数不为 null

注解的常见用途

  • 依赖注入(Spring 中使用 @Autowired 自动注入)。
  • ORM 映射(Hibernate 使用 @Entity 注解映射类到数据库表)。
  • Web 请求映射(Spring MVC 使用 @RequestMapping 映射 URL)。
  • 验证(Hibernate Validator 使用 @NotNull@Size 等注解)。

优缺点

优点

  • 简化配置和代码,减少硬编码。
  • 提高代码可读性和维护性。
  • 自动化处理,减少重复代码。

缺点

  • 性能开销:反射和注解处理可能影响性能。
  • 调试困难:注解的实际作用通常由框架处理,调试较为复杂。

问题11:内部类了解吗?匿名内部类了解吗?

内部类(Inner Class)概述

Java 中的 内部类 是指在一个类的内部定义的类。内部类能够访问外部类的成员(包括私有成员),并且可以通过外部类的实例创建。

内部类的类型

1.成员内部类: 定义在外部类的成员位置,可以访问外部类的所有成员(包括私有成员)。

class Outer {
    private String name = "Outer class";
    
    class Inner {
        public void display() {
            System.out.println(name); // 可以访问外部类的私有成员
        }
    }
}

2.静态内部类: 使用 static 修饰的内部类,它不能访问外部类的非静态成员,必须通过外部类的类名来访问。静态内部类的实例可以独立于外部类的实例存在。

class Outer {
    private static String message = "Static Inner Class";
    
    static class StaticInner {
        public void show() {
            System.out.println(message); // 只能访问外部类的静态成员
        }
    }
}

3.局部内部类: 定义在方法内部的类,通常是局部变量的一部分。它只能在方法内部使用。

class Outer {
    public void outerMethod() {
        class LocalInner {
            public void display() {
                System.out.println("Local inner class");
            }
        }
        LocalInner local = new LocalInner();
        local.display();
    }
}

4.匿名内部类: 是没有名字的内部类,通常用于简化代码,特别是在事件监听器和回调中常用。匿名内部类的语法通常是直接在创建对象的同时定义类,省去了定义内部类的步骤。

匿名内部类

匿名内部类是 没有类名 的内部类,它通过继承一个类或实现一个接口来创建一个新的类实例。通常,匿名内部类用于需要创建类的实例并立即使用的场景,尤其是在接口的回调方法、事件监听器等情况下。

匿名内部类的语法

ClassName obj = new ClassName() {
    // 重写类的方法
    @Override
    public void method() {
        System.out.println("Method implemented in anonymous class");
    }
};

使用匿名内部类的例子

1.实现接口

interface Greeting {
    void greet(String name);
}

public class AnonymousInnerClassExample {
    public static void main(String[] args) {
        // 匿名内部类实现接口
        Greeting greeting = new Greeting() {
            @Override
            public void greet(String name) {
                System.out.println("Hello, " + name);
            }
        };
        greeting.greet("Alice");
    }
}

2.继承类

class Animal {
    void sound() {
        System.out.println("Animal makes sound");
    }
}

public class AnonymousInnerClassExample {
    public static void main(String[] args) {
        // 匿名内部类继承类
        Animal animal = new Animal() {
            @Override
            void sound() {
                System.out.println("Dog barks");
            }
        };
        animal.sound();
    }
}

匿名内部类的特点

  1. 简洁性:它可以让你在创建对象的同时定义类,而不需要显式地定义一个新类。
  2. 不能有构造器:匿名内部类没有名称,因此不能定义构造器。
  3. 只能继承一个类或实现一个接口:匿名内部类必须继承一个类或者实现一个接口,不能多重继承。
  4. 常用于事件监听:在 GUI 编程中,匿名内部类常用来实现事件监听器等。

问题12:BIO,NIO,AIO 有什么区别? 

BIO(Blocking I/O)、NIO(Non-blocking I/O)和 AIO(Asynchronous I/O)是 Java 中三种不同的 I/O 模型,它们主要的区别在于 I/O 操作的阻塞特性和异步处理的能力。下面是它们的详细对比:


1. BIO(Blocking I/O)

特点:

  • 阻塞式 I/O:每次 I/O 操作(读取或写入)都会阻塞当前线程,直到操作完成。
  • 每个 I/O 操作都需要一个线程来完成,当请求很多时,可能会创建大量线程,造成性能瓶颈。

流程:

  1. 客户端发起连接请求。
  2. 服务器接受连接请求,分配一个线程进行处理。
  3. 该线程在 I/O 操作时会被阻塞,直到完成操作(读或写)。

优缺点:

  • 优点:实现简单、直观,适合小规模并发或单线程应用。
  • 缺点:性能较差,线程过多时会导致高开销,限制了系统的并发处理能力。

适用场景:适用于连接数较少、并发量不高的传统应用。


2. NIO(Non-blocking I/O)

特点:

  • 非阻塞 I/O:引入了 SelectorChannel 等概念,允许多个 I/O 操作共享一个或多个线程,避免每个连接占用一个线程。线程不会因 I/O 操作而阻塞,线程可以在等待 I/O 完成的同时做其他事情。
  • 事件驱动:NIO 使用非阻塞模式,线程可以轮询 (polling) 检查 I/O 操作是否完成,通过 Selector 来监听多个通道(Channel)的 I/O 状态。

流程:

  1. 客户端发起连接请求。
  2. 服务器通过 Selector 监听多个通道(Channel)上的 I/O 事件,线程不会被阻塞,而是轮询所有通道。
  3. 一旦某个通道的 I/O 操作准备好,线程就会处理相应的操作。

优缺点:

  • 优点:支持高并发,使用少量线程就能处理大量连接。
  • 缺点:编程复杂,处理多个连接时需要编写较为复杂的代码(如 SelectorChannel)。

适用场景:适用于高并发应用,如 Web 服务器、聊天服务器等。


3. AIO(Asynchronous I/O)

特点:

  • 异步 I/O:在 AIO 中,I/O 操作的执行完全是异步的,线程不需要等待 I/O 完成。I/O 请求会通过操作系统内核来处理,操作系统会在完成 I/O 操作时通知应用程序。
  • 线程发出 I/O 请求后,立即返回,I/O 操作在后台完成。当 I/O 完成时,操作系统会通过回调函数通知应用程序。

流程:

  1. 客户端发起连接请求。
  2. 服务器通过异步接口发出 I/O 请求。
  3. 当 I/O 操作完成时,操作系统通过回调函数通知服务器。

优缺点:

  • 优点:高效,能够利用操作系统的异步 I/O 支持,减少了应用层的线程等待时间,极大提高了并发处理能力。
  • 缺点:实现较为复杂,底层需要支持异步 I/O,且需要操作系统的支持(如 Linux 的 epoll 或 Windows 的 IOCP)。

适用场景:适用于大规模、高并发、低延迟的应用,特别是需要大量并发连接而不希望使用过多线程的场景。


总结对比

特性BIO(阻塞 I/O)NIO(非阻塞 I/O)AIO(异步 I/O)
阻塞方式阻塞式操作非阻塞操作完全异步,不阻塞线程
线程模型每个连接一个线程一个线程处理多个连接,通过轮询(Selector通过操作系统异步处理,通知回调
性能性能较差,连接数多时会消耗大量线程性能较好,支持高并发性能最好,几乎不依赖线程阻塞
编程复杂度简单易懂,代码直观编程复杂,需要使用 SelectorChannel编程复杂,操作系统支持,通常通过回调处理
适用场景低并发、传统应用高并发、大量连接的场景超高并发、低延迟、大规模并发连接的应用

总结

  • BIO 适用于低并发场景,简单易懂,但性能较差。
  • NIO 适用于中到高并发场景,能高效利用少量线程处理大量连接,但编程复杂。
  • AIO 提供最好的性能,适用于极高并发的场景,但实现复杂并依赖操作系统的异步支持。

不同的 I/O 模型适用于不同的应用需求,选择合适的模型能有效提升程序性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/961768.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

从 SAP 功能顾问到解决方案架构师:破茧成蝶之路

目录 行业瞭望&#xff1a;架构师崭露头角 现状剖析&#xff1a;功能顾问的局限与机遇 能力跃迁&#xff1a;转型的核心要素 &#xff08;一&#xff09;专业深度的掘进 &#xff08;二&#xff09;集成能力的拓展 &#xff08;三&#xff09;知识广度的延伸 &#xff0…

unity学习23:场景scene相关,场景信息,场景跳转

目录 1 默认场景和Assets里的场景 1.1 scene的作用 1.2 scene作为project的入口 1.3 默认场景 2 场景scene相关 2.1 创建scene 2.2 切换场景 2.3 build中的场景&#xff0c;在构建中包含的场景 &#xff08;否则会认为是失效的Scene&#xff09; 2.4 Scenes in Bui…

36、【OS】【Nuttx】OSTest分析(2):环境变量测试

背景 2025.1.29 蛇年快乐&#xff01; 接之前wiki 35、【OS】【Nuttx】OSTest分析&#xff08;1&#xff09;&#xff1a;stdio测试&#xff08;五&#xff09; 已经分析完了第一个测试项&#xff0c;输入输出端口测试&#xff0c;接下来分析下环境变量测试&#xff0c;也比较…

使用Ollama本地部署DeepSeek R1

前言 DeepSeek是一款开源的智能搜索引擎&#xff0c;能够通过深度学习技术提高搜索的智能化水平。如果你正在寻找一种方式来将DeepSeek部署在本地环境中&#xff0c;Ollama是一个非常方便的工具&#xff0c;它允许你在本地快速部署并管理各种基于AI的模型。 在本篇博客中&…

libOnvif通过组播不能发现相机

使用libOnvif库OnvifDiscoveryClient类&#xff0c; auto discovery new OnvifDiscoveryClient(QUrl(“soap.udp://239.255.255.250:3702”), cb.Build()); 会有错误&#xff1a; end of file or no input: message transfer interrupted or timed out(30 sec max recv delay)…

Visual Studio使用GitHub Copilot提高.NET开发工作效率

GitHub Copilot介绍 GitHub Copilot 是一款 AI 编码助手&#xff0c;可帮助你更快、更省力地编写代码&#xff0c;从而将更多精力集中在问题解决和协作上。 GitHub Copilot Free包含哪些功能&#xff1f; 每月 2000 代码补全&#xff0c;帮助开发者快速完成代码编写。 每月 …

HTB:Forest[WriteUP]

连接至HTB服务器并启动靶机 分配IP&#xff1a;10.10.16.21 靶机IP&#xff1a;10.10.10.161 靶机Domain&#xff1a;forest.htb 目录 连接至HTB服务器并启动靶机 信息收集 使用rustscan对靶机TCP端口进行开放扫描 将靶机TCP开放端口号提取并保存 使用nmap对靶机TCP开放端…

项目集成GateWay

文章目录 1.环境搭建1.创建sunrays-common-cloud-gateway-starter模块2.目录结构3.自动配置1.GateWayAutoConfiguration.java2.spring.factories 3.pom.xml4.注意&#xff1a;GateWay不能跟Web一起引入&#xff01; 1.环境搭建 1.创建sunrays-common-cloud-gateway-starter模块…

STM32 PWM驱动直流电机

接线图&#xff1a; 代码配置&#xff1a; 根据驱动舵机的代码来写&#xff0c;与舵机不同的是&#xff0c;这次的引脚接到了PA2上&#xff0c;所以需要改一下引脚以及改为OC3通道。 另外还需在配置两个GPIO引脚&#xff0c;来控制电机的旋转方向&#xff0c;这里连接到了PA4与…

强大到工业层面的软件

电脑数据删不干净&#xff0c;简直是一种让人抓狂的折磨&#xff01;明明已经把文件扔进了回收站&#xff0c;清空了&#xff0c;可那些残留的数据就像牛皮癣一样&#xff0c;怎么也除不掉。这种烦恼简直无处不在&#xff0c;让人从头到脚都感到无比烦躁。 首先&#xff0c;心…

一分钟搭建promehteus+grafana+alertmanager监控平台

为什么要自己搭建一个监控平台 平时进行后端开发&#xff0c;特别是微服务的后端可开发&#xff0c;一定少不了对接监控平台&#xff0c;但是平时进行一些小功能的测试又没有必要每次都手动安装那么多软件进行一个小功能的测试&#xff0c;这里我使用docker-compose搭建了一个…

记一次STM32编译生成BIN文件过大的问题(基于STM32CubeIDE)

文章目录 问题描述解决方法更多拓展 问题描述 最近在一个项目中使用了 STM32H743 单片机&#xff08;基于 STM32CubeIDE GCC 开发&#xff09;&#xff0c;它的内存分为了 DTCMRAM RAM_D1 RAM_D2 …等很多部分。其中 DTCM 的速度是比通常的内存要快的&#xff0c;缺点是不支持…

前端-Rollup

Rollup 是一个用于 JavaScript 的模块打包工具&#xff0c;它将小的代码片段编译成更大、更复杂的代码&#xff0c;例如库或应用程序。它使用 JavaScript 的 ES6 版本中包含的新标准化代码模块格式&#xff0c;而不是以前的 CommonJS 和 AMD 等特殊解决方案。ES 模块允许你自由…

崇州市街子古镇正月初一繁华剪影

今天是蛇年正月初一&#xff0c;下午笔者步出家门&#xff0c;逛到了崇州市街子古镇井水街&#xff0c;想看看景象如何。结果看到的是车水马龙、人流如织&#xff0c;繁花似锦&#xff0c;热闹非凡&#xff0c;原来今天开始预订此地摆下的长街宴。心里高兴&#xff0c;便用手机…

【橘子Kibana】Kibana的分析能力Analytics简易分析

一、kibana是啥&#xff0c;能干嘛 我们经常会用es来实现一些关于检索&#xff0c;关于分析的业务。但是es本身并没有UI,我们只能通过调用api来完成一些能力。而kibana就是他的一个外置UI&#xff0c;你完全可以这么理解。 当我们进入kibana的主页的时候你可以看到这样的布局。…

python3+TensorFlow 2.x(二) 回归模型

目录 回归算法 1、线性回归 (Linear Regression) 一元线性回归举例 2、非线性回归 3、回归分类 回归算法 回归算法用于预测连续的数值输出。回归分析的目标是建立一个模型&#xff0c;以便根据输入特征预测目标变量&#xff0c;在使用 TensorFlow 2.x 实现线性回归模型时&…

OpenAI-Edge-TTS:本地化 OpenAI 兼容的文本转语音 API,免费高效!

文本转语音&#xff08;TTS&#xff09;技术已经成为人工智能领域的重要一环&#xff0c;无论是语音助手、教育内容生成&#xff0c;还是音频文章创作&#xff0c;TTS 工具都能显著提高效率。今天要为大家介绍的是 OpenAI-Edge-TTS&#xff0c;一款基于 Microsoft Edge 在线文本…

练习(复习)

大家好&#xff0c;今天我们来做几道简单的选择题目来巩固一下最近学习的知识&#xff0c;以便我们接下来更好的学习。 这道题比较简单&#xff0c;我们前面学过&#xff0c;在Java中&#xff0c;一个类只能继承一个父类&#xff0c;但是一个父类可以有多个子类&#xff0c;一个…

Meta推动虚拟现实:Facebook如何进入元宇宙时代

随着科技的不断进步&#xff0c;虚拟现实&#xff08;VR&#xff09;与增强现实&#xff08;AR&#xff09;技术的快速发展&#xff0c;世界正在逐步迈向一个全新的数字时代——元宇宙。而在这个革命性的转变中&#xff0c;Meta&#xff08;前身为Facebook&#xff09;作为全球…

go gin配置air

一、依赖下载 安装最新&#xff0c;且在你工作区下进行安装&#xff0c;我的是D:/GO是我的工作区&#xff0c;所有项目都在目录下的src&#xff0c; go install github.com/air-verse/airlatest 如果出现类似报错&#xff1a; 将图中第三行 github.com/air-verse/air 替换最…