我们如何在Java中使用泛型来使它们看起来更好呢?



我使用列表元素的属性之一将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;
}

Yiack ! . .

我非常喜欢泛型,在它们之前我使用java,我不想回到cast everything时代,但是当泛型包含一个泛型元素作为元素时,事情就变得混乱了。

我知道在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的处理方式,但我认为太多的魔法可能会伤害(但这当然是主观的)

是否有解决这个问题的方法?

您定义了一个名为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;
}

上面您将看到类型参数定义为<T extends JavaFileObject>,并且您可以在需要的任何地方重用T。

在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]的子类。这是避免讨厌的泛型签名的关键,不要再使用<? extends T><? super T>了!

它也很容易使用:

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)

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

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

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个类型参数

我个人会尝试打破这样的东西(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;
  }

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

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),尽管如果我们愿意允许自己进行强制转换,我们可以使方法更简洁。

首先,你的方法不对吗?我的意思是,它是不是应该更像

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库,则可能已经有了这个实用程序方法。如果你把它和Curtain Dog给你的答案结合起来,你会得到

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

把大家的建议混在一起,我就是这样做的:

我创建了一个新的类DiagnosticList来包装ArrayList<Diagnostic<? extends JavaFileObject>>

非常简单:

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

这是很多可读的。

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

最新更新