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. 总结

本文展示了使用newclass等关键字创建对象的方法。

我们了解到克隆或反序列化等操作也能创建对象。

此外,Java中创建对象的方式远不止这些,很多我们可能一直在用却从未深究。

所有示例的完整代码可在GitHub获取。


原始标题:Different Ways to Create an Object in Java