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 String
s (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?!
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.
well
T extends Object
makes no sense to begin with– Eugene
Aug 21 at 13:18