1. 引言
Java是一门面向对象编程(OOP)语言。这意味着Java通过对象(通常组织在类中)来建模状态和行为。
本教程中,我们将探讨创建对象的几种不同方式。
在大部分示例中,我们会使用一个简单的Rabbit类:
public class Rabbit {
String name = "";
public Rabbit() {
}
// getters/setters
}
我们的Rabbit不一定有name,但必要时可以设置。
现在,让我们创建一些Rabbit对象吧!
2. 使用new操作符创建对象
使用new关键字可能是最常见的创建对象方式:
Rabbit rabbit = new Rabbit();
上例中,我们将Rabbit的新实例赋值给名为rabbit的变量。
new关键字表示我们需要对象的新实例,它通过调用对象内的构造方法实现。
注意:如果类中没有显式定义构造方法,会使用默认构造方法。
3. 使用*Class.newInstance()*方法创建对象
既然Java是面向对象的语言,将Java的核心概念存储为对象就很合理。
例如Class对象,它存储了Java类的所有信息。
*要访问Rabbit的类对象,我们使用Class.forName()*并传入完整类名**(包含类所在包名)。
*获取到Rabbit对应的类对象后,调用newInstance()方法**,该方法会创建Rabbit*对象的新实例:
Rabbit rabbit = (Rabbit) Class
.forName("com.baeldung.objectcreation.objects.Rabbit")
.newInstance();
注意:需要将新对象实例强制转换为Rabbit类型。
更简洁的变体是使用class关键字而非Class对象:
Rabbit rabbit = Rabbit.class.newInstance();
也可以使用Constructor类实现类似效果:
Rabbit rabbit = Rabbit.class.getConstructor().newInstance();
这些方法都利用了对象内置的*newInstance()*方法。
**⚠️ newInstance()方法依赖可见的构造方法。
例如,如果Rabbit只有私有构造方法,使用上述newInstance方法会抛出IllegalAccessException:
java.lang.IllegalAccessException: Class com.baeldung.objectcreation.CreateRabbits can not access
a member of class com.baeldung.objectcreation.objects.Rabbit with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.Class.newInstance(Class.java:436)
4. 使用*clone()*方法创建对象
现在看看如何克隆对象。
**✅ clone()方法会复制对象并分配新内存。
但并非所有类都能克隆。
只有实现了Clonable接口的类才能被克隆。
该类还需实现clone()方法。我们创建CloneableRabbit类(与Rabbit相同但实现了*clone()*方法):
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
克隆CloneableRabbit的代码如下:
ClonableRabbit clonedRabbit = (ClonableRabbit) originalRabbit.clone();
如果考虑使用*clone()*方法,不妨改用Java复制构造方法。
5. 使用反序列化创建对象
讲完常规方法,我们来点"骚操作"。
可以通过反序列化创建对象(从外部数据读取并创建对象)。
首先需要一个可序列化的类。
复制Rabbit并实现Serializable接口使其可序列化:
public class SerializableRabbit implements Serializable {
//类内容
}
然后我们写一个名为Peter的Rabbit到测试目录的文件:
SerializableRabbit originalRabbit = new SerializableRabbit();
originalRabbit.setName("Peter");
File resourcesFolder = new File("src/test/resources");
resourcesFolder.mkdirs(); //确保目录存在
File file = new File(resourcesFolder, "rabbit.ser");
try (FileOutputStream fileOutputStream = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);) {
objectOutputStream.writeObject(originalRabbit);
}
最后读取回来:
try (FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);) {
return (SerializableRabbit) ois.readObject();
}
检查name属性会发现,新创建的Rabbit对象就是Peter。
这个概念本身是个大主题,称为反序列化。
6. 其他创建对象的方式
深入挖掘会发现,很多操作本质上都在创建对象。
下面列出常用类和罕见类的例子:
6.1. 函数式接口
通过函数式接口创建对象:
Supplier<Rabbit> rabbitSupplier = Rabbit::new;
Rabbit rabbit = rabbitSupplier.get();
这段代码使用Supplier函数式接口提供Rabbit对象。
通过方法引用操作符(双冒号操作符)Rabbit::new实现。
双冒号操作符文章包含更多示例,比如处理带参数的构造方法。
6.2. Unsafe.AllocateInstance
简单提个不推荐的方法。
sun.misc.Unsafe是Java核心类库中的底层类,不推荐在业务代码中使用。
但它包含allocateInstance方法,可以不调用构造方法创建对象。
**❌ Unsafe不推荐在核心库外使用**,此处不提供示例。
6.3. 数组
Java中初始化数组也能创建对象。
代码结构类似使用new关键字:
Rabbit[] rabbitArray = new Rabbit[10];
但运行时发现它没有显式调用构造方法。虽然外部代码风格相似,但内部机制完全不同。
6.4. 枚举
再看常见对象——枚举。
枚举是特殊的类,我们以面向对象方式看待它。
定义枚举:
public enum RabbitType {
PET,
TAME,
WILD
}
每次初始化枚举都会创建对象,这与运行时创建对象的前例不同。
7. 总结
本文展示了使用new或class等关键字创建对象的方法。
我们了解到克隆或反序列化等操作也能创建对象。
此外,Java中创建对象的方式远不止这些,很多我们可能一直在用却从未深究。
所有示例的完整代码可在GitHub获取。