Thursday, September 3, 2015

Introduction to Java 8 Streams

Java 8 has been released a while ago, but lots of developpers still do not make use of its full potential. One of key features of Java 8 that can change how developpers look at collections is: Streams.

Streams introduce a new programming style and let you process collections in a declarative way rather than using loops or iterators.

Let's suppose we have a list of strings and we want to : retrieve strings that are less than 4 charachters, make them upper case, sort the result, and finally print it.

List myList = Arrays.asList("yet","another", "proof", "that", "java8", "streams","rocks"); 

The conventional way of doing it is to loop through the list and retrieve one element at a time, test if they fill the condition, turn them upper case, store them in a new list, sort the list and finally print it. 
 List result = new ArrayList();
  for(int i = 0; i < myList.size(); i++){
   if(myList.get(i).length() <= 4){
    result.add(myList.get(i).toUpperCase()); 
   }
  }
  Collections.sort(result);
  

  for(int i = 0; i < result.size(); i++)
       System.out.println(result.get(i));


Using streams, the process is much shorter and can be written as:

myList.stream()
  .filter( s -> s.length() <= 4)
  .map(s -> {return s.toUpperCase();})
  .sorted()
  .forEach(System.out::println);
Result :
THAT
YET
Another useful feature of Streams API is when the developpers needs to apply a formula such as sum or average or any other mathematical formula.

Suppose we have the following object:

public class Tag {
	
	public enum TYPE{
		NEWS, SPORTS
		
	}
	
	private String name;
	
	private int count;
	
	private TYPE type;
	
	public Tag(String name, int count, TYPE type) {
		this.name = name;
		this.count = count;
		this.type = type;
	}
	
	/**
	 * @return the name
	 */
	public String getName() {
		return name;
	}
	
	/**
	 * @param name the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * @return the count
	 */
	public int getCount() {
		return count;
	}

	/**
	 * @param count the count to set
	 */
	public void setCount(int count) {
		this.count = count;
	}

	/**
	 * @return the type
	 */
	public TYPE getType() {
		return type;
	}

	/**
	 * @param type the type to set
	 */
	public void setType(TYPE type) {
		this.type = type;
	}
	
	@Override
	public String toString(){
		return "{name:"+name+", count:"+count+"}";
	}
	
}
Let's suppose we have a list of Tags and we want to calculate the sum or average of the "count" property.
	List tagsList = Arrays.asList(
				new Tag("tag1", 10, Tag.TYPE.NEWS),
				new Tag("tag2", 20, Tag.TYPE.SPORTS), 
				new Tag("tag3", 40, Tag.TYPE.SPORTS));
Once again, the conventional way to do it is loop through all elements apply the desired formula. Using Stream, it can be done in the following way:
		int countSum = tagsList.stream()
		.mapToInt(Tag::getCount)
		.sum();
Average:
		tagsList.stream()
		.mapToInt(Tag::getCount)
		.average()
		.ifPresent(System.out::println);
Another very useful feature of Streams is grouping elements by a common property:
		
		Map<TYPE, List> map = tagsList.stream()
		.collect(Collectors.groupingBy(Tag::getType));

System.out.println(map);
Result:
{NEWS=[{name:tag1, count:10}], SPORTS=[{name:tag2, count:20}, {name:tag3, count:40}]}


The list is still long, and the possibilities are endless. But since this is just an introduction, we will stop here. We will go through more advanced stuff later on.

Full example at: https://github.com/zak905/java8features/tree/master/src/opencode/java8features/streams


2 comments:

  1. Don't know if this problem is isolated to my phone, but I can't see any of your posted code.

    ReplyDelete
    Replies
    1. I used a JS library for pretty-printing, maybe this is the reason. I will check! Thanks for reporting it

      Delete