Java -- 如何处理构造函数中的类型擦除



假设我的类中有两个构造函数:

public User (List<Source1> source){
...
}
public User (List<Source2> source) {
...
}

假设这两个构造函数提供有关用户的相同信息,并且是针对不同用例构造用户的相同有效方法。

在 Java 中,由于类型擦除,您无法执行此操作 - Java 不接受两个参数为 List<?>的构造函数。

那么,有什么方法可以解决这个问题呢?什么是不矫枉过正但仍尊重基本 OO 的解决方案?仅仅因为Java没有强大的泛型支持而必须围绕此构建工厂方法或其他接口似乎是错误的。

以下是我能想到的可能性:

1) 接受List<?>作为构造函数的参数,并在构造函数中解析您需要哪种逻辑,或者如果它不是任何可接受的类型,则抛出异常。

2) 创建一个接受任一 List 的类,构造适当的 User 对象,然后返回它。

3) 围绕 List<Source1>List<Source2> 创建包装器,这些包装器可以传递给 User 构造函数。

4)子类这个家伙有两个类,除了构造函数之外,所有功能都被继承了。一个构造函数接受源 1,另一个接受源 2。

5)用构建器包装这个家伙,其中有两种不同的构建器方法用于实例化的两个不同数据源。

我的问题是这些:

1)这样做是Java的缺陷,还是有意的设计决策?直觉是什么?

2)在维护良好代码而不引入不必要的复杂性方面,哪种解决方案最强?为什么?

这个问题是类似的:在Java中围绕类型擦除设计构造函数,但没有详细说明,它只是提出了各种解决方法。

通常的方法是使用工厂方法:

public static User createFromSource1(List<Source1> source) {
    User user = new User();
    // build your User object knowing you have Source1 data
    return user;
}
public static User createFromSource2(List<Source2> source) {
    User user = new User();
    // build your User object knowing you have Source2 data
    return user;
}

如果你只想使用 Source1Source2 进行构造(即你没有默认构造函数),你只需隐藏你的构造函数,强制客户端使用你的工厂方法:

private User () {
    // Hide the constructor
}

出现此问题是因为您无法以不同的方式命名构造函数,如果这些是普通方法,您将如何克服这个问题。由于构造函数名称固定为类名,因此此代码模式是区分然后给出相同类型擦除的唯一方法。

1:保持与擦除的向后兼容性。

2: 你的类可以使用泛型吗?像这样:

public class User<T> {
    private List<T> whatever;
    public User(List<T> source){
       ....
    }
}

我不确定这是否是你的意思(2)

public class User<T> {
    public User(List<T> source){
    }
}

或者可能更好:

public class User<T extends SomeTypeOfYours> {
    public User(List<T> source){
    }
}

惠尔SomeTypeOfYours是一种超类型的Source1Source2

基本问题是该语言是在泛型存在之前设计的(构造函数名称是固定的),因此它无法处理由于类型擦除而导致的冲突,这通常通过重命名方法来区分它们。

一种"解决方法",无需诉诸工厂方法,是添加另一个非类型参数以使编译器能够区分它们:

public User(List<Source1>, Source1 instance) {
    // ignore instance
}
public User(List<Source2>, Source2 instance) {
    // ignore instance
}

不过这有点蹩脚,因为您可以用任何东西替换这些额外的参数(例如IntegerString,或者只是让其中一个省略第二个参数),它仍然可以工作。此外,额外的参数被忽略 - 它的存在只是为了区分构造函数。尽管如此,它确实允许代码在不添加任何额外方法或特殊代码的情况下工作。

我喜欢工厂的想法,或者泛型用户(如 @Markus Mikkolainen 所建议的那样),但一种可能的替代方案是将列表的类作为第二个参数传递并打开它,例如

public User<List<?> source, Class<?> clazz) {
   switch(clazz) {
      case Source1.class: initForSource1(); break;
      case Source2.class: initForSource2(); break;
      default: throw new IllegalArgumentException();
   }
}

如果存在一些共同的祖先类,则<?>可能是其他东西。 我可以想象很多情况下这是一个糟糕的主意,但在少数情况下,这可能是可以接受的。

最新更新