我们可以用 Java 中的泛型做什么来让它们看起来更好:

发布于 2024-11-18 12:25:41 字数 1750 浏览 5 评论 0 原文

我有这种方法可以使用列表元素的属性之一将 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 并且我不想回到铸造一切时代,但是当泛型包含它本身作为元素的泛型元素时,事情就会变得混乱。

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

有解决方法吗?

I have this method to transform a List to a Map using one of the properties of the elements of the list:

For short it looks like this:

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!..

I like genercis a lot, I use java prior to them and I don't want to go back to the cast everything era, but when a generic contains as element a generic element it self, things go messy.

I know in Java1.7 we will be able to use the "diamond" operator, but there should be another way.

This is what it would look like in a non-generic version:

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

Approximately, I didn't try to compile it.

How other languages handle this? C# for instance?, Scala? I liked a lot the way SML or Haskell do handle, but something I think too much magic may hurt ( but this is subjective of course )

Is there a workaround for this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

流星番茄 2024-11-25 12:25:42

在 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 的代码


UPDATE

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

给定任何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+] 中的 + 将此类标记为协变,因此 RichDiagnostic[A]如果 AB 的子类,则 被视为 RichDiagnostic[B] 的子类。这是避免令人讨厌的通用签名的关键,不再有

它也很容易使用:

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)

构建此集合只需一个 -射击操作,并且如果将条目添加到基础列表中则必须重复,但我怀疑这不会成为问题。

In Scala, this would look something like:

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

It's not tested, as I have no access to the code for Diagnostic


UPDATE

This'll teach me to post late at night, it's actually far easier...

Given any sequence of Diagnostic objects:

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

they can easily be grouped with a single method:

val diagMap = diags.groupBy(_.getCode)

But it's a little bit more complicated than that!

A bigger problem is that Diagnostic is part of the Java standard library, so you're not able to rewrite it with variance annotations (more on that after the code). A wrapper would do the trick though, and fortunately it's not too big:

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
}

The + in [S+] marks this class as covariant, so a RichDiagnostic[A] is considered to be a subclass of RichDiagnostic[B] if A is a subclass of B. This is the key to avoiding nasty generic signatures, no more <? extends T> or <? super T>!

It's easy enough to use, too:

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

If the Diagnostics are originally supplied as a Java List then methods like map won't automatically be available to you, but conversion is trivial:

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)

Building this collection is a one-shot operation, and will have to be repeated if entries are added to the underlying list, but I suspect that won't be a problem.

飘逸的'云 2024-11-25 12:25:42

很好的例子。在通用版本中,有 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 个类型参数。

Great example. In the generic version, there are 19 type arguments; in the raw version, there is only 1 cast. Since this is just a private method, I'd go with the raw version. Even if it's more public, it can still keep the raw method body, but with full generic signature. Probably something like

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

With more general typing in signature, and Java 7, we can have

<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 type arguments.

人心善变 2024-11-25 12:25:42

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

Personally I would try breaking something like this (Eclipse compiled - not tried running)

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;
  }
↙温凉少女 2024-11-25 12:25:42

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

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

I think the 'answer' has been arrived at by some of the comments here but I don't think anyone so far has given the canonical formulation.

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

The introduction of the type parameter greatly simplifies the internals of the method, while keeping the expressiveness of the signature.

It should be noted that this is a different method to the question as posed, but on balance is probably more correct. The difference being the method given here will ensure that the parameterised type of the Diagnostic is the same for both the input and output of the method.

Unfortunately in this case the invocation of the two constructors prevents us making further use of type parameters (in particular for the Map), though if we were willing to permit ourselves a cast we could make the method even more terse.

2024-11-25 12:25:42

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

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

First, isn't your method wrong?...I mean, shouldn't it be more like

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

Also, you can always emulate the diamond operator with static utility methods that give you some sort of type inference. That is to say

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

public static <T> ArrayList<T> newArrayList() {
    return new ArrayList<T>();
}

and then your method will look like

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

At least instantiations will be smaller....
Note that you may already have this utility methods if you are using google guava library.
And if you combine it with the answer Curtain Dog gave you, you get

    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;
}
っ左 2024-11-25 12:25:42

综合大家在这里的建议,这就是我所做的:

我创建了一个新类 DiagnosticList 来包装 ArrayList>

这非常简单:

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

这是有很多可读性的。

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

Mashing up everyone's here suggestions, this is what I did:

I created a new class DiagnosticList to wrap the ArrayList<Diagnostic<? extends JavaFileObject>>

It is dead simple:

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

And then I could cange the method signature.

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

Which is way lot readable.

While I might change the original program semantics, I think I will benefit in maintainability.

爱冒险 2024-11-25 12:25:41

您定义一个名为 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。这会让它变得更干净一点。

You define one type parameter named T. Then you can use T within your generic like this:

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

Above you will see the type parameter defined as <T extends JavaFileObject> and you reuse T everywhere you need to. This will make it a bit cleaner.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文