Java中创建泛型类型的实例

泛型提供了一种优雅的方法,可以在我们的代码库中引入额外的抽象层,同时提高代码可重用性和增强代码质量。

使用泛型数据类型时,有时我们想创建它们的新实例。然而,由于 Java 中泛型的设计方式,我们可能会遇到不同的挑战。

在本教程中,我们将首先分析为什么实例化泛型类型不像实例化类那么简单。然后,我们将探索创建泛型类型的几种方法。

理解类型擦除
在开始之前,我们应该意识到泛型类型在编译时和运行时的行为不同。

由于一种称为类型擦除的技术,泛型类型在运行时不会被保留。简而言之,类型擦除是在编译时需要泛型类型并在运行时丢弃此信息的过程。编译器会从泛型类和方法中删除与类型参数和类型实参相关的所有信息。

此外,类型擦除使使用泛型的 Java 应用程序能够与引入泛型之前创建的库保持向后兼容性。

考虑到上述情况,我们不能使用new关键字后跟构造函数来创建泛型类型的对象:

public class GenericClass<T> {
    private T t;
    public GenericClass() {
        this.t = new T(); // DOES NOT COMPILE
    }
}

由于泛型是编译时概念并且信息在运行时被删除,因此为new T( ) 生成字节码是不可能的,因为T是未知类型。

此外,泛型类型将替换为第一个边界,如果未设置边界,则替换为Object类。我们示例中的T类型没有边界,因此 Java 将其视为Object类型。

示例设置
让我们设置本教程中将使用的示例。我们将创建一个简单的服务来发送消息。

首先,让我们用send()方法定义Sender接口:

public interface Sender {
    String send();
}

接下来,让我们创建一个用于发送电子邮件的具体Sender实现:

public class EmailSender implements Sender {
    private String message;
    @Override
    public String send() {
        return "EMAIL";
    }
}

然后,我们将创建另一个用于发送通知的实现,但它本身是一个通用类:

public class NotificationSender<T> implements Sender {
    private T body;
    @Override
    public String send() {
        return "NOTIFICATION";
    }
}

现在一切就绪,让我们探索创建泛型类型实例的不同方法。

使用反射
实例化泛型类型的最常见方法之一是通过普通 Java 和反射。

要创建泛型类型的实例,我们至少需要知道我们想要创建的对象的类型。

让我们定义一个SenderServiceReflection泛型类,负责创建不同服务的实例:

public class SenderServiceReflection<T extends Sender> {
    private Class<T> clazz;
    public SenderServiceReflection(Class<T> clazz) {
        this.clazz = clazz;
    }
}

我们定义了Class<T>的实例变量,其中存储了我们想要实例化的类的类型的信息。

接下来,让我们创建一个负责创建类实例的方法:

public T createInstance() {
    try {
        return clazz.getDeclaredConstructor().newInstance();
    } catch (Exception e) {
        throw new RuntimeException("Error while creating an instance.");
    }
}

这里我们调用了getDeclaredConstructor().newInstance()来实例化一个新对象。此外,实际类型应该有一个无参数构造函数,以使上述代码正常工作。

此外,值得注意的是,自 Java 9 以来,直接调用Class<T>上的newInstance()方法已被弃用。

接下来,让我们测试一下我们的方法:

@Test
void givenEmailSender_whenCreateInstanceUsingReflection_thenReturnResult() {
    SenderServiceReflection<EmailSender> service = new SenderServiceReflection<>(EmailSender.class);
    Sender emailSender = service.createInstance();
    String result = emailSender.send();
    assertEquals("EMAIL", result);
}

但是,使用 Java 反射有其局限性。例如,如果我们尝试实例化NotificationSender类,我们的解决方案将不起作用:

SenderServiceReflection<NotificationSender<String>> service = new SenderServiceReflection<>(NotificationSender<String>.class);

如果我们尝试将NotificationSender<String>.class传递给构造函数,我们将收到编译错误:

Cannot select from parameterized type

使用供应商接口
Java 8 通过利用Supplier功能接口提供了一种创建泛型类型实例的便捷方法:

public class SenderServiceSupplier<T extends Sender> {
    private Supplier<T> supplier;
    public SenderServiceSupplier(Supplier<T> supplier) {
        this.supplier = supplier;
    }
    public T createInstance() {
        return supplier.get();
    }
}

这里,我们定义了Supplier<T>实例变量,该变量通过构造函数的参数设置。为了检索通用实例,我们调用了它的单个get()方法。

让我们使用方法引用创建一个新实例:

@Test
void givenEmailSender_whenCreateInstanceUsingSupplier_thenReturnResult() {
    SenderServiceSupplier<EmailSender> service = new SenderServiceSupplier<>(EmailSender::new);
    Sender emailSender = service.createInstance();
    String result = emailSender.send();
    assertEquals("EMAIL", result);
}

