casting & generics to enable list concatenation in Java 8

casting & generics to enable list concatenation in Java 8



Apologies that is probably the worst Title I've used but I can't quite think how to word it.



I'm calling a method table.getColData(COL_1) which returns a generic


public <T extends Object> List<T> getColData(final String col)



I am calling it twice and getting two lists of strings. I want to concatenate these and have ended up with this -


List<String> a = table.getColData(col1);
List<String> b = table.getColData(col2);

List <String> c = Stream.concat(a.stream(), b.stream()).collect(Collectors.toList());



which works nicely I think. I can't find a way to avoid the 2 declarations though without getting an error as the concat thinks it has a list of objects that are not Strings (prompt: change type of c to List<Object>) ?


concat


String


c


List<Object>



Is there an easy way to do this to make it look a little more polished?!





well T extends Object makes no sense to begin with
– Eugene
Aug 21 at 13:18


T extends Object





Wouldn't this work? List<String> c = table.getColData(col1); c.addAll(table.getColData(col2));
– Aaron
Aug 21 at 13:26


List<String> c = table.getColData(col1); c.addAll(table.getColData(col2));





@Holger You concat Lists here and not streams and even with streams i don't think that it will compile because the table.getColData(col2) return is inferred to Object
– davidxxx
Aug 21 at 13:32


table.getColData(col2)


Object





just interested now, what does getColData return?
– Eugene
Aug 21 at 13:34


getColData





@davidxxx ok, Stream.concat(table.<String>getColData(col1).stream(), table.<String>getColData(col2).stream()) then.
– Holger
Aug 21 at 13:37


Stream.concat(table.<String>getColData(col1).stream(), table.<String>getColData(col2).stream())




5 Answers
5



You are limited by the inference of the compiler.


List <String> c = Stream.concat(getColDataStream(col1).stream(), getColDataStream(col2).stream()).collect(Collectors.toList());



cannot compile because getColDataStream() return is inferred as List<Object> as you don't specify a target type from the invocation side.

You can concatenate two streams of List<Object> but it not will produce Stream<String> but Stream<Object>.

Introducing two intermediary variables is not necessary the best way.


getColDataStream()


List<Object>


List<Object>


Stream<String>


Stream<Object>



1) As alternative as suggested by Holger you could specify the T type from the target side :


T


Stream.concat(table.<String>getColData(col1).stream(), table.<String>getColData(col2).stream())
.collect(Collectors.toList());



2) You could also transform Stream<Object> to Stream<String>in a map() operation :


Stream<Object>


Stream<String>


map()


List<String> c = Stream.concat(table.getColData(col1).stream(), table.getColData(col2).stream())
.map(s -> (String) s)
.collect(Collectors.toList());



3) or introducing an additional method that prevents any explicit cast by concatenating streams of the lists, and collecting it in a List that it returns :


List


public <T> List<T> concatAndCollectToList(final List<T> a, List<T> b)
return Stream.concat(a.stream(), b.stream())
.collect(Collectors.toList());



You can now do just :


List<String> c = concatAndCollectToList(table.getColData(col1), table.getColData(col2));





Thanks very much. I think 1 was what I was intending to ask and so I've marked this as the answer (also as it is so complete!) but 2 (combined with Nikolas's answer further down is where I would like to be!).
– gringogordo
Aug 21 at 14:58





@gringogordo You are very welcome. Fine ! I think that no solution is really better than others. It is more a taste question.
– davidxxx
Aug 21 at 18:15



To be honest, I would add a type witness (a method argument) that is not used, to make this method type safe:


public static <T> List<T> getColData(String col, Class<T> clazz)
// whatever you did as before



And in such a case your type-safety would be in place:


List<String> set = Stream.concat(
getColData("", String.class).stream(),
getColData("", String.class).stream())
.collect(Collectors.toList());





I thought type witness usually means <String>getColData(...). Is a class parameter like this also called type witness?
– kapex
Aug 21 at 14:14


<String>getColData(...)





