使用泛型处理构造函数中的类型擦除

时间:2015-08-07 14:50:51

标签: java generics constructor type-erasure

我正在尝试创建一个只能容纳两个对象中的一个的类,我想用泛型来做这个。这是一个想法:

public class Union<A, B> {

    private final A a;    
    private final B b;

    public Union(A a) {
        this.a = a;
        b = null;
    }

    public Union(B b) {
        a = null;
        this.b = b;
    }

    // isA, isB, getA, getB...

}

当然这不起作用,因为由于类型擦除,构造函数具有相同的类型签名。我意识到一个解决方案是让一个构造函数同时使用两个,但我希望其中一个值为空,因此使用单个参数构造函数似乎更优雅。

// Ugly solution
public Union(A a, B b) {
    if (!(a == null ^ b == null)) {
        throw new IllegalArgumentException("One must exist, one must be null!");
    }
    this.a = a;
    this.b = b;
}

对此有优雅的解决方案吗?

  

编辑1:我正在使用Java 6.

  

编辑2:我想要这样做的原因是因为我有一个可以返回两种类型之一的方法。我做了一个没有泛型的具体版本,但想知道我是否可以使它通用。 是的,我意识到拥有一种具有两种不同返回类型的方法是手头的真正问题,但如果有一个很好的方法,我仍然很好奇。

     

我认为durron597's answer是最好的,因为它指出Union<Foo, Bar>Union<Bar, Foo>的行为应该相同,但它们不相同(这是我决定停止追求此行为的主要原因)。 这是一个比“丑陋”的构造函数更加丑陋的问题。

     

我觉得最好的选择可能是这个摘要(因为接口不能决定可见性)并制作isAgetA内容protected,然后实现类有更好的命名方法来避免<A, B>!= <B, A>问题。我将添加我自己的答案以及更多细节。

  

最终修改:对于它的价值,我决定使用静态方法作为伪构造函数(public static Union<A, B> fromA(A a)public static Union<A, B> fromB(B b))是最好的方法(同时使真实构造函数私有)。当Union<A, B>Union<B, A>被用作返回值时,它们永远不会真实地相互比较。

  

另一个编辑,6个月了:当我问这个时,我真的不敢相信我是多么天真,静态工厂方法显然是绝对正确的选择,显然是一个明智的选择。 / p>      

除此之外,我发现Functional Java非常有趣。我还没有使用过它,但是在搜索'java disjunct union'时我确实发现了这个Either,这正是我想要的。虽然功能Java仅适用于Java 7和8,但是幸运的是我现在正在使用的Java 8项目。

5 个答案:

答案 0 :(得分:5)

这样做真的没有任何意义。什么时候才有意义?例如(假设,目前,您的初始代码有效):

var encodeUrl = function(url) {
        return url.split('//')[0] + '//'
             + url.split('//')[1]
                  .replace(/[a-z ]/gi, function(c) {
                      return '%' + c.charCodeAt(0).toString(16);
                  });
    }

url = 'http://www.helloworld.com/';
encodedUrl = encodeUrl(url)
document.body.innerHTML = 'Raw url: <pre>' + url + '</pre>EncodedUrl: <pre>' + encodedUrl + '</pre>Result: <a href="' + encodedUrl + '">TEST</a>';

但如果你这样做了,那么:

