What can we do with generics in Java to make them look better:

I have this method for converting a List to Map using one of the properties of list items:

In 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 before them, and I donโ€™t want to go back to the cast of all ages, but when a common element contains a common element as an element, it all becomes dirty.

I know that in Java1.7 we can use the diamond operator, but there must be a different way.

This is what would look like 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; } 

For example, I did not try to compile it. Sub>

How do other languages โ€‹โ€‹deal with this? C # like ?, Scala? I really liked how SML or Haskell do it, but something I think too much magic can hurt (but this is subjective of course)

Is there a workaround for this?

+6
source share
7 answers

You define one type parameter named T Then you can use T inside your generic type:

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

You will see a type parameter defined as <T extends JavaFileObject> , and you will use T again wherever you need. This will make it a little cleaner.

+7
source

In Scala, it looks something like this:

 // 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 has not been tested since I do not have access to the code for Diagnostic


UPDATE

It will teach me to publish late at night, it is actually a lot easier ...

For any sequence of Diagnostic objects:

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

they can be easily grouped using one method:

 val diagMap = diags.groupBy(_.getCode) 

But it's a little harder than that!

The big problem is that Diagnostic is part of the standard Java library, so you cannot rewrite it using annotations with deviations (more on this after the code). However, the wrapper would do the trick, and fortunately it is 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 } 

+ in [S+] denotes this class as covariant, therefore RichDiagnostic[A] is considered a subclass of RichDiagnostic[B] if A is a subclass of B This is the key to preventing unpleasant common signatures, no more than <? extends T> <? extends T> or <? super T> <? super T> !

Too easy to use:

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

If Diagnostics is initially delivered as a Java list, then methods like map will not automatically be available to you, but the 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) 

Creating this collection is a one-time operation and should be repeated if entries are added to the base list, but I suspect that this is not a problem.

+4
source

Great example. There are 19 type arguments in the general version; in the original version, only 1 roll. Since this is just a private method, I would go with the original version. Even if it is more public, it will still be able to save the body of the unprocessed method, but with a full common signature. Maybe 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.

+3
source

Personally, I would try to break something like this (Eclipse compiled - did not try to work)

 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; } 
+2
source

I think that the โ€œanswerโ€ was received by some comments, but I do not think that anyone has so far given a canonical wording.

 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 a type parameter greatly simplifies the interior of the method, while maintaining the expressiveness of the signature.

It should be noted that this is a different method for the question posed, but on the balance sheet is probably more correct. The difference is that the method given here ensures that the parameterized type of Diagnostics will be the same for both input and output of the method.

Unfortunately, in this case, calling two constructors does not allow us to use type parameters (in particular, for the Map), although if we were ready to resolve ourselves, we could make this method even more concise.

+1
source

Firstly, 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); 

In addition, you can always emulate the diamond operator using static utility methods that give you some type of output. I.e

 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 instances will be smaller. Note that you may have these utility methods if you use the goava goava library. And if you combine it with the answer received from Curtain Dog, you will 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; } 
+1
source

Clicking all the suggestions here is what I did:

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

This 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 can create a 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; } 

How many pages can be read.

While I can change the original semantics of the program, I think I will be useful in maintainability.

0
source

Source: https://habr.com/ru/post/892077/


All Articles