integers = new ArrayList<>(); for(int i=0; i Download presentation Presentation is loading. Please wait.
1
Java’s Stream API
2
Introduction Given a collection c and a function f, sometime we’d like to create a new collection c’ such that for each element x in c we’ll have f(x) in c’. For instance, we have the collection called intStrings with the following elements: (“1”, “2”, “3”, “4”), and the function Integer::parseInt which takes a string and parse it into an integer. Well, we could of course do the following: List<String> intStrings = Arrays.asList("1", "2", "3", "4"); List<Integer> integers = new ArrayList<>(); for(int i=0; i<intStrings.size(); i++){ int parsed = Integer.parseInt(intStrings.get(i)); integers.add(parsed); }
3
Introduction This code indeed works as intended and we get a new list, called “integers” with the elements (1, 2, 3, 4). However, as the amount of code and complexity of tasks scales up, such programming style can hurt our code’s readability. Instead, what if we could tell our collection to simply apply that function on each of it’s elements? Fortunately, we can do this, using the java Stream API! List<String> intStrings = Arrays.asList("1", "2", "3", "4"); List<Integer> integers = intStrings.stream() map(Integer::parseInt) collect(Collectors.toList());
4
Introduction Functional programming is a programming paradigm – a style of building the structure and elements of computer program. Treats computation as the evaluation of mathematical functions. Avoids changing-state and mutable data. A different approach than the object oriented programming which relies on the object’s state.
5
Functional vs Imperative Programming
6
Anonymous Class Definition: An anonymous class is a class that have no name. Allows declaring and instantiating a class at the same time. An anonymous inner class can be useful when making an instance of an object with certain “extras” such as overloading methods of a class or interface, without having to actually subclass a class. A drawback with anonymous classes is they can't have explicit constructors. You can't pass them any parameters when they are instantiated. One of the key benefits of an anonymous class is encapsulation.
7
Anonymous Class For instance:
8
Lambda Expressions Definition: A lambda expression is a block of code that is treated as an object. It can be passed as an argument to another function, or used for constructing the return value of a function. In java: lambda expressions are just a simpler way of instantiating an anonymous class. We use the following syntax to instantiate a lambda expression: (<arg 1>, …, <arg n>) -> <returned expression>; (<arg 1>, …, <arg n>) -> { <expr 1>; … <expr n>; return <expr>; };
9
Lambda Expressions In the following example:
10
Functions as variables / parameters
11
In Java In java we don’t really pass functions as arguments.
12
In Java Take a look at the previous example of anonymous class:
13
The functional interface
14
The functional interface
15
The Stream API Stream is an API (Application Programming Interface) that supports functional- style operation on streams (or collections) of elements. Intermediate operations (such as map, filter, sorted) allows to perform manipulation on the stream object. Terminal operations (collect, forEach, reduce) allows to aggregate the elements of a manipulated stream into the desired result. Let’s take a look at some of these operations. Assuming the following definition: List<Integer> lst = Arrays.asList(1, 2, 3, 4, 5);
16
Stream.map() map(f) - Takes a lambda expression f as an argument.
17
Stream.filter() filter(f) - Takes a lambda expression f as an argument. F is an object of type Predicate<T>: takes T as an argument and returns a boolean, indicating whether that argument matches the predicate. Returns a stream of elements composed of each x in lst such that f(x)=true. Stream<Integer> even = lst.stream() filter((x) -> x % 2 == 0); // Values are (2, 4)
18
Stream.reduce() reduce(id, f) - Takes an element id, and a lambda expression f as an argument. id is an object of the same type T of lst. f takes two arguments: accumulate element (starts with id) and the next element of the list (both of the same type T), perform an operation on both of them and return the result (also of type T), which in turn becomes the accumulated value of the next iteration. Returns the accumulated element once done. int sum = lst.stream() reduce(0, (acc, next) -> acc + next); // Value is 15
19
Stream.forEach() forEach(f) - Takes a lambda expression f as an argument. f is an object of type Consumer<T>: takes an element T from the stream and perform some operations. No value returned. Equivalent to our famous ‘for x : lst’ loops. We can achieve the same goal by using method-reference: lst.stream().forEach((x) -> System.out.println(x)); // Prints the elements of lst lst.stream().forEach(System.out::println);
20
Few examples Take a look at the following class for practice:
21
Few examples Create a list with the names of all the characters from the continent of Kalimdor: List<String> names = characters.stream() filter((x) -> x.getContinent() == Continent.Kalimdor) map(Character::getName) collect(Collectors.toList()); Note that in order to create a list out of a stream we need to call: .collect(Collectors.toList())
22
Few examples Find the average hit points of characters at level 120:
23
Few examples Print all characters, sorted by their hit points:
24
Few examples Given a list of names, initialize a list of characters with the same name, title ‘Grunt’, city ‘Orgrimmar’, level 15, continent ‘Kalimdor’ and hit points between 200 to 300: List<String> gruntsNames = Arrays.asList("Durotan", "Grom", "Garrosh", "Garona", "Nazgrim", "Varok"); List<Character> grunts = gruntsNames.stream() map((name) -> new Character(name, "Grunt", "Orgrimmar", 15, (Math.random() * ), Continent.Kalimdor)) collect(Collectors.toList());
Similar presentations © 2025 SlidePlayer.com. Inc. Log in
Similar presentations
Presentation on theme: "Java’s Stream API."— Presentation transcript:
Characteristic Imperative approach Functional approach Programmer focus How to perform tasks (algorithms) and how to track changes in state What information is desired and what transformations are required? State changes Important Almost non-existent Order of execution Low importance Primary flow control Loops, conditionals and function calls Function calls, including recursion Primary manipulation unit Instances of structures or classes Functions as first-class objects and data collections
public interface Printer { void print(); } public static void main(String[] args){ Printer p1 = new Printer() { @Override public void print() { System.out.println("This is a message from p1"); } }; Printer p2 = new Printer() { @Override public void print() { System.out.println("This is a message from p2."); } }; }
Adder is an interface with the method ‘perform’ that takes an Integer and returns an Integer. “add1” is an anonymous class that implements adder. “_add1” is an equivalent lambda expression. We can call ‘add1.perform(x)’ which returns x+1. public interface Adder { int perform(int x); } public static void main(String[] args){ Adder add1 = new Adder() { @Override public int perform(int x) { return x+1; } }; Adder _add1 = x -> x+1; } int y = add1.perform(3);
Some languages allows passing function as parameters to another functions, or instantiate functions as variables. This is supported by some languages (including C, C++ and C#) The famous Language-Integrated-Query (LINQ) of C# provides a simple mechanism, very similar to java’s stream. Both C and C++ allows the programmer to treat functions as variables. This mechanism works similarly in modern languages like python and javascript.
Instead we use interfaces with a single method. Anonymous classes with a single method can be turned into lambda expressions. It’s called Syntactic Sugar. Java simulates its lambda expressions and functional interface in an object-oriented style, as form of abstraction.
The anonymous ‘new Printer()…’ can be replaced with the following equal lambda expression: Hint: your Intellij will suggest by default to replace anonymous classes with lambda expressions when possible. public interface Printer { void print(); } Printer p = new Printer() { @Override public void print() { System.out.println("Hello world!"); } }; Printer p = () -> System.out.println("Hello world!");
Basic entities you should know: Supplier<T> Simulates a method that takes no arguments and returns T by calling get(). Consumer<T> Simulates a void method that takes an argument of type T by calling accept(T) public interface Supplier<T> { T get(); } public interface Consumer<T> { void accept(T t); }
Basic entities you should know: Function<T, K> Simulates a method that takes an argument of type T and returns a value of type K by calling apply(T) Predicate<T> Simulates a method that takes an argument of type T and returns a boolean indicating whether T matches some specifications by calling test(T). public interface Function<T, R> { R apply(T t); } public interface Predicate<T> { boolean test(T t); }
f is an object of type Function<T, K>: takes T as an argument and returns K (can be anything). Returns a stream of elements composed of f(x) for each x in lst. Stream<Integer> squared = lst.stream() map((x) -> x * x); // Values are: (1, 4, 9, 16, 25)
public class Character { String name; String title; String city; int level; double hitPoints; Continent continent; public Character(String name, String title, String city, int level, double hitPoints, Continent continent) { this.name = name; this.title = title; this.city = city; this.level = level; this.hitPoints = hitPoints; this.continent = continent; } } enum Continent { Kalimdor, Eastern_Kingdoms, Northrend } Take a look at the following class for practice: And initialize the following list within our main: List<Character> characters = Arrays.asList( new Character("Arthas Menethil", "Lich King", "Icecrown", 80, 1500, Continent.Northrend), new Character("Thrall", "Warchief", "Orgrimmar", 90, 1200, Continent.Kalimdor), new Character("Jaina Proudmoore", “Lord Admiral", "Theramore", 120, 1000, Continent.Eastern_Kingdoms), new Character("Tyrande Whisperwind", "Priestess of Elune", "Teldrassil", 120, 1100, Continent.Kalimdor), new Character("Sylvanas Windrunner", "Dark Ranger", "Undercity", 120, 1100, Continent.Eastern_Kingdoms) );
List<Double> hitPoints = characters.stream() .filter((character) -> character.getLevel() == 120) .map(Character::getHitPoints) .collect(Collectors.toList()); double average = hitPoints.stream() .reduce(0.0, (acc, next) -> acc + next / hitPoints.size());
characters.stream() sorted(Comparator.comparing(Character::getHitPoints)) forEach(System.out::println); Note: we might want to implement Character::toString in order to get an informative result. Also note the usage of ‘Comparator.comparing’: this allows us to compare two object of type Character without actually implementing the ‘Comparable’ interface – useful when you would like to compare elements based on different attributes.
Similar presentations
All rights reserved.