Union<String, Integer> union = new Union("Hello");
// stuff
if (union.isA()) { ...

即使类和数据相同,也会有不同的行为。您对Union<Integer, String> union = new Union("Hello"); // stuff if (union.isA()) { ... isA的概念基本上是&#34;左对比&#34; - 更重要的是哪一个是左对比哪个是哪一个是String而哪一个是整数。换句话说,isBUnion<String, Integer>非常不同,这可能不是您想要的。

如果我们举例说明会发生什么:

Union<Integer, String>

某事List<Union<?, ?>> myList; for(Union<?, ?> element : myList) { if(element.isA()) { // What does this even mean? 的事实并不重要,除非你关心它是左派还是右派,在这种情况下你应该称之为。

如果这个讨论不是左派与右派,那么唯一重要的是在创建类时使用你的特定类型。简单地拥有一个接口会更有意义;

A

你甚至可以做&#34;是&#34;抽象类中的方法:

public interface Union<A, B> {
  boolean isA();
  boolean isB();
  A getA();
  B getB();
}

然后,当您实际实例化该类时,无论如何都将使用特定类型......

public abstract class AbstractUnion<A, B> {
  public boolean isA() { return getB() == null; }
  public boolean isB() { return getA() == null; }
}

然后,当您实际选择了实施类型时,您实际上就会知道自己得到了什么。

除此之外:如果在阅读完上述所有内容之后,您仍然希望按照初始问题中描述的方式执行此操作,那么正确的方法是使用静态工厂方法和私有构造函数,如{{3}所述}。但是,我希望你能进一步思考你真正需要你的课程在真实用例中做什么。

答案 1 :(得分:2)

我会使用私有构造函数和2个静态创建者

public class Union<A, B> {

        private final A a;    
        private final B b;

        // private constructor to force use or creation methods
        private Union(A a, B b) {
            if ((a == null) && (b == null)) { // ensure both cannot be null
                throw new IllegalArgumentException();
            }
            this.a = a;
            this.b = b;
        }

        public static <A, B> Union<A, B> unionFromA(A a) {
            Union<A,B> u = new Union(a, null);
            return u;
        }

        public static <A, B> Union<A, B> unionFromB(B b) {
            Union<A,B> u = new Union(null, b);
            return u;
        }
    ...
}

答案 2 :(得分:2)

如果你必须使用构造函数,那么很可能没有一个优雅的解决方案。

使用工厂方法,您可以拥有一个优雅的解决方案,保留a和b的最终结果 工厂方法将使用“丑陋”构造函数,但这是好的,因为它是实现的一部分。公共接口保留了所有需求,以便从构造函数转换为工厂方法。

这样做的目的是让Union<A,B>Union<B,A>durron597's answer互换。
这不是完全可能的,因为我稍后会举例说明,但我们可以非常接近。

public class Union<A, B> {

    private final A a;
    private final B b;

    private Union(A a, B b) {
        assert a == null ^ b == null;
        this.a = a;
        this.b = b;
    }

    public static <A, B> Union<A, B> valueOfA(A a) {
        if (a == null) {
            throw new IllegalArgumentException();
        }
        Union<A, B> res = new Union<>(a, null);
        return res;
    }

    public static <A, B> Union<A, B> valueOfB(B b) {
        if (b == null) {
            throw new IllegalArgumentException();
        }
        Union<A, B> res = new Union<>(null, b);
        return res;
    }

    public boolean isClass(Class<?> clazz) {
        return a != null ? clazz.isInstance(a) : clazz.isInstance(b);
    }

    // The casts are always type safe.
    @SuppressWarnings("unchecked")
    public <C> C get(Class<C> clazz) {
        if (a != null && clazz.isInstance(a)) {
            return (C)a;
        }
        if (b != null && clazz.isInstance(b)) {
            return (C)b;
        }
        throw new IllegalStateException("This Union does not contain an object of class " + clazz);
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Union)) {
            return false;
        }
        Union union = (Union) o;
        Object parm = union.a != null ? union.a : union.b;
        return a != null ? a.equals(parm) : b.equals(parm);
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 71 * hash + Objects.hashCode(this.a);
        hash = 71 * hash + Objects.hashCode(this.b);
        return hash;
    }
}

以下是如何使用和如何使用它的示例 useUnionAsParm2显示了此解决方案的局限性。编译器无法检测用于接受任何包含String的Union的方法的错误参数。我们必须求助于运行时类型检查。

public class Test {

    public static void main(String[] args) {
        Union<String, Integer> alfa = Union.valueOfA("Hello");
        Union<Integer, String> beta = Union.valueOfB("Hello");
        Union<HashMap, String> gamma = Union.valueOfB("Hello");
        Union<HashMap, Integer> delta = Union.valueOfB( 13 );
        // Union<A,B> compared do Union<B,A>. 
        // Prints true because both unions contain equal objects
        System.out.println(alfa.equals(beta));    

        // Prints false since "Hello" is not an Union.
        System.out.println(alfa.equals("Hello")); 

        // Union<A,B> compared do Union<C,A>. 
        // Prints true because both unions contain equal objects
        System.out.println(alfa.equals(gamma));   

        // Union<A,B> compared to Union<C,D>
        // Could print true if a type of one union inherited or implement a
        //type of the other union. In this case contained objects are not equal, so false.
        System.out.println(alfa.equals(delta));

        useUnionAsParm(alfa);
        // Next two lines produce compiler error
        //useUnionAsParm(beta);
        //useUnionAsParm(gamma);

        useUnionAsParm2(alfa);
        useUnionAsParm2(beta);
        useUnionAsParm2(gamma);
        // Will throw IllegalStateException
        // Would be nice if it was possible to declare useUnionAsParm2 in a way
        //that caused the compiler to generate an error for this line.
        useUnionAsParm2(delta);
    }

    /**
     * Prints a string contained in an Union.
     *
     * This is an example of how not to do it.
     *
     * @param parm Union containing a String
     */
    public static void useUnionAsParm(Union<String, Integer> parm) {
        System.out.println(parm.get(String.class));
    }

    /**
     * Prints a string contained in an Union. Correct example.
     *
     * @param parm Union containing a String
     */
    public static void useUnionAsParm2(Union<? extends Object, ? extends Object> parm) {
        System.out.println( parm.get(String.class) );
    }

}

答案 3 :(得分:1)

&#34;联盟&#34;这里说错了。我们不是在讨论两种类型的并集,它们将包括任何类型的所有对象,可能具有重叠。

这个数据结构更像是一个元组,其中一个额外的索引指向一个重要的元素。一个更好的词可能是&#34;选项&#34;。事实上,java.util.Optional是一个特例。

所以,我可能会像这样设计它

interface Opt2<T0,T1>

    int ordinal();  // 0 or 1
    Object value();

    default boolean is0(){ return ordinal()==0; }

    default T0 get0(){ if(is0()) return (T0)value(); else throw ... }

    static <T0,T1> Opt2<T0,T1> of0(T0 value){ ... }

答案 4 :(得分:0)

正如durron597's answer指出的那样,从概念上讲,Union<Foo, Bar>Union<Bar, Foo>应该表现相同但行为却截然不同。哪个是A哪个是B无关紧要,哪个类型更重要。

这是我认为最好的,

// I include this because if it's not off in its own package away from where its
// used these protected methods can still be called. Also I specifically use an
// abstract class so I can make the methods protected so no one can call them.

package com.company.utils;

public abstract class Union<A, B> {

    private A a;
    private B b;

    protected Union(A a, B b) {
        assert a == null ^ b == null: "Exactly one param must be null";
        this.a = a;
        this.b = b;
    }

    // final methods to prevent over riding in the child and calling them there

    protected final boolean isA() { return a != null; }

    protected final boolean isB() { return b != null; }

    protected final A getA() {
        if (!isA()) { throw new IllegalStateException(); }
        return a;
    }

    protected final B getB() {
        if (!isB()) { throw new IllegalStateException(); }
        return b;
    }
}

并实施。在使用它的地方(com.company.utils除外),只能找到具有明确名称的方法。

package com.company.domain;

import com.company.utils.Union;

public class FooOrBar extends Union<Foo, Bar> {

    public FooOrBar(Foo foo) { super(foo, null); }

    public FooOrBar(Bar bar) { super(null, bar); }

    public boolean isFoo() { return isA(); }

    public boolean isBar() { return isB(); }

    public Foo getFoo() { return getA(); }

    public Bar getBar() { return getB(); }

}

另一个想法可能是Map<Class<?>, ?>或其他东西,或者至少要保留这些值。我不知道。所有这些代码都很糟糕。它是由设计不佳的方法产生的,需要多种返回类型。