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 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.
Challenge 1: Filtering
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
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;
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
String[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" };
List<String> nameList = newList(map(filter(zipWithIndex(names), pred),
Transformers.<String> right()));
static boolean pred(Map.Entry<Integer, String> candidate) {
return candidate.getValue().length() <= candidate.getKey() + 1;
}Challenge 3: Selecting/Mapping
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
List<String> nameList1 = newList("Anders", "David", "James", "Jeff", "Joe", "Erik");
foreach(map(nameList1, prepend("Hello! ")),
PrintStream_.println8.apply(System.out));Challenge 4: Selecting Many/Flattening
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
Map<String, List<String>> map = newMap(
Pair.of("UK", newList("Bermingham", "Bradford", "Liverpool")),
Pair.of("USA", newList("NYC", "New Jersey", "Boston", "Buffalo")));
List<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;
firstFour = stream(numbers).limit(4)
.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;
found = stream(names).collect(partitioningBy( c -> c.startsWith("S")))
.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;
skippedList = stream(vipNames).substream(3).collect(toList());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 = friends.OrderBy(c => c).ToArray();Java Streams
String[] friends = { "Sam", "Pamela", "Dave", "Anders", "Erik" };
friends = stream(friends).sorted().toArray(String[]::new);Java6
String[] friends = { "Sam", "Pamela", "Dave", "Anders", "Erik" };
friends = newArray(sort(friends), String.class);Challenge 10: Ordering/Sorting Elements by Specific Criterium
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
String[] friends = { "Sam", "Pamela", "Dave", "Anders", "Erik" };
friends = newArray(sort(friends, by(String_.length)), String.class);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;
comparator = comparing((Function<String,Integer>)String::length,
Integer::compare)
.thenComparing((Comparator<String>)String::compareTo);
fruits = stream(fruits) .sorted(comparator)
.toArray(String[]::new);Java6
String[] fruits = { "grape", "passionfruit", "banana", "apple",
"orange", "raspberry", "mango", "blueberry" };
fruits = newArray(sort(fruits, by(String_.length).then(
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;
groups = stream(names).collect(groupingBy(String::length));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
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
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"};
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
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
IntStream multiplesOfEleven = intRange(1,100).filter(n -> n % 11 == 0);
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";
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
String[] salutations = { "Mr.", "Mrs.", "Ms", "Master" };
String[] firstNames = { "Samuel", "Jenny", "Joyace", "Sam" };
String lastName = "McEnzie";
foreach(map(zip(salutations, firstNames, repeat(lastName)), mkString(" ")),
PrintStream_.println8.apply(System.out));
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?