Skip to content

trejnado/java8

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Table of Contents

Requirements

Wymagania: IDE (Intellij Idea, Eclipse, etc.) z JDK 8 i git, żeby można było komitowac (opcjonalnie). Jeśli Intellij to wystarczy Community Edition.

Scope

  • Changes in interfaces (default and static methods)
  • Optional
  • Lambda Expressions
  • Streams
  • Date Time API
  • Other things (Map, Base64, JavaFx, G1, etc.)

Java 8 heads towards functional programming, where things are immutable, and therefore new classes are mostly immutable. You write code with similar mindset.

Interfaces in Java 8

What was before Java 8

  • single inheritance of implementations
  • multiple inheritance of types (interfaces)
  • interfaces cannot provide implementations

Default methods

Provide real implementations and can be overridden. So interfaces became more like abstract classes.

If a class implements two interfaces with the same name of default method, then it must be overridden or it won't compile.

Static methods

Cannot be overridden.

Example interface in Java 8

It has: abstract methods, default methods, and static methods.

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t); // abstract method

    default <V> Function<V, R> compose(Function<V, T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<R, V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

Functional interfaces

@FunctionalInterface tells the compiler to check if the interface has only single abstract method. If not then it won't compile it.

Interfaces with SAM, but without the annotation are also functional merely won't have compile type check - when you add another abstract method to such interface it will compile, but classes that implement them won't.

Functional interface, like any other interface, can have default and static methods. Also, when two interfaces provide the same default method, then implementing class must override the method or it won't compile.

java.util.function - reusable interfaces to work in functional style

Predicate - function to test a collection

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t); //SAM

    default Predicate<T> and(Predicate<? super T> other) {
        //...
    }

    default Predicate<T> negate() {
        //...
    }

    default Predicate<T> or(Predicate<? super T> other) {
        //...
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        //...
    }
}
  1. Example

    Predicate<Player> isGoodPlayer = p -> p.getPoints() >= 20;
    if (isGoodPlayer.test(player)) {
        // ...
    }
    
    // for (Player p : players) { if (isGoodPlayer.test(p)) return player; }
    players.stream().filter(isGoodPlayer).map(Player::getName).collect(toList());
    

Function<T,R> - takes T and transforms it into R

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

    default <V> Function<V, R> compose(Function<V, T> before) {
        //...
    }

    default <V> Function<T, V> andThen(Function<R, V> after) {
        //...
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

BiFunction<T,T,R> takes two T and returns R

Function<Player,Stat> takeStats = p -> new Stat(p);

Supplier - takes nothing and returns R

@FunctionalInterface
public interface Supplier<T> {

    T get();
}

Supplier<Player> recruiter = Player::new;
Supplier<Player> recruiter = () -> Player("Michael Jordan", 23);

team.add(recruiter.get());

Consumer - takes T and returns nothing (for side effects)

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        //...
    }
}

Consumer<Player> incScore = p -> p.setScore(p.getScore()+2);

for (Player player : player) {
    incScore.accept(player);
}

Operators

  1. XUnaryOperator (X: double/int/long): same type out

  2. XBinaryOperator (X: double/int/long): two X in, same type out

  3. XPredicate: double/long/int in, boolean out

  4. XConsumer: double/long/int in, nothing out

New methods in old collections

Set, List (Collection)

Map

Exercises

Remove all players with less than 20 points and print names and points of those who left

Optional

Result of computation that may not exists and what to do about it. For example finding max/min element in empty collection. Or sum of empty array/list/set of numbers.

Examples of how not to use

// long and less readable
if (Optional.ofNullable(x).isPresent()) {...}

// old school, but short and clear:
if (x != null) {
 ...
}

Lambda expressions

What's their purpose?

They have benefits of Inner/Anonymous Classes, namely:

  • they have access to members of class instantiating them
  • don't require create a new class (can be defined inline)
  • can be assigned to fields
  • can be passed as parameters.

To provide concise and expressive syntax.

button.addActionListener(
    new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            handleButton(e);
        }
    }
);

button.addActionListener(e -> handleButton(e));

Syntax

Single Abstract Method

They require an interface with Single Abstract Method (SAM).

A functional interface is an interface that contains exactly one abstract method. Additionally it may contain default methods and/or static methods. Because a functional interface contains exactly one abstract method, you can omit the name of that method when you implement it using a Lambda Expression.

Example interface, class and method

Consider the following interface:

interface Tester<T> {
    boolean test(T t);
}

Sample class for processing:

public class Player {
    public int points;
}

Both can be used like this:

public void printPlayer(List<Player> players, Tester<Data> tester) {
    for (Player p: players) {
        if (tester.test(p)) {
            System.out.println(p);
        }
    }
}

Tester as an anonymous object

In Java 7 the Tester could be implemented like this:

