【问题标题】:Parameterize a class with one of a fixed set of types使用一组固定类型之一参数化一个类
【发布时间】:2018-02-26 16:59:14
【问题描述】:

假设我有一个泛型类Foo,它可以容纳T 类型的对象。此外,假设我希望能够使用属于两种类型之一的对象来实例化该类。最后,假设这两种类型的最低公共上界是一个比我想要允许的这两种类型有更多子类的类型,所以我不能简单地为类型参数指定一个上限(如@ 987654324@),因为这样我就可以用我期望的两种类型之外的其他类型来实例化该类。

为了说明,假设我希望Foo 保存StringInteger。最低的公共上限是Object,因此指定一个上限并不能解决问题。

当然,我可以做一些类似的事情

class Foo<T> {

    private T obj;

    public Foo(T obj) throws IllegalArgumentException {
        if (!(obj instanceof String || obj instanceof Integer)) {
            throw new IllegalArgumentException("...");
        }
        this.obj = obj;
    }
}

但是,在这种情况下,我仍然可以使用 any 对象调用构造函数;如果我尝试使用既不是 String 也不是 Integer 的东西来实例化它,我将在 runtime 时遇到异常。

我想做得更好。我希望编译器静态推断(即在编译时)我只能使用StringInteger 的对象来实例化这个类。

我在想一些类似的东西可能会奏效:

class Foo<T> {

    private T obj;

    public Foo(String s) {
        this((T) s);
    }

    public Foo(Integer i) {
        this((T) i);
    }

    private Foo(T obj) {
        this.obj = obj;
    }
}

这行得通,但它看起来非常非常奇怪。编译器警告(可以理解)未经检查的强制转换。当然我可以压制这些警告,但我觉得这不是要走的路。此外,看起来编译器实际上无法推断类型T。我惊讶地发现,使用 Foo 类的后一个定义,我可以这样做,例如:

Foo<Character> foo = new Foo<>("hello");

当然,这里的类型参数应该是String,而不是Character。但是编译器让我摆脱了上述任务。

  1. 有没有办法实现我想要的,如果有,如何实现?
  2. 附带问题:为什么编译器允许我在没有 警告 的情况下对上述 Foo&lt;Character&gt; 类型的对象进行赋值(当使用类 @987654341 的后一个定义时) @)? :)

【问题讨论】:

标签: java class generics java-8


【解决方案1】:

尝试使用static工厂方法来防止编译器警告。

class Foo<T> {

    private T obj;

    public static Foo<String> of(String s) {
        return new Foo<>(s);
    }

    public static Foo<Integer> of(Integer i) {
        return new Foo<>(i);
    }

    private Foo(T obj) {
        this.obj = obj;
    }
}

现在您使用以下方法创建实例:

Foo<String> foos = Foo.of("hello");

Foo<Integer> fooi = Foo.of(42);

Foo<Character> fooc = Foo.of('a'); // Compile error

但是以下内容仍然有效,因为您可以声明任何类型 T 的 Foo,但不能实例化它:

Foo<Character> fooc2;

Foo<Character> fooc3 = null;

Foo<Object> fooob1;

Foo<Object> fooob2 = null;

【讨论】:

  • 完美!正是我想要的。 :)
【解决方案2】:
  1. 一个字:界面。您希望您的 Z 包装 A 或 B。创建一个实现 A 和 B 的最小公分母的接口。让您的 A 和 B 实现该接口。 AFAIK,没有其他可靠的方法可以做到这一点。您已经对构造函数等进行的操作是唯一的另一种可能性,但它伴随着您已经注意到的警告(必须使用未经检查的强制转换、静态工厂包装器或其他代码气味)。

注意:如果不能直接修改A和/或B,请事先为它们创建包装类WAWB

示例:

interface Wrapper {
    /* either a marker interface, or a real one - define common methods here */
}

class WInt implements Wrapper {
    private int value;
    public WInt( int value ) { this.value = value; }
}

