Java 泛型的类型别名

Posted

技术标签:

【中文标题】Java 泛型的类型别名【英文标题】:Type aliases for Java generics 【发布时间】:2009-03-25 21:25:13 【问题描述】:

我有一组相当复杂的 Java 泛型类。比如我有一个界面

interface Doable<X,Y> 
  X doIt(Y y);

和实现

class DoableImpl implements Doable<Foo<Bar<Baz,Qux>>,Foo<Bar<Zot,Qux>>> 
  Foo<Bar<Baz,Qux>> doIt(Foo<Bar<Zot,Qux>> fooBZQ)  ... 

在实际实现中,Doable 有很多方法,所以Foo&lt;Bar&lt;Baz,Qux&gt;&gt; 等会一遍又一遍地出现。 (信不信由你,泛型类型比这更痛苦。我已经为示例简化了它们。)

我想简化这些,以节省自己的打字时间并减轻眼睛的压力。我想要的是为Foo&lt;Bar&lt;Baz,Qux&gt;&gt; 等设置一个简单的“类型别名”,例如FooBBQFooBZQ

我目前的想法是定义包装类:

class FooBBQ  
  public static FooBBQ valueOf(Foo<Bar<Baz,Qux>> fooBBQ)  
    return new FooBBQ(fooBBQ); 
  
  private Foo<Bar<Baz,Qux>> fooBBQ;
  private FooBBQ(Foo<Bar<Baz,Qux>> fooBBQ)  
    this.fooBBQ = fooBBQ; 
  
  public Foo<Bar<Baz,Qux>> toGeneric() 
    return fooBBQ;
  


class FooBZQ  /* pretty much the same... */ 

class DoableImpl implements Doable<FooBBQ,FooBZQ>  
  FooBBQ doIt(FooBZQ fooBZQ)  ... 

这很好用,但也有一些缺点:

    我们需要为每个通用实例定义单独的包装器。包装类很短且风格化,但我想不出一种宏化它们的方法。

    我们有调用valueOftoGenericFooBBQFoo&lt;Bar&lt;Baz,Qux&gt;&gt; 之间转换的转换开销(从概念上讲,如果不是在操作上)。例如,如果doIt 调用了一些库例程,该例程需要Foo&lt;Bar&lt;Zot,Qux&gt;&gt;(实际实现会这样做),我们最终会得到类似

    return FooBBQ.valueOf( libraryCall( fooBZQ.toGeneric() ) )
    

    我们原本应该有的地方

    return libraryCall(fooBZQ);
    

还有其他方法可以在这里获得我想要的“类型别名”行为吗?也许使用一些第三方宏工具集?或者我是否需要接受我将不得不进行大量输入,一种方式(在实现中使用泛型类型)或另一种方式(为它们编写包装器)?也许让这么多泛型参数飞来飞去只是个坏主意,我需要重新考虑这个问题?

[更新] 好的,我禁止任何进一步的“不要那样做”的答案。假定Foo&lt;Bar&lt;Baz,Qux&gt;&gt; 在我的问题域中具有真正的价值(Pete Kirkham 可能是正确的,它具有 足够 的价值来获得具有描述性名称的适当包装类)。但这是一个编程问题;不要试图定义问题。

【问题讨论】:

我要对最后一个说“可能是”,但实际上并没有任何建设性的反馈:P 看起来很乱!祝你好运! 我认为这个问题对 Java 来说是一个很好的建议。如果您可以向 Oracle 提出类型别名功能,我认为这将是一个很棒的语言功能,可以简化语言并使源代码更具可读性。 今天深入了解 Java 并想清理一些冗长的代码......我不仅没有得到类型推断,而且也没有类型别名。 呜咽 我同意@BasilMusa,类型别名看起来像是该语言缺少的功能。从语法上讲,它可能类似于 class FooBBQ = Foo&lt;Bar&lt;Baz,Qux&gt;&gt; 考虑项目 8。 Java 在 blog.jooq.org/2014/11/03/10-things-you-didnt-know-about-java 上有类型别名 【参考方案1】:

如果您想要完全类型安全,我认为如果没有某种包装类,您将无法做得更好。但是,为什么不让这些类继承/实现原始的通用版本,就像这样:

public class FooBBQ extends Foo<Bar<Baz,Qux>> 
...

