简体   繁体   English

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

[英]Java — How to deal with type erasure in constructors?

Let's say I have two constructors in my class: 假设我的课程中有两个构造函数:

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

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

Let's say that both of these constructors provide the same information about a User and are equally valid ways to construct a user for different use cases. 假设这两个构造函数都提供了有关用户的相同信息,并且是为不同用例构建用户的同等有效方法。

In Java, you can't do this because of type erasure -- Java won't accept two constructors that have as parameters List< ? 在Java中,由于类型擦除而无法执行此操作 - Java将不接受两个具有作为参数List <?的构造函数。 >. >。

So, what is the way to get around this? 那么,解决这个问题的方法是什么? What is a solution that is not overkill but still respects basic OO? 什么是解决方案不是过度杀伤但仍然尊重基本的OO? It seems wrong to have to construct a factory method or other interface around this just because Java doesn't have strong generics support. 由于Java没有强大的泛型支持,因此必须构建一个工厂方法或其他接口似乎是错误的。

Here are the possibilities I can think of: 以下是我能想到的可能性:

1) Accept a List<?> as a parameter for the constructor and parse in the constructor which kind of logic you need, or throw an exception if it's not any of the accepted types. 1)接受List<?>作为构造函数的参数,并在构造函数中解析您需要哪种逻辑,或者如果它不是任何可接受的类型则抛出异常。

2) Create a class that accepts either List, constructs the appropriate User object, and returns it. 2)创建一个接受List的类,构造相应的User对象,并返回它。

3) Create wrappers around List<Source1> and List<Source2> that can be passed to the User constructor instead. 3)创建可以传递给User构造函数的List<Source1>List<Source2>包装器。

4) Subclass this guy with two classes, where all of the functionality is inherited except for the constructor. 4)使用两个类对这个人进行子类化,其中除了构造函数之外,所有的功能都是继承的。 The constructor of one accepts Source1, the other accepts Source2. 一个的构造函数接受Source1,另一个接受Source2。

5) Wrap this guy with a builder where are two different builder methods for the two different sources of data for instantiation. 5)用一个构建器包装这个人,其中两个不同的构建器方法用于实例化两个不同的数据源。

My questions are these: 我的问题是这些:

1) Is the need to do this a flaw with Java, or an intentional design decision? 1)是否需要使用Java或有意的设计决策来做这个缺陷? What is the intuition? 什么是直觉?

2) Which solution is strongest in terms of maintaining good code without introducing unneeded complexity? 2)在保持良好代码而不引入不必要的复杂性方面哪种解决方案最强? Why? 为什么?

This question is similar: Designing constructors around type erasure in Java but does not go into specifics, it just suggests various work-arounds. 这个问题是类似的: 在Java中围绕类型擦除设计构造函数但没有详细说明,它只是提出了各种解决方法。

The usual approach is to use 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;
}

If you only want construction using Source1 or Source2 (ie you don't have a default constructor), you simply hide your constructor, forcing clients to use your factory methods: 如果你只是想使用建筑Source1Source2 (即你没有一个默认的构造函数),您只需隐藏你的构造函数,强制客户端使用你的工厂方法:

private User () {
    // Hide the constructor
}

This problem arises because you can't name constructors differently, which would be how you'd overcome this if these were normal methods. 出现这个问题是因为你不能以不同的方式命名构造函数,如果这些是正常的方法,那就是你如何克服它。 Because constructor names are fixed as the class name, this code pattern is only way to distinguish then give the same type erasure. 因为构造函数名称被固定为类名,所以这种代码模式只能区分,然后给出相同类型的擦除。

1: Maintaining backward compatibility with erasure. 1:保持与擦除的向后兼容性。

2: Can your class use generics? 2:你的班级可以使用泛型吗? Something like this: 像这样的东西:

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

I am not sure if this is what you meant by (2) 我不确定这是不是你的意思(2)

The basic problem is the language was designed (with the constructor name being fixed) before generics existed, so it can't handle collisions due to type erasure, which would normlaly be handled by renaming methods to distinguish them. 基本问题是在存在泛型之前设计了语言(构造函数名称是固定的),因此它不能处理由于类型擦除引起的冲突,类型擦除会通过重命名方法来区分它们来处理。

One "workaround", without resorting to factory methods, is to add another non-typed parameter to enable the compiler to distinguish them: 一个“解决方法”,不依赖于工厂方法,是添加另一个非类型参数,以使编译器能够区分它们:

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

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

This is a little lame though, as you could replace those extra parameters with anything (eg Integer and String , or simply have one of them omit the second parameter) and it would still work. 这有点蹩脚,因为你可以用任何东西替换那些额外的参数(例如IntegerString ,或者只是让其中一个省略第二个参数),它仍然可以工作。 Further, the extra parameter is ignored - it exists only to distinguish the constructors. 此外,忽略额外参数 - 它仅用于区分构造函数。 Nevertheless, it does allow the code to work without adding any extra methods or special code. 尽管如此,它确实允许代码在不添加任何额外方法或特殊代码的情况下工作。

public class User<T> {

    public User(List<T> source){

    }

}

or probably better: 或者可能更好:

public class User<T extends SomeTypeOfYours> {

    public User(List<T> source){

    }
}

Wheer SomeTypeOfYours is a super-type of Source1 and Source2 . Someer SomeTypeOfYoursSource1Source2的超类型。

I like the Factory idea in general, or genericizing User (as suggested by @Markus Mikkolainen), but one possible alternative would be to pass the class of the list as a 2nd argument and switch on that, eg 我喜欢一般的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();
   }
}

The <?> might be something else if there is some common ancestor class. 如果有一些共同的祖先类, <?>可能是其他东西。 I can imagine many cases where this is a poor idea, but a few where it might be acceptable. 我可以想象很多情况下这是一个糟糕的想法,但有些可能是可以接受的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM