Crash Course on Lambda Expressions and Streams
Functional programming has been on the rise for quite some time now, and rightly so. Many non-functional programming languages had long adopted at least some amount of functional programming principles, and while Java had been lagging behind, it’s finally jumped on the bandwagon with the latest version, Java 8.
Array.map(), I had a hard time understanding LINQ in C#. In that sense, it’s perfectly understandable for me that most Java programmers, even seasoned ones, are shying away from lambdas and streams and prefer to stick to good old lists and
for loops in their daily usage.
In the light of that observation, I decided to write this article to help programmers establish a ground on what these “strange” facilities are. I will be explaining what streams and lambda expressions are, and which functional operations are executed and what does it mean to be executed lazily. Hopefully, this article will be helpful to both newcoming Java 8 programmers, and C# programmers who are not familiar with the LINQ API.
For a downloadable version of the examples in this article, visit the corresponding Gists at:
To handle a lambda expression, Java first maps it to a special type called a functional interface and then executes a specific method found on that interface. These interfaces are defined as follows:
|Functional Interface*||Input Types||Output Types||Primary Method||Description|
|Supplier<T>||None||T||get()||Returns a value of type T|
|Consumer<T>||T||None||accept(T)||Consumes a value of type T|
|BiConsumer<T, U>||T, U||None||accept(T, U)||Consumes values of types T and U|
|Function<T, R>||T||R||apply(T)||Consumes a value of type T and returns a value of type R|
|BiFunction<T, U, R>||T, U||R||apply(T, U)||Consumes values of types T and U, and returns a value of type R|
|UnaryOperator<T>||T||T||apply(T)||Consumes a value of type T and returns a value of type T|
|BinaryOperator<T, T>||T||T||apply(T, T)||Consumes two values of type T and returns a value of type T|
|Predicate<T>||T||boolean||test(T)||Consumes a value of type T and returns a boolean|
|BiPredicate<T, U>||T, U||boolean||test(T, U)||Consumes values of types T and U, and returns a boolean|
*: Since primitive types cannot be specified as type parameters, these interfaces all have overrides for primitive types, such as
As you might have noticed, Java’s built-in functional interfaces only accept up to 2 input parameters. For more, you can either use a technique called Currying, which basically means nesting functional interfaces in each other, or you can also create your own functional interfaces. Here’s an example:
C#, on the other hand, maps them to two types:
Func<T, R>, both of which are
delegates themselves and can receive up to 16 parameters. A delegate in C# is like a function pointer from C/C++ but it’s type-safe and contains a built-in iterator for callees. This iterator allows multiple functions to register themselves on delegates so that they’re invoked when the delegate itself is invoked. If you want to learn more about delegates, simply visit the MSDN article about delegates.
|Delegate||Input Types||Output Types||Description|
|Action<T1..T16>||T1..T16||None||Consumes up to 16 values of type T1 to T16|
|Func<T1..T16, R>||T1..T16||R||Consumes up to 16 values of type T1 to T16 and returns a value of type R|
|Predicate<T>||T||bool||Consumes a value of type returns a boolean|
Code-wise, a lambda expression is defined inside another method or function’s scope, so it doesn’t have an access modifier. Since its types can be inferred, it doesn’t need to explicitly define its parameter and return types either. Furthermore, if the expression is a one-liner and contains a single expression (i.e. a sum or product), it doesn’t even have to have a return statement and curly braces.
The short-hand method to define a lambda expression in Java is as follows (notice how Java uses the same arrow notation used in lambda calculus):
And a few actual implementations:
Now that we now how to express an anonymous function, it’s trivial to compose a method that takes an anonymous function as a parameter:
And if you want to generate and return a lambda expression:
A stream is a special kind of collection that only evaluates its values when they’re requested. In other words, it’s a lazy collection. This laziness allows them to be unrestricted by the time factor and thus able to be used on collection of infinite or at least unknown sizes, without worrying too much about concurrency (more of that in a later topic!). The C#’s term for a stream is enumerable. Here’s the definition:
Streams can be explored in a much more detailed fashion, but for the sake of simplicity I’ll pass the subject to a future article.
Since the functional operations are only defined on the stream class. As before, we won’t go into too much detail and will stick to turning generic collections into streams instead. To do that in Java, simply call the
stream() method of a generic collection and you’ll get an appropriate
Stream<T> instance. In the case of C#, all collections implement the
IEnumerable interface, and since this is where functional operations are declared, there’s nothing you need to do before using them. If somehow you stumble upon a collection that doesn’t extend IEnumerable and therefore not contain any functional operations, try calling the
AsEnumerable() extension method.
Since streams are conceptually lazy, you’ll need to materialize them into generic collections when you want to do some constant-time operations with them (i.e. count their items). In Java, you’ll need to use the
Collectors class to achieve this, and in C#, you can simply call the appropriate extension methods defined on IEnumerable<T>. Here’s the method to materialize a stream into a list (there’s more, of course, but they vary too much from Java to C#, for more information visit Oracle Docs page for Java 8 Collectors or MSDN page for C#’s IEnumerable methods):
In Java, every instance of
Object and its sub-classes can be
null, so you have to manually check for null values in your business. The downside of traditional null-checking is that it’s error-prone because it’s incredibly easy to forget to do. The
Optional<T> class introduced in Java 8 aims to overcome that by wrapping objects and requiring you to be explicit about null-checking. C# does not have a direct equivalent, but the
? suffix which is the equivalent of using
Nullable<T> can be used to some extent.
In a functional programming perspective, Optional is a Stream with a single element, so the functional operations listed in the Functional Operations section does apply to it as well. One extra method that Optional defines is the
ifPresent(Consumer<T> fn) method which invokes the provided lambda expression when the contained value is present, which can be extremely useful.
To wrap an object in an Optional, simply call the
Optional.ofNullable(T t) method. And to create an empty Optional, do
These functional operations are used to transform a stream into another without changing its integrity. This is essentially what makes streams inherently concurrent and thread-safe. Also, since streams are lazily evaluated, it is possible to queue up multiple functional operations without any interference. The queued operations will only be executed when the stream is collected, therefore will not cause too much performance hit. In fact, this property is what made LINQ-to-SQL possible in the first place.
Based on a given transformation function, returns a new stream (not necessarily to different types or values) from a stream.
|Java||Stream<R> map(Function<T, R> mapper)|
|C#||IEnumerable<R> Select(Func<T, R> mapper)|
T: input, R: output
map to a stream and unfolds the returning stream. This is helpful in cases where your map function produces a collection but you want to group all output into a single collection.
|Java||Stream<R> flatMap(Function<T, Stream<R>> mapper)|
|C#||IEnumerable<R> SelectMany(Func<T, IEnumerable<R>> mapper)|
T: input, R: output
Returns a new stream containing only the elements on a stream that passes the given predicate function.
|Java||Stream<T> filter(Function<T, Boolean> predicate)|
|C#||IEnumerable<T> Where(Func<T, bool> predicate)|
Offsets a stream, returning a new stream containing the remainder of a stream after a given number of elements. Not technically a functional operation, but still useful with streams.
|Java||Stream<T> skip(long count)|
|C#||IEnumerable<T> Skip(int count)|
Limits a stream, returns a new stream containing the given number of elements taken from a stream.
|Java||Stream<T> limit(long count)|
|C#||IEnumerable<T> Take(int count)|
Returns a new stream containing only the unique elements in a stream. Used the the default equality comparer defined on the type
equals in Java,
Equals in C#).
Java and C# handle this differently. The
sort method in Java, given a comparator function, sorts the elements in a stream into a new stream. In C#, however, the equivalent
OrderBy method does not accept a comparative lambda expression, but rather, expects a lambda expression that returns a comparable data type. Since all types in C# have an implicit comparator function, it’s possible to simply provide which property to sort on, given a custom class.
Also known as aggregate. Given an initial value and a combinator function, iterates over a stream and produces a final, scalar result. For example, the sum of a stream is a reduce operation with an initial value of 0 and a combinator function that adds two values to each other. The idea behind collector methods such as
Collectors.toList() is also this, they start with an initially empty collection and add to it while they iterate over the stream.
Well, that’s it for this article. I hope it’s been helpful. There’s more to talk about functional programming, of course, and especially regarding how it’s inherently more suited for concurrent/parallel programming, but that topic’s for another article, hopefully.