Java 8 and Best Practices

Post on 19-Feb-2017

30 views 0 download

transcript

Jaffna Meetup 04

JAVA 8

Brought to you by WSO2

November 2016

1

Reference book• Based on training done by Sameera Jayasoma (Associate Director/Architect @ wso2)• I’ve used the book “Java 8 In Action” as the reference book.• You can get all the sample sources from here.

2

What’s new in Java 8?• Lambda Expressions• Default Methods• Method references • Type Annotations and Pluggable Type Systems• Repeating Annotations• Method Parameter Reflection.• Security Enhancements.• Streams/Parallel Streams• Optional • Nashorn • New Date and Time API• No more permanent Generation

3

Why Java 8?

• More concise code• Simpler use of multicore processors• No official support for previous versions

4

Memory management

5

-XX:MetaspaceSize

Functions as Values in Java 8

• Java 8 now treats Functions, or in other words methods as first-class citizens.

• Java used to treat primitives and object as first-class citizens.

• This allows us to pass methods around at runtime. This proved to be very useful in other functional languages.

6

Behaviour Parameterization

• Is a software development pattern• easy to handle software requirement changes.

• Allows a block of code to be executed later• You can pass this block of code as an argument to a method.

• Now this method’s behaviour depends on the behaviour of this block of code.

• This is called behaviour parameterization.

• Consider a pet store. How do you filter out all the cats from the pet store?

7

Attempt 1: Filtering Cats• A first solution would look like following.

8

public static List<Pet> filterCats(List<Pet> petList) {

List<Pet> catList = new ArrayList<>();

for (Pet pet : petList) {

if ("cat".equals(pet.getCategory())) {

catList.add(pet);

}

}

return catList;

}

• But now you need to filter pets who are more than 12 months older?

• What would you do?• Introduce another duplicate method?

Attempt 2: Parameterizing the age• You can parameterize the age and be more flexible to such

changes

9

public static List<Pet> filterPetsByAge(List<Pet> petList, int age) {

List<Pet> filteredPetList = new ArrayList<>();

for (Pet pet : petList) {

if (pet.getAgeMonths() > age) {

filteredPetList.add(pet);

}

}

return filteredPetList;

}• You can now call this method as follows.

List<Pet> petList01 = filterPetsByAge(petList, 12);

List<Pet> petList02 = filterPetsByAge(petList, 24);

Attempt 3: Filtering with every attribute

• Ugly attempt of merging all attributes appears as follows.

10

public static List<Pet> filterPets(List<Pet> petList, String category, int age,

boolean flag) {

List<Pet> filteredPetList = new ArrayList<>();

for (Pet pet : petList) {

if ((flag && pet.getCategory().equals(category)) ||

(!flag && pet.getAgeMonths() > age)) {

filteredPetList.add(pet);

}

}

return filteredPetList;

}• You can now call this method as follows. This is extremely bad!!! List<Pet> petList01 = filterPets(petList, "cat", 0, true);

List<Pet> petList02 = filterPets(petList, "", 12, false);

Behaviour Parameterization... • How can we model the filtering criteria properly to cope with

the changing requirements?• This criteria returns a boolean for the given list of pets.

• What if we define an interface to model the selection criteria.

11

public interface PetFilter {

boolean test(Pet pet);

}

• Now we can easily come up with multiple implementations as follows. public class OlderPetFilter implements PetFilter{

@Override

public boolean filter(Pet pet) {

return pet.getAgeMonths() > 12;

}

}

Attempt 4: Filtering by abstract criteria

• Modified filter method, which uses a PetFilter,

12

public static List<Pet> filterPets(List<Pet> petList, PetFilter filter) {

List<Pet> filteredPetList = new ArrayList<>();

for (Pet pet : petList) {

if (filter.test(pet)) {

filteredPetList.add(pet);

}

}

return filteredPetList;

}

• Now the behaviour of the filterPets method depends on the code you pass to it via PetFilter object.• I.e. We’ve parameterized the behaviour of the filterPets

method.

Tackling verbosity• Consider the following PetFilter. Here we are interested in

only the return statement. All other lines are boilerplate code which contribute to verbosity.

