S
StringJoiner is used to construct a sequence of characters separated by a delimiter and optionally starting with a supplied prefix and ending with a supplied suffix.
Prior to adding something to the StringJoiner
, its sj.toString()
method will, by default, return prefix + suffix
. However, if the setEmptyValue
method is called, the emptyValue
supplied will be returned instead. This can be used, for example, when creating a string using set notation to indicate an empty set, i.e. "{}"
, where the prefix
is "{"
, the suffix
is "}"
and nothing has been added to the StringJoiner
.
import java.util.StringJoiner; public class StringJoinerDemo { public static void main(String[] args) { String delimeter = "-"; StringJoiner names = new StringJoiner(delimeter); names.add("Laulau"); names.add("Kinga"); names.add("Fusi"); System.out.println(names); } }
Result:
Laulau-Kinga-Fusi
Using prefix and suffix
public static void demoWithPrefixAndSuffix() { String delimeter = ","; String prefix = "("; String suffix = ")"; StringJoiner names = new StringJoiner(delimeter, prefix, suffix); names.add("Laulau"); names.add("Kinga"); names.add("Fusi"); System.out.println(names); }
Result:
(Laulau,Kinga,Fusi)
Using merge
public static void demoWithMerge() { String delimeter = ","; String prefix = "("; String suffix = ")"; StringJoiner names1 = new StringJoiner(delimeter, prefix, suffix); names1.add("Laulau"); names1.add("Kinga"); names1.add("Fusi"); System.out.println(names1); StringJoiner names2 = new StringJoiner(delimeter, prefix, suffix); names2.add("John"); names2.add("Peter"); names2.add("Andrew"); System.out.println(names2); StringJoiner names3 = names1.merge(names2); System.out.println(names3); }
Result:
(Laulau,Kinga,Fusi) (John,Peter,Andrew) (Laulau,Kinga,Fusi,John,Peter,Andrew)
The Collectors class is used with Stream.collect() to package elements for a terminal operation. Something like this:
List<String> alphabet = Arrays.asList("a", "b", "c", "d"); List<String> otherAlphabet = alphabet.stream() .collect(toList());
The Collectors class has three important and useful methods: Collectors.toList, Collectors.toMap, and Collectors.toSet.
You have seen toList() above. Here is toMap().
Map<String, Integer> alphabetMap = alphabet.stream() .collect(toMap(letter -> letter, String::length))
Here is Collectors.toSet().
Set<String> result = alphabet.stream() .collect(toSet());
Here is Collectors.toCollection().
List<String> alphabetList = alphabet.stream() .collect(toCollection(LinkedList::new))
Here is Collectors.joining().
String joins = alphabet.stream() .collect(joining(" "));
"a b c d"
Here is Collectors.counting().
Long count = alphabet.stream() .collect(counting());
Result
4
Collectors.summarizingDouble() / Collectors.summarizingInt() / Collectors.summarizingLong(). This returns a SummaryStatistics class that contains statistical information about the elements.
public static void demoSummarizing() { DoubleSummaryStatistics alphabetStats = alphabet.stream() .collect(Collectors.summarizingDouble(letter -> letter.length())); System.out.println("average: "+alphabetStats.getAverage()); System.out.println("count: "+alphabetStats.getCount()); System.out.println("max: "+alphabetStats.getMax()); System.out.println("sum: "+alphabetStats.getSum()); System.out.println("min: "+alphabetStats.getMin()); }
Result
average: 1.0 count: 5 max: 1.0 sum: 5.0 min: 1.0
Collectors.groupingBy() is used for grouping objects by some property and storing results in a Map instance.
User[name=Aaron Holloway,age=16,height=6.626572070342046,gender=FEMALE] User[name=Nora Reyes,age=8,height=5.909744090999723,gender=FEMALE] User[name=Nia Allen,age=29,height=6.415385593055398,gender=MALE] User[name=Quinton Hoover,age=25,height=6.549790026231361,gender=MALE] User[name=Alannah Salinas,age=20,height=6.4459305179442605,gender=FEMALE] User[name=Jaylin Fleming,age=13,height=6.265460947673736,gender=FEMALE]
public static void demoGroupBy() { System.out.println("\ndemo group by\n"); Map<String, Set<User>> userMap = users.stream() .collect(Collectors.groupingBy(user -> user.getGender(), Collectors.toSet())); Set<User> males = userMap.get("MALE"); System.out.println("male size: "+males.size()+" . "+males); Set<User> females = userMap.get("FEMALE"); System.out.println("female size: "+females.size()+" . "+females); }
Result
demo group by male size: 2 . [User[name=Nia Allen,age=29,height=6.415385593055398,gender=MALE], User[name=Quinton Hoover,age=25,height=6.549790026231361,gender=MALE]] female size: 4 . [User[name=Aaron Holloway,age=16,height=6.626572070342046,gender=FEMALE], User[name=Jaylin Fleming,age=13,height=6.265460947673736,gender=FEMALE], User[name=Alannah Salinas,age=20,height=6.4459305179442605,gender=FEMALE], User[name=Nora Reyes,age=8,height=5.909744090999723,gender=FEMALE]]
Collectors.partioningBy() accepts a Predicate instance and collects Stream elements into a Map instance that stores Boolean values as keys and collections as values.
User[name=Johanna Dickson,age=22,height=6.4127723755001504,gender=FEMALE] User[name=Julian Jenkins,age=23,height=6.775657455958665,gender=FEMALE] User[name=Antwan Wheeler,age=26,height=6.637100653384031,gender=FEMALE] User[name=Samantha Parsons,age=14,height=5.622675954086426,gender=MALE] User[name=Nia Camacho,age=5,height=6.9896558044147294,gender=MALE] User[name=Giselle Morris,age=6,height=5.580378836408688,gender=MALE]
Result
demo partioning by adult size: 3 . [User[name=Johanna Dickson,age=22,height=6.4127723755001504,gender=FEMALE], User[name=Julian Jenkins,age=23,height=6.775657455958665,gender=FEMALE], User[name=Antwan Wheeler,age=26,height=6.637100653384031,gender=FEMALE]] youngens size: 3 . [User[name=Samantha Parsons,age=14,height=5.622675954086426,gender=MALE], User[name=Nia Camacho,age=5,height=6.9896558044147294,gender=MALE], User[name=Giselle Morris,age=6,height=5.580378836408688,gender=MALE]]
Foreach is a way of traversing a list like for loop and while loop. Variables from outside of Foreach must be final or effective final.
Foreach traversing through a List
public static void foreachWithList() { List<String> items = new ArrayList<>(); items.add("A"); items.add("B"); items.add("C"); items.add("D"); items.add("E"); // lambda // Output : A,B,C,D,E items.forEach(item -> System.out.println(item)); // Output : C items.forEach(item -> { if ("C".equals(item)) { System.out.println(item); } }); // method reference // Output : A,B,C,D,E items.forEach(System.out::println); // Stream and filter // Output : B items.stream().filter(s -> s.contains("B")).forEach(System.out::println); }
Foreach traversing through a Map
public static void foreachWithMap() { Map<String, Integer> items = new HashMap<>(); items.put("A", 10); items.put("B", 20); items.put("C", 30); items.put("D", 40); items.put("E", 50); items.put("F", 60); items.forEach((k,v)->System.out.println("Item : " + k + " Count : " + v)); items.forEach((k,v)->{ System.out.println("Item : " + k + " Count : " + v); if("E".equals(k)){ System.out.println("Hello E"); } }); }
The key aspect of Stream API is it’s the ability to perform very sophisticated operations such as search, filter, map, or otherwise manipulate data. Stream API is used to process collections of objects. A stream is a sequence of objects that supports various methods which can be pipelined to produce the desired result.
As a general rule, a stream operation by itself does not modify the data source. For example, sorting a stream does not change the order of the source. Rather, sorting stream results in creating a new stream that produces the sorted result.
Features:
Intermediate Operations On Streams (Intermediate operation is lazily executed and returns a stream as a result, hence various intermediate operations can be pipelined.)
List number = Arrays.asList(2,3,4,5); List square = number.stream().map(x->x*x).collect(Collectors.toList()); String number = Stream.of(9, 4, 6, 1, 3, 5).map(num -> num + "").collect(Collectors.joining(",")); // 9,4,6,1,3,5 double average = Stream.of("1","5","10","4").mapToInt(Integer::parseInt).average().getAsDouble(); // 5
List names = Arrays.asList("Reflection","Collection","Stream"); List result = names.stream().filter(s->s.startsWith("S")).collect(Collectors.toList());
List names = Arrays.asList("Reflection","Collection","Stream"); List result = names.stream().sorted().collect(Collectors.toList()); List<Integer> numbers = Stream.of(9,4,6,1,3,5).sorted((a, b) -> { return a.compareTo(b); }).collect(Collectors.toList()); System.out.println("numbers: " + numbers.toString());
numbers: [1, 3, 4, 5, 6, 9]
Terminal Operations On Streams(Terminal operations mark the end of the stream and return the result)
List number = Arrays.asList(2,3,4,5,3); Set square = number.stream().map(x->x*x).collect(Collectors.toSet()); Set<Integer> set = Stream.of(1, 2, 3).collect(Collectors.toCollection(HashSet::new)); //[1, 2, 3] Map<Integer,String> map = Stream.of(1, 2, 3).collect(Collectors.toConcurrentMap(num -> num, num -> num+"")); //{1=1, 2=2, 3=3}
List number = Arrays.asList(2,3,4,5); number.stream().map(x->x*x).forEach(y->System.out.println(y));
List number = Arrays.asList(2,3,4,5); int even = number.stream().filter(x->x%2==0).reduce(0,(ans,i)-> ans+i);
int initialValue = 5; int reducedNumber = Stream.of(1, 2, 3).reduce(initialValue, (num, index)-> { System.out.println("num: "+num+", index: "+index); return index; }); System.out.println("reducedNumber: "+reducedNumber);
num: 3, index: 1 num: 1, index: 2 num: 2, index: 3 reducedNumber: 3
String listToString = Stream.of("Folau", "Kinga", "Laulau").collect(Collectors.joining(", ", "(", ")")); System.out.println("listToString: " + listToString);
listToString: (Folau, Kinga, Laulau)
Examples
foreach – terminal operation
Performs an action for each element of this stream.
List<String> words = Arrays.asList("I", "can", "program", "in", "Java"); words.stream().forEach(System.out::println);
allMatch() – terminal operation
Returns whether all elements of this stream match the provided predicate. May not evaluate the predicate on all elements if not necessary for determining the result. If the stream is empty then true
is returned and the predicate is not evaluated.
// Creating a list of Integers List<Integer> list = Arrays.asList(3, 4, 6, 12, 20); // Check if all elements of stream // are divisible by 3 or not using // Stream allMatch(Predicate predicate) boolean answer = list.stream().allMatch(n-> n % 3 ==0);
map() – intermediate operation
Returns a stream consisting of the results of applying the given function to the elements of this stream.
// Creating a list of Integers List<Integer> list = Arrays.asList(3, 6, 9, 12, 15); // Using Stream map(Function mapper) and // displaying the corresponding new stream list.stream().map(number -> number * 3).forEach(System.out::println)
collect() – terminal operation
Stream.collect() method used to receive elements from a stream and store them in a collection.
users.add(new User("John", "john@gmail.com",100000)); users.add(new User("Peter", "peter@gmail.com", 50000)); // find employees whose salaries are above 100000 List<User> filteredList = users.stream().filter(user->user.getSalary() > 100000).collect(Collectors.toList()); // Collections to Map Map<Integer, String> result1 = users.stream().collect( Collectors.toMap(User::getId, User::getName)); Map<Integer, String> result2 = users.stream().collect( Collectors.toMap(u -> u.getId(), u -> u.getName())); Map result3 = list.stream().collect( Collectors.toMap( u -> u.getName(), u -> u.getId(), (oldValue, newValue) -> oldValue, LinkedHashMap::new ));
int sum = Stream.of(1, 2, 3).collect(Collectors.summingInt(Integer::intValue)); System.out.println("sum: " + sum);
sum: 6
long count() – terminal operation
Returns the count of elements in this stream.
long count = Arrays.asList("I","can","program","in","java").stream().count(); System.out.printf("There are %d elements in the stream %n", count);
iterate() – intermediate operation
Create values on demand
//Stream.iterate(initial value, next value) Stream.iterate(0, n -> n + 1) .limit(10) .forEach(x -> System.out.println(x)); int numOfIterations = 5; int initialValue = 3; Stream.iterate(initialValue, n -> { return n * 2; }).limit(numOfIterations).forEach(x -> System.out.println(x));
Stream Creation
Stream.of("Folau", "Kinga", "Laulau").forEach((name) -> { System.out.println("name1: " + name); }); Stream.builder().add("Folau").add("Kinga").add("Laulau").build().forEach((name) -> { System.out.println("name2: " + name); });
findFirst()
findFirst() returns the first element in the stream.
Stream.of("Folau", "Kinga", "Laulau").filter(name -> name.contains("lau")).forEach((name) -> { System.out.println("name: " + name); }); //name: Folau //name: Laulau
parallel – run executions in multiple threads.
int numOfIterations = 20; int initialValue = 3; int sum = Stream.iterate(initialValue, n -> { return n * 2; }).limit(numOfIterations).parallel().collect(Collectors.summingInt(Integer::intValue)); System.out.println("sum: " + sum);
Resources
Why the default method?
When you implement an interface, that interface may change. It may add more methods by which you will be forced to implement. Default methods solved this issue in where you don’t need to implement default methods. There are just there for you to use. There is no need for you to modify anything in your class.
The most typical use of default methods in interfaces is to incrementally provide additional functionality to a given type without breaking down the implementing classes. In addition, they can be used to provide additional functionality around an existing abstract method:
public interface Screen { public boolean turnOn(); public boolean turnOff(); // default method default void defaultMethod() { System.out.println("Doing Screen default things..."); } }
public class DefaultStaticMethodDemo implements Screen{ public static void main(String[] args) { DefaultStaticMethodDemo demo = new DefaultStaticMethodDemo(); demo.defaultMethod(); } @Override public boolean turnOn() { // TODO Auto-generated method stub return true; } @Override public boolean turnOff() { // TODO Auto-generated method stub return false; } }
Implementing multiple interfaces with the same default method names.
public interface Vehicle { // default method default void defaultMethod() { System.out.println("Doing Vehicle default things..."); } }
It won’t compile. You have to provide your own implementation.
public class DefaultStaticMethodDemo implements Screen, Vehicle{ public static void main(String[] args) { DefaultStaticMethodDemo demo = new DefaultStaticMethodDemo(); demo.defaultMethod(); } @Override public boolean turnOn() { // TODO Auto-generated method stub return true; } @Override public boolean turnOff() { // TODO Auto-generated method stub return false; } }
public class DefaultStaticMethodDemo implements Screen, Vehicle{ public static void main(String[] args) { DefaultStaticMethodDemo demo = new DefaultStaticMethodDemo(); demo.defaultMethod(); } @Override public boolean turnOn() { // TODO Auto-generated method stub return true; } @Override public boolean turnOff() { // TODO Auto-generated method stub return false; } @Override public void defaultMethod() { // TODO Auto-generated method stub Vehicle.super.defaultMethod(); } }
Having static methods in your interface helps group all related methods into one place. You have all your interface functions as well as utility functions.
public interface Vehicle { public boolean turnOn(); // default method default void defaultMethod() { System.out.println("Doing Vehicle default things..."); } static double getApproximateDistance(double milePerGallon, double gallons) { return milePerGallon*gallons; } }