I gave a talk at Devoxx UK 2013 entitled Accelerated Lambda Programming. Here is the slide presentation from that talk.
There are just a few introductory slides in the slide deck, after which most of the talk consisted of live programming demos in NetBeans. Below is the sample code from the demo, cleaned up, merged into a single file, and updated for Lambda build b88. The conference was several weeks ago, and I did the demos using build b82. The APIs have changed a little bit, but not that much, certainly much less than the amount they changed in the few weeks leading up to b82.
(Here is a link to JDK 8 early access builds with lambda support. The lambda support is at this writing still being integrated into the JDK 8 mainline, so it may still be a few weeks before you can run this code on the mainline JDK 8 builds. Also, here is a link to NetBeans builds with lambda support. Most recent builds should work fine.)
I’ve included extensive annotations along with the code that attempt to capture the commentary I had made verbally while giving the talk. At some point the video is supposed to be posted, but it isn’t yet (and in any case subscription might be required to view the video).
The APIs are still in a state of flux and they may still change. Please let me know if you have trouble getting this stuff to work. When all the lambda APIs are integrated into the JDK 8 mainline, I’ll update the sample code here if necessary.
Enjoy!
package com.example; import java.io.*; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.*; /** * Sample code from my "Accelerated Lambda Programming" talk at Devoxx UK, * March 2013. I went through most of the examples in the talk, but I think * I missed a couple. At least, I had intended to present all of the examples * shown here. (-: * * @author smarks */ public class AcceleratedLambda { static List<String> strings = Arrays.asList( "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"); // ========== SOURCES AND OPERATIONS ========== static void sources_and_operations() throws IOException { // Use a List as a stream source. // This just prints out each string. strings.stream() .forEach(x -> System.out.println(x)); // Use an int range as a stream source. Note that parameter x is now // inferred to be an int whereas above it was a String. IntStream.range(0, 10) .forEach(x -> System.out.println(x)); // Use a string as a stream of chars // This prints integers 97, 98, 99, ... huh? The chars() method // provides an IntStream so println prints numbers. "abcdef".chars() .forEach(x -> System.out.println(x)); // Cast values to char so that it prints 'a' through 'f'. "abcdef".chars() .forEach(x -> System.out.println((char)x)); // Reads and prints each line of a text file. try (FileReader in = new FileReader("input.txt"); BufferedReader br = new BufferedReader(in)) { br.lines() .forEach(x -> System.out.println(x)); } // Change the terminal operation to collect the values // into a List. List<String> result = strings.stream() .collect(Collectors.toList()); System.out.println("result = " + result); // Collect the values into a Set instead of a List. // This will probably print the values in a different order, // since the set's iteration order is probably different from // the order of insertion. Set<String> result2 = strings.stream() .collect(Collectors.toSet()); System.out.println("result2 = " + result2); // Counts the number of values in the stream. long result3 = strings.stream() .count(); System.out.println("result3 = " + result3); // Check that every value matches a predicate. boolean result4 = strings.stream() .allMatch(s -> s.length() < 10); System.out.println("result4 = " + result4); // Check that at least one value matches a predicate. boolean result5 = strings.stream() .anyMatch(s -> s.length() > 5); System.out.println("result5 = " + result5); // Now add an intermediate operation: filter the stream // through a predicate, which passes through only the values // that match the predicate. List<String> result6 = strings.stream() .filter(s -> s.length() > 3) .collect(Collectors.toList()); System.out.println("result6 = " + result6); // Map (transform) a value into a different value. List<String> result7 = strings.stream() .map(s -> s.substring(0,1)) .collect(Collectors.toList()); System.out.println("result7 = " + result7); // Take a slice of a stream. // Also try substream(n) and limit(n). List<String> result8 = strings.stream() .substream(2, 5) .collect(Collectors.toList()); System.out.println("result8 = " + result8); // Add intermediate operations to form a longer pipeline. // The peek method calls the lambda with each value as it passes by. List<String> result9 = strings.stream() .filter(s -> s.length() > 4) .peek(s -> System.out.println(" peeking at " + s)) .map(s -> s.substring(0,1)) .collect(Collectors.toList()); System.out.println("result9 = " + result9); // Most operations are stateless in that they operate on each // value as it passes by. Some operations are "stateful". The // distinct() operation builds up a set internally and passes // through only the values it hasn't seen yet. List<String> result10 = strings.stream() .map(s -> s.substring(0,1)) .distinct() .collect(Collectors.toList()); System.out.println("result10 = " + result10); // The sorted() operation is also stateful, but it has to buffer // up all the incoming values and sort them before it can emit // the first value downstream. List<String> result11 = strings.stream() .map(s -> s.substring(0,1)) .sorted() .collect(Collectors.toList()); System.out.println("result11 = " + result11); } // ========== SEQUENTIAL, LAZY, AND PARALLEL PROCESSING ========== // A fairly stupid primality tester, useful for consuming a lot // of CPU given a small amount of input. Don't use this code for // anything that really needs prime numbers. static boolean isPrime(long num) { if (num <= 1) return false; if (num == 2) return true; long limit = (long)Math.sqrt(num); for (long i = 3L; i <= limit; i += 2L) { if (num % i == 0) return false; } return true; } public static void lazy_and_parallel() { // Adjust these parameters to change the amount of CPU time // consumed by the prime-checking routine. long start = 1_000_000_000_000_000_001L; // 10^15 (quadrillion) + 1 long count = 100L; // Sequential check. Takes about 30 seconds on a 2009 MacBook Pro // (2.66GHz Core2Duo). There should be four primes found. long time0 = System.currentTimeMillis(); LongStream.range(start, start + count, 2L) .filter(n -> isPrime(n)) .forEach(n -> System.out.println(n)); long time1 = System.currentTimeMillis(); System.out.printf("sequential: %.1f seconds%n", (time1-time0)/1000.0); // Truncate the stream after three results. The full range need not // be checked, so this completes more quickly (about 23 seconds). LongStream.range(start, start + count, 2L) .filter(n -> isPrime(n)) .limit(3) .forEach(n -> System.out.println(n)); long time2 = System.currentTimeMillis(); System.out.printf("limited: %.1f seconds%n", (time2-time1)/1000.0); // Run the full range in parallel. With two cores, this takes about // half the time of the first run (plus some overhead) completing // typically in 16 seconds. Note that results are probably returned // in a different order. // Where are the threads? A parallel stream is split into tasks // which are executed by the "common fork-join thread pool," new in // Java SE 8. See java.util.concurrent.ForkJoinPool. LongStream.range(start, start + count, 2L) .parallel() .filter(n -> isPrime(n)) .forEach(n -> System.out.println(n)); long time3 = System.currentTimeMillis(); System.out.printf("parallel: %.1f seconds%n", (time3-time2)/1000.0); } // ========== REDUCTION ========== static List<String> words = Arrays.asList( "Experience", "is", "simply", "the", "name", "we", "give", "our", "mistakes."); // Oscar Wilde // Compute the sum of the lengths of the words. // The non-streamy approach, using a for-loop. static void length0() { int total = 0; for (String s : words) total += s.length(); System.out.println(total); } // An attempt at a streamy approach, mutating a captured local variable. // DOES NOT WORK. Captured locals cannot be mutated (they must be // effectively final). // If captured locals could be mutated, they would need to outlive // their enclosing scope, thus they'd have to reside on the heap. This // implies (a) they'd be visible from multiple threads and be subject // to race conditions; and (b) they'd be susceptible to memory leaks. // See: http://www.lambdafaq.org/ // what-are-the-reasons-for-the-restriction-to-effective-immutability/ // static void length1() { // int total = 0; // words.stream() // .map(s -> s.length()) // .forEach(len -> total += len); // System.out.println(total); // } // Work around the inability to mutate a captured local by using a // single-element array. DO NOT DO THIS. THIS IS BAD STYLE. This basically // buys into all the disadvantages local variables would have if they // were moved to the heap. Array elements cannot be synchronized, and // they cannot be volatile either, so it is essentially impossible to // write race-free algorithms with them. AGAIN, DO NOT DO THIS. YOU WILL // GET BURNED. static void length2() { int[] total = new int[0]; words.stream() .map(s -> s.length()) .forEach(len -> total[0] += len); System.out.println(total[0]); } // Work around the inability to mutate a captured local variable by // mutating an AtomicInteger. This is allowed, since the reference to // the AtomicInteger is final, but the AtomicInteger itself can be // mutated. What's more, it can be mutated safely from multiple threads. // This works, but is poor style, as it results in contention among // the threads all attempting to mutate the same variable. See // slides 8-9. static void length3() { AtomicInteger total = new AtomicInteger(0); words.stream() .map(s -> s.length()) .forEach(n -> total.addAndGet(n)); System.out.println(total.get()); } // Summation using reduction. See slides 10-14. static void length4() { int total = words.stream() .map(s -> s.length()) .reduce(0, (i, j) -> i + j); System.out.println(total); } // Use method reference instead of a lambda for addition. static void length5() { int total = words.stream() .map(s -> s.length()) .reduce(0, Integer::sum); System.out.println(total); } // Use convenience method sum() instead of explicit reduce() method. // Note that we've switched from map() to mapToInt() here. The // plain map() results in Stream<Integer>, which works fine above, // but it does add boxing and unboxing overhead. Using mapToInt() // results in an IntStream which not only is more efficient, it also // has the convenience sum() method on it. static void length6() { int total = words.stream() .mapToInt(s -> s.length()) .sum(); System.out.println(total); } // ========== GROUPING ========== // These couple grouping examples illustrate "mutable reduction" // operations. (See the java.util.stream package documentation.) // Many kinds of reductions, such as summation, combine values to // create new values. Sometimes we want to build up a data structure // like a map. We could combine maps to create new maps, but this would // result in excessive copying. Instead, we perform careful mutation in // a collect() call at the end of a pipeline. // A "Collector" is an object that represents a set of functions that can // handle parallel mutation and combining of intermediate results. Of // course, the intermediate and final results must be thread-safe data // structures, if the reduction is done in parallel. // A full explanation of a Collector is beyond the scope of this example. // As set of prepared Collector objects can be obtained from the Collectors // utility class. We will show a particular kind of Collector that does // grouping. The idea is, for each value in the stream, a "classifier" // function is run over it. Typically, multiple values in the stream will // produce the same result from the classifier function. The values from // the stream are then gathered into a Map, whose keys are the results // of the classifier function, and whose values are lists of values that // correspond to the classifier results. // This example groups words by their first letter. Thus, given a stream // of strings // // one two three four five six seven eight nine ten // // the resulting map would have key-value pairs // // "t" => ["two", "three", "ten"] // "f" => ["four", "five"] // "o" => ["one"] // // and so forth. static void grouping1() { Map<String, List<String>> grouping1 = strings.stream() .collect(Collectors.groupingBy(s -> s.substring(0,1))); System.out.println("grouping1 = " + grouping1); } // The example above has a hard-coded policy of accumulating // the grouped stream values into a list. What if we didn't want // a list, but instead we wanted to combine the grouped values // together? // The groupingBy() method has an overload that takes a "downstream" // collector that takes each grouped value and combines it with // other values in the same grouping. // In this example we don't want to combine all the grouped values into // a list, but instead we want to get the sum of their lengths. To // do this, we use a similar groupingBy() call, but add a second // argument Collectors.reducing() to which we specify how to combine // (reduce) the values. For a reduction we have to provide an initial // value of zero; the second argument is how to get the length of a // single string, and the third argument is how to combine the length // of the current string with the running total so far. Note, this // reduction occurs *within* each group. // Thus, the result is Map whose values aren't lists, but instead are // integers representing the sums of the lengths of the strings in // that group: // // "t" => 11 // "f" => 8 // "o" => 3 // // and so forth. static void grouping2() { Map<String, Integer> grouping2 = strings.stream() .collect(Collectors.groupingBy(s -> s.substring(0,1), Collectors.reducing(0, s -> s.length(), Integer::sum))); System.out.println("grouping2 = " + grouping2); } // Comment or uncomment the statements below to control // what you want to run. public static void main(String[] args) throws IOException { // sources_and_operations(); // lazy_and_parallel(); // length2(); // length3(); // length4(); // length5(); // length6(); // grouping1(); grouping2(); } }
Leave a Reply