使用一组固定类型对类进行参数化

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

为了说明,假设我希望Foo 保留StringInteger 。 最低公共上限是Object ,因此指定上限不会起作用。

当然,我可以做一些事情

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

但是,在这种情况下,我仍然可以使用任何对象调用构造函数; 如果我尝试使用既不是String也不是Integer来实例化它,我将在运行时获得exception。

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

我正在思考这些方面可能会做的事情:

 class Foo { 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 foo = new Foo("hello"); 

当然,type参数应该是String ,而不是Character 。 但编译器让我逃脱了上述任务。

  1. 有没有办法实现我想要的,如果有,怎么样?
  2. 旁边的问题:为什么编译器让我放弃了对上面类型为Foo的对象的赋值,甚至没有警告 (当使用后面的Foo类定义时)? 🙂

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

 class Foo { private T obj; public static Foo of(String s) { return new Foo<>(s); } public static Foo of(Integer i) { return new Foo<>(i); } private Foo(T obj) { this.obj = obj; } } 

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

 Foo foos = Foo.of("hello"); Foo fooi = Foo.of(42); Foo fooc = Foo.of('a'); // Compile error 

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

 Foo fooc2; Foo fooc3 = null; Foo fooob1; Foo fooob2 = null; 
  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 { private Wrapper w; public Foo(Wrapper w) { this.w = w; } } 
  1. 因为钻石类型推断你打电话给你的private Foo(T obj) 。 因此,它等于调用Foo foo = new Foo("hello");
  1. 简而言之:你试图在javagenerics中创建两个类的联合,这是不可能的,但有一些解决方法。 看这篇文章

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

generics不适合这里。

任何类可以用作类型时,使用generics。 如果允许IntegerString ,则不应使用generics。 改为创建两个类FooIntegerFooString

无论如何,实现应该是非常不同的。 因为IntegerString是非常不同的东西,你可能会以不同的方式处理它们。 “但我正以同样的方式处理它们!” 你说。 那么FooFoo 。 如果你可以使用相同的实现来处理IntegerString ,你可能也可以以相同的方式处理BarDouble以及其他任何东西。

关于你的第二个问题,编译器会看到你想创建一个Foo ,所以它试图找到一个合适的重载。 并且它找到要调用的Foo(T)重载,因此就编译器而言,该语句完全正常。