13

public class OlderPetFilter implements PetFilter{

@Override

public boolean filter(Pet pet) {

return pet.getAgeMonths() > 12;

}

}

• When you want to pass new behaviour to filterPets method, you’re forced to declare classes that implement the PetFilter interface.

• This is unnecessary overhead; can we do better?

Attempt 5: Using an anonymous class• Anonymous classes allow you to declare and instantiate a

class at the same time.• Don’t have a name. • Allow you to create ad-hoc implementations.

• Parameterizing the behaviour of the method filterPets directly inline.

14

List<Pet> filteredPetList = filterPets(petList, new PetFilter() {

@Override

public boolean test(Pet pet) {

return "cat".equals(pet.getCategory());

}

});

• Anonymous classes are still not good enough,• first, they tend to be very bulky because they take a lot of

space,• second, many programmers find them confusing to use.

Attempt 6: Using a lambda expression• The previous code can be rewritten as follows in Java 8

using a lambda expression: List<Pet> filteredPetList = filterPets(petList, (Pet pet) -> "cat".equals(pet.getCategory()));

• This codes looks a lot cleaner than the previous attempt

15

Sorting with a Comparator

Using an anonymous class,

16

petList.sort(new Comparator<Pet>() {

@Override

public int compare(Pet p1, Pet p2) {

return p1.getAgeMonths() - p2.getAgeMonths();

}

});

With lambda expression, petList.sort((Pet p1, Pet p2) -> p1.getAgeMonths() - p2.getAgeMonths());

Lambda Expressions

17

Lambdas in a nutshell

• Anonymous • We say anonymous because it doesn’t have an explicit name like a method would

normally have: less to write and think about!• Function

• We say function because lambda isn’t associated with a particular class like a method is. But like a method, a lambda has a list of parameters, a boy, a return type, and a possible list of exceptions that can be thrown.

• Passed around• A lambda expression can be passed as argument to a method or stored in a variable.

• Concise • You don’t need to write a lot of boilerplate like you do for anonymous classes.

18

(Pet p1, Pet p2) -> p1.getAgeMonths() - p2.getAgeMonths();

Lambda parameters

Lambda body

Arrow

Lambda expressions

19

Before: petList.sort(new Comparator<Pet>() {

@Override

public int compare(Pet p1, Pet p2) {

return p1.getAgeMonths() - p2.getAgeMonths();

}

});

After (with lambda expressions) petList.sort((Pet p1, Pet p2) -> p1.getAgeMonths() - p2.getAgeMonths());

Lambda expressions...

20

This lambda expression has three parts,

1. A list of parameters In this case it mirrors the parameters of the compare method of a Comparator.

2. An arrow The arrow -> separates the list of parameters from the body of the

lambda.

3. The body of the lambda Compare two Pets using their age in months. The expression is considered the lambda’s return value.

Valid lambda expressions in Java 8

21

// 1) This expression has one parameter of type String and returns an int. The lambda doesn't have a return statement here because the return

is implied

(String s) -> s.length();

// 2) This expression has one parameter of type Pet and returns a boolean.

(Pet p) -> p.getAgeMonths() > 15;

// 3) This expression has two parameters of type int with no return (void return). Note that lambda expressions can contain multiple

statements, in this case two.

(int x, int y) -> {

System.out.println("Result:");

System.out.println(x + y);

};

// 4) This expression has no parameter and return an int.

() -> 42;

// 5) This expression has two parameters of type Pet and returns an int.

(Pet p1, Pet p2) -> p1.getAgeMonths() > p2.getAgeMonths();

Syntax of a lambda

The basic syntax of a lambda is either,

(parameters) -> expression

or (note the curly braces for statements)

(parameters) -> { statements;}

22

Examples of lambdas

23

Use case Examples of lambdas

A boolean expression (List<String> list) -> list.isEmpty()

Creating objects () -> new Pet(“cat”, “13”)

Consuming from an object (Pet p) -> { System.out.println(p.getAgeMonths());}

Select/extract from an object (String s) -> s.length()

Combine two values (int a, int b) -> a*b