class WString implements Wrapper {
    private String value;
    public WString( String value ) { this.value = value; }
}

class Foo<T> {   
    private Wrapper w;   
    public Foo(Wrapper w) { this.w = w; }
}
  1. 因为你打电话给你的private Foo(T obj) 由于钻石类型推断。因此,它等于调用Foo&lt;Character&gt; foo = new Foo&lt;Character&gt;("hello");

【讨论】:

  • 谢谢!我想这是一种方法,但它会产生一些开销,所以我更喜欢@Andreas 的解决方案。
  • @MalteSkoruppa 我不想争论这一点,因为 Java 已经在 J​​7 附近的某个地方采用了工厂方式,但是老实说,当没有真正的工厂利益(例如缓存)时,使用工厂而不是 c-tors , 重用, 广泛的静态上下文) 使 调用者 (代码使用者) 的生活变得更加困难,这是由于语义噪音和可能的static 问题(无法用newInstance 抽象创建等)。我同意 Sweeper 的观点——你的问题不在于 Java,而在于抽象被破坏。
  • 事情就是这样。实际上,我可以只使用T 的上限,而不用担心Foo 的实例化,即使它是“意外”的东西:我的实现可以很好地处理它。只是,在应用程序的大局中,用其他任何东西实例化Foo 是没有意义的,所以我只是想防止自己不小心这样做。但是再想一想,我想我不应该担心:Foo 并不真正关心,如果某些外部方法想用其他东西实例化FooFoo 为什么要关心? :) 再次感谢!
  • @MalteSkoruppa 我同意你的观点,特别是如果你注意到类型擦除的运行时警告:Foo 确实不在乎,因为T 实际上是一个 Object 在引擎盖下整个时间!
【解决方案3】:
  1. 长话短说:您试图在 java 泛型中创建两个类的联合,这是不可能的,但有一些解决方法。 See this post

  2. 编译器在 T 参数中使用 Character 类。然后使用 String 构造函数,其中 String 被强制转换为 T(在本例中为字符)。 尝试将私有字段 obj 用作 Character 很可能会导致错误,因为保存的值是最终类 String 的实例。

【讨论】:

  • 谢谢你。你的解释是关于第 2 点的最清楚的解释。对我来说很有意义!
【解决方案4】:

泛型在这里不适合。

any 类可以用作类型时,使用

泛型。如果您允许IntegerString,则不应使用泛型。改为创建两个类FooIntegerFooString

无论如何,实现应该是完全不同的。因为Integers 和Strings 是非常不同的东西,你可能会以不同的方式处理它们。 “但我正在以同样的方式处理它们!”你说。那么Foo&lt;Double&gt;Foo&lt;Bar&gt; 有什么问题。如果您可以使用相同的实现处理IntegerString,那么您可能也可以使用相同的方式处理BarDouble 以及其他任何东西。

关于你的第二个问题,编译器会看到你想创建一个Foo&lt;Character&gt;,所以它会尝试找到一个合适的重载。并且它找到了要调用的 Foo(T) 重载,因此就编译器而言,该语句非常好。

【讨论】:

  • 谢谢!实际上,是的,我可以使用相同的实现处理Foo&lt;Double&gt;Foo&lt;Bar&gt;。我只是想阻止实例化此类对象的可能性,因为它们在应用程序逻辑中没有意义——即,只是出于安全原因。另外,请注意StringInteger 只是示例。实际上,我想要允许的类型更多,因此为每个类型创建一个类会产生很多的开销。 :)
  • @MalteSkoruppa 如果是这种情况,您应该重新考虑抽象,因为它显然已被破坏。 Foo 在这种情况下没有任何实际意义。 stackoverflow.com/questions/1697562/… 涵盖了同样的问题,答案几乎相同 - 尝试为您的数据找出更好的抽象。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-31
  • 2023-03-16
  • 2014-06-21
  • 2022-01-06
  • 1970-01-01
相关资源
最近更新 更多