【问题标题】:Java -- How to deal with type erasure in constructors?Java——如何处理构造函数中的类型擦除?
【发布时间】:2012-06-07 18:41:43
【问题描述】:

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

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

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

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

在 Java 中,由于类型擦除,您不能这样做——Java 不会接受两个以 List.

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

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

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

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

3) 创建围绕 List&lt;Source1&gt;List&lt;Source2&gt; 的包装器,可以改为传递给 User 构造函数。

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

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

我的问题是:

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

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

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

【问题讨论】:

  • 为什么首先要这样做,为什么不对整个班级进行类型参数化?或者只是创建在这种情况下是一个很好的模式的工厂。
  • 您可以编写一个函数,然后在函数开头测试列表的类型,然后根据类型进行类型转换...或使用更好的语言
  • ...或者您可以使用可具体化的类型,例如数组。
  • 为什么有 2 个类的信息相同?为什么它们至少不实现相同的接口?
  • 我同意@MarkusMikkolainen 的观点,我会使用工厂模式。我不确定我能回答这是一个缺陷还是设计决定,但它可能与向后兼容性有关。

标签: java type-erasure


【解决方案1】:

通常的做法是使用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
}

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

【讨论】:

  • 提问者说:“仅仅因为 Java 没有强大的泛型支持,就必须围绕这个构造工厂方法或其他接口似乎是错误的。” ...但这只是 Java 构造函数比静态工厂方法更具限制性的一种方式。 (另见:Effective Java 和stackoverflow.com/questions/4029622/…
【解决方案2】:

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

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

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

我不确定这是否是您所说的 (2)

【讨论】:

    【解决方案3】:

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

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

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

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

    【讨论】:

      【解决方案4】:
      public class User<T> {
      
          public User(List<T> source){
      
          }
      
      }
      

      或者可能更好:

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

      SomeTypeOfYoursSource1Source2 的超类型。

      【讨论】:

        【解决方案5】:

        我一般喜欢 Factory 的想法,或者泛化 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();
           }
        }
        

        如果有一些共同的祖先类,&lt;?&gt; 可能是别的东西。我可以想象很多情况下这是一个糟糕的想法,但也有少数情况下可以接受。

        【讨论】:

        • switch case 是否允许这个通过类?
        • @PrabhatGaur 嗯,我想打开类名(字符串)。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多