这消除了对toGeneric() 方法的需要,而且在我看来更清楚的是,它只是一个类型别名。此外,可以将泛型类型转换为 FooBBQ 而不会出现编译器警告。如果可能的话,我个人倾向于制作Foo, Bar, Baz... 接口,即使在实现中会出现一些代码重复。

现在,在不了解具体问题域的情况下,很难说您是否需要,比如FooBBQ,就像您的示例中一样,或者可能是:

public class FooBar<X, Y> extends Foo<Bar<X, Y>> 
...

另一方面,您是否考虑过简单地将 Java 编译器配置为不显示一些泛型警告,而简单地省略泛型定义的部分?或者,使用策略性放置的@SuppressWarnings("unchecked")?换句话说,你能不能让DoableImpl 只“部分泛化”:

class DoableImpl implements Doable<Foo<Bar>>,Foo<Bar>> 
    Foo<Bar> doIt(Foo<Bar> foobar)  ...  

为了减少代码混乱而忽略警告?同样,如果没有具体示例,很难做出决定,但这是您可以尝试的另一件事。

【讨论】:

Extending Foo> 让您免于单向转换(即隐式向上转换),但代价是必须编写更复杂的构造函数(用于显式向下转换) .我不清楚这是一场胜利。我想尽可能避免使用@SuppressWarnings 注释。 战略性地省略通用定义的部分 (+1),即将它们保持在 API 级别,考虑在实现的其他地方省略它。【参考方案2】:

Scala 对类型别名有很好的支持。例如:

type FooBBQ = Foo[Bar[Baz,Qux]]

我意识到,如果您没有切换到 Scala 的选项,这个答案将无济于事。但是,如果您确实可以选择切换,您可能会更轻松。

【讨论】:

【参考方案3】:

也许有这么多泛型参数只是一个坏主意,我需要重新考虑这个问题?

很有可能。是否需要将“Doit”专门化为 8 个维度?

在很多情况下,这些类型并不存在于真空中,您应该考虑您的“包装器”代表什么域对象,而不是将它们用作编码方便。

【讨论】:

【参考方案4】:

嗯,Java 没有类型别名,所以你不走运。但是,类型别名有时可以替换为类型变量!所以我们用更多的泛型来解决泛型太多的问题!

由于您已经从示例中删除了所有内容,我无法猜测在哪里或是否需要引入额外的类型变量,但这是一种可能的分解:

class DoableImplFoo<A,B> implements Doable<Foo<A>,Foo<B>> 
  public DoableImplFoo(SomePropertyOf<A,B> aAndBAreGoodEnough)  ... 
  Foo<A> doIt(Foo<B> fooB)  ... 

稍后当您将A 实例化为Bar&lt;Baz,Qux&gt;BBar&lt;Zot,Qux&gt; 时,您可能会发现再次出现了一些样板,但它可能比您原来拥有的要少。

【讨论】:

【参考方案5】:

我会说,是的,你需要重新考虑这个问题。声明

 class DoableImpl implements Doable<Foo<Bar<Baz,Qux>>,Foo<Bar<Zot,Qux>>> 
    Foo<Bar<Baz,Qux>> doIt(Foo<Bar<Zot,Qux>> fooBZQ)  ...  
 

是过度使用泛型的一个非常明显的例子。

【讨论】:

我不同意。这可能不是泛型的过度使用……它可能只是对泛型的不明确使用。如果它提供了额外的类型安全,那么它可能是值得的,但也必须有一种方法可以更清楚地做到这一点。 如果我说它在实际代码中实际上相当清楚(只是非常冗长),你会相信我吗?显然,通过 Foo、Bar 和 Qux 参数化您的对象是没有意义的...... 我见过(也做过)更糟的事,有时它是有道理的......但它们可能很难理解。然而,类型安全是值得的。 我正在玩 Java 中的代数(环、字段、各种构造它们的方法),并且我正在接近这个级别的泛型。所以它可能真的需要。但是应该尽量保持简单。 @JesperE 你有什么设计模式或技巧可以帮助你,有什么建议可以避免过度使用泛型吗?

以上是关于Java 泛型的类型别名的主要内容,如果未能解决你的问题,请参考以下文章

如何获取java泛型的参数类型

java泛型泛型的内部原理:类型擦除以及类型擦除带来的问题

java泛型 泛型的内部原理:类型擦除以及类型擦除带来的问题

如何在运行时获取泛型的类型

Java泛型

Java泛型