How to reference the result of reduce() operation in Java 8? - java

I was trying to write a mkString function in Java8, a la Scala's useful mkString and ran into 2 issues that I could use some help on:
I am unable to make the first argument of mkString a generic Collection reference like Collection<Object> c and have invokers call with ANY type of collection.
Unable to reference the returned result of reduce() in-line to access the result's length to remove the extra leading separator.
Here's the code :
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
System.out.println(mkString(numbers, ","));
}
public static String mkString(Collection<Integer> c, String sep) {
return c.stream()
.map(e -> String.valueOf(e))
.reduce("", (a, b) -> a + sep + b)
.substring(1, <<>>.length);
}

Note that if you're doing this not for self-education but to actually use it in some production code, you might want to consider the built-in Collectors.joining collector:
String result = numbers.stream()
.map(Object::toString)
// or
// .map(x -> x.toString()) // exactly the same
// or
// .map(String::valueOf) // handles nulls by turning them to the string "null"
.collect(Collectors.joining(","));
It has several overloads, similar to Scala's mkString. Still, this collector only accepts CharSequences, so you need to convert your values to strings explicitly as a separate map step.
Additionally, there is the String.join method, which also works for a collection of CharSequences. If you specifically have one of those (e.g. List<String>), it might be more convenient to use this method rather than converting the collection to a stream first:
List<String> strings = ...;
String result = String.join(",", strings);
// vs
String result = strings.stream().collect(Collectors.joining(","))

If I remember my java correctly, you can declare the argument type as Collection<?> to be able to pass a collection of any objects.
As to biting the separator off, I think, just .substring(1) will do what you want.

You can do it like :
public static <T> String mkString(Collection<T> c, String sep) { // generic impl
return c.stream()
.map(String::valueOf)
.reduce("", (a, b) -> a + sep + b)
.substring(1); // substring implementation to strip leading character
}

Any type of collection in java means Collection<?>, which semantically is the same as Collection<T> (in your case), it is said that if the type parameter is used only once) it can safely be replaced with a wildcard. But, since you want to be able to concat any collection, you should also ask for the callers to supply a Function that would transform from that type to a String representation, thus your method would become:
public static <T> String mkString(Collection<T> c,
Function<T, ? extends CharSequence> mapper,
String sep) {
return c.stream()
.map(mapper)
.collect(Collectors.joining(sep));
}

You can utilize String.join with a generic type:
public static <T> String mkString(Collection<T> c, String sep) {
return String.join(sep, c.stream()
.map(e -> String.valueOf(e))
.collect(Collectors.toList()));
}
Here it is in action with both Strings and other objects.

Related

map pipeline operator associated with function interface

I can't understand why the String::toUpperCase() expression works fine inside the Stream map pipeline. When I look at this example here:
Stream.of("test1", "test2", "test3", "test4")
.filter(s -> s.contains("r"))
.map(s -> s + "map")
.map(String::toUpperCase)
.forEach(System.out::println);
When I look to the definition of the map operator used in the example below map(Function<? super String, ? extends String> mapper) I saw a function design pattern is been used.
In this example .map(s -> s + "map") is fine, as I understand we are looking for Function more precisely the R apply(T t);, it is totally what the lambda expression said s -> s + "map" here we have a function with a parameter s and it returns s + String "map" and it conforms to this spec. T and R, they are present.
On the other side the second one map(String::toUpperCase), I can't understand why the expression toUpperCase is considered as a Function interface, I should note the core of this function is like this
public String toUpperCase() {
return toUpperCase(Locale.getDefault());
}
and we are looking for R apply(T t); there is no T parameter in this method toUpperCase? Why does this one work?
What's much easier to understand in terms of the apply method of the Function interface is the anonymous class representation of the method reference String::toUpperCase. It goes like this -
new Function<String, String>() {
#Override
public String apply(String str) { // read as given a String return a String (uppercased)
return str.toUpperCase();
}
}
The string arguments(str) provided to the above apply method are the ones from the Stream after the previous map operation.
It's called a method reference and it's syntactic sugar for a lambda expression. In other words the following:
String::toUpperCase
is equivalent to:
s -> s.toUpperCase()
It's a method that takes a String s and returns a String with all letter from s uppercase, it's a Function<String, String>.

How to stream value of Java List (Varargs) in a method?

