【问题标题】:What can we do with generics in Java to make them look better:我们可以用 Java 中的泛型做些什么来让它们看起来更好:
【发布时间】:2011-09-29 04:16:12
【问题描述】:

我有这种方法可以使用列表元素的属性之一将List 转换为Map

简而言之,它看起来像这样:

private Map<String, List<Diagnostic<? extends JavaFileObject>>> toMap( List<Diagnostic<? extends JavaFileObject>> diagnostics ) {
    Map<String, List<Diagnostic<? extends JavaFileObject>>> result = new HashMap<String, List<Diagnostic<? extends JavaFileObject>>>();
    for ( Diagnostic<? extends JavaFileObject> d : diagnostics ) {
        List<Diagnostic<? extends JavaFileObject>> list = null;
        if ( !result.containsKey( d.getCode() ) ) {
            list = new ArrayList<Diagnostic<? extends JavaFileObject>>();
            result.put( d.getCode(), list );
        } else {
            list = result.get( d.getCode() );
        }
        assert list != null;
        list.add( d );
    }
    return result;
}

耶!..

我非常喜欢泛型,我在它们之前使用 java,我不想回到 cast all 时代,但是当泛型包含作为元素的泛型元素时,它自身,事情变得一团糟。

我知道在 Java1.7 中我们将能够使用“diamond”运算符,但应该有另一种方式。

这是它在非通用版本中的样子:

private Map toMap( List diagnostics ) { 
    Map result = new HashMap();
    for( Object o  : diagnostics ) {
        Diagnostic d = ( Diagnostic ) o; 
        List list = null;
        if( !result.containsKey( d.getCode() ) ) { 
            list = new ArrayList();
            result.put( d.getCode() , list );
         } else { 
            list = result.get( d.getCode() );
         }
         assert list != null;
         list.add( d );
     }
     return result;
}

大概,我没有尝试编译它。

其他语言如何处理这个问题?例如 C#?,Scala?我非常喜欢 SML 或 Haskell 处理的方式,但我认为过多的魔法可能会伤害(但这当然是主观的)

有解决办法吗?

【问题讨论】:

  • 你的程序需要 还是可以只使用 或接口?
  • 我感受到了你的痛苦——你看过 Lombok 并且它使用val 来推断类型吗? (projectlombok.org/features/val.html) 它并没有清除整个混乱,但它确实缩小了代码。 C# 中的var 提供了类似的解决方案。
  • 一个明显的解决方法是在你的方法中使用原始类型,如果你接受编译器警告和类型错误的风险,但我同意干净的代码是丑陋的。
  • @JustinKSU 我需要&lt;? extends JavaFileObject&gt;我以后会用它

标签: c# java generics scala static-typing


【解决方案1】:

您定义了一个名为T 的类型参数。然后你可以像这样在你的泛型中使用T

private <T extends JavaFileObject> Map<String, List<Diagnostic<T>> toMap(List<Diagnostic<T> diagnostics) {
    Map<String, List<Diagnostic<T>> result = new HashMap<String, List<Diagnostic<T>>();
    for (Diagnostic<T> d : diagnostics ) {
        List<Diagnostic<T>> list = null;
        if ( !result.containsKey(d.getCode())) {
            list = new ArrayList<Diagnostic<T>>();
            result.put( d.getCode(), list );
        } else {
            list = result.get( d.getCode() );
        }
        assert list != null;
        list.add( d );
    }
    return result;
}

您将在上面看到定义为&lt;T extends JavaFileObject&gt; 的类型参数,并且您可以在任何需要的地方重用T。这样会更干净一些。

【讨论】:

  • 如果你想让它更干净,只需创建一个封装List&lt;Diagnostic&lt;T&gt;&gt;的类型
  • 如果&lt;D extends Diagnostic&gt; Map&lt;String, List&lt;D&gt;&gt; toMap( List&lt;D&gt; diagnostics ) 更好 - 该方法只关心它是一些诊断对象的列表,没有别的。
  • 不确定,该方法被另一部分使用,而另一部分又关心JavaFileObject 的东西。我可以施放它,但是... :-/
  • 那行不通,因为Diagnostic 不知道任何关于JavaFileObject 的信息,它怎么知道它的类型? Diagnostic 和 JavaFileObject 是两个互不相关的不同实体。唯一的关系是 Diagnostic 聚合了一个 JavaFileObject
  • @irreputable:至少使用&lt;D extends Diagnostic&lt;?&gt;&gt;
【解决方案2】:

在 Scala 中,这看起来像:

// collections are immutable by default, but we want the mutable flavour
import collection.mutable

// An alias so we don't keep repeating ourself
type DiagMultiMap[T] = mutable.Map[String, mutable.Set[Diagnostic[T]]]

//pimp DiagMultiMap with the addDiagnostic method
class MapDiag[T](theMap: DiagMultiMap[T]) {
  def addDiagnostic(d: Diagnostic[T]): Unit = {
    val set = theMap.getOrElseUpdate(d.getCode) {mutable.Set.empty}
    set += d
  }
}