Compare two values (Pet p1, Pet p2) -> p1.getAgeMonths() - p2.getAgeMonths()

Where and how to use lambdas

• Here we can assign a lambda to a variable and also we can pass a lambda to the filter method.

• So where exactly can you use lambdas?

24

Predicate<Pet> petPredicate = (Pet pet) -> "cat".equals(pet.getCategory());

List<Pet> filteredPets = filter(petList, petPredicate);

• You can use a lambda in the context of a functional interface.

• Here filter method expects a Predicate<T>, which is a functional interface.

• So what are functional interfaces?

Functional interface• A functional interface is an interface that specifies exactly

one abstract method.

25

// java.util.Comparator

public interface Comparator<T> {

int compare(T o1, T 02);

}

// java.util.Runnable

public interface Runnable {

void run();

}

// java.util.Callable

public interface Callable<V> {

V call();

}

• Lambda expressions let you provide the implementation of the abstract method of functional interface directly inline.

• Expression can be treated as an instance of a functional interface.

@FunctionalInterface

• Function interfaces in the new Java API are annotated with @FunctionalInterface.

• This new annotation is indicates that the interface is intended to be a functional interface.• Compiler will throw error if the annotated interface is not a

functional interface.• This annotation is not mandatory, but it’s a good practise to

use it.

26

The execute around pattern• A recurrent pattern in resource processing is to open a

resource, do some processing, and then close the resource. • The setup and cleanup phases are always similar and

surround the important code doing the processing• This is called the execute around pattern.

27

public static String processFile() throws IOException {

try (BufferedReader br = new BufferedReader(new FileReader("pets.txt"))) {

return br.readLine();

}

}

Type inference...

• Now you can omit the types of the parameters from a lambda expression

28

Comparator<Pet> p = (Pet p1, Pet p2) -> p1.getAgeMonths() - p2.getAgeMonths();

Comparator<Pet> p = (p1, p2) -> p1.getAgeMonths() - p2.getAgeMonths();

• With type inference

29

Streams API

Java Collections API - Limitations• Collections is the most heavily used API in Java.• Allows you to group and process data.• Consider a collection of Pet objects in the PetStore sample.

• How would you group a list of Pet objects by category?• How would you find the oldest Pet in the PetStore?• How would you find the list of Cats who are older than 15

months.• Collections API is far from perfect to manipulate data and achieve

above requirements. • If you are to implement above requirements using Collections API,

then you need use iterators.• It’s very hard to leverage multicore architectures with iterators

30

Imperative Style Programming

• Saying how to do something,• In terms of sequences of actions to be taken.

• Mutability• Too many moving parts• Hard make the code concurrent.

31

Declarative Style Programming• Focus not on how, but what to do,

• Expressive• Concise• Immutability

• Most databases let you specify database operations declaratively.• E.g. consider the following SQL query

32

SELECT category, ageInMonths FROM Pets WHERE ageInMonths > 15

• Here you don’t specify how to filter data, instead you express only you what you expect.

• Can’t do we something similar with Collections?

Streams

• Streams are an update to Java API that lets you manipulate collections of data in a declarative way.• You express a query rather than code

• Stream can be processed in parallel transparently• You don’t need to write any multithreaded code.

33

Java 7

34

// 1) Filter the elements using and accumulator

List<Pet> youngPetList = new ArrayList<>();

for (Pet pet : petList) {

if (pet.getAgeMonths() < 15) {

youngPetList.add(pet);

}

}

// 2) Sort the pets with an anonymous class.

Collections.sort(youngPetList, new Comparator<Pet>() {

@Override

public int compare(Pet pet1, Pet pet2) {

return Integer.compare(pet1.getAgeMonths(), pet2.getAgeMonths());

}

});

// 3) Process the sorted list to select the names of dishes

List<String> youngPetNames = new ArrayList<>();

for (Pet pet : youngPetList) {

youngPetNames.add(pet.getName());

}

Java 8

35

List<String> youngPetNames =

petList.stream()

.filter(pet -> pet.getAgeMonths() < 15)

.sorted(Comparator.comparing(Pet::getAgeMonths))

.map(pet -> pet.getName())