printPlayer(al, new Tester<Player>() {
    public boolean test(Player player) {
        return player.points > 20;
    }
});

Tester implementation as Lambda Expression

In Java 8 the call to above method can be simplified as follows:

printPlayer(al, (Player p) -> { return p.points > 20; } );

Notice that method name can be omitted here, because the interface has only one abstract method, therefore the compiler can figure out the name.

Return is implicit in one liners

It's the same as above:

printPlayer(players, (Player player) -> player.points > 20);

Curly brackets and the return keyword also are not needed, because both the method and the expression player.points > 1 also return a boolean. So, the compiler is able to figure out that the value of this expression should be returned from the method.

Types of arguments are inferred

This can be shortened even more to:

printPlayer(players, p -> p.points > 20);

The declaration of p is gone! The compiler can figure out all this, because the interface has only one abstract method and that method also has only one parameter. So you don't have to write all those things in your code.

Common functional interfaces

JDK 8 ships with a bunch of general interfaces that can be used in many situations. The interfaces are defined in java.util.function package.

Scope

Lambda Expressions don't introduce a new scope. Variables in LE have the same scope as their outside object. So, "this" refers to the outside object.

Effective final

Anonymous Java classes can access variables from method parameters or method local only if they are declared final. In Lambda Expressions it is the same case, but they also can access variables that are "effectively" final. Variables that never change once assigned.

Method references

Method Reference Example Equivalent lambda
AClass::staticMethod Math::min (a,b) -> Math.min(a,b)
var::instanceMethod list::add (t) -> list.add(t)
AClass::instanceMethod List::add (l,e) -> l.add(e)
AClass::new ArrayList::new () -> new ArrayList()

var::instanceMethod vs AClass::instanceMethod

List<Integer> result = new ArrayList<>();
source.forEach(by2::add);
// -> on each source integer: by2.add(s)

AClass::instancMethod version produces lambda that takes one more argument that the method expects. The first argument is the object on which the method is called; the rest are the parameters to the method:

List<String> result = process(names, String::toUpperCase);
// -> on each string: s.toUpperCase()

This way or another must match SAM.

Constructor references

AClass::new

Type of method reference

It is inferred from the context and depends on to what it is assigned to. When not assigned it has no type and won't compile!

Function<Double, Double> round = Math::rint;
Fun<D,D> a = new Function<D,D> { D apply(D x) { return Math::rint;}}
round.compose(Math::sqrt).apply(64d, 2d); // ok

// Math::rint is not assigned, so has no type:
Math::rint.compose(Math::sqrt); // compile error!

Higher Order Functions (Functions that return functions)

Predicate<Player> isStrong = p -> p.getStrength() >= 100;
Predicate<Player> hasStamina = p -> p.getStamina() > 0;
allMatches(players, isStrong.and(hasStamina));

Closures

public static Predicate<Player> buildIsSP(int minStrength) {
    return p -> p.getStrength() >= minStrength; // returns Lambda
}

public static Function<Integer, Predicate<Player>> createIsStrongPredicate
    // returns Function that will produce the Predicate:
    = minStrength -> (p -> p.getStrength() >= minStrength);

Exercises

Count frequencies of chars using Java 8 functions

Map.merge

Refactor repeating code from before Java 8 to use Lambda Expressions

  • loop that finds matching element
  • implement map (transformation of elements)
  • implement reduce (using BinaryOperator)

Implement composeAll(Function<T,T>… functions)

Examples of how not to use

Show long lambdas

Stream API

What they are

Wrappers around data sources (arrays, lists, etc.) that use lambda expressions.

They are pipelines of operations on data sources (collections). They don't have own data (don't copy it from source collection).

Streams are pipelines of operations that consume the data so they cannot be reused!

How to build a Stream

You can build a Stream from:

  • individual elements: Stream.of(x, y, …);
  • array: Stream.of(numsArray);
  • a collection: list.stream()
  • a function: Stream.generate(Supplier), Stream.iterate(val, F)
  • a StreamBuilder: someBuilder.builder()
  • a String: "aoeu".chars(), Stream.of(String.split(…))
  • other Streams: distinct, filter, limit, map, sorted, substream

Gotchas

  • see specialized streams

  • 1-item stream instead of an array

    int[] nums = {1, 2, 3};
    Stream.of(nums); // Stream with 1 element, which is an array
    
    Integer[] numbers = {1, 2, 3};
    Stream.of(numbers); // 3-element stream of Integers
    

How to turn streams into arrays and collections

Back into array:

Player[] array = players.stream().toArray(Player[]::new);

Back into a collection:

// import java.util.stream.collectors.Collectors;
List<Player> processed = players.stream().collect(Collectors.toList());

Example - forEach

Traditional for loop process sequentially:

