Что мы можем сделать с дженериками в Java, чтобы они выглядели лучше: - PullRequest
6 голосов
/ 06 июля 2011

У меня есть этот метод для преобразования 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;
}

Як! ..

Мне очень нравится genercis, я использую 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, но я думаю, что слишком много магии может повредить (но это, конечно, субъективно)

Есть ли обходной путь для этого?

Ответы [ 7 ]

7 голосов
/ 06 июля 2011

Вы определяете один тип параметра с именем 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 везде, где вам нужно.Это сделает его немного чище.

4 голосов
/ 07 июля 2011

В 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+] помечает этот класс как ковариантный, поэтому RichDiagnostic[A] считается подклассомRichDiagnostic[B], если A является подклассом B.Это ключ к тому, чтобы избежать неприятных общих подписей, не более <? extends T> или <? super T>!

Его также достаточно просто использовать:

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

Если диагностика изначально поставлялась какJava List, тогда такие методы, как 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)

Создание этой коллекции - операция за один раз, и ее придется повторять, если записидобавлены в базовый список, но я подозреваю, что это не будет проблемой.

3 голосов
/ 06 июля 2011

Отличный пример.В общей версии есть 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 аргументов типа.

2 голосов
/ 07 июля 2011

Лично я бы попытался сломать что-то вроде этого (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 голос
/ 07 июля 2011

Во-первых, разве ваш метод неправильный? ... Я имею в виду, не должно ли это быть больше похоже на

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

Кроме того, вы всегда можете эмулировать оператор diamond с помощью статических служебных методов, которые дают вам какой-то тип вывода. То есть

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;
}
1 голос
/ 07 июля 2011

Я думаю, что «ответ» был получен некоторыми комментариями здесь, но я не думаю, что кто-то до сих пор дал каноническую формулировку.

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), хотя, если бы мы хотели позволить себе приведение, мы могли бы сделать метод еще более кратким.

0 голосов
/ 07 июля 2011

Собирая все здесь предложения, вот что я сделал:

Я создал новый класс 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;
}

Это очень удобно для чтения.

Хотя я мог бы изменить исходную семантику программы, я думаю, что я получу пользу от удобства сопровождения.

...