//an implicit conversion to enable the pimp
implicit def mapDiagPimp[T](theMap: DiagMultiMap[T]) = new MapDiag(theMap)

//This is how we make one
def mkDiagnosticMultiMap[T](entries: Seq[Diagnostic[T]]): DiagMultiMap[T] = {
  val theMap = new mutable.HashMap[String, mutable.Set[Diagnostic[T]]]()
  entries foreach { theMap addDiagnostic _ }
  theMap
}

它没有经过测试,因为我无法访问 Diagnostic 的代码


更新

这会教我在深夜发帖,实际上要容易得多...

给定任何Diagnostic对象序列:

val diags = List(new Diagnostic(...), new Diagnositic(...), ...)

它们可以很容易地用一种方法进行分组:

val diagMap = diags.groupBy(_.getCode)

但比这要复杂一点!

一个更大的问题是Diagnostic 是Java 标准库的一部分,因此您无法使用方差注释来重写它(更多内容在代码之后)。包装器可以解决问题,幸运的是它不是太大:

class RichDiagnostic[S+](underlying: Diagnostic[S]) {
  def code: String = underlying.getCode
  def columnNumber: Long = underlying.getColumnNumber
  def endPosition: Long = underlying.getEndPosition
  def kind: Diagnostic.Kind = underlying.getKind
  def lineNumber: Long = underlying.getLineNumber
  def messageFor(locale: Locale): String = underlying.getMessage(locale) 
  def position: Long = underlying.getPosition
  def source: S = underlying.getSource
  def startPosition: Long = underlying.getStartPosition
  implicit def toUnderlying: Diagnostic[S] = underlying
}

[S+] 中的+ 将此类标记为协变,因此如果AB 的子类,则RichDiagnostic[A] 被认为是RichDiagnostic[B] 的子类。这是避免讨厌的通用签名的关键,不再有&lt;? extends T&gt;&lt;? super T&gt;

它也很容易使用:

val richDiags = diags.map(d => new RichDiagnostic(d))
val diagMap = richDiags.groupBy(_.code)

如果诊断最初是作为 Java 列表提供的,那么像 map 这样的方法将不会自动提供给您,但转换很简单:

import collection.JavaConverters._

//the toList isn't strictly necessary, but we get a mutable Buffer otherwise
val richDiags = diagsFromJava.asScala.toList.map(d => new RichDiagnostic(d))
val diagMap = richDiags.groupBy(_.code)

构建此集合是一次性操作,如果将条目添加到基础列表中,则必须重复此操作,但我怀疑这不会有问题。

【讨论】:

  • +1 感谢您的回答,诊断适用于:javax.tool.Diagnostic in Java1.6
  • 太棒了,我看过那堂课后会回去更新答案。
【解决方案3】:

很好的例子。在泛型版本中,有 19 个类型参数;在原始版本中,只有 1 个演员。由于这只是一个私有方法,我会使用原始版本。即使它更加公开,它仍然可以保留原始方法体,但具有完整的通用签名。大概是这样的

Map<String, List<Diagnostic<? extends JavaFileObject>>> 
toMap( List<Diagnostic<? extends JavaFileObject>> diagnostics )
{
    Map result = new HashMap();
    for( Diagnostic d  : diagnostics ) 
    {
        List list = (List)result.get( d.getCode() );
        if(list==null)
            result.put( d.getCode(), list=new ArrayList());
         list.add( d );
    }
    return result;
}

通过更通用的签名输入和 Java 7,我们可以拥有

<D extends Diagnostic<?>>
Map<String, List<D>> toMap( List<D> diagnostics )
{
    Map<String, List<D>> result = new HashMap<>();
    for( D d  : diagnostics ) 
    {
        List<D> list = result.get( d.getCode() );
        if(list==null)
            result.put( d.getCode(), list=new ArrayList<>());
         list.add( d );
    }
    return result;
}

void test()
{
    List<Diagnostic<? extends JavaFileObject>> x = null;

    Map<String, List<Diagnostic<? extends JavaFileObject>>> map = toMap(x);
}

8 种类型参数。

【讨论】:

  • 值得注意的是,即使(为了与 Java 6 兼容)我们不使用 &lt;&gt;,您的第二个示例仍然比问题中的代码有显着改进。
【解决方案4】:

我个人会尝试破坏这样的东西(Eclipse 已编译 - 未尝试运行)

private class MapDiag extends HashMap<String, List<Diagnostic<? extends JavaFileObject>>>{
    private static final long serialVersionUID = 1L;

    void add(Diagnostic<? extends JavaFileObject> d){
      List<Diagnostic<? extends JavaFileObject>> list = null;
      if (containsKey(d.getCode())){
        list = get(d.getCode());
      }
      else {
        list = new ArrayList<Diagnostic<? extends JavaFileObject>>();
        put( d.getCode(), list );
      }
      list.add(d);
    }
  }

  private MapDiag toMap2( List<Diagnostic<? extends JavaFileObject>> diagnostics ) {
    MapDiag result = new MapDiag();
    for ( Diagnostic<? extends JavaFileObject> d : diagnostics ) {
      result.add(d);
    }
    return result;
  }

