Java Arena
Step 6 of 9

Streams of objects

Streams work on lists of objects too, and that's where they're most useful. The lambdas call the objects' methods: a filter asks about a getter, and map turns each object into one of its values.

The cycling club from step 1 now keeps each ride as an object, with the rider's name and the distance:

Main.java
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Ride> rides = new ArrayList<>();
        rides.add(new Ride("Noor", 42));
        rides.add(new Ride("Pablo", 65));
        rides.add(new Ride("Noor", 18));
        rides.add(new Ride("Kaia", 80));
        rides.add(new Ride("Pablo", 51));

        rides.stream()
                .filter(ride -> ride.getKm() > 50)
                .forEach(System.out::println);
    }
}
Ride.java
public class Ride {
    private String rider;
    private int km;

    public Ride(String rider, int km) {
        this.rider = rider;
        this.km = km;
    }

    public String getRider() {
        return this.rider;
    }

    public int getKm() {
        return this.km;
    }

    @Override
    public String toString() {
        return this.rider + ": " + this.km + " km";
    }
}
Output
Pablo: 65 km
Kaia: 80 km
Pablo: 51 km

The stream is a stream of Ride objects, so ride in the lambda is a Ride, and the lambda can call its methods. forEach(System.out::println) prints each ride with its toString.

From objects to values

map can turn each ride into one of its values. The names of the club's riders, each once, in alphabetical order:

List<String> riders = rides.stream()
        .map(ride -> ride.getRider())
        .distinct()
        .sorted()
        .collect(Collectors.toList());
System.out.println(riders);

For the five rides above, this prints [Kaia, Noor, Pablo]. After map, the stream holds Strings, so distinct and sorted work on the names.

A getter works as a method reference too: Ride::getRider is short for ride -> ride.getRider(), just as String::toUpperCase was a method of the value itself, named by its class.

Sums and averages of a value

mapToInt with a getter turns the rides into their distances, as plain int values, ready for sum and average:

Main.java
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Ride> rides = new ArrayList<>();
        rides.add(new Ride("Noor", 42));
        rides.add(new Ride("Pablo", 65));
        rides.add(new Ride("Noor", 18));
        rides.add(new Ride("Kaia", 80));
        rides.add(new Ride("Pablo", 51));

        double average = rides.stream()
                .mapToInt(Ride::getKm)
                .average()
                .getAsDouble();
        int noorsTotal = rides.stream()
                .filter(ride -> ride.getRider().equals("Noor"))
                .mapToInt(Ride::getKm)
                .sum();
        System.out.println("Average ride: " + average + " km");
        System.out.println("Noor: " + noorsTotal + " km");
    }
}
Ride.java
public class Ride {
    private String rider;
    private int km;

    public Ride(String rider, int km) {
        this.rider = rider;
        this.km = km;
    }

    public String getRider() {
        return this.rider;
    }

    public int getKm() {
        return this.km;
    }

    @Override
    public String toString() {
        return this.rider + ": " + this.km + " km";
    }
}
Output
Average ride: 51.2 km
Noor: 60 km
  • The average is 256 / 5 = 51.2.
  • The filter compares names with equals, as always for Strings, and then only Noor's rides reach mapToInt: 42 + 18 = 60.

Lists of objects

collect works for objects as it does for numbers. rides.stream().filter(ride -> ride.getKm() > 50).collect(Collectors.toList()) gives a List<Ride> of the long rides. The new list holds the same Ride objects as the old one, not copies (module 22): a change made to a ride through one list shows through the other.

One thing doesn't work yet: sorted() on a stream of Rides. Java knows how to order numbers and Strings, but not rides: by distance, or by name? Module 38 shows how to tell it. Until then, map the objects to values first, and sort those.

Your task

Challenge 1 of 3

Ride.java is ready. In Main.java, write the method totalKm(List<Ride> rides, String rider), which returns the total distance of the given rider's rides, as an int, and 0 if the rider has none. Use a stream. The check runs:

List<Ride> rides = new ArrayList<>();
rides.add(new Ride("Noor", 42));
rides.add(new Ride("Pablo", 65));
rides.add(new Ride("Noor", 18));
System.out.println(totalKm(rides, "Noor"));

and expects:

60

Challenge 2

What does this program print?

Challenge 3

Screening.java is ready: a screening has a film, the number of tickets sold and the number of seats, and isSoldOut() says whether every seat is sold. In Main.java, write the method soldOutFilms(List<Screening> screenings), which returns a new list of the films that had at least one sold-out screening, each once, in alphabetical order. Use a stream, and Screening::isSoldOut in the filter. The check runs:

List<Screening> screenings = new ArrayList<>();
screenings.add(new Screening("Vertigo", 120, 120));
screenings.add(new Screening("Heat", 64, 90));
screenings.add(new Screening("Alien", 90, 90));
screenings.add(new Screening("Vertigo", 80, 80));
System.out.println(soldOutFilms(screenings));

and expects:

[Alien, Vertigo]