Contents:
- Re-think
- Outline
- Revisit – Defining a method
- Lambda Expressions
- Syntax
- Implicit Parameter Types
- Implicit Return
- One Parameter
- Method Reference
- Lambdas Summary
- Quiz
- More Practise
- Function Interface
- An example - Consumer
- Instance Instantiation
- Functional Interfaces and Lambdas
- Using Functional Interfaces
- Streams
- Revisit - working with Collections
- What are Streams?
- Stream Pipelines
- Lazy vs eager operations
- Stream Advantages
- Creating Streams from Arrays
- Creating Streams from Collections
- Creating ordered sequence of integer Streams
- Common Intermediate Operations
- Filtering
- One way to understand a stream’s method
- Sorting
- Transforming
- Terminal Operations
- “Iterating"
Re-think
Openning Problem
Given a list or array of Person objects
public static List<Person> generatePersonList() { List<Person> l = new ArrayList<>(); l.add(new Person("John", "Tan", 32, 2)); l.add(new Person("Jessica", "Lim", 28, 3)); l.add(new Person("Mary", "Lee", 42, 2)); l.add(new Person("Jason", "Ng", 33, 1)); l.add(new Person("Mike", "Ong", 22, 0)); return l; }
class Person { private String firstName; private String lastName; private int age; private int kids; // Constructors, getters // omitted for brevity }
How can we print the full names of the 3 youngest people?
Opening Problem-Algorithm
- Sort the list of people by age, in ascending order
- Get the first 3 people from the stored list
- Get the respective names
A Java Solution
static void sortList(List<Person> persons) { for (int i = 0; i < persons.size(); i++) { for (int j = i + 1; j < persons.size(); j++) { if (persons.get(j).getAge() < persons.get(i).getAge()) { // Switch position of two persons Person temp = persons.get(i); persons.set(i, persons.get(j)); persons.set(j, temp); } } } }
-
Sort the list by age using Bubble SortNote: there’re many other ways to implement it.
static List<String> getTopThreeNames( List<Person> persons) { List<String> retNames = new ArrayList<String>(); for (int i = 0; i < 3; i++) { Person curPerson = persons.get(i); retNames.add(curPerson.getFirstName() + " " + curPerson.getLastName()); } return retNames; }
Get the first 3 people in the sorted list, and.
Get the respective names.
A LINQ Solution
If working with C#, we can use LINQ
Persons.OrderBy(person => person.Age) .Take(3) .Select(person => new { FullName = person.FirstName + " " + person.LastName });
Outline
Revisit – Defining a method
A method definition consist of
- Method name
- Parameters
- Return type
- Method body
- Return value
int min( int num1, int num2){ if(num1<num2){ return num1; } return num2 }
How to make method definition shorter /more concise ?
Lambda Expressions
A lambda expression represents an anonymous methods, in short-hand notation
it consists of:
Method name- Parameters
Return type- Method boday
- Return value
()->{} Lambda Expression (e1,e2)->e1+e2
Syntax
a lambda consists of a parameter list followed by theh arrow token (->) and a body
(parameter list) -> {statements}
Method:
int min(int num1,int num2){ if(num1<num2){ return num1; } return num2; }
Lambda:
(int num1,int num2)->{ if(num1<num2){ return num1; } return num2; }
Implicit Parameter Types
The parameter types are usually omitted
Method:
int min(int num1, int num2) { if (num1 < num2) { return num1; } return num2; }
Lambda:
(num1, num2) -> { if (num1 < num2) { return num1; } return num2; }
The compiler can determine the parameter types by the lambda’s context
Implicit Return
When the body contains only one expression, the return keyword and curly braces {} may also be omitted
Method:
int sum(int num1, int num2) { return num1 + num2; }
Lambda:
(num1, num2) -> { return num1 + num2; } //...... (num1, num2) -> num1 + num2
The compiler can also determine the return type by the lambda’s context
Question
What is/are the data type(s) of variables n1, n2, and n1 + n2?
public static void sum() { double[] arr = { 1.1, 2.2, 3.3 }; double sum = DoubleStream.of(arr) .reduce((n1, n2) -> n1 + n2)) .getAsDouble(); System.out.println("Sum: " + sum); }
One Parameter
When the parameter list contains only one parameter, parameter parentheses may also be omitted
//Method: void printValue( double myValue) { System.out.println(myValue); } //Lambda: (myValue) -> System.out.println(myValue) //Lambda myValue -> System.out.println(myValue)
In the previous slide, how can the compiler know the method’s return type is integer while it is void in this slide?
Method Reference
When a lambda expression does nothing but calls one existing method, we can just refer to ClassName::methodName
//Method: void printValue(double myValue) { System.out.println(myValue); } //Lambda: myValue -> System.out.println(myValue) //Method Reference: System.out::println
//Method: int compare(Person a, Person b) { return a.compareByAge(b); } //Lambda (a, b) -> a.compareByAge(b) //Method Reference: Person::compareByAge
Lambdas Summary
- A lambda is a method with no method name, no return type
- A nd remove types of parameters
- One expression in method body? Remove return keyword and curly braces {}
- Only one parameter? Remove parameter parentheses ()
Quiz
Convert each of the following methods to lambdas or method references, in its shortest form
int pow(int base, int exp) { int res = 1; for (int i = 0; i < exp; i++) { res *= base; } return res; } //Answer: (base, exp) -> { int res = 1; for (int i = 0; i < exp; i++) { res *= base; } return res; }
Convert each of the following methods to lambdas or method references, in its shortest form
boolean isPrime(int num) { for (int divisor = 2; divisor < num - 1; divisor++) { if (num % divisor == 0) return false; } return true; } //Answer: num -> { for (int divisor = 2; divisor<num-1; divisor++) { if (num % divisor == 0) return false; } return true; }
More Practise
Convert the following methods to lambda expressions or method references, in its shortest form
String getFullName(Person p) { return p.getFirstName() + " " + p.getLastName(); } //Answer: p -> p.getFirstName() + " " + p.getLastName()
void printFullName(Person p) { System.out.println(p.getFirstName() + " " + p.getLastName()); } //Answer: p -> System.out.println(p.getFirstName() + " "+ p.getLastName())
String getFirstName(Person p) { return p.getFirstName(); } class Person { private String firstName; private String lastName; // Other code omitted // for brevity } //Answer: Person::getFirstName
Function Interface
- A functional interface contains exactly one abstract method, called functional method
- Compiler can map a lambda to some respective functional interfaces
interface Consumer<T> { void accept(T t); } interface Predicate<T> { boolean test(T t); } interface BinaryOperator<T> { T apply(T t1, T t2); }
An example - Consumer
Interface Consumer, method accept(T) Perform a*task* with the given T, e.g.,
interface Consumer<T> { void accept(T t); }
Like other interfaces, we need to implement the
abstract method when implementing a functional
interface
How to implement and instantiate instances of functional interfaces?
Instance Instantiation
We can implement and instantiate an instance of a functional interface by new keyword and implement its abstract methods
new Consumer< Integer>() { @Override public void accept(Integer num) { System.out.println(num); } }
- To instantiate an instance, start with new and the interface name Consumer
- Because interface Consumer supports generic type, specify the generic type. In here, it is Integer
- Implement the interface’s abstract method. In here, accept(T) becomes accept(Integer)
Functional Interfaces and Lambdas
We can also implement and instantiate an instance of a functional interface with the respective lambdas
new Consumer<Integer>() { @Override public void accept(Integer num) { System.out.println(num); } } //lambda num -> System.out.println(num) //Lambda System.out::println
Both lambdas can be used as instances of Consumer
Conversely, given the above lambdas, can the compiler know how to map to Consumer?
Hint: a functional interface only has 1 functional method
Using Functional Interfaces
Functional interfaces are usually used as method parameters For example,later we’ll study streams, which can be iterated using
forEach(Consumer) method
Integer[] arr = {1, 2, 3, 4}; Arrays.stream(arr) .forEach( new Consumer<Integer>() { @Override public void accept(Integer num) { System.out.println(num); } }); Integer[] arr = {1, 2, 3, 4}; Arrays.stream(arr) .forEach( num -> System.out.println(num));
In both cases, an instance of Consumer is used as the method parameters
A Example -Pridicate
Interface Predicate, method test(T) Test whether the T argument satisfy a condition
interface Predicate<T> { boolean test(T t); }
Like Consumer, we can implement and instantiate an instance of Predicate as follows
new Predicate<Person>() { @Override public boolean test(Person p) { return p.getKids() == 2; } } //Lambda
An example – BinaryOperator
Interface BinaryOperator, method apply(T, T)Performs an operation on the 2 arguments (such as a calculation) and returns a value of the same type
interface BinaryOperator<T> { T apply(T t1, T t2); }
Some lambdas that may be used as instances of
BinaryOperator
(x, y) -> x + y (str1, str2) -> str1 + " " + str2 (x, y) -> { if (x > y) return x - y; return y - x; };
Common Functional Interfaces
interface | Method | Arguments | What does it do | Return |
---|---|---|---|---|
Consumer | accept | T | Perform a task with T, e.g., printing | void |
Function | apply | T | Call a method on the T argument and return that method’s result | R |
Predicate | test | T | Test whether the T argument satisfies a condition | bool |
Supplier | get | Empty | Produce a value of type T, e.g., creating a collection object | T |
UnaryOperator | apply | T | Perform an operation on the T argument and return a value of T | T |
BinaryOperator | apply | T, T | Perform an operation on the two | T |
Streams
Revisit - working with Collections
When processing a collection, we usually
- Iterate over its elements
- Do some work with each element
public static int sumOfEven(int[] arr) { int sum = 0; for (int num: arr) { if (num % 2 == 0) { sum += num; } } return sum; }
What are Streams?
- A Stream is a sequence of elements on which we perform tasks
- Specify only what we want to do
- Then simply let the Stream deal with how to do it
public static int sumOfEven2(int[] arr) { return IntStream .of(arr) .filter(x -> x%2==0) .sum(); }
Stream Pipelines
Source=>Create Stream=>Operation 1=>Operation2=>.....Operation N=>Terminal Operation=>Result
Source: Usually , an array or a collection
Operation: Filtering, sorting,type conversions, mapping...
Terminal operation: Aggregate results, eg., count, sum or collecting a collection.
IntStream.of(arr).filter(x -> x % 2 ==0).sum() //arr: Source //of: Create stream //filter: Intermediate operation //sum: Terminal operation
Lazy vs eager operations
Intermediate operations are lazy
=> Not perform until a
terminal operation is
called
Terminal operations are eager
=> Perform right away when being called
Stream Advantages
- Allows us to write more declarative and more concise programs
- Allows us to focus on the problem rather than the code
- Facilitates parallelism
Creating Streams from Arrays
Streams can be created from arrays with different
approaches
public static void streamFromArray1() { int[] arr = {3, 10, 6, 1, 4}; IntStream.of(arr) .forEach(e -> System.out.print(e + " ")); } //3 10 6 1 4 public static void streamFromArray2() { Integer[] arr = {2, 9, 5, 0, 3}; Arrays.stream(arr) .forEach(e -> System.out.print(e + " ")); } //2 9 5 0 3
Creating Streams from Collections
Streams can also be created from any implementation of Collection interface, e.g., List, Set…
public static void streamFromList() { List<String> myList = new ArrayList<>(); myList.add("Hi"); myList.add("SA"); myList.add("students"); myList.stream() .forEach(System.out::println); } //Hi //SA //students
Creating ordered sequence of integer Streams
Ordered sequence of integers can be created using
IntStream.range() and IntStream.rangeClosed()
public static void orderedSequenceStream1() { IntStream .range(1, 10) .forEach(e -> System.out.print(e + " ")); } //1 2 3 4 5 6 7 8 9 public static void orderedSequenceStream2() { IntStream .rangeClosed(1, 10) .forEach(e -> System.out.print(e + " ")); } //1 2 3 4 5 6 7 8 9 10
Common Intermediate Operations
Method | Parameter | Description |
---|---|---|
filter | Predicate | Returns a stream consisting of the elements of this stream that match the given predicate. |
sorted | Comparator | Returns a stream consisting of the elements of this stream, sorted according to the provided Comparator. |
map | Function | Returns a stream consisting of the results of applying the given function to the elements of this stream. |
distinct | No | Returns a stream consisting of the distinct elements (according to Object.equals(Object)) of this stream. |
limit | long | Returns a stream consisting of the elements of this stream, truncated to be no longer than the given number in length. |
skip | long | Returns a stream consisting of the remaining elements of this stream after discarding the first n elements of the stream. If this stream contains fewer than n elements then an empty stream will be returned |
Filtering
Elements in a stream can be filtered using filter(Predicate)
public static List<Person> generatePersonList() { List<Person> l = new ArrayList<>(); l.add(new Person("John", "Tan", 32, 2)); l.add(new Person("Jessica", "Lim", 28, 3)); l.add(new Person("Mary", "Lee", 42, 2)); l.add(new Person("Jason", "Ng", 33, 1)); l.add(new Person("Mike", "Ong", 22, 0)); return l; } public static void filtering() { List<Person> persons = generatePersonList(); persons .stream() .filter( x -> x.getKids() == 2) .forEach(System.out::println); } class Person { private String firstName; private String lastName; private int age; private int kids; // Other code omitted // for brevity } //John, Tan, 32, 2 //Mary, Lee, 42, 2
One way to understand a stream’s method
.filter(x -> x.getKids() == 2)
- I want to filter elements in a stream
- Ok, you need to call filter() method, and give me a Predicate in form of a lambda
- I will loop through every element in the stream, and with the current element…
- Let’s name that element as x, the left side of the lambda
- You need to let me know what to do with x, using any method of the data type in the stream holding x. For example, Person in the last slide
- I want to filter only x having 2 kids, so I return a Boolean Expression with such condition. It is put in the body, the right side of the lambda
=>Most of stream methods happen in this manner. Step 2, 4 and 6 change depending on the scenario
Sorting
Streams can be sorted using sorted(Comparator) As usual, a Comparator object can be created using a lambda
public static List<Person> generatePersonList() { List<Person> l = new ArrayList<>(); l.add(new Person("John", "Tan", 32, 2)); l.add(new Person("Jessica", "Lim", 28, 3)); l.add(new Person("Mary", "Lee", 42, 2)); l.add(new Person("Jason", "Ng", 33, 1)); l.add(new Person("Mike", "Ong", 22, 0)); return l; } public static void sortBySingleField() { List<Person> persons = generatePersonList(); persons .stream() .sorted( (p1, p2) -> p1.getFirstName().compareTo( p2.getFirstName())) .forEach(x -> System.out.println(x)); } //Jason, Ng, 33, 1 //Jessica, Lim, 28, 3 //John, Tan, 32, 2 //Mary, Lee, 42, 2 //Mike, Ong, 22, 0
Alternatively, a Comparator object can be created using Comparator.comparing(Function)
public static List<Person> generatePersonList() { List<Person> l = new ArrayList<>(); l.add(new Person("John", "Tan", 32, 2)); l.add(new Person("Jessica", "Lim", 28, 3)); l.add(new Person("Mary", "Lee", 42, 2)); l.add(new Person("Jason", "Ng", 33, 1)); l.add(new Person("Mike", "Ong", 22, 0)); return l; } public static void sortBySingleField () { List<Person> persons = generatePersonList(); persons .stream() .sorted( Comparator.comparing( Person::getFirstName)) .forEach(x -> System.out.println(x)); } //Jason, Ng, 33, 1 //Jessica, Lim, 28, 3 //John, Tan, 32, 2 //Mary, Lee, 42, 2 //Mike, Ong, 22, 0
Streams can also be sorted with multiple fields and in reversed order
public static List<Person> generatePersonList() { List<Person> l = new ArrayList<>(); l.add(new Person("John", "Tan", 32, 2)); l.add(new Person("Jessica", "Lim", 28, 3)); l.add(new Person("Mary", "Lee", 42, 2)); l.add(new Person("Jason", "Ng", 33, 1)); l.add(new Person("Mike", "Ong", 22, 0)); return l; } public static void sortByMultiFields () { List<Person> persons = generatePersonList(); persons.stream() .sorted(Comparator .comparing(Person::getKids) .thenComparing(Person::getAge) .reversed()) .forEach(x -> System.out.println(x)); } //Jason, Ng, 33, 1 //Jessica, Lim, 28, 3 //John, Tan, 32, 2 //Mary, Lee, 42, 2 //Mike, Ong, 22, 0
Transforming
Each of elements in a Stream can be transformed to another value (even another type) using map(Function)
public static List<Person> generatePersonList() { List<Person> l = new ArrayList<>(); l.add(new Person("John", "Tan", 32, 2)); l.add(new Person("Jessica", "Lim", 28, 3)); l.add(new Person("Mary", "Lee", 42, 2)); l.add(new Person("Jason", "Ng", 33, 1)); l.add(new Person("Mike", "Ong", 22, 0)); return l; } public static void transforming1() { List<Person> persons = generatePersonList(); persons .stream() .sorted(Comparator.comparing( Person::getFirstName)) .map( x -> x.getFirstName() + " " + x.getLastName()) .forEach(System.out::println); } //Jason Ng //Jessica Lim //John Tan //Mary Lee //Mike Ong
Of course, map(Function) can also be applied to
other types of streams
public static void transforming2() { int[] arr = {0, 1, 2, 3, 4, 5}; IntStream.of(arr) .map(e -> e * 2) .forEach(e -> System.out.print(e + " ")); } //0 2 4 6 8 10 public static void transforming3() { String[] arr = {"aa", "bb", "cc", "dd"}; Arrays.stream(arr) .map(String::toUpperCase) .forEach(e -> System.out.print(e + " ")); } //AA BB CC DC
A stream can be mapped to a numeric stream
public static void transforming4() { String[] names = {"John", "Jessica", "Mary", "Jason", "Mike"}; int maxLength = Stream.of(names) .mapToInt(x -> x.length()) .max() .getAsInt(); System.out.println("Name with maximum length is " + maxLength); //Name with maximum length is 7
Terminal Operations
Method | Parameters | Description |
---|---|---|
forEach | Consumer | Performs an action for each element of this stream. |
reduce | T, BinaryOperator | Performs a reduction on the elements of this stream, using the provided identity value and an associative accumulation function, and returns the reduced value. |
reduce | BinaryOperator | Performs a reduction on the elements of this stream, using an associative accumulation function, and returns an Optional describing the reduced value, if any. |
min | Comparator | Returns the minimum element of this stream according to the provided Comparator. This is a special case of a reduction. |
max | Comparator | Returns the maximum element of this stream according to the provided Comparator. This is a special case of a reduction. |
average | No Return | the average of all elements in a numeric stream. |
“Iterating"
Performs an action for each element of the stream using forEach(Consumer)
public static void forEachStream() { List<String> list = new ArrayList<>(); list.add("aa"); list.add("bb"); list.add("cc"); list .stream() .forEach( e -> System.out.print(e + " ")); } //aa bb cc
- Call forEach()
- Given a Consumer object in the form of lambda as the argument We can think like this: given each element e, what Java should do with it (and return nothing as defined in Consumer interface)? In here,we ask Java to print the value of element e
=>List also has forEach() method, operating in the
same manner
Top comments (0)