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

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); // trueCharacter 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); // 100System.out.println("d = " + d); // 500System.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(),导致 NPESystem.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 不能拆箱成 intSystem.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(); // NPESystem.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;}@Overridepublic 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;@Overridepublic 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() {// 重写类的方法@Overridepublic 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() {@Overridepublic 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() {@Overridevoid 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 模型适用于不同的应用需求,选择合适的模型能有效提升程序性能。

相关文章:

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

问题1&#xff1a;Java 中有哪 8 种基本数据类型&#xff1f;它们的默认值和占用的空间大小知道不&#xff1f; 说说这 8 种基本数据类型对 应的包装类型。 在 Java 中&#xff0c;有 8 种基本数据类型&#xff08;Primitive Types&#xff09;&#xff1a; 基本数据类型关键…...

WPS mathtype间距太大、显示不全、公式一键改格式/大小

1、间距太大 用mathtype后行距变大的原因 mathtype行距变大到底怎么解决-MathType中文网 段落设置固定值 2、显示不全 设置格式&#xff1a; 打开MathType编辑器点击菜单栏中的"格式(Format)"选择"间距(Spacing)"在弹出的对话框中调整"分数间距(F…...

宇宙大爆炸是什么意思

根据宇宙大爆炸学说&#xff0c;宇宙间的一切都在彼此远离&#xff0c;而且距离越远&#xff0c;远离的速度越快。我们只能在地球上观察这种现象&#xff0c;而我们观察到的速度符合如下公式&#xff0c;其中 为哈勃常数&#xff0c; 为距离&#xff0c; 为速度&#xff08;…...

MotionLCM 部署笔记

目录 依赖项 humanml3d&#xff1a; sentence-t5-large 下载数据&#xff1a; 报错&#xff1a;No module named sentence_transformers 继续报错&#xff1a;from transformers.integrations import CodeCarbonCallback 解决方法&#xff1a; GitHub - Dai-Wenxun/Moti…...

VLLM性能调优

1. 抢占 显存不够的时候&#xff0c;某些request会被抢占。其KV cache被清除&#xff0c;腾退给其他request&#xff0c;下次调度到它&#xff0c;重新计算KV cache。 报这条消息&#xff0c;说明已被抢占&#xff1a; WARNING 05-09 00:49:33 scheduler.py:1057 Sequence gr…...

ESP32-S3模组上跑通esp32-camera(39)

接前一篇文章:ESP32-S3模组上跑通esp32-camera(38) 一、OV5640初始化 2. 相机初始化及图像传感器配置 上一回继续对reset函数的后一段代码进行解析。为了便于理解和回顾,再次贴出reset函数源码,在components\esp32-camera\sensors\ov5640.c中,如下: static int reset…...

Linux《基础指令》

在之前的Linux《Linux简介与环境的搭建》当中我们已经初步了解了Linux的由来和如何搭建Linux环境&#xff0c;那么接下来在本篇当中我们就要来学习Linux的基础指令。在此我们的学习是包括两个部分&#xff0c;即指令和关于Linux的基础知识&#xff1b;因此本篇指令和基础知识的…...

9.进程间通信

9.进程间通信 **1. 进程间通信&#xff08;IPC&#xff09;概述****2. 无名管道&#xff08;Pipe&#xff09;****3. 有名管道&#xff08;FIFO&#xff09;****4. 信号通信&#xff08;Signal&#xff09;****5. 练习与作业****6. 信号的应用****7. 总结** 1. 进程间通信&…...

Windows中本地组策略编辑器gpedit.msc打不开/微软远程桌面无法复制粘贴

目录 背景 解决gpedit.msc打不开 解决复制粘贴 剪贴板的问题 启用远程桌面剪贴板与驱动器 重启RDP剪贴板监视程序 以上都不行&#xff1f;可能是操作被Win11系统阻止 最后 背景 远程桌面无法复制粘贴&#xff0c;需要查看下主机策略组设置&#xff0c;结果按WinR输入…...

供应链系统设计-供应链中台系统设计(十二)- 清结算中心设计篇(一)

