Modern Java Notes
Notes for the modern Java (Java 8+.)
Method reference and lambdas
-
Java 8+ treats functions and lambdas as first-class citizens, which means we can pass functions around using method reference. Note that lambdas can only capture
final
variables in the same scope.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
inventory.sort(comparing(Apple::getWeight)); // Or inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())); // And the types can be inferred inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight())); // Instead of Collections.sort(inventory, new Comparator<Apple>() { public int compare(Apple a1, Apple a2) { return a1.getWeight().compareTo(a2.getWeight()); } }); File[] hiddenFiles = new File(".").listFiles(File::isHidden); // Instead of File[] hiddenFiles = new File(".").listFiles(new FileFilter() { public boolean accept(File file) { return file.isHidden(); } }); filterApples(inventory, (Apple a) -> a.getWeight() > 150 ); // We can also use a Predicate to achieve behavior parameterization. public interface Predicate<T> { boolean test(T t); } public static <T> List<T> filter(List<T> list, Predicate<T> p) { List<T> result = new ArrayList<>(); for(T e: list) { if(p.test(e)) { parameter T result.add(e); } } return result; } filter(numbers, (Integer i) -> i % 2 == 0); Thread t = new Thread(() -> System.out.println("Hello world")); // Instead of Thread t = new Thread(new Runnable() { public void run() { System.out.println("Hello world"); } }); // Callable is like the upgraded Runnable. It sends the task to a tread pool and the result // is stored in a Future. ExecutorService executorService = Executors.newCachedThreadPool(); Future<String> threadName = executorService.submit( () -> Thread.currentThread().getName()); // Instead of Future<String> threadName = executorService.submit(new Callable<String>() { @Override public String call() throws Exception { return Thread.currentThread().getName(); } }); // We can also use .and() .or() to create more complicated lambdas. Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(apple -> apple.getWeight() > 150).or(apple -> GREEN.equals(apple.getColor())); // Composing functions. f.andThen(g) // g(f(x)) f.compose(g) // f(g(x))
Streams
-
Streams let us manipulate collections in a declarative way. By using streams, we get parallel processing for free.
1 2 3 4 5 6 7
import static java.util.stream.Collectors.toList; // Sequential. List<Apple> heavyApples = inventory.stream() .filter((Apple a) -> a.getWeight() > 150).collect(toList()); // Parallel. Or we can also use .parallel(). List<Apple> heavyApples = inventory.parallelStream() .filter((Apple a) -> a.getWeight() > 150).collect(toList());
-
Streams are like generators in Python. They are processed in-demand. Some stream functions:
1 2 3 4 5 6 7 8
List<String> lowCaloricDishesName = menu.parallelStream() .filter(d -> d.getCalories() < 400) .sorted(comparing(Dishes::getCalories)) .map(Dish::getName) .distinct() .limit(3) .collect(toList()); // or .count() or .forEach()
-
Use
.flatMap()
to flatten each stream into a single stream.1 2 3 4 5 6
List<String> uniqueCharacters = words.stream() .map(word -> word.split("")) .flatMap(Arrays::stream) .distinct() .collect(toList());
-
.anyMatch()
,.allMatch()
, and.nonMatch()
return a bool. -
Some other functions:
1 2 3 4
int sum = number.stream().reduce(0, (a, b) -> a + b); sum = numbers.stream().reduce(0, Integer::sum); Optional<Integer> sum = numbers.stream().reduce(Integer::sum); Optional<Integer> max = numbers.stream().reduce(Integer::max);
-
Numeric streams. Summing a stream is expensive due to boxing. We can use
IntStream
instead.1 2 3 4 5 6 7 8 9 10
int calories = menu.stream() .mapToInt(Dish::getCalories) .sum(); // We can convert IntStream back using .boxed(). // For max and min, IntStream returns OptionalInt. // range() is end exclusive. IntStream evenNumbers = IntStream.rangeClosed(1, 100) .filter(x -> x % 2 == 0); int fifty = evenNumbers.count()
-
Create streams:
1 2 3 4 5 6 7 8 9 10 11 12
Stream<String> stream = Stream.of("Modern", "Java", "In", "Action"); stream.map(String::toUpperCase).forEach(System.out::println) int[] nums = [1, 2, 3, 4] int ten = Arrays.stream(nums).sum(); // For unbounded infinite streams: IntStream.iterate(0, n -> n + 4) .takeWhile(n -> n < 100) .forEach(System.out::println); Stream.generate(Math::random) .limit(5) .forEach(System.out::println); IntStream ones = IntStream.generate(() -> 1);
-
Collectors:
.toList()
,.counting()
,.maxBy()
,.groupingBy()
for.collect()
. -
Summarization:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
int totalCalories = menu.stream().collect(summingInt(Dish::getCalories); // or averagingInt(), summarizingInt(). // is the same as: int totalCalories = menu.stream().collect(reducing( 0, Dish::getCalories, (i, j) -> i + j)); // and: int totalCalories = menu.stream().collect(reducing( 0, Dish::getCalories, Integer::sum)); String shortMenu = menu.stream().map(Dish::getName).collect(joining(", ")); // .map() can be omitted if there's a toString() implemented. // .groupingBy() and .partitioningBy() public boolean isPrime(int candidate) { int candidateRoot = (int) Math.sqrt((double) candidate); return IntStream.rangeClosed(2, candidateRoot).nonMatch(i -> candidate % i == 0); } public Map<Boolean, List<Integer>> partitionPrimes(int n) { return IntStream.rangeClosed(2, n).boxed() .collect(partitioningBy(candidate -> isPrime(candidate))); }
Default methods
-
Default methods for an interface allow concrete implementations not have to change.
1 2 3 4 5
// In List default void sort(Comparator<? super E> c) { Collections.sort(this, c); } // This made it possible to call apples.sort().
Optional
-
Optional<T>
is a better null type:1 2 3 4 5 6 7 8 9
menu.stream() .filter(Dish::isVegetarian) .findAny() // or findFirst(). Returns Optional<T> .ifPresent(dish -> System.out.println(dish.getName())); isPresent() // returns a bool isPresent(Consumer<T> block) // executed only when the optional is not null get() // returns the value if present; otherwise it throws NoSuchElementException orElse(T other) // other is the default value if it's not present.
Miscellaneous
-
Diamond operator
<>
:1
List<String> listOfStrings = new ArrayList<>(); // The type here will be inferred.