for (Flight flight : flights) {
    flight.setCarrier("XX");
}

Java 8 forEach can be made concurrent automatically by using parallelStream:

flights.parallelStream().forEach(f -> f.setCarrier("XX"));

With forEach you cannot:

  • break out of loop using "break" nor "return"
  • modify local variables outside loop (remember Effective Final?)

Common operations on streams

Stream filter(Predicate) - element selection

Stream map(Function<T,R>) - transformation T->R

Stream reduce - collection into single value

reduce(BiF, 0, [1, 2, 3]) -> BiF(0, 1) = 1 -> BiF(1, 2) = 3 -> BiF(3, 3) = 6

allMatch(Predicate), anyMatch(Predicate), noneMatch(Predicate)

int[] nums = {1, 2, 3};
boolean allEven = Arrays.stream(nums).allMatch(isEven);

count()

int[] nums = {1, 2, 3};
int size = Arrays.stream(nums).count();

flatMap - join streams

static <T,R> Stream<R> flatMap(Function<T,Stream<R>> streaming);

Exercises

  1. Find even and odd numbers in an array (use Predicates)

    Integer[] numbers = {1, 2, 3, 4, 5};
    
    // Predicate isEven = n -> n % 2 == 0;
    // Predicate isOdd = Predicate.negate(isEven);
    // Stream.of(numbers).filter(isEven).collect(toList());
    List<Integer> even = ...;
    List<Integer> odd = ...;
    
  2. Count distinct chars in names of players using flatMap

Collectors - create different outputs

List - toList

Set - toSet

Any collection - toCollection(Supplier)

toCollection(TreeSet::new) -> new Supplier { Collection get() { new TreeSet(); }}

Map

  1. partitioningBy(Predicate) -> Map<Boolean,T>

  2. groupingBy(Function<T,R> keyFunction) -> Map<Pos,List>

    // group students by name:
    Map<String, List<Student>> roster = students.stream()
        .collect(groupingBy(Student::getName));
    
  3. Composing collectors

    Grouping collector can use another (downstream) collector that will have possibility to post-process values. The result will the same keys, but values from the "post-processor".

    Map<String, Integer> roster = students.stream()
        .collect(
            // group by name:
            groupingBy(Student::getName,
                // count occurrences
                Collectors.counting()));
    

String - toStringJoiner("delimiter")

Exercises

  1. Rewrite finding even/odd numbers, but with help of Collectors

  2. Find best scorers on each position

Lazy evaluation

It means that operations on a stream are deferred (postponed) until the system notices that they are really needed. Only then they are evaluated.

List<Integer> numbers = asList(null, 2, 3, 4, 5);
Integer result = numbers.stream()
    .filter(Objects::nonNull)
    .map(n -> n + 1)
    .filter(n -> n > 2)
    .filter(4 -> n % 2 == 0)
    .anyMatch()
    .orElse(null);

Specialized streams

IntStream, LongStream, DoubleStream - specialized streams with restrictions on operations (e.g. map must produce int/long/double), but with some common methods for those types - sum, avg, min, max, etc.

int[] nums = { 1, 2, 3 };
IntStream intStream = Arrays.stream(nums);

Integer[] numbers = { 1, 2, 3 };
Stream<Integer> stream = Arrays.stream(numbers); // or Stream.of(numbers)

Types of operations on Stream

Intermediate operations

Intermediate (non-terminal) operations produce Streams, are added to processing pipeline, but are not executed immediately - only after Short-Circuit operation is found.

When executed, they operate on one element at a time. So pipeline of intermediate operations is executed on the first element of the stream, then on the second, and so on.

Terminal operations

Terminal operations consume the Stream and no more operations can executed on it. Examples: forEach(Consumer), findFirst(), allMatch(Predicate).

Short-circuit operations

They cause evaluation of intermediate operations that appear earlier in the pipeline. Both intermediate and terminal operations can be short-circuit:

  • Intermediate: limit, substream
  • Terminal: forEach, findFirst

Parallel streams

Marking a stream as parallel automatically allows it to be processed concurrently, without implementing Runnables nor using ExecutorService. It doesn't mean that the operations will be run parallel, but may be.

By default it uses the common ForkJoinPool.

Use for slow operations.

Consumer<Player> slowConsumer = p -> {
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    } finally {
        System.out.println(p);
    }
};
List<Players> players;
players.stream().forEach(slowConsumer);
players.stream().parallel().forEach(slowConsumer);

Infinite Streams (unbounded)

They are not really infinite streams, but streams that don't have fixed size, where values are calculated when are needed.

Stream.generate(Supplier)

Good with stateful suppliers.

Stream.iterate(T seed, UnaryOperator)

"seed" - the first value of the stream The operator takes the first element and returns second. Repeatedly.

