1. Java 8¶
1.1. Lambda¶
Functional approach to writing methods. Anonymous functions can be passed as arguments to other methods or stored in variables. Simplifies the use of APIs that rely on callbacks or event handlers.
More complex expressions can be put between curly braces
Lambdas are used when passed as a parameter to a function
ArrayList<Integer> nums = new ArrayList<>();
nums.add(42);
nums.add(300);
nums.add(90000);
nums.forEach( (n) -> {
System.out.println(n);
});
1.1.1. Functional Interfaces¶
Functional Interfaces:
1.1.1.1. Consumer¶
Consumer:
accepts a single input and returns no output
void accept(T t);
Single Abstractor Method (SAM), Accepts single argument of type T-
default Consumer<T> andThen(Consumer<? super T> after);
default method used for composition of multiple consumers. Returns a functional Consumer interface that can be daisy chained in sequence. -
Composing Multiple consumers
Stream<String> cities = Stream.of("London", "New York", "Mexico City"); Consumer<List<String>> upperCaseConsumer = list -> { for (int i=0; i < list.size(); i++) { list.set(i, list.get(i).toUpperCase()); } }; Consumer<List<String>> printConsumer = list -> list.stream().forEach(System.out::println); upperCaseconsumer.andThen(printConsumer).accept(cities);
-
Using
Consumer
interface to store a lambda expression in a variable -
Using Lambda expression as a method parameter
interface StringFunction { String run(String str); } public class Main { public static void main(String[] args) { StringFunction exclaim = (s) -> s + "!"; StringFunction ask = (s) -> s + "?"; printFormatted("Hello", exclaim); // Hello! printFormatted("Hello", ask); // Hello? } public static void printFormatted(String str, StringFunction format) { String result = format.run(str); System.out.println(result); } }
1.1.1.2. Supplier¶
Supplier:
indicates that this implementation is a supplier of results
-
get()
-
Primary use of this interface is to enable deferred execution. For example using it with an optional. This method is triggered if optional does not have data
1.1.1.3. Predicate¶
Predicate:
Boolean-valued function of an argument. Mainly used to filter data from Steam. The filter method of a stream accepts a predicate to filter the data and returns a new stream satisfying the predicate
- test()
accepts an argument and returns a boolean value
List<String> names = Arrays.asList("Roman", "Scott", "Alex");
Predicate<String> nameStartsWithS = str -> str.startsWith("S");
names.stream().filter(nameStartsWithS).forEach(System.out::println);
-
Predicate provides default and static methods for composition and other
-
Example use
1.1.1.4. Function¶
Function:
a generic interface that takes 1 argument and produces a result. Has a Single Abstract Method (SAM) which accepts an argument of type T and produces a result of type R. Ex stream.map method.
List<String> names = Array.asList("Roman", "Scott", "Alex");
Function<String, Integer> nameMappingFunction = String::length;
List<Integer> nameLength = name.stream().map(nameMappingFunction).collect(Collectors.toList());
1.2. Streams¶
A new API for processing collections of data in a declarative way. Consists of a source, followed by zero or more intermediate operations;and a terminal operation. Streams support lazy evaluation, parallel execution, and functional operations such as map, filter, reduce, and collect.
- Stream is not a data structure and it never modifies the underlying data source.
1.2.1. Stream Creation¶
// Array
private static Employee[] arrayOfEmps = {
new Employee(1, "Jeff Bezos", 100000.0),
new Employee(2, "Bill Gates", 200000.0),
new Employee(3, "Mark Zuckerberg", 300000.0)
};
Stream.of(arrayOfEmps);
// or obtain stream from already existing list
List<Employee> empList = Arrays.asList(arrayOfEmps);
empList.stream();
Java 8 added a new stream()
method to the Collection interface
// Create a stream out of individual objects
Stream.of(arrayOfEmps[0], arrayOfEmps[1], arrayOfEmps[2]);
// Or using a Stream.builder()
Stream.Builder<Employee> empStreamBuilder = Stream.builder();
empStreamBuilder.accept(arrayOfEmps[0]);
empStreamBuilder.accept(arrayOfEmps[1]);
empStreamBuilder.accept(arrayOfEmps[2]);
Stream<Employee> empStream = empStreamBuilder.build();
1.2.2. Intermediate Operators¶
-
map
produces a new stream after applying a function to each element of the original stream. -
filter
Produces a new stream that contains elements that pass the given predicate -
findFirst
returns an Optional for the first entry in the stream -
toArray
Returns array of the stream -
flatMap
Fattens the data structure -
peek
Similar to forEach(), but unlike it it’s not terminal. Returns a new stream which can be used further.Employee[] arrayOfEmps = { new Employee(1, "Jeff Bezos", 100000.0), new Employee(2, "Bill Gates", 200000.0), new Employee(3, "Mark Zuckerberg", 300000.0) }; List<Employee> empList = Arrays.asList(arrayOfEmps); empList.stream() .peek(e -> e.salaryIncrement(10.0)) .peek(System.out::println) .collect(Collectors.toList());
peek
should only be used for debugging to observe the vales passing through. Do not use for any logic or side effects. As this might not be called in some instances
-
anyMatch
take a predicate and return a boolean true if any match -
allMatch
take a predicate and return a boolean true if all match -
noneMatch
take a predicate and return a boolean true if none match -
#ct
returns the #ct elements in the stream, eliminating duplicates. It uses the equals() method of the elements to decide whether two elements are equal or not -
limit
Limits stream to n number of elements -
skip
skips n number of elements in the stream -
sorted
sorts elements-
Natural Order
List<String> list = Arrays .asList("9", "A", "Z", "1", "B", "Y", "4", "a", "c"); List<String> sortedList = list .stream() .sorted() .collect(Collectors.toList()); // or List<String> sortedList = list.stream() .sorted((o1,o2)-> o1.compareTo(o2)) .collect(Collectors.toList()); // or List<String> sortedList = list.stream() .sorted(Comparator.naturalOrder()) .collect(Collectors.toList()); // 1 4 9 A B Y Z a c
-
In Reverse Order
List<String> list = Arrays .asList("9", "A", "Z", "1", "B", "Y", "4", "a", "c"); List<String> sortedList = list.stream() .sorted((o1,o2)-> o2.compareTo(o1)) .collect(Collectors.toList()); // OR List<String> sortedList = list.stream() .sorted(Comparator.reverseOrder()) .collect(Collectors.toList()); // c a Z Y B A 9 4 1
-
Object Sort
// name and age fields static List<User> users = Arrays.asList( new User("C", 30), new User("D", 40), new User("A", 10), new User("B", 20), new User("E", 50)); List<User> sortedList = users.stream() .sorted((o1, o2) -> o1.getAge() - o2.getAge()) .collect(Collectors.toList()); // or List<User> sortedList = users.stream() .sorted(Comparator.comparingInt(User::getAge)) .collect(Collectors.toList()); /* User{name='A', age=10} User{name='B', age=20} User{name='C', age=30} User{name='D', age=40} User{name='E', age=50} */
-
Object Reverse Order
-
1.2.3. Terminal Operations¶
-
forEach
Loops over stream elementsList<String> names = Arrays.asList("Larry", "Steve", "James"); names .stream() .forEach(System.out::println);
- It’s a terminal operation: after the operation is performed, the stream pipeline is considered consumed and can no longer be used
-
collect
Gets stuff out of the stream once we are done with it. Performs mutable fold operations (repackaging elements to some data structures and applying some additional logic.) -
toArray
Like Collect previously, but specifically return array count
Counts elements in the streamsum
sum numerical elements of the stream.-
max
-
min
-
average
-
reduce
aggregates -
summaryStatistics
-
joining
-
toSet
-
toCollection
extract the elements into any other collection by passing in aSupplier<Collection>
. We can also use a constructor reference for the Supplier -
partitioningBy
groupingBy
mapping
reducing
1.2.4. Method Types and Pipelines¶
Operations:
Intermediate:
returns stream on which further processing can be doneTerminal:
Mark the stream as consumed, after which point it can no longer be used further.
1.2.5. Stream Pipeline¶
-
A stream pipeline consists of a steam source, followed by zero or more intermediate operation, and a terminal operation.
-
Intermediate and terminal
-
Short-circuit operation: allows computations on infinite streams to complete in finite time.
1.2.6. Lazy Evaluation¶
- Computation of the source data is only performed when the terminal operation is initiated, and source elements are consumed only as needed
- All Intermediate operations are lazy, so they’re not executed until a result of a processing is actually needed.
For example, list.stream().filter(x -> x > 0).map(x -> x * 2).sum()
is a stream expression that filters a list of numbers by keeping only the positive ones, doubles each element, and returns the sum of the resulting list.
Processing streams lazily allows avoiding examining all the data when that’s not necessary. This behaviour becomes even more important when the input stream is infinite and not just very large.
1.2.7. Stream call specializations¶
There are primitive streams IntStream
, LongStream
, and DoubleStream
You can use them like this
Integer latestEmpId = empList.stream()
.mapToInt(Employee::getId) // Convert to Int stream
.max()
.orElseThrow(NoSuchElementException::new);
1.2.7.1. .of creation¶
Stream.of(1, 2, 3)
is not the same as IntStream.of(1, 2, 3)
, one is Stream<Integer>
another is IntStream
same with map()
and mapToInt()
TODO:
- Default methods: You can provide default implementations for interface methods, which can be overridden by implementing classes if needed. Default methods enable backward compatibility and multiple inheritances of behaviour in interfaces. For example,
interface A { default void foo() { System.out.println("A"); } }
is an interface with a default method foo(). - Method references: You can refer to existing methods by name instead of writing lambda expressions. Method references are useful when you want to pass a method as an argument to another method or use it as a constructor reference. For example,
System.out::println
is a method reference that refers to the println method of the System.out class. - Optional: A new class that represents a value that may or may not be present. Optional helps you avoid null pointer exceptions and write more robust code by forcing you to explicitly handle the absence of a value. For example,
Optional<String> name = Optional.ofNullable(getName()); name.ifPresent(System.out::println);
is an example of using Optional to get a name from a method that may return null and print it if it is present.