@kapex I have no idea about the absolute correct name for this... sorry :| I call it type witness, but might have some other official name
– Eugene
Aug 21 at 14:15






@kapex and btw its called type witness in the oracle tutorials, but type arguments in the JLS
– Eugene
Aug 21 at 14:17


JLS





This is a solid solution. Because of runtime type erasure providing at run-time the class is best. 1. inside getColData one might add to the resulting list objects casted: list.add(clazz.cast(resultSet.getObject(col))); 2. outside it is entirely type-safe.
– Joop Eggen
Aug 21 at 14:42


getColData


list.add(clazz.cast(resultSet.getObject(col)));



Firstly, the T extends Object is redundant since every object T extends from Object since the definition.


T extends Object


T


Object



Since the method returns List<T>, it's unknown at the compilation time what type is T, therefore it's not possible to pass those two generic results to Stream - the collected result is List<Object> regardless the T.


List<T>


T


Stream


List<Object>


T



You have to map each of the values to String - at this point you have to assure that all the list items are convertible into String using casting or conversion:


String


String


List<String> c = Stream
.concat(table.getColData(col1).stream(), table.getColData(col2).stream())
.map(s -> (String) s)
.collect(Collectors.toList());



I recommend you the shorter and more readable way which uses Stream::flatMap:


Stream::flatMap


List<String> c = Stream.of(table.getColData(col1), table.getColData(col2))
.flatMap(list -> list.stream().map(s -> (String) s))
.collect(Collectors.toList());



The result depends on what exactly the method getColData returns and whether it is convertible to String (@Eugene).


getColData


String





what do you that method can return under the current declaration?
– Eugene
Aug 21 at 13:37





@Eugene: What do you mean? I don't understand your question :(
– Nikolas
Aug 21 at 13:39





public static <T> List<T> getColData(String col) List<Integer> l = new ArrayList<>(); l.add(1); return (List<T>) l; as an example of the method (otherwise the only thing you can return would be List.of() or Collections.emptyList() or null) and later do List<String> l = getColData(""); System.out.println(l.get(0).length()); which will break at runtime, of course
– Eugene
Aug 21 at 13:45


public static <T> List<T> getColData(String col) List<Integer> l = new ArrayList<>(); l.add(1); return (List<T>) l;


List.of()


Collections.emptyList()


null


List<String> l = getColData(""); System.out.println(l.get(0).length());





The compiler will infer whatever target type is provided, like in the question’s List<String> a = table.getColData(col1);. So you can write Stream.<List<String>>of( table.getColData(col1), table.getColData(col2)) .flatMap(List::stream) .collect(Collectors.toList());
– Holger
Aug 21 at 13:45



List<String> a = table.getColData(col1);


Stream.<List<String>>of( table.getColData(col1), table.getColData(col2)) .flatMap(List::stream) .collect(Collectors.toList());





@Holger which sucks to me, that type parameter is not used as a method argument, thus you can basically return whatever List you want with a cast to List<T>
– Eugene
Aug 21 at 13:46


List<T>



The simplest would be:


Stream.of(col1, col2)
.map(table::<String>getColData)
.flatMap(List::stream)
.collect(Collectors.toList());



Since your getColData method returns a T, you can specify what type T is by using a type witness <String>. The rest is the java syntax for method references.


getColData


T


T


<String>



Also, the use of generics can be questioned here. This is equivalent to having


public List<Object> getColData(final String col)



and casting your list to a list of String:


String


Stream.of(col1, col2)
.map(table::getColData)
.map(o -> (String) o)
.flatMap(List::stream)
.collect(Collectors.toList());





Some description why table::<String>getColDat and how does it work would not hurt ;)
– Nikolas
Aug 21 at 16:05


table::<String>getColDat





Yes, I just updated my answer to include some details about that!
– Alex M
Aug 21 at 17:10



The simplest approach would be to just add a generic argument to the method using <>


<>


Stream.concat(
table.<String>getColData(col1).stream(),
table.<String>getColData(col2).stream())
.collect(toList());






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)