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:
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.
LINQ :
string[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" };
List<string> filteredNames = names.Where(c => c.Contains("am"))
.ToList();
Java Streams :
String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
List<String> filteredNames = stream(names)
.filter(c -> c.contains("am"))
.collect(toList());
Java6 :
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;
Stream<Integer> indices = intRange(1, names.length).boxed();
nameList = zip(indices, stream(names), SimpleEntry::new)
.filter(e -> e.getValue().length() <= e.getKey())
.map(Entry::getValue)
.collect(toList());
Java6 :
LINQ :
List<string> nameList1 = new List(){ "Anders", "David", "James",
"Jeff", "Joe", "Erik" };
nameList1.Select(c => "Hello! " + c).ToList()
.ForEach(c => Console.WriteLine(c));
Java Streams :
List<String> nameList1 = asList("Anders", "David", "James",
"Jeff", "Joe", "Erik");
nameList1.stream()
.map(c -> "Hello! " + c)
.forEach(System.out::println);
Java6 :
LINQ :
Dictionary<string, List<string>> map = new Dictionary<string,List<string>>();
map.Add("UK", new List<string>() {"Bermingham", "Bradford", "Liverpool"});
map.Add("USA", new List<string>() {"NYC", "New Jersey", "Boston", "Buffalo"});
var cities = map.SelectMany(c => c.Value).ToList();
Java Streams :
Map<String, List<String>> map = new LinkedHashMap<>();
map.put("UK", asList("Bermingham","Bradford","Liverpool"));
map.put("USA", asList("NYC","New Jersey","Boston","Buffalo"));
FlatMapper<Entry<String, List<String>>,String> flattener;
flattener = (entry,consumer) -> { entry.getValue().forEach(consumer); };
List<String> cities = map.entrySet()
.stream()
.flatMap( flattener )
.collect(toList());
Java6 :
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;
firstFour = stream(numbers).limit(4)
.boxed()
.collect(toList());
Java6 :
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;
found = stream(names).collect(partitioningBy( c -> c.startsWith("S")))
.get(true);
Java6 :
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;
skippedList = stream(vipNames).substream(3).collect(toList());
Java6 :
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 :
Java6 :
LINQ :
string[] friends = { "Sam", "Pamela", "Dave", "Anders", "Erik" };
friends = friends.OrderBy(c => c).ToArray();
Java Streams :
String[] friends = { "Sam", "Pamela", "Dave", "Anders", "Erik" };
friends = stream(friends).sorted().toArray(String[]::new);
Java6 :
LINQ :
string[] friends = { "Sam", "Pamela", "Dave", "Anders", "Erik" };
friends = friends.OrderBy(c => c.Length).ToArray();
Java Streams :
String[] friends = { "Sam", "Pamela", "Dave", "Anders", "Erik" };
friends = stream(friends)
.sorted(comparing((ToIntFunction<String>)String::length))
.toArray(String[]::new);
Java6 :
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;
comparator = comparing((Function<String,Integer>)String::length,
Integer::compare)
.thenComparing((Comparator<String>)String::compareTo);
fruits = stream(fruits) .sorted(comparator)
.toArray(String[]::new);
Java6 :
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;
groups = stream(names).collect(groupingBy(String::length));
Java6 :
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 :
LINQ :
List<string> friends1 = new List<string>() {"Anders", "David","James",
"Jeff", "Joe", "Erik"};
List<string> friends2 = new List<string>() { "Erik", "David", "Derik" };
var allMyFriends = friends1.Union(friends2);
Java Streams :
List<String> friends1 = asList("Anders","David","James","Jeff","Joe","Erik");
List<String> friends2 = asList("Erik","David","Derik");
Stream<String> allMyFriends = concat(friends1.stream(),
friends2.stream()).distinct();
Java6 :
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"};
Optional<String> found = stream(otherFriends).findFirst();
Optional<String> maybe = stream(otherFriends).filter(c -> c.length() == 5)
.findFirst();
if(maybe.isPresent()) {
//do something with found data
}
Java6 :
LINQ :
Java Streams :
Java6 :
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 :
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 :
LINQ :
string[] salutations = {"Mr.", "Mrs.", "Ms", "Master"};
string[] firstNames = {"Samuel", "Jenny", "Joyace", "Sam"};
string lastName = "McEnzie";
salutations.Zip(firstNames, (sal, first) => sal + " " + first)
.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 :
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?