The joining(", ") collector will collect and join all Strings into a single string using the given separator. The returning type of collect in this case is String, but you are trying to assign the result to a List. If you want to collect Strings into a List, use Collectors.toList().
If you have a collection with Book instances, then it will be enough to map a stream of Books to a stream of Strings once.
Difference between lamdba & method refrence
A lamdba expression may be written as a block, containing multiple operations:
b -> { // here you can have other operations return b.getName(); }if lambda has single operation, it can be shortened:
b -> b.getName()Method reference is just a "shortcut" for a lambda with a single operation. This way:
b -> b.getName()can be replaced with:
Book::getNamebut if you have a lambda like this:
b -> b.getName().toLowerCase()you cant use a reference to the
getNamemethod, because you are making and additional call totoLowerCase().
The joining(", ") collector will collect and join all Strings into a single string using the given separator. The returning type of collect in this case is String, but you are trying to assign the result to a List. If you want to collect Strings into a List, use Collectors.toList().
If you have a collection with Book instances, then it will be enough to map a stream of Books to a stream of Strings once.
Difference between lamdba & method refrence
A lamdba expression may be written as a block, containing multiple operations:
b -> { // here you can have other operations return b.getName(); }if lambda has single operation, it can be shortened:
b -> b.getName()Method reference is just a "shortcut" for a lambda with a single operation. This way:
b -> b.getName()can be replaced with:
Book::getNamebut if you have a lambda like this:
b -> b.getName().toLowerCase()you cant use a reference to the
getNamemethod, because you are making and additional call totoLowerCase().
If you are using Collectors.joining(), the result will be a single concatenated String:
String names = books.stream()
.map( b -> b.getName() )
.filter(n -> (n != null) && !n.isEmpty())
.collect(Collectors.joining(", "));
The Collectors.toList() is the one that returns a List:
List<String> namesList = books.stream()
.map( b -> b.getName() )
.filter(n -> (n != null) && !n.isEmpty())
.collect(Collectors.toList());
Book::getName is a method reference and will have the same result as b -> b.getName(). Method reference is clearer and enables to pass other existing methods as a parameter to methods such as map(), as long as the passed method conforms to the signature of the expected functional interface. In this case, map() expects an instance of the Function interface. Thus, you can give any reference to a method that conforms to the signature of the abstract R apply(T t) method from such an interface.
Since you are mapping a Book to a String, the actual signature for the method to be given to the map() must be String apply(Book t). This can be read as "receive a Book and return a String". This way, any method you pass that conforms to this definition is valid. When you pass a method reference Book::getName, the getName method itself doesn't conform to the signature presented above (because it has no parameter at all), but it conforms to the definition of such a signature: you pass a book and return a String from its name.
Thus, consider that, inside the class where you have your book list, you also have a method which performs any operation over a Book, returning a String. The method below is an example that receives a Book and gets the first 10 characters from its name:
public String getReducedBookName(Book b){
if(b.getName() == null)
return "";
String name = b.getName();
return name.substring(0, name.length() > 10 ? 10 : name.length());
}
You can also pass this method (which is not inside the Book class) as parameter to the map() method:
String names = books.stream()
.map(this::getReducedBookName)
.filter(n -> !n.isEmpty())
.collect(Collectors.joining(", "));
String.join
With Java 8 you can do this without any third party library.
If you want to join a Collection of Strings you can use the String.join() method:
List<String> list = Arrays.asList("foo", "bar", "baz");
String joined = String.join(" and ", list); // "foo and bar and baz"
Collectors.joining
If you have a Collection with another type than String you can use the Stream API with the joining Collector:
List<Person> list = Arrays.asList(
new Person("John", "Smith"),
new Person("Anna", "Martinez"),
new Person("Paul", "Watson ")
);
String joinedFirstNames = list.stream()
.map(Person::getFirstName)
.collect(Collectors.joining(", ")); // "John, Anna, Paul"
The StringJoiner class may also be useful.
All the references to Apache Commons are fine (and that is what most people use) but I think the Guava equivalent, Joiner, has a much nicer API.
You can do the simple join case with
Joiner.on(" and ").join(names)
but also easily deal with nulls:
Joiner.on(" and ").skipNulls().join(names);
or
Joiner.on(" and ").useForNull("[unknown]").join(names);
and (useful enough as far as I'm concerned to use it in preference to commons-lang), the ability to deal with Maps:
Map<String, Integer> ages = .....;
String foo = Joiner.on(", ").withKeyValueSeparator(" is ").join(ages);
// Outputs:
// Bill is 25, Joe is 30, Betty is 35
which is extremely useful for debugging etc.