Java 8 adds support for bulk data operations (JEP 107) in the Collections library. It is unlikely every forms of bulk data operations will be added to the retrofitted Collections framework, but the language designers are making every possible effort in accommodating general use cases of “filter/map/reduce for Java”. Libraries should make use of parallelization on multi-core platforms in order to perform massive parallelizable operations and this means literally kicking-in your cores to its fullest potential. This area of OpenJDK is undergoing heavy changes, it is possible these APIs are changed or improved before milestone 6 when JDK 8 is officially feature complete. But, they are stable enough to give it a spin today. You can grab the lambda build from here. I have been using the latest Intellij 12 preview for my hands on with the new features in Java 8. It actually supports most of the new Java 8 languauge features and intellisense rocks.
The Streams proposal is well thought of and brings the much sought after functional power to Java and you won’t regret upgrading from Java 6 -> Java 8, if you are still hanging around with Java 6 in that time frame. This one is just a preview of what is coming in Java 8.
Let us implement a method to check if a keyword list exists in a tweet. Here is one way to implement it in plain old Java.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class TweetUtil { | |
public static void main(String[] args) { | |
final List<String> keywords = Arrays.asList("brown", "fox", "dog", "pangram"); | |
final String tweet = "The quick brown fox jumps over a lazy dog. #pangram http://www.rinkworks.com/words/pangrams.shtml"; | |
exists(keywords, tweet); | |
} | |
public static boolean exists(List<String> keywords, String tweet) { | |
for (String keyword : keywords) { | |
if (tweet.contains(keyword)) return true; | |
} | |
return false; | |
} | |
} |
Now, that I am taking the Functional programming course, I would like to compare it with Scala, the mature functional language on the JVM. In Scala, this can be implemented as one liners and there are couple different ways to do this, one using exists and other using the foldLeft function, although exists is my favorite as it is clean and less cryptic.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object TweetUtil { | |
def main(args: Array[String]) { | |
val keywords = List("brown", "fox", "dog", "pangram") | |
val tweet = "The quick brown fox jumps over a lazy dog. #pangram http://www.rinkworks.com/words/pangrams.shtml" | |
keywords.exists(tweet.contains) | |
(keywords.foldLeft(false)( _ || tweet.contains(_) )) | |
} | |
} |
I can understand why functional programmers are more obsessed with “small is beautiful” theme and hate outright verbosity. Now that all dust has settled with Java’s once uncertain future and stalled innovation, its new steward Oracle is betting on to bring back its lost glory in round 2 and you see that happening with Java 8. Finally, the language brings freshness to Java camp in offering functional constructs to Java and make functional programming in Java a breeze. Here is a teaser:
https://gist.github.com/3847359.js
Isn’t nice to have a one liners in Java too? Java is close, but could reduce some noise as you have to call stream() on the Collection which might seem unnecessary. Stream interface supports a fold operation very similar to Scala’s foldLeft function and implements using a similar approach as of Scala.
Now, let us take this example further, filter tweets matching keywords. This was inspired by Week 3 assignment from the “Functional Programming Principles in Scala” course which btw I am enjoying it so far. Again, you can see Java makes it a cut. I am trying to keep it not being a spoiler.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
List<String> google = Arrays.asList("android", "Android", "galaxy", "Galaxy", "nexus", "Nexus"); | |
List<String> apple = Arrays.asList("ios", "iOS", "iphone", "iPhone", "ipad", "iPad"); | |
List<Tweet> tweets = TweetReader.fromTweetSet(TweetReader.allTweets); | |
// Using anyMatch and method reference | |
List<Tweet> googleTweets = tweets.stream().filter(t -> (google.stream().anyMatch(t.text::contains))).collect(toList()); | |
List<Tweet> appleTweets = tweets.stream().filter(t -> (apple.stream().anyMatch(t.text::contains))).collect(toList()); | |
// Using reduce | |
List<Tweet> googleTweets2 = tweets.stream().filter(t -> google.stream().reduce(false, (Boolean b, String keyword) -> b || t.text.contains(keyword), (l, r) -> l | r)).collect(toList()); | |
List<Tweet> appleTweets2 = tweets.stream().filter(t -> apple.stream().reduce(false, (Boolean b, String keyword) -> b || t.text.contains(keyword), (l, r) -> l | r)).collect(toList()); |
In this example, you notice that you have to use stream() as well as into() operations to get the result of stream processing. I believe this is a limitation with the iteration 2 Streams implementation which was referred as “the bun problem” that we have to live with.
Streams in Java 8 is making implementing bulk operations a no brainer. While it involves some effort in learning the new concepts, it opens up a new dimension to library developers. Thanks to JSR 335 which brings Java up to speed with other cool languages out there in the planet.
For fun, I want to toss in a new exists() API to the Stream interface so I could use it to filter on my keywords list. This can be done with the help of extension methods, another new concept in Java 8.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package java.util.stream; | |
import java.util.function.Predicate; | |
public interface MyStream<T> extends Stream<T> { | |
default boolean exists(Predicate<? super T> predicate) { | |
return anyMatch(predicate); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package java.util.stream; | |
import java.util.Spliterator; | |
import java.util.function.Predicate; | |
import java.util.function.Supplier; | |
public class MyReferencePipeline<T, U> extends ReferencePipeline<T, U> implements MyStream<U> { | |
public MyReferencePipeline(Supplier<? extends Spliterator<?>> source, int sourceFlags, boolean parallel) { | |
super(source, sourceFlags, parallel); | |
} | |
public MyReferencePipeline(Spliterator<?> source, int sourceFlags, boolean parallel) { | |
super(source, sourceFlags, parallel); | |
} | |
@Override | |
public boolean exists(Predicate<? super U> predicate) { | |
return evaluate(MatchOps.makeRef(predicate, MatchOps.MatchKind.ANY)); | |
} | |
} |
Basically, I am delegating the implementation to anyMatch() for simplicity as both reveal the same purpose. Here is a custom List implementation that exposes this API on the collection. This is a primitive implementation, which btw I am trying to keep this updated with the recent library changes. I hope there is a better way to do this, but the point is, it enables developers to extend the Streams framework and fill in special cases.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package java.util.stream; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.Collection; | |
public class MyList<E> extends ArrayList<E> { | |
public MyList(int initialCapacity) { | |
super(initialCapacity); | |
} | |
public MyList() { | |
super(); | |
} | |
public MyList(Collection<? extends E> c) { | |
super(c); | |
} | |
@Override | |
public MyStream<E> stream() { | |
return new MyReferencePipeline<>(Arrays.spliterator((E[]) this.toArray(), 0, this.size()), StreamOpFlag.IS_SIZED | StreamOpFlag.IS_ORDERED, false); | |
} | |
@Override | |
public Stream<E> parallelStream() { | |
return new MyReferencePipeline<>(Arrays.spliterator((E[]) this.toArray(), 0, this.size()), StreamOpFlag.IS_SIZED | StreamOpFlag.IS_ORDERED, true); | |
} | |
} |
exists() variant of the code is shown below.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Run with -Xbootclasspath/p:target/classes as ReferencePipeline access is now package local, so use this trick | |
List<Tweet> googleTweets3 = tweets.stream().filter(tweet -> (new MyList<>(google).stream().exists(tweet.text::contains))).collect(toList()); | |
List<Tweet> appleTweets3 = tweets.stream().filter(tweet -> (new MyList<>(apple).stream().exists(tweet.text::contains))).collect(toList()); |
There are several new concepts in the library that requires functional mindset to fully benefit from it. You can switch these Streams to use a parallel version, by using parallel() instead of stream() on the Collection. Stream operations are classified as lazy and eager based on their implementation.
I hope to uncover some of these new concepts in the coming months. I wish function types made into Java 8, but that is different story for another day. Enjoy functional programming in Java!
I have covered Lambdas in the past and this excellent JavaZone talk by Angelika Langer should bring you up to speed with the current state of affairs with Lambdas in Java 8. You can also find Maurice Naftalin’s LambdaFAQ useful.
Lambdas in Java 8 from JavaZone on Vimeo.
Update (10/20/2012) : Updated samples to use method reference and built with the latest Lambda build 61. Clarified the bun-problem with streams proposal.
Update (11/20/2012) : Updated MyList implementation to compile with the latest API changes and much cleaner implementation.
Update (2/3/2013) : Updated code to compile with the latest lambda build 75.
Update (6/15/2013) : Updated code to compile with the latest lambda build 94.
Man, you shouldn’t public answers before deadline. I can’t see statement that your code comes from coursera.org . Your posts is linked from java.dzone.com and I’m almost sure your code has too many hints (so someone could lost fun from implementing own solution). Edit it pls.
LikeLike
I did mention that examples were inspired from progfun course. I was careful in not being a spoiler. I updated the post to be more generic. Hope this helps!
LikeLike