java new(创建)对象时结尾带上{}和不带的区别
Java 中 new Person () 与 new Person (){} 的区别:匿名类原理与实践
在 Java 中,创建对象看似只有new 类名()一种常规方式,但new 类名(){} 这种带大括号的写法却容易让人困惑。本文通过案例对比两种创建方式的差异,解析匿名类的本质,并介绍其在泛型处理中的实用场景。
一、两种对象创建方式的直观对比
首先定义一个基础的Person类,作为后续对比的载体:
// 基础Person类
public class Person {
// 普通成员方法
public void say() {
System.out.println("hello");
}
}
接下来看两种创建Person对象的写法:
| 创建方式 | 代码示例 | 直观感受 |
|---|---|---|
| 常规方式 | Person p1 = new Person(); |
熟悉,直接创建 Person 实例 |
| 带大括号的方式 | Person p2 = new Person(){}; |
陌生,大括号的作用是什么? |
二、类信息差异:揭开 Main$1 的神秘面纱
要理解二者的区别,最直接的方式是打印对象的类信息,观察底层类型是否一致。
案例:打印类信息对比
public class Main {
public static void main(String[] args) {
// 常规方式创建对象
Person p1 = new Person();
// 带大括号方式创建对象
Person p2 = new Person(){};
// 打印两个对象的类信息
System.out.println("p1的类信息:" + p1.getClass());
System.out.println("p2的类信息:" + p2.getClass());
}
}
运行结果:
p1的类信息:class Person
p2的类信息:class Main$1
关键结论:
-
p1的类型是原始的 Person 类,符合预期; -
p2的类型是Main$1(Main是当前主类名,$1表示这是主类中定义的第一个匿名类),并非原始 Person 类。
这说明:new Person(){} 并没有直接创建 Person 实例,而是创建了一个继承自 Person 的匿名子类的实例!
三、匿名子类的本质:继承与重写
为了进一步验证 “匿名子类” 的结论,我们从两个角度展开:查看父类信息、重写父类方法。
1. 验证父类:匿名子类的父类是 Person
通过getSuperclass()方法获取p2的父类,确认其继承关系:
public class Main {
public static void main(String[] args) {
Person p2 = new Person(){};
System.out.println("p2的类信息:" + p2.getClass());
System.out.println("p2的父类信息:" + p2.getClass().getSuperclass());
}
}
运行结果:
p2的类信息:class Main$1
p2的父类信息:class Person
结论:Main$1(匿名类)的父类确实是Person,印证了 “匿名子类” 的本质。
2. 重写父类方法:匿名类的核心能力
既然是子类,就可以重写父类的方法,还能添加静态初始化块和实例初始化块(代码块)。案例如下:
public class Main {
public static void main(String[] args) {
// 创建Person的匿名子类实例,并重写方法、添加初始化块
Person p2 = new Person(){
// 静态初始化块(类加载时执行,仅执行一次)
static {
System.out.println("匿名类静态初始化");
}
// 实例初始化块(对象创建时执行,每次new都执行)
{
System.out.println("匿名类实例初始化");
}
// 重写父类Person的say()方法
@Override
public void say() {
super.say(); // 调用父类的say()方法
System.out.println("匿名子类重写:hello + 1");
}
};
// 调用重写后的say()方法
p2.say();
}
}
运行结果:
匿名类静态初始化
匿名类实例初始化
hello // 父类say()的输出
匿名子类重写:hello + 1 // 子类重写后的追加输出
关键说明:
-
静态初始化块:在匿名类加载时执行,仅一次;
-
实例初始化块:在匿名类实例创建时执行,每次
new都会执行; -
重写方法:通过
@Override标注,可扩展或修改父类的方法逻辑。
四、匿名类的实用场景:泛型类型获取
泛型在 Java 中存在 “类型擦除” 机制 —— 编译后泛型参数会被擦除为Object,无法直接获取原始泛型类型。但通过 “匿名子类继承泛型类” 的方式,可以优雅地解决这个问题。
场景背景:常规方式无法获取泛型类型
定义一个泛型类Person<E>:
// 泛型类
public class Person<E> {
private E e;
}
若直接创建Person<Integer>实例,无法获取泛型参数Integer:
public class Main {
public static void main(String[] args) {
// 常规方式创建泛型实例
Person<Integer> p = new Person<>();
// 编译后泛型被擦除,无法直接获取Integer类型
System.out.println(p.getClass().getGenericSuperclass());
// 输出:java.lang.Object(父类是Object,无泛型信息)
}
}
解决方案:匿名子类保留泛型信息
通过new Person<Integer>(){} 创建匿名子类实例,子类会保留父类的泛型参数信息,从而可以获取:
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public class Main {
public static void main(String[] args) {
// 创建Person<Integer>的匿名子类实例
Person<Integer> p = new Person<>(){};
// 1. 获取匿名子类的父类(即Person<Integer>)
Type genericSuperclass = p.getClass().getGenericSuperclass();
// 2. 强制转换为ParameterizedType(带泛型参数的类型)
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
// 3. 获取泛型参数数组(此处只有一个参数Integer)
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
// 4. 输出泛型类型
System.out.println("泛型类型:" + actualTypeArguments[0]);
}
}
运行结果:
泛型类型:class java.lang.Integer
优势对比:
传统方式需要创建一个显式的子类(如PersonS extends Person<Integer>)才能保留泛型信息,而匿名类无需额外定义子类,代码更简洁优雅:
| 方式 | 代码复杂度 | 适用场景 |
|---|---|---|
| 显式子类 | 需额外定义子类(如 PersonS) | 需多次复用该泛型类型 |
| 匿名子类 | 无需额外定义类,一行搞定 | 单次使用,追求简洁 |
五、总结:两种创建方式的核心差异
| 对比维度 | new Person() | new Person(){} |
|---|---|---|
| 创建的对象类型 | 原始 Person 类的实例 | Person 的匿名子类的实例 |
| 类名 | 明确(Person) | 隐式(如 Main$1) |
| 能否重写方法 | 不能(仅使用父类方法) | 能(可重写父类方法、添加初始化块) |
| 泛型信息保留 | 不保留(类型擦除后无法获取) | 保留(可获取父类泛型参数) |
| 适用场景 | 常规对象创建,无需扩展父类逻辑 | 临时扩展父类逻辑、泛型类型获取等场景 |
通过本文的案例可以发现:new 类名(){} 的本质是创建 “匿名子类实例”,而非原始类实例。这种写法在需要临时扩展类逻辑、解决泛型类型获取等场景中,能发挥简洁高效的作用。

浙公网安备 33010602011771号