概述 在之前的文章中&#xff0c;我们通过之前的两篇文章中&#xff0c;如下所示&#xff1a; 供应链系统设计-供应链中台系统设计&#xff08;十&#xff09;- 清结算中心概念片篇 供应链系统设计-供应链中台系统设计&#xff08;十一&#xff09;- 清结算中心概念片篇 说…...

Vue.js 单页应用(SPA)开发教程:从零开始构建你的第一个项目

单页应用&#xff08;SPA&#xff0c;Single Page Application&#xff09;是现代前端开发的主流模式。Vue.js 是一个非常适合构建 SPA 的框架&#xff0c;它通过 Vue Router 实现页面导航&#xff0c;通过组件化开发和状态管理实现复杂的交互功能。本篇教程将带你了解 SPA 的基…...

Linux C openssl aes-128-cbc demo

openssl 各版本下载 https://openssl-library.org/source/old/index.html#include <stdio.h> #include <string.h> #include <openssl/aes.h> #include <openssl/rand.h> #include <openssl/evp.h>#define AES_KEY_BITS 128 #define GCM_IV_SIZ…...

你了解哪些Java限流算法?

大家好&#xff0c;我是锋哥。今天分享关于【你了解哪些Java限流算法?】面试题。希望对大家有帮助&#xff1b; 你了解哪些Java限流算法? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Java 中常用的限流算法主要有以下几种&#xff0c;它们广泛应用于处理流量控…...

【漫话机器学习系列】065.梯度(Gradient)

梯度&#xff08;Gradient&#xff09; 在数学和机器学习中&#xff0c;梯度是一个向量&#xff0c;用来表示函数在某一点的变化方向和变化率。它是多变量函数的一阶偏导数的组合。 梯度的定义 设有一个标量函数 &#xff0c;它对 ​ 是可微的&#xff0c;则该函数在某一点的…...

BswM(基础软件管理)详解

BswM&#xff08;基础软件管理&#xff09;详解 BswM&#xff08;Basic Software Manager&#xff09; 是 AUTOSAR BSW 的核心模块之一&#xff0c;负责协调基础软件&#xff08;BSW&#xff09;各模块的行为&#xff0c;根据系统状态、规则或事件动态配置其他模块。其设计目标…...

上位机知识篇---GitGitHub

文章目录 前言Git&GitHub是什么&#xff1f;GitGitHub Git和GitHub的区别定位功能使用方式开源协作 Git常用命令操作1. 配置2. 仓库操作3. 文件操作4. 分支与合并5.远程操作6.撤销更改7.查看历史 GitHub常用操作1.创建仓库2.Fork仓库3.Pull Request4.Issue跟踪5.代码审查 G…...

网站快速收录:提高页面加载速度的重要性

本文转自&#xff1a;百万收录网 原文链接&#xff1a;https://www.baiwanshoulu.com/32.html 网站快速收录中&#xff0c;提高页面加载速度具有极其重要的意义。以下从多个方面详细阐述其重要性&#xff1a; 一、提升用户体验 减少用户等待时间&#xff1a;页面加载速度直接…...

Vue.js组件开发-实现全屏背景图片滑动切换特效

使用 Vue 实现全屏背景图片滑动切换特效的详细步骤、代码、注释和使用说明。 步骤 创建 Vue 项目&#xff1a;使用 Vue CLI 创建一个新的 Vue 项目。准备图片资源&#xff1a;准备好要用于背景切换的图片&#xff0c;并将它们放在项目的合适目录下。编写 HTML 结构&#xff1…...

DeepSeek r1本地安装全指南

环境基本要求 硬件配置 需要本地跑模型&#xff0c;兼顾质量、性能、速度以及满足日常开发需要&#xff0c;我们需要准备以下硬件&#xff1a; CPU&#xff1a;I9内存&#xff1a;128GB硬盘&#xff1a;3-4TB 最新SSD&#xff0c;C盘确保有400GB&#xff0c;其它都可划成D盘…...

LitGPT - 20多个高性能LLM,具有预训练、微调和大规模部署的recipes

文章目录 一、关于 LitGPT二、快速启动安装LitGPT高级安装选项 从20多个LLM中进行选择 三、工作流程1、所有工作流程2、微调LLM3、部署LLM4、评估LLM5、测试LLM6、预训练LLM7、继续预训练LLM 四、最先进的功能五、训练方法示例 六、项目亮点教程 一、关于 LitGPT LitGPT 用于 …...