.collect(Collectors.toList());

// This exploits a multicore architecture and execute this code in parallel.

List<String> youngPetNames =

petList.parallelStream()

.filter(pet -> pet.getAgeMonths() < 15)

.sorted(Comparator.comparing(Pet::getAgeMonths))

.map(pet -> pet.getName())

.collect(Collectors.toList());

• This code is written in a declarative way. You specify what you want to achieve as opposed to specifying how to implement an operation.

• You chain together several building-block operations to express a complicated data processing pipeline while keeping your code readable and its intent is clear.

What is a Stream?

• A sequence of elements from a source that supports data processing operations.

• Sequence of elements - Like a collection, a stream provides an interface to a sequenced set of values of a specific type.

• Source - Streams consume from a data-provisioning source such as collections, arrays, or I/O resources.

• Data processing operations - Supports database-like operations and common operations from functional programming languages. • e.g. filter, map, reduce, find, match, sort etc.

36

Streams sample

37

List<String> youngPetNames =

//Get a stream from the pet list.

petList.stream()

// Filter pets who not older than 15 months.

.filter(pet -> pet.getAgeMonths() < 15)

// Sort the pets using their age.

.sorted(Comparator.comparing(Pet::getAgeMonths))

// Get the names of the pets.

.map(pet -> pet.getName())

// Select only the first three.

.limit(3)

// Store the resutls in another list.

.collect(Collectors.toList());

Streams sample...

38

Streams vs Collections

39

Stream operations...

40

41

Working with streamsFiltering and slicing

Filtering with a predicate• filter method takes a Predicate (a function returning a boolean) and

returns a stream including all the elements that match the predicate.

• E.g. You can create a vegetarian menu by filtering all the vegetarian dishes from a menu.

42

List<Dish> vegetarianMenu =

menu.stream()

.filter(Dish::isVegetarian)

.collect(Collectors.toList());

Filtering unique elements

43

• distinct method returns a stream with unique elements.• According to the implementations of the hashCode and equals

methods of the objects.• Consider the following example which filters all even numbers from

a list and makes sure that there are no duplicates.

Filtering unique elements...

44

List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);

numbers.stream()

.filter(i -> i % 2 == 0)

.distinct()

.forEach(System.out::println);

Truncating a stream

45

• limit(n) method returns another stream that’s no longer than a given size.• If the stream is ordered, the first elements are returned up to a

maximum of n. • Note that limit also works on unordered streams (e.g. if the

source is a Set). • You shouldn’t assume any order on the result in this case.

• How would you create a List by selecting the first three dishes that have more than 300 calories?

Truncating a stream...

46

List<Dish> dishes =

menu.stream()

.filter(dish -> dish.getCalories() > 300)

.limit(3)

.collect(Collectors.toList());

Skipping elements

47

• skip(n) method returns another stream that discards the first n elements. • If the stream has fewer elements than n, then an empty stream

is returned.• For example, the following code skips the first two dishes that have

more than 300 calories and the return the rest.menu.stream()

.filter(dish ->

dish.getCalories() > 300)

.skip(2)

.collect(Collectors.toList());

48

Working with streamsMapping

Applying function to each elements

49

• map method takes a Function as an argument. This Function is applied to each element, mapping it to new element.

• For example, in the following code you pass a method reference Dish::getName to the map method to extract the names of the dishes in the stream.

• Because the method getName returns a String, the stream outputted by the map method is of type Stream<String>

List<String> dishNames =

menu.stream()

.map(Dish::getName)

.collect(Collectors.toList());

50

Working with streamsBuilding streams

Building streams

51

• Streams from values. Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");

stream.map(String::toUpperCase).forEach(System.out::println);

// You can get an empty stream using the empty method as follow:

Stream<String> emptyStream = Stream.empty();

• Streams from arrays. int[] numbers = {2, 3, 5, 7, 11, 13};

int sum = Arrays.stream(numbers).sum();

Building streams..

52

• Streams from files.

long uniqueWords = 0;

// Streams are autoclosable.

