// This program contains some examples of Java 8 constructs. Three of the most // important higher-order functions to understand are map, filter, and reduce. // higher order function are methods that take in methods as an argument import java.util.*; import java.util.stream.*; import java.util.function.*; public class Java8Demo { public static void main(String[] args) { listExample(); primesExample(); parallelExample(); functionalExample(); } // some Java 8 examples using a list of strings public static void listExample() { List words = Arrays.asList("is", "mayonnaise", "an", "instrument?"); // old fashioned way to print the words System.out.println("For loop approach"); for (int i = 0; i < words.size(); i++) { System.out.print(words.get(i) + " "); } System.out.println(); // Java 5 introduced the foreach loop and Iterable interface System.out.println("For-each loop approach"); for (String word : words) { System.out.print(word + " "); } System.out.println(); // Java 8 has a forEach method as part of the Iterable interface // The expression is known as a "lambda" (an anonymous function) words.stream().forEach((word) -> System.out.print(word + " ")); System.out.println(); // but in Java 8, why use a lambda when you can refer directly to the // appropriate function? words.stream().forEach(System.out::print); System.out.println(); // this isn't correct, you'll complain, because we want to have spaces // attached to the output...no problem, introduce a call on map to // transform the data before it is printed words //List: {"is", "mayonnaise", "an", "instrument"} .stream() //Stream: {"is", "mayonnaise", "an", "instrument"} .map((word) -> word + " ") // Stream: {"is ", "mayonnaise ", "an" , "instrument "} .forEach(System.out::print); //void - print each string in the Stream System.out.println(); } // example of finding the sum of the primes between 1 and 20,000 with and // without parallel processing public static void primesExample() { System.out.println("Loop-based approach"); // perform a slow operation with the iterative approach printSumIterative(); System.out.println("Stream approach"); // perform a slow operation and report elapsed time printSum(IntStream.range(1, 20001)); System.out.println("Parallel Stream Approach"); // do it again, but with a parallel stream printSum(IntStream.range(1, 20001).parallel().parallel()); System.out.println(); } // Returns true if n is a prime number, determined with in an // iterative manner, and false otherwise public static boolean isPrimeLoop(int n) { int factors = 0; for (int i = 1; i <= n; i++) { if (n % i == 0) { factors++; } } return factors == 2; } // Returns true if n is a prime number, determined with a // stream, and false otherwise public static boolean isPrime(int n) { return IntStream .range(1, n + 1) .filter((i) -> n % i == 0) .reduce(0, (factors, num) -> factors + 1) == 2; } // Prints the sum of all the primes between 1 and 20,000 // along with the time it took, done iteratively public static void printSumIterative() { long start = System.currentTimeMillis(); int sum = 0; for (int i = 1; i <= 20000; i++) { if (isPrimeLoop(i)) { sum += i; } } double elapsed = (System.currentTimeMillis() - start) / 1000.0; System.out.println("n = " + sum + ", time = " + elapsed); } // times how long it takes to find the sum of the primes in the stream public static void printSum(IntStream s) { // run the algorithm and time it long start = System.currentTimeMillis(); int n = s.filter(Java8Demo::isPrime).sum(); double elapsed = (System.currentTimeMillis() - start) / 1000.0; System.out.println("n = " + n + ", time = " + elapsed); } // it's great to be able to make a stream parallel to make a program run // faster, but sometimes that doesn't make sense, as in this example public static void parallelExample() { System.out.println("Let's print 1 through 20"); System.out.print("sequentially:"); IntStream.range(1, 21) .forEach(n -> System.out.print(" " + n)); System.out.println(); System.out.print("in parallel :"); IntStream.range(1, 21) .parallel() .forEach(n -> System.out.print(" " + n)); System.out.println(); System.out.println(); } // Prints out all the values of the input array with // n added to them public static void addN(int[] input, int n) { for (int i = 0; i < input.length; i++) { System.out.println(input[i] + n); } System.out.println(); } // Prints out all the values of the input array with // n multiplied to them public static void multiplyN(int[] input, int n) { for (int i = 0; i < input.length; i++) { System.out.println(input[i] * n); } System.out.println(); } // Notes that addN and multiplyN are insanely similar! They have the // same control flow, the only difference is the operation that we // use between each element of the input array and the value n. // However, we don't have a way to factor out this code, until now! public static void functionalExample() { int[] numbers = {1,2,3}; addN(numbers, 3); multiplyN(numbers, 3); // We can store "lambdas" as a variable and pass it in as a // parameter! We can now store methods in variables. BinaryOperator add = (a,b) -> a + b; BinaryOperator multiply = (a,b) -> a * b; operateN(numbers, 3, add); operateN(numbers, 3, multiply); } // Prints out all the values of the input array with the operation // applied to each element with n public static void operateN(int[] input, int n, BinaryOperator operation) { for (int i = 0; i < input.length; i++) { // operation is a function that takes in two arguments and returns the // result of it System.out.println(operation.apply(input[i], n)); } System.out.println(); } }