Java Streams vs C# LINQ vs Java6
2013-04-10
A while back I ran into an article comparing C# LINQ to the upcoming Java8 Streams API: http://blog.informatech.cr/2013/03/24/java-streams-preview-vs-net-linq/.
I'm not really experienced with C# but I have a feeling that the language as a whole is quite verbose and, well, bad? Except for LINQ which performs magic with monadic comprehension.
I've been coding in Java for the most of my career, so I know a thing or two about it:
- it's really verbose.
- it's really, really, verbose.
- it's not nearly as verbose as you think. It's often just bad practice and inferior style that makes it so verbose and incomprehensible.
Since Java is still the language of my enterprise-day-job, I decided to ease my pain a bit, so I implemented my own functional utility library. I got a bit carried away, so I ended up with some annotation processors to bring something like poor-mans-first-class-functions into Java.
Since the library often provides rather clean ways to express ones intent, I wanted to see how it would compare to LINQ and Java Streams. So here it goes, examples from the Informatech blog supplemented with examples using plain old Java6 (released in 2007) using my functional library with annotation processors.
#Edit: I have written a follow-up with updated examples.
Challenge 1: Filtering
LINQ :
string[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" };
<string> filteredNames = names.Where(c => c.Contains("am"))
List.ToList();
Java Streams :
String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
List<String> filteredNames = stream(names)
.filter(c -> c.contains("am"))
.collect(toList());
Java6 :
String[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" };
List<string> filteredNames = newList(filter(names, contains("am")));
Challenge 2: Indexed Filtering
LINQ :
string[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" };
var nameList = names.Where((c, index) => c.Length <= index + 1).ToList();
Java Streams :
String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
List<String> nameList;
<Integer> indices = intRange(1, names.length).boxed();
Stream= zip(indices, stream(names), SimpleEntry::new)
nameList .filter(e -> e.getValue().length() <= e.getKey())
.map(Entry::getValue)
.collect(toList());
Java6 :
String[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" };
List<String> nameList = newList(map(filter(zipWithIndex(names), pred),
.<String> right()));
Transformers
static boolean pred(Map.Entry<Integer, String> candidate) {
return candidate.getValue().length() <= candidate.getKey() + 1;
}
Challenge 3: Selecting/Mapping
LINQ :
<string> nameList1 = new List(){ "Anders", "David", "James",
List"Jeff", "Joe", "Erik" };
.Select(c => "Hello! " + c).ToList()
nameList1.ForEach(c => Console.WriteLine(c));
Java Streams :
List<String> nameList1 = asList("Anders", "David", "James",
"Jeff", "Joe", "Erik");
.stream()
nameList1.map(c -> "Hello! " + c)
.forEach(System.out::println);
Java6 :
List<String> nameList1 = newList("Anders", "David", "James", "Jeff", "Joe", "Erik");
foreach(map(nameList1, prepend("Hello! ")),
.println8.apply(System.out)); PrintStream_
Challenge 4: Selecting Many/Flattening
LINQ :
<string, List<string>> map = new Dictionary<string,List<string>>();
Dictionary.Add("UK", new List<string>() {"Bermingham", "Bradford", "Liverpool"});
map.Add("USA", new List<string>() {"NYC", "New Jersey", "Boston", "Buffalo"});
mapvar cities = map.SelectMany(c => c.Value).ToList();
Java Streams :
Map<String, List<String>> map = new LinkedHashMap<>();
.put("UK", asList("Bermingham","Bradford","Liverpool"));
map.put("USA", asList("NYC","New Jersey","Boston","Buffalo"));
map
<Entry<String, List<String>>,String> flattener;
FlatMapper= (entry,consumer) -> { entry.getValue().forEach(consumer); };
flattener
List<String> cities = map.entrySet()
.stream()
.flatMap( flattener )
.collect(toList());
Java6 :
Map<String, List<String>> map = newMap(
.of("UK", newList("Bermingham", "Bradford", "Liverpool")),
Pair.of("USA", newList("NYC", "New Jersey", "Boston", "Buffalo")));
PairList<String> cities = newList(flatten(map.values()));
Challenge 5: Taking an Arbitrary Number of Items
LINQ :
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 };
var first4 = numbers.Take(4).ToList();
Java Streams :
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,13 };
List<Integer> firstFour;
= stream(numbers).limit(4)
firstFour .boxed()
.collect(toList());
Java6 :
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 };
List<Integer> firstFour = newList(take(newArray(numbers), 4));
Challenge 6: Taking Items Based on Predicate
LINQ :
string[] moreNames = { "Sam", "Samuel", "Dave", "Pascal", "Erik", "Sid" };
var sNames = moreNames.TakeWhile(c => c.StartsWith("S"));
Java Streams :
String[] names = { "Sam","Samuel","Dave","Pascal","Erik","Sid" };
List<String> found;
= stream(names).collect(partitioningBy( c -> c.startsWith("S")))
found .get(true);
Java6 :
String[] names = { "Sam", "Samuel", "Dave", "Pascal", "Erik", "Sid" };
List<String> found = newList(takeWhile(names, startsWith("S")));
Challenge 7: Skipping an Arbitrary Number of Items
LINQ :
string[] vipNames = { "Sam", "Samuel", "Samu", "Remo", "Arnold","Terry" };
var skippedList = vipNames.Skip(3).ToList();//Leaving the first 3.
Java Streams :
String[] vipNames = { "Sam", "Samuel", "Samu", "Remo", "Arnold","Terry" };
List<String> skippedList;
= stream(vipNames).substream(3).collect(toList()); skippedList
Java6 :
String[] vipNames = { "Sam", "Samuel", "Samu", "Remo", "Arnold", "Terry" };
List<String> skippedList = newList(drop(vipNames, 3));
Challenge 8: Skipping Items Based on Predicate
LINQ :
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 20 };
var skippedList = numbers.SkipWhile(c => c < 10);
Java Streams :
//With current streams API I found no way to implement this idiom.
Java6 :
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 20 };
List<Integer> skippedList = newList(dropWhile(newArray(numbers), lessThan(10)));
Challenge 9: Ordering/Sorting Elements
LINQ :
string[] friends = { "Sam", "Pamela", "Dave", "Anders", "Erik" };
= friends.OrderBy(c => c).ToArray(); friends
Java Streams :
String[] friends = { "Sam", "Pamela", "Dave", "Anders", "Erik" };
= stream(friends).sorted().toArray(String[]::new); friends
Java6 :
String[] friends = { "Sam", "Pamela", "Dave", "Anders", "Erik" };
= newArray(sort(friends), String.class); friends
Challenge 10: Ordering/Sorting Elements by Specific Criterium
LINQ :
string[] friends = { "Sam", "Pamela", "Dave", "Anders", "Erik" };
= friends.OrderBy(c => c.Length).ToArray(); friends
Java Streams :
String[] friends = { "Sam", "Pamela", "Dave", "Anders", "Erik" };
= stream(friends)
friends .sorted(comparing((ToIntFunction<String>)String::length))
.toArray(String[]::new);
Java6 :
String[] friends = { "Sam", "Pamela", "Dave", "Anders", "Erik" };
= newArray(sort(friends, by(String_.length)), String.class); friends
Challenge 11: Ordering/Sorting Elements by Multiple Criteria
LINQ :
string[] fruits = {"grape", "passionfruit", "banana",
"apple", "orange", "raspberry",
"mango", "blueberry" };
//Sort the strings first by their length and then alphabetically.
//preserving the first order.
var sortedFruits = fruits.OrderBy(fruit =>fruit.Length)
.ThenBy(fruit => fruit);
Java Streams :
String[] fruits = {"grape", "passionfruit", "banana","apple",
"orange", "raspberry","mango", "blueberry" };
Comparator<String> comparator;
= comparing((Function<String,Integer>)String::length,
comparator Integer::compare)
.thenComparing((Comparator<String>)String::compareTo);
= stream(fruits) .sorted(comparator)
fruits .toArray(String[]::new);
Java6 :
String[] fruits = { "grape", "passionfruit", "banana", "apple",
"orange", "raspberry", "mango", "blueberry" };
= newArray(sort(fruits, by(String_.length).then(
fruits byNatural())), String.class);
Challenge 12: Grouping by a Criterium
LINQ :
string[] names = {"Sam", "Samuel", "Samu", "Ravi", "Ratna", "Barsha"};
var groups = names.GroupBy(c => c.Length);
Java Streams :
String[] names = {"Sam", "Samuel", "Samu", "Ravi", "Ratna", "Barsha"};
Map<Integer,List<String>> groups;
= stream(names).collect(groupingBy(String::length)); groups
Java6 :
String[] names = { "Sam", "Samuel", "Samu", "Ravi", "Ratna", "Barsha" };
Map<Integer, List<String>> groups = groupBy(names, String_.length);
Challenge 13: Filter Distinct Elements
LINQ :
string[] songIds = {"Song#1", "Song#2", "Song#2", "Song#2", "Song#3", "Song#1"};
//This will work as strings implement IComparable
var uniqueSongIds = songIds.Distinct();
Java Streams :
String[] songIds = {"Song#1", "Song#2", "Song#2", "Song#2", "Song#3", "Song#1"};
//according to Object.equals
stream(songIds).distinct();
Java6 :
String[] songIds = { "Song#1", "Song#2", "Song#2", "Song#2", "Song#3", "Song#1" };
newSet(songIds);
Challenge 14: Union of Two Sets
LINQ :
<string> friends1 = new List<string>() {"Anders", "David","James",
List"Jeff", "Joe", "Erik"};
<string> friends2 = new List<string>() { "Erik", "David", "Derik" };
Listvar allMyFriends = friends1.Union(friends2);
Java Streams :
List<String> friends1 = asList("Anders","David","James","Jeff","Joe","Erik");
List<String> friends2 = asList("Erik","David","Derik");
<String> allMyFriends = concat(friends1.stream(),
Stream.stream()).distinct(); friends2
Java6 :
List<String> friends1 = newList("Anders", "David", "James", "Jeff", "Joe", "Erik");
List<String> friends2 = newList("Erik", "David", "Derik");
Set<String> allMyFriends = union(newSet(friends1), newSet(friends2));
Challenge 15: First Element
LINQ :
string[] otherFriends = {"Sam", "Danny", "Jeff", "Erik", "Anders","Derik"};
string firstName = otherFriends.First();
string firstNameConditional = otherFriends.First(c => c.Length == 5);
Java Streams :
String[] otherFriends = {"Sam", "Danny", "Jeff", "Erik", "Anders","Derik"};
<String> found = stream(otherFriends).findFirst();
Optional
<String> maybe = stream(otherFriends).filter(c -> c.length() == 5)
Optional.findFirst();
if(maybe.isPresent()) {
//do something with found data
}
Java6 :
String[] otherFriends = { "Sam", "Danny", "Jeff", "Erik", "Anders", "Derik" };
Option<String> found = headOption(otherFriends);
Option<String> maybe = find(otherFriends, String_.length.andThen(equalTo(5)));
for (String m: maybe) {
// ...
}
Challenge 16: Generate a Range of Numbers
LINQ :
var multiplesOfEleven = Enumerable.Range(1, 100).Where(c => c % 11 == 0);
Java Streams :
= intRange(1,100).filter(n -> n % 11 == 0); IntStream multiplesOfEleven
Java6 :
Iterable<Integer> multiplesOfEleven = filter(range(1, 100), mod(11).andThen(equalTo(0)));
Challenge 17: All
LINQ :
string[] persons = {"Sam", "Danny", "Jeff", "Erik", "Anders","Derik"};
bool x = persons.All(c => c.Length == 5);
Java Streams :
String[] persons = {"Sam", "Danny", "Jeff", "Erik", "Anders","Derik"};
boolean x = stream(persons).allMatch(c -> c.length() == 5);
Java6 :
String[] persons = { "Sam", "Danny", "Jeff", "Erik", "Anders", "Derik" };
boolean x = forAll(persons, String_.length.andThen(equalTo(5)));
Challenge 18: Any
LINQ :
string[] persons = {"Sam", "Danny", "Jeff", "Erik", "Anders","Derik"};
bool x = persons.Any(c => c.Length == 5);
Java Streams :
String[] persons = {"Sam", "Danny", "Jeff", "Erik", "Anders","Derik"};
boolean x = stream(persons).anyMatch(c -> c.length() == 5);
Java6 :
String[] persons = { "Sam", "Danny", "Jeff", "Erik", "Anders", "Derik" };
boolean x = exists(persons, String_.length.andThen(equalTo(5)));
Challenge 19: Zip
LINQ :
string[] salutations = {"Mr.", "Mrs.", "Ms", "Master"};
string[] firstNames = {"Samuel", "Jenny", "Joyace", "Sam"};
string lastName = "McEnzie";
.Zip(firstNames, (sal, first) => sal + " " + first)
salutations.ToList()
.ForEach(c => Console.WriteLine(c + " " + lastName));
Java Streams :
String[] salutations = {"Mr.", "Mrs.", "Ms", "Master"};
String[] firstNames = {"Samuel", "Jenny", "Joyace", "Sam"};
String lastName = "McEnzie";
zip(
stream(salutations),
stream(firstNames),
(sal,first) -> sal + " " +first)
.forEach(c -> { System.out.println(c + " " + lastName); });
Java6 :
String[] salutations = { "Mr.", "Mrs.", "Ms", "Master" };
String[] firstNames = { "Samuel", "Jenny", "Joyace", "Sam" };
String lastName = "McEnzie";
foreach(map(zip(salutations, firstNames, repeat(lastName)), mkString(" ")),
.println8.apply(System.out)); PrintStream_
Conclusion
Based on these examples I have a funny feeling that Java8 Streams API is going to be a failure. And since developers will not be able to extend it with useful constructs, it may well end up being just another nail in the coffin.
Of these examples, personally, I find the Java6 code to be the most readable. Even with its oddities, of which most are caused by the original authors decision to use ints (instead of Integers) and Lists (instead of Iterables). The ability to do this has been around since 2007, and Java8 will be released in... 2014?
I'm a bit biased, though, so what do you think?