Videos
One idea might be to borrow SQL-related exercises -- find problems asking the reader to come up with some sort of SQL query to find information, and rephrase the problem and ask students to come up with an equivalent query using only Java's functional interface and lambda expressions. (I wouldn't bother telling students these questions were originally about SQL).
Translating basic SELECT queries would be pretty straightforward, I imagine -- implementing things like JOIN or GROUPBY would be a little trickier, I imagine. (You can do join by doing something like seq1.stream().flatMap(a -> seq2.stream().filter(b -> a.id == b.id).map(b -> combine(a, b)) and group-by using Collectors.groupingBy. The API isn't as elegant as it could be here, but I suppose that's Java for you.).
You can make it even trickier by asking them to translate the equivalent of nested queries or asking them to extract information from multiple lists at once (e.g. multiple 'tables'), instead of just one.
Stepping away from data manipulation, something else you could do is to introduce students to GUI frameworks or webserver backends or anything else that's event-driven -- event handlers are one example of a place where lambdas and method references shine.
My last idea is a bit of a stretch, but you could perhaps ask students to invent (or use) a mini-DSL involving lambda expressions in some way. This would be a bit more involved -- the exercise would be less about using Java's already existing interface, and more about writing their own. (It's a shame that Java doesn't have anything like C#'s extension methods, otherwise this would be much easier since you can trivially augment the existing API, which can be pretty handy).
If you need ideas for DSLs, you can maybe take inspiration from functional languages like Haskell and how they use higher-level constructs like monads and functors to build quite interesting abstractions and simplify things like error handling. I wouldn't use the word "monad" or "functor" at any point though, and you'll probably need to be careful to avoid accidentally making your exercise more about manipulating generics instead of lambdas.
One of the interesting things added to java in v8 is functional interfaces. Previously java only matched things up and verified correctness by type (type names). With functional interfaces, java now looks at the structure of code in certain situations.
I can add examples to this answer to clarify if anyone asks.
One interesting feature you can add is a method to do simple repetition. I like to teach a lot of things before if and while, but sometimes you just need repetition and the most common sort is a constant-number-of-iterations loop.
@FunctionalInterface
public interface LocalCommand
{
public void accept();
}
/**
* Repeat a method a given number of times
* @param howMany number of times to repeat the method
* @param what the method itself, as a Command. Especially useful in Java 8
*/
public static void repeat(int howMany, LocalCommand what){
for (int i = 0; i < howMany; ++i){
what.accept();
}
}
Usage:
public void aCommand(){
// do something interesting
}
repeat(3, aCommand()); // structural matching with the functional interface
repeat(3, () -> {aCommand();}); //lambda expression
repeat(3, () -> {System.out.println("Hello");}); //lambda expression
In java 8 you don't need to ever explicitly say that aCommand is, in fact, aCommand and you don't need a new enclosing type to implement the functional interface. You don't even need to specifically say that it is a functional interface, Java will grok it by structure alone.
With repeat(...) in my library I can do repetition before I need to talk about for and while loops.
I used only the simplest case of a functional interface here. You can use Consumers, etc just as well with only a bit more complexity.