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

时间:2012-06-07 18:41:43

标签: java type-erasure

让我们说我班上有两个构造函数:

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

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

让我们说这两个构造函数都提供了有关用户的相同信息,并且是为不同用例构建用户的同等有效方法。

在Java中,由于类型擦除,你不能这样做 - Java不会接受两个具有参数List&lt;的构造函数。 ? &GT;

那么,解决这个问题的方法是什么?什么是解决方案不是过度杀伤但仍然尊重基本的OO?由于Java没有强大的泛型支持,因此必须构建一个工厂方法或其他接口似乎是错误的。

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

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

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

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

4)将这个人子类化为两个类,除了构造函数之外,所有的功能都是继承的。一个的构造函数接受Source1,另一个接受Source2。

5)用一个构建器包装这个人,其中两个不同的构建器方法用于两个不同的数据源进行实例化。

我的问题是这些:

1)是否需要使用Java或有意的设计决策来解决这个问题?什么是直觉?

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

这个问题类似:Designing constructors around type erasure in Java但没有详细说明,它只是提出了各种解决方法。

5 个答案:

答案 0 :(得分:8)

通常的做法是使用factory methods

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 :(得分:1)

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

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

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

我不确定这是否是你所指的(2)

答案 2 :(得分:1)

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

一个“解决方法”,不依赖于工厂方法,是添加另一个非类型参数,以使编译器能够区分它们:

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

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

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

答案 3 :(得分:0)

public class User<T> {

    public User(List<T> source){

    }

}

或者可能更好:

public class User<T extends SomeTypeOfYours> {

    public User(List<T> source){

    }
}

Wheer SomeTypeOfYoursSource1Source2的超类型。

答案 4 :(得分:0)

我喜欢一般的工厂理念,或者对User进行泛化(如@Markus Mikkolainen所建议的那样),但一种可能的替代方法是将列表的类作为第二个参数传递并切换它,例如。

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

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