I have the following method:
public static List<A> getValuesExclusion(A exclusion) {
return Arrays.stream(values())
.filter(item -> item != exclusion)
.collect(Collectors.toList());
}
//this function returns enum list of A types that has no A type'exclusion'
Now I want to make it into a list as argument:
public static List<A> getValuesExclusion(A... exclusions){
return Arrays.stream(values())
.filter(???)
.collect(Collectors.toList());
}
My question is, how can I do the filter for the second case? I would like to retrieve an enum list that excludes all the values "exclusions" as input. Here are the attributes of class A:
public enum A implements multilingualA{
A("a"),
B("b"),
C("c"),
D("d");
...
}
If you want to make sure all the items are not included in the exclusions you could do:
public static List<A> getValuesExclusion(AType... exclusions){
return Arrays.stream(values())
.filter(e -> Arrays.stream(exclusions).noneMatch(c -> c == e))
.collect(Collectors.toList());
}
Which will create a Stream of exclusions and then use noneMatch() to ensure the given AType is not included in the Array
You should rethink whether List really is the appropriate data type for something containing unique elements. A Set usually is more appropriate.
Then, if you care for performance, you may implement it as
public static Set<A> getValuesExclusion(A... exclusions){
return exclusions.length == 0? EnumSet.allOf(A.class):
EnumSet.complementOf(EnumSet.of(exclusions[0], exclusions));
}
The class EnumSet is specifically designed for holding elements of an enum type, just storing a bit for each constant, to tell whether it is present or absent. This allows operations like complementOf, which just flips all bits using a single ⟨binary not⟩ operation, without the need to actually traverse the enum constants.
If you insist on returning a List, you can do it as easy as
public static List<A> getValuesExclusion(A... exclusions){
return new ArrayList<>(exclusions.length == 0? EnumSet.allOf(A.class):
EnumSet.complementOf(EnumSet.of(exclusions[0], exclusions)));
}
I would not go with Streams here but with the a (imho) more readable approach:
public static List<A> getValuesExclusion(AType... exclusions){
List<A> values = Arrays.asList(values());
values.removeAll(Arrays.asList(ex));
return values;
}

How to group into map of arrays?

Can a groupingBy operation on a stream produce a map where the values are arrays rather than lists or some other collection type?
For example: I have a class Thing. Things have owners, so Thing has a getOwnerId method. In a stream of things I want to group the things by owner ID so that things with the same owner ID end up in an array together. In other words I want a map like the following where the keys are owner IDs and the values are arrays of things belonging to that owner.
Map<String, Thing[]> mapOfArrays;
In my case, since I need to pass the map values to a library method that requires an array, it would be most convenient to collect into a Map<String, Thing[]>.
Collecting the whole stream into one array is easy (it doesn’t even require an explicit collector):
Thing[] arrayOfThings = Stream.of(new Thing("owner1"), new Thing("owner2"), new Thing("owner1"))
.toArray(Thing[]::new);
[Belongs to owner1, Belongs to owner2, Belongs to owner1]
Groping by owner ID is easy too. For example, to group into lists:
Map<String, List<Thing>> mapOfLists = Stream.of(new Thing("owner1"), new Thing("owner2"), new Thing("owner1"))
.collect(Collectors.groupingBy(Thing::getOwnerId));
{owner1=[Belongs to owner1, Belongs to owner1], owner2=[Belongs to owner2]}
Only this example gives me a map of lists. There are 2-arg and 3-arg groupingBy methods that can give me a map of other collection types (like sets). I figured, if I can pass a collector that collects into an array (similar to the collection into an array in the first snippet above) to the two-arg Collectors.groupingBy​(Function<? super T,? extends K>, Collector<? super T,A,D>), I’d be set. However, none of the predefined collectors in the Collectors class seem to do anything with arrays. Am I missing a not too complicated way through?
For the sake of a complete example, here’s the class I’ve used in the above snippets:
public class Thing {
private String ownerId;
public Thing(String ownerId) {
this.ownerId = ownerId;
}
public String getOwnerId() {
return ownerId;
}
#Override
public String toString() {
return "Belongs to " + ownerId;
}
}
Using the collector from this answer by Thomas Pliakas:
Map<String, Thing[]> mapOfArrays = Stream.of(new Thing("owner1"), new Thing("owner2"), new Thing("owner1"))
.collect(Collectors.groupingBy(Thing::getOwnerId,
Collectors.collectingAndThen(Collectors.toList(),
tl -> tl.toArray(new Thing[0]))));
The idea is to collect into a list at first (which is an obvious idea since arrays have constant size) and then converting to an array before returning to the grouping by collector. collectingAndThen can do that through its so-called finisher.
To print the result for inspection:
mapOfArrays.forEach((k, v) -> System.out.println(k + '=' + Arrays.toString(v)));
owner1=[Belongs to owner1, Belongs to owner1]
owner2=[Belongs to owner2]
Edit: With thanks to Aomine for the link: Using new Thing[0] as argument to toArray was inspired by Arrays of Wisdom of the Ancients. It seems that on Intel CPUs in the end using new Thing[0] is faster than using new Thing[tl.size()]. I was surprised.
you could group first then use a subsequent toMap:
Map<String, Thing[]> result = source.stream()
.collect(groupingBy(Thing::getOwnerId))
.entrySet()
.stream()
.collect(toMap(Map.Entry::getKey,
e -> e.getValue().toArray(new Thing[0])));
Probably obvious but you could have done it via:
Stream.of(new Thing("owner1"), new Thing("owner2"), new Thing("owner1"))
.collect(Collectors.toMap(
Thing::getOwnerId,
x -> new Thing[]{x},
(left, right) -> {
Thing[] newA = new Thing[left.length + right.length];
System.arraycopy(left, 0, newA, 0, left.length);
System.arraycopy(right, 0, newA, left.length, right.length);
return newA;
}
))

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>) ?
Is there an easy way to do this to make it look a little more polished?!
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.
1) As alternative as suggested by Holger you could specify the T type from the target side :
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 :
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 :
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));
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());
Firstly, the T extends Object is redundant since every object T extends from Object since the definition.
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.
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:
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:
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).
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.
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:
Stream.of(col1, col2)
.map(table::getColData)
.map(o -> (String) o)
.flatMap(List::stream)
.collect(Collectors.toList());
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());

