繁体   English   中英

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

[英]Handling type erasure in constructors with generics

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

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的答案是最好的,因为它指出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个月了 :我真的不敢相信当我问这个时我是多么天真,静态工厂方法显然是绝对正确的选择,显然是一个不用脑子。

除此之外,我发现Functional Java非常有趣。 我没有用它,但是我却觉得这Either谷歌搜索的Java间断工会“的时候,它正是我一直在寻找。 虽然功能Java仅适用于Java 7和8,但是幸运的是我现在正在使用的Java 8项目。

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

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

但如果你这样做,而是:

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

即使类和数据相同,这也会有不同的行为 你的概念isAisB基本上都是“左向右VS” -这是更重要的,其中一个是左向右VS比一个是一个String VS哪一个是一个整数。 换句话说, Union<String, Integer>Union<Integer, String>非常不同,它可能不是您想要的。

考虑如果我们举例说会发生什么,请说:

List<Union<?, ?>> myList;
for(Union<?, ?> element : myList) {
  if(element.isA()) {
    // What does this even mean? 

事物是A事实并不重要,除非你关心它是左派还是右派,在这种情况下你应该称之为。


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

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

您甚至可以在抽象类中执行“is”方法:

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

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

public UnionImpl extends AbstractUnion<String, Integer> {
  private String strValue;
  private int intValue

  public UnionImpl(String str) {
    this.strValue = str;
    this.intValue = null;
  }

  // etc.
}

然后,当你真正选择了你的实现类型时,你实际上就会知道你得到了什么。


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

我会使用私有构造函数和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;
        }
    ...
}

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

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

这是为了根据durron597的答案使Union<A,B>Union<B,A> 互换
这不是完全可能的,因为我稍后会举例说明,但我们可以非常接近。

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

}

“联盟”在这里是错误的词。 我们不是在谈论两种类型的联合,它们将包括任何类型的所有对象,可能具有重叠。

此数据结构更像是一个元组,其中一个额外的索引指向一个重要元素。 更好的说法可能就是“选择”。 实际上, 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){ ... }

正如durron597的答案所指出的那样,从概念上讲, 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<?>, ?>等等,或至少保存这些值。 我不知道。 所有这些代码都很糟糕。 它是由一个设计糟糕的方法产生的,需要多种返回类型。

暂无
暂无

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

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