此外,如果实际类型T的构造函数需要参数,我们可以使用lambda 表达式来代替:

@Test
void givenEmailSenderWithCustomConstructor_whenCreateInstanceUsingSupplier_thenReturnResult() {
    SenderServiceSupplier<EmailSender> service = new SenderServiceSupplier<>(() -> new EmailSender("Baeldung"));
    Sender emailSender = service.createInstance();
    String result = emailSender.send();
    assertEquals("EMAIL", result);
}

而且,当我们使用嵌套泛型类时,这种方法没有任何问题:

@Test
void givenNotificationSender_whenCreateInstanceUsingSupplier_thenReturnCorrectResult() {
    SenderServiceSupplier<NotificationSender<String>> service = new SenderServiceSupplier<>(
      NotificationSender::new);
    Sender notificationSender = service.createInstance();
    String result = notificationSender.send();
    assertEquals("NOTIFICATION", result);
}

使用工厂设计模式
类似地,我们可以利用工厂设计模式来完成相同的行为,而不是使用Supplier接口。

首先,让我们定义替代Supplier接口的Factory接口:

public interface Factory<T> {
    T create();
}

其次,让我们创建一个以Factory<T>作为构造函数参数的泛型类:

public class SenderServiceFactory<T extends Sender> {
    private final Factory<T> factory;
    public SenderServiceFactory(Factory<T> factory) {
        this.factory = factory;
    }
    public T createInstance() {
        return factory.create();
    }
}

接下来,让我们创建一个测试来检查代码是否按预期工作:

@Test
void givenEmailSender_whenCreateInstanceUsingFactory_thenReturnResult() {
    SenderServiceFactory<EmailSender> service = new SenderServiceFactory<>(EmailSender::new);
    Sender emailSender = service.createInstance();
    String result = emailSender.send();
    assertEquals("EMAIL", result);
}

此外,实例化NotificationSender没有任何问题:

@Test
void givenNotificationSender_whenCreateInstanceUsingFactory_thenReturnResult() {
    SenderServiceFactory<NotificationSender<String>> service = new SenderServiceFactory<>(
      () -> new NotificationSender<>("Hello from Baeldung"));
    NotificationSender<String> notificationSender = service.createInstance();
    String result = notificationSender.send();
    assertEquals("NOTIFICATION", result);
    assertEquals("Hello from Baeldung", notificationSender.getBody());
}

使用Guava
最后,让我们看看如何使用Guava库来实现这一点。

Guava 提供了TypeToken类,该类使用反射来存储运行时可用的泛型信息。它还提供了用于操作泛型类型的其他实用方法。

让我们创建SenderServiceGuava类并以TypeToken<T>作为实例变量:

public class SenderServiceGuava<T extends Sender> {
    TypeToken<T> typeToken;
    public SenderServiceGuava(Class<T> clazz) {
        this.typeToken = TypeToken.of(clazz);
    }
    public T createInstance() {
        try {
            return (T) typeToken.getRawType().getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

为了创建一个实例,我们调用了getRawType(),它返回一个运行时类类型。

让我们测试一下我们的例子:

@Test
void givenEmailSender_whenCreateInstanceUsingGuava_thenReturnResult() {
    SenderServiceGuava<EmailSender> service = new SenderServiceGuava<>(EmailSender.class);
    Sender emailSender = service.createInstance();
    String result = emailSender.send();
    assertEquals("EMAIL", result);
}

或者,我们可以将 TypeToken定义为匿名类来存储泛型类型的信息:

TypeToken<T> typeTokenAnonymous = new TypeToken<T>(getClass()) {
};
public T createInstanceAnonymous() {
    try {
        return (T) typeTokenAnonymous.getRawType().getDeclaredConstructor().newInstance();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

使用这种方法,我们 也可以将SenderServiceGuava创建为匿名类:

@Test
void givenEmailSender_whenCreateInstanceUsingGuavaAndAnonymous_thenReturnResult() {
    SenderServiceGuava<EmailSender> service = new SenderServiceGuava<EmailSender>() {
    };
    Sender emailSender = service.createInstanceAnonymous();
    String result = emailSender.send();
    assertEquals("EMAIL", result);
}

如果泛型类本身具有类型参数,则上述解决方案可以很好地工作:

@Test
void givenNotificationSender_whenCreateInstanceUsingGuavaAndAnonymous_thenReturnResult() {
    SenderServiceGuava<NotificationSender<String>> service = new SenderServiceGuava<NotificationSender<String>>() {
    };
    Sender notificationSender = service.createInstanceAnonymous();
    String result = notificationSender.send();
    assertEquals("NOTIFICATION", result);
}