What do we need the BiFunction interface for?

The definition of the BiFunction interface contains a method apply(T t, U u), which accepts two arguments. However, I don't understand the use or purpose of this interface and method. What do we need this interface for?
The problem with this question is that it's not clear whether you see the purpose of a Function, which has a method apply(T t).
The value of all the functional types is that you can pass code around like data. One common use of this is the callback, and until Java 8, we used to have to do this with anonymous class declarations:
ui.onClick(new ClickHandler() {
public void handleAction(Action action) {
// do something in response to a click, using `action`.
}
}
Now with lambdas we can do that much more tersely:
ui.onClick( action -> { /* do something with action */ });
We can also assign them to variables:
Consumer clickHandler = action -> { /* do something with action */ };
ui.onClick(clickHandler);
... and do the usual things we do with objects, like put them in collections:
Map<String,Consumer> handlers = new HashMap<>();
handlers.put("click", handleAction);
A BiFunction is just this with two input parameters. Let's use what we've seen so far to do something useful with BiFunctions:
Map<String,BiFunction<Integer,Integer,Integer>> operators = new HashMap<>();
operators.put("+", (a,b) -> a + b);
operators.put("-", (a,b) -> a - b);
operators.put("*", (a,b) -> a * b);
...
// get a, b, op from ui
ui.output(operators.get(operator).apply(a,b));
One of usages of BiFunction is in the Map.merge method.
Here is an example usage of the Map.merge method, which uses a BiFunction as a parameter. What merge does is basically replaces the value of the given key with the given value if the value is null or the key does not have a value. Otherwise, replace the value of the given key after applying the BiFunction.
HashMap<String, String> map = new HashMap<>();
map.put("1", null);
map.put("2", "Hello");
map.merge("1", "Hi", String::concat);
map.merge("2", "Hi", String::concat);
System.out.println(map.get("1")); // Hi
System.out.println(map.get("2")); // HelloHi
If a BiFunction were not used, you would have to write a lot more code, even spanning several lines.
Here is a link that shows all the usages of BiFunction in the JDK: https://docs.oracle.com/javase/8/docs/api/java/util/function/class-use/BiFunction.html
Go check it out!
An extra example of BiFunction is reduce():
public static void main(String[] args) {
List<Integer> list = new ArrayList<>(Arrays.asList(5,5,10));
Integer reduce = list.stream().reduce(0, (v1,v2) -> v1+v2);
System.out.println(reduce); // result is: 20
}

Categories