【讨论】:

  • +1 信不信由你,我实际上正在这样做:class DiagnosticList extends ArrayList&lt;Diagnostic&lt;? extends JavaFileObject&gt;&gt;{} :)
【解决方案5】:

我认为这里的一些 cmets 已经得出了“答案”,但我认为到目前为止还没有人给出规范的表述。

private <T extends Diagnostic<? extends JavaFileObject>>
        Map<String, List<T>> toMap(List<T> diagnostics) {
    Map<String, List<T>> result = new HashMap<String, List<T>>();
    for (T d : diagnostics) {
        List<T> list = null;
        if (!result.containsKey(d.getCode())) {
            list = new ArrayList<T>();
            result.put(d.getCode(), list);
        } else {
            list = result.get(d.getCode());
        }
        assert list != null;
        list.add(d);
    }
    return result;
}

类型参数的引入大大简化了方法的内部,同时保持了签名的表现力。

应该注意的是,这与提出的问题不同,但总的来说可能更正确。不同之处在于这里给出的方法将确保诊断的参数化类型对于方法的输入和输出都是相同的。

不幸的是,在这种情况下,调用两个构造函数会阻止我们进一步使用类型参数(尤其是 Map),但如果我们愿意允许自己进行强制转换,我们可以使该方法更加简洁。

【讨论】:

    【解决方案6】:

    首先,你的方法是不是错了?...我的意思是,它不应该更像

    List<T> list = null;
    if (!result.containsKey(d.getCode())) {
        list = newArrayList();          
    } else {
        list = result.get(d.getCode());
    }   
    result.put(d.getCode(), list);
    

    此外,您始终可以使用静态实用程序方法来模拟菱形运算符,从而为您提供某种类型的推断。也就是说

    public static <K, V> HashMap<K, V> newHashMap() {
        return new HashMap<K, V>();
    }
    
    public static <T> ArrayList<T> newArrayList() {
        return new ArrayList<T>();
    }
    

    然后你的方法看起来像

    private Map<String, List<Diagnostic<? extends JavaFileObject>>> toMap(List<Diagnostic<? extends JavaFileObject>> diagnostics) {
        Map<String, List<Diagnostic<? extends JavaFileObject>>> result = newHashMap();
        for (Diagnostic<? extends JavaFileObject> d : diagnostics) {
            List<Diagnostic<? extends JavaFileObject>> list = null;
            if (!result.containsKey(d.getCode())) {
                list = newArrayList();
                result.put(d.getCode(), list);
            } else {
                list = result.get(d.getCode());
            }
            assert list != null;
            list.add(d);
        }
        return result;
    }
    

    至少实例会更小.... 请注意,如果您使用的是 google guava 库,您可能已经拥有此实用程序方法。 如果你把它和窗帘狗给你的答案结合起来,你会得到

        private <T extends Diagnostic<? extends JavaFileObject>> Map<String, List<T>> toMap(List<T> diagnostics) {
        Map<String, List<T>> result = newHashMap();
        for (T d : diagnostics) {
            List<T> list = null;
            if (!result.containsKey(d.getCode())) {
                list = newArrayList();
                result.put(d.getCode(), list);
            } else {
                list = result.get(d.getCode());
            }
            assert list != null;
            list.add(d);
        }
        return result;
    }
    

    【讨论】:

      【解决方案7】:

      综合大家的建议,我就是这样做的:

      我创建了一个新类 DiagnosticList 来包装 ArrayList&lt;Diagnostic&lt;? extends JavaFileObject&gt;&gt;

      很简单:

      static final class DiagnosticList 
      extends ArrayList<Diagnostic<? extends JavaFileObject>>{
          // no arg constructor 
          public DiagnosticList(){}
          // Using a list
          public DiagnosticList(List<Diagnostic<? extends JavaFileObject>> diagnostics){
              super( diagnostics);
          }
      }
      

      然后我可以修改方法签名。

      private Map<String, DiagnosticList> toMap( DiagnosticList diagnostics ) {
          Map<String, DiagnosticList> result = new HashMap<String, DiagnosticList>();
          for ( Diagnostic<? extends JavaFileObject> d : diagnostics ) {
              DiagnosticList list = result.get(d.getCode());
              if( list == null ) {
                result.put( d.getCode(), (list = new DiagnosticList()));
              }
              list.add( d );
          }
          return result;
      }
      

      这很容易阅读。

      虽然我可能会更改原始程序的语义,但我认为我会在可维护性方面受益。

      【讨论】:

        猜你喜欢
        • 2012-05-05
        • 2012-10-12
        • 1970-01-01
        • 2011-01-29
        • 1970-01-01
        • 2016-08-24
        • 2019-06-23
        • 1970-01-01
        • 2017-03-27
        相关资源
        最近更新 更多