deepseek R1 14b显存占用

RTX2080ti 11G显卡&#xff0c;模型7b速度挺快&#xff0c;试试14B也不错。 7B显存使用5.6G&#xff0c;14B显存刚好够&#xff0c;出文字速度差不多。 打算自己写个移动宽带的IPTV播放器&#xff0c;不知道怎么下手&#xff0c;就先问他了。...

无用知识研究:对std::common_type以及问号表达式类型的理解

先说结论&#xff1a; 如果问号表达式能编译通过&#xff0c;那么std::common_type就能通过。因为common_type的底层依赖的就是?: common_type的实现里&#xff0c;利用了问号表达式&#xff1a;ternary conditional operator (?:) https://stackoverflow.com/questions/14…...

MapReduce概述

目录 1. MapReduce概述2. MapReduce的功能2.1 数据划分和计算任务调度2.2 数据/代码互定位2.3 系统优化2.4 出错检测和恢复 3. MapReduce处理流程4. MapReduce编程基础参考 1. MapReduce概述 MapReduce是面向大数据并行处理的计算模型、框架和平台:   1. 基于集群的高性能并行…...

循环神经网络(RNN)+pytorch实现情感分析

目录 一、背景引入 二、网络介绍 2.1 输入层 2.2 循环层 2.3 输出层 2.4 举例 2.5 深层网络 三、网络的训练 3.1 训练过程举例 1&#xff09;输出层 2&#xff09;循环层 3.2 BPTT 算法 1&#xff09;输出层 2&#xff09;循环层 3&#xff09;算法流程 四、循…...

Mac cursor设置jdk、Maven版本

基本配置 – Cursor 使用文档 首先是系统用户级别的设置参数&#xff0c;运行cursor&#xff0c;按下ctrlshiftp&#xff0c;输入Open User Settings(JSON)&#xff0c;在弹出的下拉菜单中选中下面这样的&#xff1a; 在打开的json编辑器中追加下面的内容&#xff1a; {"…...

WPS数据分析000005

目录 一、数据录入技巧 二、一维表 三、填充柄 向下自动填充 自动填充选项 日期填充 星期自定义 自定义序列 1-10000序列 四、智能填充 五、数据有效性 出错警告 输入信息 下拉列表 六、记录单 七、导入数据 ​编辑 八、查找录入 会员功能 Xlookup函数 VL…...

CTF从入门到精通

文章目录 背景知识CTF赛制 背景知识 CTF赛制 1.web安全:通过浏览器访问题目服务器上的网站&#xff0c;寻找网站漏洞(sql注入&#xff0c;xss&#xff08;钓鱼链接&#xff09;,文件上传&#xff0c;包含漏洞&#xff0c;xxe&#xff0c;ssrf&#xff0c;命令执行&#xff0c…...

Flutter使用Flavor实现切换环境和多渠道打包

在Android开发中通常我们使用flavor进行多渠道打包&#xff0c;flutter开发中同样有这种方式&#xff0c;不过需要在原生中配置 具体方案其实flutter官网个了相关示例&#xff08;https://docs.flutter.dev/deployment/flavors&#xff09;,我这里记录一下自己的操作 Android …...

Springboot如何使用面向切面编程AOP?

Springboot如何使用面向切面编程AOP? 在 Spring Boot 中使用面向切面编程&#xff08;AOP&#xff09;非常简单&#xff0c;Spring Boot 提供了对 AOP 的自动配置支持。以下是详细的步骤和示例&#xff0c;帮助你快速上手 Spring Boot 中的 AOP。 1. 添加依赖 首先&#xff…...

51单片机(STC89C52)开发:点亮一个小灯

软件安装&#xff1a; 安装开发板CH340驱动。 安装KEILC51开发软件&#xff1a;C51V901.exe。 下载软件&#xff1a;PZ-ISP.exe 创建项目&#xff1a; 新建main.c 将main.c加入至项目中&#xff1a; main.c:点亮一个小灯 #include "reg52.h"sbit LED1P2^0; //P2的…...