try (Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) {

uniqueWords = lines

// Generate a stream of words

.flatMap(line -> Arrays.stream(line.split(" ")))

// Remove duplicates

.distinct()

// Count the number of unique words

.count();

} catch (IOException e) {

// Deal with the exception if one occurs when opening the file.

}

53

Default methods

Evolving APIs• A Java interface groups related methods together into a contract

• Any class which implements this interface must provide an implementation for each method defined by the interface.

• This causes a problem when library designers need to update an interface to add a new method.

• Java 8 designers faced the exact same issue while introducing Streams API, but somehow they have introduced many new methods to existing APIs. For example,

• Collection.streamI()• Collection.parallelStream()• List.sort()

54

Evolving APIs...

55

API Version 01

• Consider the following Interface public interface Resizable {

int getWidth();

int getHeight();

int setWidth(int width);

int setHeight(int height);

int setAbsoluteSize(int width, int height);

}• Assume you are a library designer and you’ve included the above API in one of your libraries.

• Also assume there are external developers who have introduced their own implementations based on this API.

• Now what if you want to introduce another method to this interface.

Evolving APIs...

56

API Version 02

• Consider the following updated Interface public interface Resizable {

int getWidth();

int getHeight();

int setWidth(int width);

int setHeight(int height);

int setAbsoluteSize(int width, int height);

int setRelativeSize(int wFactor, int hFactor);

}

• This change creates few problems for existing API users.

Default methods

57

• A new feature added in Java 8 to help evolve APIs in a compatible way.

• An interface can now contain method signatures for which an implementing class doesn’t provide an implementation.

• The implementation of a default method should there in the interface itself default void setRelativeSize(int wFactor, int hFactor) {

setAbsoluteSize(getWidth() / wFactor, getHeight() / hFactor);

}

• Default methods are used extensively in the Java 8 API.

Abstract classes vs. interfaces in Java 8

58

• What’s the difference between an abstract class and an interface in Java 8? They both can contain abstract methods and methods with a body.

1. A class can extend only from one abstract class, but a class can implement multiple interfaces.

2. An abstract class can enforce a common state through instance variables (fields). An interface cannot have instance variables.

59

Default methodsUsage patterns for default methods

Optional methods

60

• There are methods in interfaces that we usually don’t implement, we always leave them empty.

• When you design an API, now you can provide a default implementation for such methods.• So the concrete classes don’t need to explicitly provide

an empty implementation.• This reduces boilerplate code.

Resolution rules

61

• What if a class implements two interfaces that have the same default method signature? Which method is the class allowed to use?

• Consider the following example.

Resolution rules...

62

public interface A {

default void hello() {

System.out.println("Hello from A");

}

}

public interface B {

default void hello() {

System.out.println("Hello from B");

}

}

public class C implements B, A {

public static void main(String[] args) {

// Which one gets printed

new C().hello();

}

}

Three resolution rules to know

63

• There are three rules to follow when a class inherits a method with the same signature from multiple places

1. Classes always win. A method declaration in the class or a superclass takes priority over and default method declaration.

2. Otherwise, sub-interfaces win: the method with the same signature in the most specific default-providing interface is selected.

3. Finally, if the choice is still ambiguous, the class inheriting from multiple interfaces has to explicitly select which default method implementation to use by overriding it and calling the desired method explicitly.

64

Parallel data processing

Fork/join framework Parallel streams Spliterator

Fork/join framework• An implementation of the ExecutorService interface• Designed to recursively split a parallelizable task into smaller tasks, and

then combine the results of each subtasks.• ExecutorService distributes those subtasks to worker threads in the

ForkJoinPool (thread pool).• These subtasks need to implement RecursiveTasks<R> interface

• R is the type of the result produced by the tasks.• RecursiveAction if the task returns no result.

• You only need to implement the following abstract method to define a RecursiveTask

65

protected abstract R compute();

Fork/join framework...

66

Fork/join framework - sample

67

/**

* Extend {@code RecursiveTask} to create a task usable with the fork/join framework.

* Executing a parallel sum using the fork/join framework.

*/

