Java 8 will go GA in March 2014. The first proposed final draft of Java 8 JSR 337 was released early this week. One of the main components of this JSR is Lambda Expressions for the language (JSR 337). This brings the functional constructs to the language, which will undeniably make Java a top contender among other functional peers (Scala, Groovy, Clojure) on the JVM for Java developers. Conciseness is only part of the story, check out how Java 8 programs start to look with Lambda expressions. While being concise for a casual reader, it clearly makes a case why thinking functional may benefit to Java developers in terms of the powerful constructs it offers. The conciseness does not take away readability of the Java program, instead it makes Java developers feel home and enables functional programming with ease, taking away the heavy lifting usually required for moving away from OO paradigm.
I stole the name “Java.next” from Neal Ford‘s excellent series of articles about functional languages on the JVM. The latest installment of the series was “Java.next: Functional coding styles”. While, the article takes on legacy Java, adding Java 8 to the mix would not hurt. So, let us take his example where you are given a list of names, some of which consist of a single character. You are asked to return the names in a comma-delimited string that contains no single-letter names, with each name capitalized. Here are the various flavors of the implementation taken from the article, while Scala, Groovy and Clojure has its functional version, Java’s implementation is imperative and old school.
Java version:
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 TheCompanyProcess { | |
public String cleanNames(List<String> listOfNames) { | |
StringBuilder result = new StringBuilder(); | |
for(int i = 0; i < listOfNames.size(); i++) { | |
if (listOfNames.get(i).length() > 1) { | |
result.append(capitalizeString(listOfNames.get(i))).append(","); | |
} | |
} | |
return result.substring(0, result.length() – 1).toString(); | |
} | |
public String capitalizeString(String s) { | |
return s.substring(0, 1).toUpperCase() + s.substring(1, s.length()); | |
} | |
} |
Scala version:
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
val employees = List("neal", "s", "stu", "j", "rich", "bob") | |
val result = employees | |
.filter(_.length() > 1) | |
.map(_.capitalize) | |
.reduce(_ + "," + _) |
Groovy version:
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
class TheCompanyProcess { | |
public static String cleanUpNames(List listOfNames) { | |
listOfNames | |
.findAll {it.length() > 1} | |
.collect {it.capitalize()} | |
.join(',') | |
} | |
} |
Clojure version:
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
(defn process [list-of-emps] | |
(reduce str (interpose "," | |
(map clojure.string/capitalize | |
(filter #(< 1 (count %)) list-of-emps))))) |
Java 8 version of functional processing
With Java 8, functional interfaces makes this possible with Lambda expressions and Streams make you even more fun when working with Collections in Java 8. Here is the functional version 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
employees.stream() | |
.filter(e -> e.length() > 1) | |
.map(s -> s.substring(0, 1).toUpperCase() + s.substring(1)) | |
.collect(joining(",")); |
Switch it to parallelStream() for parallel processing, as simple as that to kick the tires of your cores.
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
employees.parallelStream() | |
.filter(e -> e.length() > 1) | |
.map(s -> s.substring(0, 1).toUpperCase() + s.substring(1)) | |
.collect(joining(",")); |
My original intent was to compare with the Java.next languages as perceived by Neal Ford in his article. It is unfair to leave behind some of the cool languages on the JVM. Thanks Luke for bringing this up. I strongly believe top-notch IDE support (Eclipse or Intellij) is a win-win situation for users and language implementors. With that context, I updated my list to include Fantom, Gosu, Xtend, Kotlin, Ceylon in order of their age (oldest first).
Fantom version:
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
static Void main() { | |
employees := ["neal", "s", "stu", "j", "rich", "bob"] | |
Str result := employees.findAll { it.size > 1 } | |
.map { it.capitalize } | |
.join(",") | |
} |
Gosu version
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
var employees = { "neal", "s", "stu", "j", "rich", "bob" } | |
var result = employees.where(\s –> s.length > 1) | |
.map(\s –> s.capitalize()) | |
.join(",") |
Xtend version:
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
def static void main(String[] args) { | |
val employees = Arrays.asList("neal", "s", "stu", "j", "rich", "bob") | |
val result = employees.stream | |
.filter[length() > 1] | |
.map[toFirstUpper()] | |
.reduce[x, y | x + "," + y] | |
} | |
// without map() | |
def static void main(String[] args) { | |
val employees = Arrays.asList("neal", "s", "stu", "j", "rich", "bob") | |
val result = employees.stream | |
.filter[length() > 1] | |
.reduce[x, y | x.toFirstUpper + "," + y.toFirstUpper] | |
} |
Kotlin version:
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
fun main(args: Array<String>) { | |
val employees = array("neal", "s", "stu", "j", "rich", "bob") | |
val result = employees filter { it.length > 1 } map { it.capitalize() } reduce { x, y -> x + "," + y } | |
} |
Ceylon version:
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
void main() { | |
value employees = {"neal", "s", "stu", "j", "rich", "bob"}; | |
String result = ",".join(employees | |
.filter((String e) => e.longerThan(1)) | |
.map((String s) => s.segment(0, 1).uppercased + s.spanFrom(1)) | |
); | |
// using a for comprehension | |
String result = ", ".join { | |
for (i in employees) | |
if (i.longerThan(1)) | |
i.segment(0, 1).uppercased + i.spanFrom(1) | |
}; | |
} |
Personally, I feel Java 8 and Groovy has better success rate in making developers stay in their comfort zone and still enjoy functional prowess. While, I agree Clojure and Scala versions make their case to different set of developers, I believe maintaining a larger code base will be a challenge, when time to develop and market is ever shrinking, and bringing someone up-to-speed with these language tricks will not be easy. If it looks good, eat it, does not play well when it comes to programming in general. While coming to the newer JVM languages, parallelism is one factor that makes Java 8 easy peasy to deal with. These new breed of languages bring ton of cool features (topic for another blog post!) and attracts developers to the JVM, not just Java developers, which is good for the platform. It remains to be seen in the coming years how well these languages get adopted in mainstream development.
Merry Christmas and Happy New Year everyone!
Update (12/29/2013) : Updated the Java.next languages comparison to include newer JVM languages: Fantom, Gosu, Xtend, Kotlin, Ceylon.
What about the next generation of JVM languages like Ceylon, Kotlin, XTend?
LikeLike
Thanks Luke for bringing this up! I updated the post to include newer JVM languages (Fantom, Gosu, Xtend, Kotlin, Ceylon).
LikeLike
I personally believe that the new Streams API along with lambdas and extension methods will be such a huge boost for Java 8’s popularity that alternative JVM languages will suffer from a huge loss of traction. Granted, there would be dozens of other useful features to be added, such as these ones from Ceylon:
http://blog.jooq.org/2013/12/03/top-10-ceylon-language-features-i-wish-we-had-in-java/
Butthe Streams/Lambda/Extension Methods combo is really the essence of it. Looking forward to 2014!
LikeLike
Lukas, I could not agree more. Ceylon and the recent storm of JVM languages offer something for everyone and makes JVM a perfect platform to develop for.
LikeLike
I little bit improved Groovy example (removed public to make code shorter)
https://gist.github.com/stokito/8217044
LikeLike
If it’s assumed, as many of the examples did, that the “employees” list exists, then even more simple…
>> employees
.findAll {it.length() > 1}
.collect {it.capitalize()}
.join(‘,’)
And if line length is important, as the Kotlin example seemed to suggest, then a single line expression could be written…
>> employees.findAll {it.length() > 1}.collect {it.capitalize()}.join(‘,’)
LikeLike
In Scala you can simplify it further with:
val result = employees.collect {
case s if s.length > 1 => s.capitalize
}.mkString(“,”)
LikeLike
Thanks for this nice article! glad to hear from java 8 lambdas
LikeLike