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)