public class ForkJoinSumCalculator extends RecursiveTask<Long> {

// The size of the array under which this task is no longer split into subtasks.

public static final long THRESHOLD = 10_000;

// The array of numbers to be summed.

private final long[] numbers;

// The initial and final positions of the portion of the array processed by this subtask.

private final int start;

private final int end;

// Public constructor used to create the main task

public ForkJoinSumCalculator(long[] numbers) {

this(numbers, 0, numbers.length);

}

// Private constructor used to recursively create subtasks of the main task.

private ForkJoinSumCalculator(long[] numbers, int start, int end) {

this.numbers = numbers;

this.start = start;

this.end = end;

}

Fork/join framework - sample

68

protected Long compute() {

// The size of the portion of the array summed by this task.

int length = end - start;

if (length <= THRESHOLD) {

// If the size if less than or equal to the threshold, compute the result sequentially.

return computeSequentially();

}

// Create a subtask to sum the first half of the array.

ForkJoinSumCalculator leftTask = new ForkJoinSumCalculator(numbers, start, start + length / 2);

// Asynchronously execute the newly created subtask using the another

thread of the ForkJoinPool

leftTask.fork();

// Create a subtask to the sum the second half of the array.

ForkJoinSumCalculator rightTask = new ForkJoinSumCalculator(numbers, start + length / 2, end);

// Execute this second subtask synchronously, potentially allowing further recursive splits.

Long rightResult = rightTask.compute();

// Read the result of the first subtask or wait for it if it isn't ready.

Long leftResult = leftTask.join();

// The result of this task is the combination of the two subtasks.

return leftResult + rightResult;

}

Fork/join framework - sample

69

// Simple algorithm calculating the result of a subtask when it's no longer divisible.

private long computeSequentially() {

long sum = 0;

for (int i = start; i < end; i++) {

sum += numbers[i];

}

return sum;

}

• Running the ForkJoinSumCalculator. public static long forkJoinSum(long n) {

long[] numbers = LongStream.rangeClosed(1, n).toArray();

ForkJoinTask<Long> task = new ForkJoinSumCalculator(numbers);

return new ForkJoinPool().invoke(task);

}

Fork/join algorithm

70

Best practises for using the fork/join framework

71

• Invoke the join method on a task blocks the caller until the result produced by that task is ready.

• The invoke method of a ForkJoinPool shouldn’t be used from within a RecursiveTask.

• Calling the fork method on a subtask is the way to schedule it on the ForkJoinPool.

ForkJoinPool

72

• As a best practise, don’t create ForkJoinPool more than once in your application.

• Create is using the default no-argument constructor • This allows the pool to use all the processors available to

the JVM. • Calculates the number of threads based on the available

processors.

73

• Allows you to execute operations in parallel on a collection of data without much effort.

• Allows you to declaratively turn a sequential stream into a parallel one. • Uses the fork/join framework under the hood.• A parallel stream is a stream that splits its elements into multiple chunks,

processing each chunks with a different thread.• This should keep all your cores busy by equally partitioning chunks

• Suppose you need to write a method accepting a number n as an argument and returning the sum of all the numbers from 1 to the given amount. Let’s optimize this method step by step.

Parallel streams

74

• Using traditional java style.

Parallel streams...

public static long iterativeSum(long n) {

long result = 0;

for (long i = 0; i <= n; i++) {

result += i;

}

return result;

}

//Result

Iterative sum done in: 3 msecs

75

• Using infinite stream of numbers, limiting it to the passed number, and then reduce the resulting stream with a BinaryOperator thats just sums two numbers

Parallel streams...

public static long rangedSum(long n) {

return LongStream.rangeClosed(1, n)

.reduce(0L, Long::sum);

}

//Result

Sequential sum done in: 4 msecs

• Previous one runs faster because its works at a much lower level and, more important, doesn’t need to perform any boxing or unboxing for primitive values

76

Parallel streams...

77

• Turning the sequential stream into a parallel one.

Parallel streams...

public static long parallelRangedSum(long n) {

return LongStream.rangeClosed(1, n)

.parallel()

.reduce(0L, Long::sum);

}

//Result

Sequential sum done in: 1 msecs

• Here the difference is that the stream is internally divided into multiple chunks. Reduction operation can work on the various chunks independently and in parallel.

78

Parallel streams...

79

• How can we configure the thread pool size here?• Parallel streams internally use the default ForkJoinPool

• This pool has as many threads as you have processors, as returned by Runtime.getRuntime().availableProcessors().

• You can change the size of the ForkJoinPool using the following system property.

Configuring the thread pool

System.setProperty(“java.util.concurrent.ForkJoinPool.common.parallelism”, 12)

• This is a global setting, so it will affect all the parallel streams in your code.• The default value of this pool is equal to the number of processors on your

machine and it is a meaningful default. • Don’t change it unless you have a very good reason for doing so.

80

• If in doubt, measure. Turning a sequential stream into a parallel one is trivial but not always the right thing to do.

• Watch out for boxing. Automatic boxing and unboxing operations can dramatically hurt performance.

• Some operations naturally perform worse on a parallel stream than on a sequential stream. For example limit and findFirst vs. findAny

• Consider the total computational cost of the pipeline of operations performed by the stream. With N being the number of elements to be processed and Q the approximate cost of processing one of these elements through the stream pipeline, the product of N*Q gives a rough qualitative estimation of this cost. A higher value for Q implies a better chance of good performance when using a parallel stream.

Using parallel streams effectively

81

• Code readability can be very subjective. • Improve code readability means ensuring your code is understandable

and maintainable by people beside you.• How Java 8 allows you to improve code readability?

• You can reduce the verbosity of your code, making it easier to understand.

• You can improve the intent of your code by using method references and the Streams API

• Following are three simple refactorings that use lambdas, method references , and streams

• Refactoring anonymous classes to lambda expressions• Refactoring lambda expressions to method references• Refactoring imperative-style data processing to streams.

Improving code readability

82

• Try to convert anonymous classes implementing one single abstract method to lambda expressions.

• But sometime, you may get into issues• The meanings of this and super are different from

anonymous classes and lambda expressions. • Inside an anonymous class, this refers to the anonymous

class itself, but inside a lambda it refers to the enclosing class.

• Anonymous classes are allowed to shadow variables from the enclosing class. Lambda expressions can’t do that.

From anonymous classes to lambdas

83

From anonymous classes to lambdas...

84

• Converting an anonymous class to a lambda expression can make the resulting code ambiguous in the context of overloading.

• The type of the anonymous class is explicit at instantiation.• But, the type of the lambda depends on its context.

From anonymous classes to lambdas...

• You can now pass an anonymous class implementing Task without a problem.

85

• But converting the previous anonymous class to a lambda results in an ambiguous method call, because both Runnable and Task are valid target types.

From anonymous classes to lambdas...

• You can solve the ambiguity by the providing an explicit cast.

86

Best practises

List <String> myList = …;

if (myList.size > 0) { … }

if myList.isEmpty() { … }

87

Best practises

Returning Empty Collections instead of Null

public static List<Pet> getAllPets(){List<Pet> lPets = new ArrayList()<Pet>();

................return lPets;

}

88

Best practises

Avoid unnecessary Objects

public static List<Pet> getAllPets(){List<Pet> lPets = null;//if throw exception etc

................if (lPets == null) {

lPets = new ArrayList()<Pet>();}................return lPets;

}

//Slower InstantiationString bad = new String("Yet another string object");//Faster InstantiationString good = "Yet another string object";

89

Best practisesDilemma between Array and ArrayList

• Arrays have fixed size but ArrayLists have variable sizes. Since the size of Array is fixed, the memory gets allocated at the time of declaration of Array type variable. Hence, Arrays are very fast. On the other hand, if we are not aware of the size of the data, then ArrayList is More data will lead to ArrayOutOfBoundException and less data will cause wastage of storage space.

• It is much easier to Add or Remove elements from ArrayList than Array• Array can be multi-dimensional but ArrayList can be only one dimension.

90

Best practises

Difference between single quotes and double quotes

public static void main (String [] args) {System.out.println("H" + "I");System.out.println('H' + 'I');

}

91

Best practises

Choice between Float and Double

45.123456…?

Most processors take nearly the same amount of processing time to perform operations on Float and Double. Double offers far more precision in the same amount of computation time.