Exercise

  1. Write a stream that will produce Fibonacci numbers

    Return nth Fibonacci number.

Date and Time API (java.time)

They are immutable, thread-safe and cacheable.

Intro

Before Java 8 there was java.util.Date and java.util.Calendar.

Date now = new Date();

Calendar cal = Calendar.getInstance(); // now
cal.set(2017, 1, 18); // 18th of February
Date date = cal.getTime();

Date and Time representations

Instant - a point in time

It is a point on a time line with nanosecond precision.

Instant is immutable as many other classes in java.time.

Instant.MIN;    // bilion years ago
Instant 0       // beginning of the Epoch
Instant.now();  // it's now!
Instant.MAX;    // in bilion years

LocalDate - day precision

Represents objects without time - e.g. event from the past.

LocalDate today = LocalDate.now();
LocalDate battle = LocalDate.of(1410, Month.July, 15);

LocalTime

Represents a time of day and comes with a bunch of methods to manipulate time.

LocalTime now = LocalTime.now();
LocalTime workshopStarts = LocalTime.of(9, 30); // 9:30 AM

LocalTime night = LocalTime.of(23, 30).plusHours(2); // 1:30 AM

LocalDateTime - both in one object

ZonedTime, ZonedDateTime

DateTime with zone information - date and time expressed for selected time zone.

ZoneDateTime.of(y, m, d, h, m, s, ZoneId);

Length of time

Duration

It's the duration between two Instant objects.

Instant beginning = Instant.EPOCH;
Instant end = Instant.now();
Duration length = Duration.between(beginning, end);
length.toMillis();

Period

Amount of time between two LocalDates. Similar to Duration, but with date granularity whereas Duration has nanosecond.

public static int daysAgo(LocalDate pastDate) {
    return Period.between(LocalDate.now(), pastDate).getDays();
}

Gotcha: period.get(ChronoUnit) returns parts of period split according to used ChonoUnit.

TemporalAdjusters

They allow to add/substract an amount of time to Instant/LocalDate.

It has a bunch of static methods that create immutable instances of TemporalAdjuster:

  • finding the first or last day of the month
  • finding the first day of next month
  • finding the first or last day of the year
  • finding the first day of next year
  • finding the first or last day-of-week within a month, such as "first Wednesday in June"
  • finding the next or previous day-of-week, such as "next Thursday"

Usage:

Temporal t = adjuster.adjustInto(t);
Temporal t = t.with(adjuster);

Date formatters

java.time.format.DateTimeFormatter is for formatting java.time dates. It ships a bunch of predefined formatters.

Exercise

  1. Format date using new DateFormatter and given locale

    package java8.time;
    
    import java.time.Instant;
    import java.time.ZoneId;
    import java.time.format.DateTimeFormatter;
    import java.util.Locale;
    
    import static java.time.format.FormatStyle.LONG;
    
    public class DateUtil {
    
        public static String reformatDate(Instant time, Locale locale) {
            DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(LONG, LONG)
                    .withLocale(locale)
                    .withZone(ZoneId.systemDefault());
    
            return formatter.format(time);
        }
    }
    

Conversion between new and old APIs

Date date = new Date();
Instant instant = date.toInstant();
LocalDate localDate = date.toLacalDate();

date = Date.from(instant);
date = Date.from(localDate);

Time time = Time.from(localTime);
LocalTime localTime = time.toLocalTime();

Clock

For testing - SystemClock and FixedClock.

Exercises

How many days have past since given day

Find previous year day closest to the given date that has the same day of week

For example for date: 2017-03-31 (Friday) Last year date: 2016-04-01 (Friday)

 public static LocalDate lastYearSameDay(LocalDate currentDate) {
     return currentDate
             .minusYears(1)
             .with(TemporalAdjusters.nextOrSame(currentDate.getDayOfWeek()));
 }

@Test
public void lastYearLogicalDate() {
    //Current date: 2017-03-31 (Friday)
    //Last year date: 2016-04-01 (Friday)

    LocalDate date = LocalDate.of(2017, Month.MARCH, 31);
    LocalDate expected = LocalDate.of(2016, Month.APRIL, 1);

    assertEquals(expected, DateUtil.lastYearSameDay(date));
}

Other things

Concurrency

Atomic values

Parallel array operations

Completable futures

JavaScript engine - Nashorn

Base64

Methods in Strings, Numbers, etc.

G1

String deduplication

JavaFX

Annotations

Repeatable annotations

@Repeatable(Filterables.class)
@interface Filterable {
    int value();
}

// wrapping annotation
@interface Filterables {
    Filterable[] value();
}

Annotations on types

  • declarations
  • with parameters
  • with return values
  • with generic parameters

Other resources

Functional thinking

  • 4clojure: a lot of functional exercises taking you from zero to hero

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages