Wednesday, May 20, 2015

Java 8 streams and the drastic impact on loops

Java 8 is a game changer.  There is no question about that.  Java 7 barely made a splash but Java 8 is like Java 5 which introduced generics, annotations, and so on. I may be a little late to the party on Java 8 but in corporate america things become relevant a little slower.  One of the ways Java 8 has changed things is with streams and how it has changed the way we can do loops.

Here is a little code to show you how things can be different with streams.  I will go into more detail but this is just to start the discussion with code.

 
     public void doStuff() throws Exception {
  List<BlogPost > posts = new ArrayList<>();
  BlogPost post1 = new BlogPost();
  post1.setTile("num1");
  post1.setTags(Arrays.asList("A", "B", "C"));  
  posts.add(post1);
  BlogPost post2 = new BlogPost();
  post2.setTile("num1");
  post2.setTags(Arrays.asList("C", "D", "E"));  
  posts.add(post2);
  
  
  BlogPost result1 = getRelatedPostLoop(posts, "D");
  Optional<BlogPost > result2 = 
                        getRelatedPostStream(posts, "D");
  
  if (result1 != result2.get()) {
   throw new Exception("error");
  }
  
  BlogPost result3 = getRelatedPostLoop(posts, "F");
  Optional<Blogpost> result4 = 
                        getRelatedPostStream(posts, "F");
  
  if (result3 != null || result4.isPresent()) {
   throw new Exception("error");
  }
 }
 
 //Java 7 way
 private BlogPost getRelatedPostLoop(List<BlogPost> posts, 
                String tag) {
     for (BlogPost post : posts) {
         if (post.getTags().contains(tag)) {
             return post;
         }
     }
     return null;
 }
 
 //Java 8 way
 private Optional<BlogPost> getRelatedPostStream(
                List<BlogPost > posts, String tag) {

  return posts.stream()
          .filter(post -> post.getTags().contains(tag))
          .findFirst();
 }
 
 private class BlogPost {
  
  private String tile;
  private StringBuffer body;
  private List<String> tags;
  public String getTile() {
   return tile;
  }
  public void setTile(String tile) {
   this.tile = tile;
  }
  public StringBuffer getBody() {
   return body;
  }
  public void setBody(StringBuffer body) {
   this.body = body;
  }
  public List getTags() {
   return tags;
  }
  public void setTags(List<String> tags) {
   this.tags = tags;
  }
 }




Pretty cool right?  Single pass loops can be made a lot simpler with the use of streams.  The list is being used as a stream which does the same as a for loop.  The filter operation does exactly what you would expect; it filters each element in the list by a condition passed in through the argument.  Lastly findFirst pretty clearly from its name again returns the first element from the stream or null if the stream is empty.

Other hidden gems in that code thanks to Java 8:
  • List<BlogPost > posts = new ArrayList<>();

    This is a simple thing but reduces frustration.  How many times have you been frustrated because you had to cut and paste the same generic on both sides of an assignment?  You are writing the assignment inline why does the compiler really need to you specify it twice! Well now you don't have to.
  • Optional<BlogPost>

    Optional is a new class which helps make it clear that a null object response can be returned and adds in some helper methods to work with that possibly null value cleanly. 

More examples of how streams can be done to make the code a little cleaner:


  • Here is a simple way to return the filtered matches as a collection.  There is no need to create the in scope list variable to be used in the response.

 private List<BlogPost> getAllRelatedPosts(
                List<BlogPost > posts, String tag) {
  return posts.stream()
          .filter(post -> post.getTags().contains(tag))
          .collect(Collectors.toList());
 }


  • Also here is a simple way to return the filtered matches but only the distinct matches. Small change but showing the power here.

 private Set<BlogPost> getAllRelatedPosts(
                List<BlogPost> posts, String tag) {
  return posts.stream()
          .filter(post -> post.getTags().contains(tag))
          .collect(Collectors.toSet());
 }



  • Even performing operations to say get a sum total based upon a loop variable method is easy
    
     private int getTagCount(List<BlogPost> posts) {
      return posts.stream()
        .mapToInt(
                                         post -> 
                                            post.getTags().size())
        .sum();
     }
    

  • The magic continues on and on but those are things you really should explore on your own




Potentially helpful links:




No comments:

Post a Comment