在 Java 中,有 8 种基本数据类型(Primitive Types):
基本数据类型 | 关键字 | 默认值 | 占用空间 | 对应的包装类 |
---|---|---|---|---|
整数类型 | ||||
字节型 (byte) | byte |
0 |
1 字节 (8 bit) | Byte |
短整型 (short) | short |
0 |
2 字节 (16 bit) | Short |
整型 (int) | int |
0 |
4 字节 (32 bit) | Integer |
长整型 (long) | long |
0L |
8 字节 (64 bit) | Long |
浮点数类型 | ||||
单精度浮点型 (float) | float |
0.0f |
4 字节 (32 bit) | Float |
双精度浮点型 (double) | double |
0.0d |
8 字节 (64 bit) | Double |
字符类型 | ||||
字符型 (char) | char |
\u0000 (空字符) |
2 字节 (16 bit) | Character |
布尔类型 | ||||
布尔型 (boolean) | boolean |
false |
JVM 规范未明确大小(通常 1 bit) | Boolean |
额外说明:
boolean
的存储大小依赖于 JVM 实现,通常使用 1 bit(但实际存储可能会占据 1 字节)。char
采用 Unicode 编码,所以它占用 2 字节。java.lang
包中,提供了基本类型的对象封装,并支持自动装箱(Autoboxing)和拆箱(Unboxing)。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); // true
Boolean
只有 TRUE
和 FALSE
两个缓存对象,所以 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. 为什么 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); // true
valueOf()
方法使用缓存池,所以 x == y
为 true
。
(2) 使用 new Integer()
Integer x = new Integer(127);
Integer y = new Integer(127);
System.out.println(x == y); // false
new 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:AutoBoxCacheMax |
Long |
-128 ~ 127 | 使用缓存 |
Character |
0 ~ 127 | 使用缓存 |
Boolean |
只有 true 和 false |
使用缓存 |
Float |
无缓存 | 每次创建新对象 |
Double |
无缓存 | 每次创建新对象 |
✅ 最佳实践:
valueOf()
代替 new
关键字。equals()
而不是 ==
进行值比较。==
结果。Java 之所以引入 包装类型(Wrapper Classes),主要是为了让基本数据类型(primitive types)具备对象的特性,方便在面向对象编程(OOP)中使用,同时增强泛型、集合框架等的兼容性。
1. 基本数据类型不是对象
Java 中有 8 种基本数据类型(int
、char
、boolean
、float
等),它们的设计目标是提高性能,但它们不是对象:
int a = 10;
a.toString(); // ❌ 编译错误,int 没有方法
2. 包装类弥补了基本类型的不足
Java 提供了 对应的包装类型(Integer
、Double
、Boolean
等),它们是类,可以像对象一样使用:
Integer num = 10;
System.out.println(num.toString()); // ✅ 10
toString()
)。ArrayList
)。3. 适用于 Java 集合框架
Java 集合(如 ArrayList
、HashMap
)只能存储对象,不能存储基本类型:
ArrayList list = new ArrayList<>(); // ❌ 编译错误
必须使用包装类:
ArrayList list = new ArrayList<>();
list.add(10); // ✅ 自动装箱:int → Integer
原因:Java 泛型(Generics)不支持基本类型,但支持对象。
4. 支持泛型(Generics)
泛型不能直接使用基本类型:
public class Box {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
Box box = new Box<>(); // ❌ 编译错误
必须使用包装类型:
Box 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 |
非线程安全 | 包装类有线程安全实现 |
最佳实践:
自动装箱(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
// 直接存入集合
ArrayList 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
}
}
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()
。num
是 null
,调用 intValue()
抛出 NullPointerException
。2. 真实场景中的 NPE
(1) 集合取值时自动拆箱
import java.util.*;
public class UnboxingNPE {
public static void main(String[] args) {
Map 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 ageOpt = Optional.ofNullable(getUserAgeFromDB());
int age = ageOpt.orElse(0); // 如果为空,默认值 0
3. 避免自动拆箱 NPE
的最佳实践
方法 | 示例 | 优点 |
---|---|---|
手动 null 检查 |
(num != null) ? num : 0 |
直接避免 NPE |
使用 getOrDefault() |
map.getOrDefault("key", 0) |
适用于 Map |
使用 Optional |
Optional.ofNullable(val).orElse(0) |
更优雅的 null 处理 |
避免包装类用于计算 | int sum = 0; 代替 Integer sum = 0; |
避免不必要的拆装箱 |
总结
NullPointerException
,如果变量可能为 null
,一定要做 null
检查!getOrDefault()
、Optional
等方法来避免 NPE
。Integer
等包装类,尽量使用基本类型。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, Comparable {
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 World
4. 何时使用 String
、StringBuffer
、StringBuilder
?
需求 | 推荐使用 | 原因 |
---|---|---|
少量字符串拼接 | String |
代码简洁,性能影响不大 |
大量字符串拼接(单线程) | StringBuilder |
最高性能,无同步开销 |
大量字符串拼接(多线程) | StringBuffer |
线程安全,防止并发问题 |
5. 关键总结
String
是不可变的,存储在字符串常量池中,适用于少量字符串操作。StringBuffer
是线程安全的,使用 synchronized
,适用于多线程环境。StringBuilder
是非线程安全的,但性能最好,适用于单线程高性能场景。String
(简洁)。StringBuilder
。StringBuffer
。重载(Overloading) 和 重写(Overriding) 是 Java 中**多态(Polymorphism)**的重要表现形式。它们的主要区别如下:
项 |
方法重载(Overloading) | 方法重写(Overriding) |
---|---|---|
定义 | 在同一个类中,方法名相同,参数列表不同(参数个数或类型不同) | 在父类和子类之间,方法名、参数列表都相同,子类对父类的方法进行重新实现 |
方法名 | 必须相同 | 必须相同 |
参数列表 | 必须不同(参数类型、数量或顺序) | 必须相同 |
返回值 | 可以不同 | 必须相同或是父类返回值的子类(协变返回类型) |
访问修饰符 | 可以不同 | 不能更严格,但可以更宽松 |
抛出异常 | 可以不同 | 不能抛出比父类更大的异常(可以抛出更小的或不抛出异常) |
发生范围 | 同一个类内部 | 子类继承父类后 |
是否依赖继承 | 不需要继承 | 必须有继承关系 |
调用方式 | 通过方法签名的不同,在编译时决定调用哪个方法(静态绑定,编译期多态) | 通过子类对象调用,运行时决定调用哪个方法(动态绑定,运行期多态) |
在 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); // 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,比较的是内容
尽管 s1
和 s2
指向不同的对象,但 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,内容相同
}
}
这里 p1
和 p2
是不同对象,但 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) { ... }
}
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)依赖反射来实现灵活的配置和动态行为。反射为框架提供了以下几方面的优势:
依赖注入:
动态代理:
ORM(对象关系映射):
配置和扩展性:
总结
Java 注解概述
Java 注解是一种提供元数据的机制,用于向代码中添加额外的信息,通常通过反射等方式进行处理。它本身不直接影响程序执行,但可以提供对代码的附加信息,用于编译检查、代码生成、运行时处理等。
注解解决的问题
简化代码和配置: 注解帮助减少配置文件或硬编码,提升开发效率。比如在 Spring 中使用 @Autowired
注解自动注入依赖。
提高可读性: 注解使得代码自文档化,开发者能通过注解清晰地知道代码的意图。例如,@Override
注解标明方法是覆盖父类方法。
自动化处理: 通过注解和反射,框架能够自动化处理某些功能,如 Spring 框架通过 @RequestMapping
处理 HTTP 请求。
验证和编译时检查: 使用注解可以进行数据验证或编译时检查,比如 @NotNull
注解确保字段或参数不为 null
。
注解的常见用途
@Autowired
自动注入)。@Entity
注解映射类到数据库表)。@RequestMapping
映射 URL)。@NotNull
、@Size
等注解)。优缺点
优点
缺点
内部类(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();
}
}
匿名内部类的特点
BIO(Blocking I/O)、NIO(Non-blocking I/O)和 AIO(Asynchronous I/O)是 Java 中三种不同的 I/O 模型,它们主要的区别在于 I/O 操作的阻塞特性和异步处理的能力。下面是它们的详细对比:
1. BIO(Blocking I/O)
特点:
流程:
优缺点:
适用场景:适用于连接数较少、并发量不高的传统应用。
2. NIO(Non-blocking I/O)
特点:
Selector
和 Channel
等概念,允许多个 I/O 操作共享一个或多个线程,避免每个连接占用一个线程。线程不会因 I/O 操作而阻塞,线程可以在等待 I/O 完成的同时做其他事情。Selector
来监听多个通道(Channel)的 I/O 状态。流程:
Selector
监听多个通道(Channel)上的 I/O 事件,线程不会被阻塞,而是轮询所有通道。优缺点:
Selector
和 Channel
)。适用场景:适用于高并发应用,如 Web 服务器、聊天服务器等。
3. AIO(Asynchronous I/O)
特点:
流程:
优缺点:
epoll
或 Windows 的 IOCP
)。适用场景:适用于大规模、高并发、低延迟的应用,特别是需要大量并发连接而不希望使用过多线程的场景。
总结对比
特性 | BIO(阻塞 I/O) | NIO(非阻塞 I/O) | AIO(异步 I/O) |
---|---|---|---|
阻塞方式 | 阻塞式操作 | 非阻塞操作 | 完全异步,不阻塞线程 |
线程模型 | 每个连接一个线程 | 一个线程处理多个连接,通过轮询(Selector ) |
通过操作系统异步处理,通知回调 |
性能 | 性能较差,连接数多时会消耗大量线程 | 性能较好,支持高并发 | 性能最好,几乎不依赖线程阻塞 |
编程复杂度 | 简单易懂,代码直观 | 编程复杂,需要使用 Selector 和 Channel |
编程复杂,操作系统支持,通常通过回调处理 |
适用场景 | 低并发、传统应用 | 高并发、大量连接的场景 | 超高并发、低延迟、大规模并发连接的应用 |
总结
不同的 I/O 模型适用于不同的应用需求,选择合适的模型能有效提升程序性能。