网站做任务领q币,发稿网,合肥网站制作方案,上海排名前十的装修公司问题1#xff1a;Java 中有哪 8 种基本数据类型#xff1f;它们的默认值和占用的空间大小知道不#xff1f; 说说这 8 种基本数据类型对 应的包装类型。
在 Java 中#xff0c;有 8 种基本数据类型#xff08;Primitive Types#xff09;#xff1a;
基本数据类型关键…问题1Java 中有哪 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 bitBoolean
额外说明
boolean 的存储大小依赖于 JVM 实现通常使用 1 bit但实际存储可能会占据 1 字节。char 采用 Unicode 编码所以它占用 2 字节。包装类Wrapper Classes 在 java.lang 包中提供了基本类型的对象封装并支持自动装箱Autoboxing和拆箱Unboxing。
问题2包装类型的常量池技术了解么
1. 什么是包装类型的常量池
Java 的 Byte、Short、Integer、Long、Character 和 Boolean 类在一定范围内会缓存对象避免重复创建提高性能。 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 b 为 true。Integer c 128; 和 Integer d 128; 超出缓存范围创建不同对象c d 为 false。
(2) Boolean 常量池
Boolean bool1 true;
Boolean bool2 true;
System.out.println(bool1 bool2); // trueBoolean 只有 TRUE 和 FALSE 两个缓存对象所以 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); // falseCharacter 只缓存 0 ~ 127超出范围会创建新对象。
3. 为什么 Float 和 Double 没有缓存池
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); // truevalueOf() 方法使用缓存池所以 x y 为 true。
(2) 使用 new Integer()
Integer x new Integer(127);
Integer y new Integer(127);
System.out.println(x y); // falsenew Integer() 直接创建新对象不使用缓存所以 x y 为 false。
最佳实践推荐使用 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:AutoBoxCacheMaxLong-128 ~ 127使用缓存Character0 ~ 127使用缓存Boolean只有 true 和 false使用缓存Float无缓存每次创建新对象Double无缓存每次创建新对象
✅ 最佳实践
使用 valueOf() 代替 new 关键字。使用 equals() 而不是 进行值比较。了解缓存范围避免意外的 结果。
问题3为什么要有包装类型
Java 之所以引入 包装类型Wrapper Classes主要是为了让基本数据类型primitive types具备对象的特性方便在面向对象编程OOP中使用同时增强泛型、集合框架等的兼容性。
1. 基本数据类型不是对象
Java 中有 8 种基本数据类型int、char、boolean、float 等它们的设计目标是提高性能但它们不是对象
int a 10;
a.toString(); // ❌ 编译错误int 没有方法不能直接调用方法。不能存储在**集合Collection**中。不能作为泛型的类型参数。
2. 包装类弥补了基本类型的不足
Java 提供了 对应的包装类型Integer、Double、Boolean 等它们是类可以像对象一样使用
Integer num 10;
System.out.println(num.toString()); // ✅ 10允许基本类型调用方法比如 toString()。能够存入 泛型集合如 ArrayListInteger。支持 自动装箱/拆箱让基本类型和对象能无缝转换。
3. 适用于 Java 集合框架
Java 集合如 ArrayList、HashMap只能存储对象不能存储基本类型
ArrayListint list new ArrayList(); // ❌ 编译错误必须使用包装类
ArrayListInteger list new ArrayList();
list.add(10); // ✅ 自动装箱int → Integer原因Java 泛型Generics不支持基本类型但支持对象。
4. 支持泛型Generics
泛型不能直接使用基本类型
public class BoxT {private T value;public void set(T value) { this.value value; }public T get() { return value; }
}Boxint box new Box(); // ❌ 编译错误必须使用包装类型
BoxInteger 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 引入的特性使得基本数据类型int、char、boolean 等和它们的包装类Integer、Character、Boolean 等之间可以自动转换简化代码编写。
1. 自动装箱Autoboxing
把基本数据类型 自动转换成 对应的包装类对象
Integer num 10; // 相当于 Integer num Integer.valueOf(10);10 是 int 类型自动转换为 Integer 对象。底层调用 Integer.valueOf(int) 方法如果在 -128 ~ 127 之间会使用缓存池否则创建新对象。
2. 自动拆箱Unboxing
把包装类对象 自动转换成 基本数据类型
Integer num 10; // 自动装箱
int a num; // 自动拆箱相当于 int a num.intValue();num 是 Integer 对象自动转换成 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// 直接存入集合ArrayListInteger 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()。由于 num 是 null调用 intValue() 抛出 NullPointerException。
2. 真实场景中的 NPE
(1) 集合取值时自动拆箱
import java.util.*;public class UnboxingNPE {public static void main(String[] args) {MapString, Integer scores new HashMap();scores.put(Alice, 95);scores.put(Bob, null); // Bob 没有分数int bobScore scores.get(Bob); // NPE: null 不能拆箱成 intSystem.out.println(Bobs 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
OptionalInteger ageOpt Optional.ofNullable(getUserAgeFromDB());
int age ageOpt.orElse(0); // 如果为空默认值 03. 避免自动拆箱 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 等包装类尽量使用基本类型。
问题6String、StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?
1. String、StringBuffer 和 StringBuilder 的区别
在 Java 中String、StringBuffer 和 StringBuilder 都是用于表示字符串的类但它们的可变性、线程安全性和性能不同。
特性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, ComparableString {private final char value[];
}value 是 final 类型的 字符数组 (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, 指向同一个对象s1 和 s2 指向的是同一个字符串常量池对象而不会新建对象减少内存占用。
如果 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 可以安全地作为 HashMap 的 key不必担心 key 被修改导致哈希值变化。
3. StringBuffer 和 StringBuilder 的区别
StringBuffer 和 StringBuilder 都是 可变的字符串类但它们的主要区别是线程安全性。
(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 World4. 何时使用 String、StringBuffer、StringBuilder
需求推荐使用原因少量字符串拼接String代码简洁性能影响不大大量字符串拼接单线程StringBuilder最高性能无同步开销大量字符串拼接多线程StringBuffer线程安全防止并发问题 5. 关键总结
String 是不可变的存储在字符串常量池中适用于少量字符串操作。StringBuffer 是线程安全的使用 synchronized适用于多线程环境。StringBuilder 是非线程安全的但性能最好适用于单线程高性能场景。推荐 少量拼接用 String简洁。单线程高性能用 StringBuilder。多线程环境用 StringBuffer。
问题7重载和重写的区别
重载Overloading 和 重写Overriding 是 Java 中**多态Polymorphism**的重要表现形式。它们的主要区别如下 项 方法重载Overloading方法重写Overriding定义在同一个类中方法名相同参数列表不同参数个数或类型不同在父类和子类之间方法名、参数列表都相同子类对父类的方法进行重新实现方法名必须相同必须相同参数列表必须不同参数类型、数量或顺序必须相同返回值可以不同必须相同或是父类返回值的子类协变返回类型访问修饰符可以不同不能更严格但可以更宽松抛出异常可以不同不能抛出比父类更大的异常可以抛出更小的或不抛出异常发生范围同一个类内部子类继承父类后是否依赖继承不需要继承必须有继承关系调用方式通过方法签名的不同在编译时决定调用哪个方法静态绑定编译期多态通过子类对象调用运行时决定调用哪个方法动态绑定运行期多态 问题8 和 equals() 的区别
在 Java 中 和 equals() 都可以用来比较对象但它们的本质、适用范围和行为有所不同。
比较项引用/值比较equals()对象内容比较比较方式比较内存地址引用比较对象的内容可重写适用范围基本数据类型 和 引用类型只能用于对象默认行为对于对象默认比较地址Object 类的 equals() 方法需要重写 equals() 方法以比较内容适用于基本数据类型的值比较引用是否相同判断两个对象是否逻辑相等
1. 的行为
(1) 用于基本数据类型
对于 基本数据类型int、double、char、boolean 等 直接比较值
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不是同一个对象虽然 s1 和 s2 的内容相同但它们指向不同的内存地址所以 返回 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); // falses1 在堆中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比较的是内容尽管 s1 和 s2 指向不同的对象但 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内容相同}
}这里 p1 和 p2 是不同对象但 equals() 被重写为比较 name所以返回 true。
3. vs equals() 总结
比较项equals()基本数据类型比较值不能用对象引用比较地址默认比较地址但可重写String 比较地址 比较内容已重写可否重写不可重写可重写按需求自定义逻辑适用场景判断是否为同一对象判断对象内容是否相等 4. 推荐使用方式
1.基本数据类型用
int a 100;
int b 100;
System.out.println(a b); // true2.引用类型判断是否为同一个对象用
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) { ... }
}问题9Java 反射反射有什么优点/缺点你是怎么理解反射的为什么框架需要反射
Java 反射Reflection概述
Java 反射是 Java 提供的一种强大功能它允许我们在运行时 动态地获取类的信息如类的方法、字段、构造方法等并对它们进行操作。通过反射我们可以 动态地创建对象、调用方法、访问属性甚至可以在运行时加载类。
反射的基本概念
Class 类Java 中所有类的元数据都由 Class 类表示。通过 Class 类你可以获得类的构造方法、字段、方法等信息。Method 类通过反射可以获取类的所有方法并执行它们。Field 类通过反射可以访问类的字段。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) 让私有方法可以被访问。
反射的优点 动态性 反射允许你在运行时 动态地加载类动态地创建对象以及 动态地调用方法这让程序可以非常灵活地应对不同的情况。例如Spring 框架使用反射来根据配置文件 自动注入依赖而不需要在代码中硬编码类。 灵活性 通过反射你可以访问类的私有方法和字段甚至是 访问不存在的类成员这使得在某些场景下开发者可以灵活处理一些特殊情况。 框架和库的开发 框架和库例如 Hibernate、Spring、JUnit通过反射来实现灵活的功能。通过反射框架可以在运行时了解类的信息并做出相应的处理而无需显式地了解每个类。 与遗留代码的兼容性 使用反射可以访问没有源代码的类例如在 Java 库中使用的第三方库或组件反射可以帮助在运行时动态地调用和修改类成员。 反射的缺点 性能开销 反射操作通常比直接调用方法慢得多因为它会绕过编译时的类型检查。每次反射都会涉及到一些额外的计算如查找方法、创建实例等因此 性能开销较大。对于需要频繁调用的代码反射可能会导致性能瓶颈。 安全性问题 反射可以访问类的私有成员这可能会暴露 敏感数据 或者 破坏类的封装性带来 安全隐患。因此反射有时会被禁用尤其是在安全敏感的应用中。 代码可读性和可维护性差 使用反射的代码不如普通的面向对象代码清晰和易于理解。因为你不能通过直接查看代码或接口来确定一个类的行为反射代码可能会变得难以调试和维护。 错误较难发现 反射的代码通常在编译时无法捕获错误错误通常会在运行时出现这使得 调试变得困难。例如反射可能会尝试调用不存在的方法或者访问不存在的字段这些问题通常只有在程序运行时才能被发现。 为什么框架需要反射
许多框架如 Spring、Hibernate依赖反射来实现灵活的配置和动态行为。反射为框架提供了以下几方面的优势 依赖注入 Spring 框架通过反射来实现 依赖注入。当应用启动时Spring 容器会通过反射获取各个类的构造方法、属性等信息然后根据配置自动为类注入所需的依赖。 动态代理 在 AOP面向切面编程中Spring 使用反射技术生成 动态代理类通过代理对象的反射拦截目标方法的执行实现诸如日志记录、事务控制等功能。 ORM对象关系映射 Hibernate 等 ORM 框架通过反射来将 数据库表映射成 Java 对象并实现自动的持久化操作。通过反射Hibernate 可以动态地从类中获取字段信息将数据持久化到数据库。 配置和扩展性 反射为框架提供了 高度的扩展性使得框架可以在运行时动态地加载不同的类或组件而不需要在编译时知道所有的细节。比如插件式框架可以通过反射动态加载和调用外部插件。 总结
反射是 Java 提供的一种强大机制可以在运行时动态地获取类的信息并操作它们。反射的优点包括 动态性、灵活性尤其适用于框架开发和与遗留代码的兼容。然而反射也有一些缺点主要是 性能开销、代码可维护性差、潜在的安全隐患。框架需要反射主要是为了提供 灵活的依赖注入、动态代理、对象关系映射 等功能以便在运行时根据需求灵活调整。
问题10谈谈对 Java 注解的理解解决了什么问题
Java 注解概述
Java 注解是一种提供元数据的机制用于向代码中添加额外的信息通常通过反射等方式进行处理。它本身不直接影响程序执行但可以提供对代码的附加信息用于编译检查、代码生成、运行时处理等。
注解解决的问题 简化代码和配置 注解帮助减少配置文件或硬编码提升开发效率。比如在 Spring 中使用 Autowired 注解自动注入依赖。 提高可读性 注解使得代码自文档化开发者能通过注解清晰地知道代码的意图。例如Override 注解标明方法是覆盖父类方法。 自动化处理 通过注解和反射框架能够自动化处理某些功能如 Spring 框架通过 RequestMapping 处理 HTTP 请求。 验证和编译时检查 使用注解可以进行数据验证或编译时检查比如 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();}
}匿名内部类的特点
简洁性它可以让你在创建对象的同时定义类而不需要显式地定义一个新类。不能有构造器匿名内部类没有名称因此不能定义构造器。只能继承一个类或实现一个接口匿名内部类必须继承一个类或者实现一个接口不能多重继承。常用于事件监听在 GUI 编程中匿名内部类常用来实现事件监听器等。
问题12BIO,NIO,AIO 有什么区别?
BIOBlocking I/O、NIONon-blocking I/O和 AIOAsynchronous I/O是 Java 中三种不同的 I/O 模型它们主要的区别在于 I/O 操作的阻塞特性和异步处理的能力。下面是它们的详细对比 1. BIOBlocking I/O
特点
阻塞式 I/O每次 I/O 操作读取或写入都会阻塞当前线程直到操作完成。每个 I/O 操作都需要一个线程来完成当请求很多时可能会创建大量线程造成性能瓶颈。
流程
客户端发起连接请求。服务器接受连接请求分配一个线程进行处理。该线程在 I/O 操作时会被阻塞直到完成操作读或写。
优缺点
优点实现简单、直观适合小规模并发或单线程应用。缺点性能较差线程过多时会导致高开销限制了系统的并发处理能力。
适用场景适用于连接数较少、并发量不高的传统应用。 2. NIONon-blocking I/O
特点
非阻塞 I/O引入了 Selector 和 Channel 等概念允许多个 I/O 操作共享一个或多个线程避免每个连接占用一个线程。线程不会因 I/O 操作而阻塞线程可以在等待 I/O 完成的同时做其他事情。事件驱动NIO 使用非阻塞模式线程可以轮询 (polling) 检查 I/O 操作是否完成通过 Selector 来监听多个通道Channel的 I/O 状态。
流程
客户端发起连接请求。服务器通过 Selector 监听多个通道Channel上的 I/O 事件线程不会被阻塞而是轮询所有通道。一旦某个通道的 I/O 操作准备好线程就会处理相应的操作。
优缺点
优点支持高并发使用少量线程就能处理大量连接。缺点编程复杂处理多个连接时需要编写较为复杂的代码如 Selector 和 Channel。
适用场景适用于高并发应用如 Web 服务器、聊天服务器等。 3. AIOAsynchronous I/O
特点
异步 I/O在 AIO 中I/O 操作的执行完全是异步的线程不需要等待 I/O 完成。I/O 请求会通过操作系统内核来处理操作系统会在完成 I/O 操作时通知应用程序。线程发出 I/O 请求后立即返回I/O 操作在后台完成。当 I/O 完成时操作系统会通过回调函数通知应用程序。
流程
客户端发起连接请求。服务器通过异步接口发出 I/O 请求。当 I/O 操作完成时操作系统通过回调函数通知服务器。
优缺点
优点高效能够利用操作系统的异步 I/O 支持减少了应用层的线程等待时间极大提高了并发处理能力。缺点实现较为复杂底层需要支持异步 I/O且需要操作系统的支持如 Linux 的 epoll 或 Windows 的 IOCP。
适用场景适用于大规模、高并发、低延迟的应用特别是需要大量并发连接而不希望使用过多线程的场景。 总结对比
特性BIO阻塞 I/ONIO非阻塞 I/OAIO异步 I/O阻塞方式阻塞式操作非阻塞操作完全异步不阻塞线程线程模型每个连接一个线程一个线程处理多个连接通过轮询Selector通过操作系统异步处理通知回调性能性能较差连接数多时会消耗大量线程性能较好支持高并发性能最好几乎不依赖线程阻塞编程复杂度简单易懂代码直观编程复杂需要使用 Selector 和 Channel编程复杂操作系统支持通常通过回调处理适用场景低并发、传统应用高并发、大量连接的场景超高并发、低延迟、大规模并发连接的应用 总结
BIO 适用于低并发场景简单易懂但性能较差。NIO 适用于中到高并发场景能高效利用少量线程处理大量连接但编程复杂。AIO 提供最好的性能适用于极高并发的场景但实现复杂并依赖操作系统的异步支持。
不同的 I/O 模型适用于不同的应用需求选择合适的模型能有效提升程序性能。