19

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

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

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

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

在 Java 中,由于类型擦除,您不能这样做——Java 不会接受两个以 List< 为参数的构造函数?>。

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

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

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

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

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

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

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

我的问题是:

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

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

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

4

5 回答 5

10

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

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;
}

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

private User () {
    // Hide the constructor
}

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

于 2012-06-07T19:03:33.690 回答
1

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

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

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

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

于 2012-06-07T18:48:54.667 回答
1

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

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

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

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

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

于 2012-06-08T01:55:24.717 回答
0
public class User<T> {

    public User(List<T> source){

    }

}

或者可能更好:

public class User<T extends SomeTypeOfYours> {

    public User(List<T> source){

    }
}

Wheer是和SomeTypeOfYours的超类型。Source1Source2

于 2012-06-07T18:54:15.350 回答
0

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

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

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

于 2012-06-07T19:20:45.403 回答