Advanced Java

Tipps & Tricks

1. Lambdas

2. Streams

3. Debugging

Don't force

Only use when you are sure

Not applicable everywhere

Lambdas

Basics

Lambdas are short-forms for functions

Great for tasks that are 1-5 lines long

Helpful to break tasks in to elementary blocks

(a, b) -> {
    // CODE here
    return abc;
}

Input list

Lambda arrow

Function braces

Raw code

Consumer<T>

// Notation
(input) -> {
    execute(input);
}

// Example
list.forEach((input) -> {
    execute(input);
});

// Equivalent Code
for (int i = 0; i < list.size(); i++) {
    __consumer_lambda(list.get(i));
}

private void __consumer_lambda(Object input) {
    execute(input);
}
  • Consumes 1 Object, returns void
  • Often used in forEach functions

Supplier<T>

// Notation
() -> {
    return new Object();
}

// Example
stream.reduce(() -> {
    return new Integer(0);
}, (a, b) -> {
    return a + b;
});

// Equivalent Code
private Object __supplier_lambda() {
    return new Object();
}
  • Consumes nothing, supplies 1 object
  • Rarely used, more in PProg

Function<T, R>

// Notation
(a) -> {
    return new Person(a);
}

// Example
stream.map((a) -> {
    return new Person(a);
});

// Equivalent Code
for (int i = 0; i < ageList.size(); i++) {
    int age = ageList.get(i);
    Person person = __function_lambda(age);
    personList.add(person);
}

private Person __function_lambda(int age) {
    return new Person(age);
}
  • Consumes 1 object, returns 1 object
  • Used to map from a→b

BiFunction<T, U, R>

// Notation
(a, b) -> {
    return (a + b)/2;
}

// Example
stream.reduce((a, b) -> {
    return a + "-" + b;
});

// Equivalent Code
String reduced = list.get(0);
for (int i = 1; i < list.size(); i++) {
	reduced = __bifunction_lambda(reduced, list.get(i));
}

private String __bifunction_lambda(String a, String b) {
    return a + "-" + b;
}
  • Consumes 2 objects, returns 1 object
  • Used to sort

Sugar

Nicer syntax

  • One liners don't need braces {}
  • Existing functions can be referenced with ::
  • Use .x().x() for chaining
(a) -> {
    return a - 1;
}
(a) -> a - 1
(a) -> {
    return this.func(a);
}
this::func
Stream<?> a = list.stream();
Stream<?> b = a.filter(...);
Stream<?> c = b.map(...);
Stream<?> d = c.distinct();
Stream<?> d = list.stream()
    .filter(...)
    .map(...)
    .distinct();

Streams!

This is ugly

int[] ages; // This is given from Thomas Gross
int pos;
int temp;

for (int i = 0; i < ages.length; i++) { // Sort ages
    pos = i; 
    for (int j = i+1; j < ages.length; j++) {
        if (ages[j] < ages[pos]) {
            pos = j;
        }
    }

    temp = ages[pos];
    ages[pos] = ages[i]; 
    ages[i] = temp; 
}

List<Person> personList = new LinkedList<>();

for (int i = 0; i < ages.length; i++) {
    if (ages[i] > 25) {                         // Exclude by age
        personList.add(new Person(ages[i]));
    }
}

return personList.get(0); // Return first

min. 28 Points of failure

This is better

int[] ages; // This is given from Thomas Gross

return IntStream.of(ages)
          .sorted()
          .filter(age -> age > 25)
          .mapToObj(age -> new Person(age))
          .findFirst()
          .get();

Sort

Filter by age

Map to person

Retrieve first

7 Points of Failure

Streams are:

  1. less prone to errors
  2. pre-built
  3. easy to reuse
  4. available (almost) everywhere
(new ArrayList<>()).stream();
(new LinkedList<>()).stream();
(new HashSet<>()).stream();
(new HashMap<>()).entrySet().stream();
(new PriorityQueue<>()).stream();

Stream Structure

  • Start often via .stream()
  • Intermediate steps
  • Terminal operation

Intermediate

Terminal

  • .map(Function<A,B>)
  • .filter(Predicate<A>)
  • .distinct()
  • .sorted()
  • .limit(long)
  • .flatMap(Function<A,B>)
  • .reduce(BinaryOperator<T>)
  • .allMatch(Predicate<T>)
  • .anyMatch(Predicate<T>)
  • .forEach(Consumer<T>)
  • .count()
  • .collect()
  • .min()
  • .max()

Example 1

Get the unique surnames in uppercase of the first 15 book authors that are 50 y/o or older.

class Book {
    Author author;
}

class Author {
    String firstname;
    String surname;
    int age;
}

List<Book> library;
library.stream()
    .map(book -> book.getAuthor())
    .filter(author -> author.getAge() >= 50)
    .map(author -> author.getSurname())
    .distinct()
    .limit(15)
    .map(name -> name.toUpperCase())
    .collect(Collectors.toList());

Example 2

Compute the sum of ages of all female authors younger than 25

class Book {
    Author author;
}

class Author {
    String firstname;
    String surname;
    int age;
    Gender gender;
}

enum Gender {
    MALE, FEMALE;
}

List<Book> library;
library.stream()
    .map(book -> book.getAuthor())
    .distinct()
    .filter(author -> author.getAge() < 25)
    .filter(author -> author.getGender() == FEMALE)
    .mapToInt(author -> author.getAge())
    .sum();

Collectors

DO NOT TRY THIS AT HOME*

* unless you are really, really, really good

     // Accumulate names into a List
     List<String> list = people.stream().map(Person::getName).collect(Collectors.toList());

     // Accumulate names into a TreeSet
     Set<String> set = people.stream().map(Person::getName).collect(Collectors.toCollection(TreeSet::new));

     // Convert elements to strings and concatenate them, separated by commas
     String joined = things.stream()
                           .map(Object::toString)
                           .collect(Collectors.joining(", "));

     // Compute sum of salaries of employee
     int total = employees.stream()
                          .collect(Collectors.summingInt(Employee::getSalary)));

     // Group employees by department
     Map<Department, List<Employee>> byDept
         = employees.stream()
                    .collect(Collectors.groupingBy(Employee::getDepartment));

     // Compute sum of salaries by department
     Map<Department, Integer> totalByDept
         = employees.stream()
                    .collect(Collectors.groupingBy(Employee::getDepartment,
                                                   Collectors.summingInt(Employee::getSalary)));

     // Partition students into passing and failing
     Map<Boolean, List<Student>> passingFailing =
         students.stream()
                 .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));

Debugging

Stuck? Use auto-complete

name(Argument) : returnType

filter(Predicate<Integer>) : Steam<Integer>

filter die Daten

das jede Zahl hier true ergibt

gib uns wieder einen Stream zurück

Crtl + Space

Stuck? Use auto-complete

x 2

EXERCISE!

Exercise

Print the titles of all books that aren't in the library.

Get Titles

Check in library

Print

Get all books

MODULAR DESIGN

Exercise

Print the titles of all books that aren't in the library.

Print

Get Titles

Check in library

Get all books

MODULAR DESIGN

Exercise

Print the titles of all books that aren't in the library.

Print

Get Titles

Check in library

Get all books

MODULAR DESIGN

Exercise

Print the titles of all books that aren't in the library.

Print

Get Titles

Check in library

Get all books

MODULAR DESIGN

Exercise

Print the titles of all books that aren't in the library.

Print

Get Titles

Check in library

Get all books

MODULAR DESIGN

Exercise

Print the titles of all books that aren't in the library.

Print

Get Titles

Check in library

Get all books

author.authored
library.contains()
book.title
System.out.println()

MODULAR DESIGN

Exercise

Print the titles of all books that aren't in the library.

class Book {
    Author author;
    String title;
}

class Author {
    String firstname;
    String surname;
    int age;
    List<Book> authored;
}

List<Book> library;
library.stream()
    .map(book -> book.author)
    .flatMap(author -> author.authored.stream())
    .filter(book -> !library.contains(book))
    .distinct()
    .map(book -> book.title)
    .forEach(title -> System.out.println(title));

Hint: flatMap(item -> item.list.stream())

Previous Exams

A1, FS18

Hint 1: Automate the iteration

Hint 2: Calculate the difference per entry

A2, HS18

Hint 1: Collections.reverseOrder()

Hint 2: .limit()

A2, HS18

A4, FS19

Hint 1: Collectors.toMap()

A4, FS19

public class Inspektor {
	// Store a reference to all stalls that have previously been scanned
	private List<Stall> stalls = new ArrayList<Stall>();
	// Store references to stalls that had parasites at some point in time
	private Set<Stall> parasiteStalls = new HashSet<Stall>();
	
	public Map<String, String> besteStaelle() {
		return stalls.stream()
			// Grab a stream of all distinct manufacturer names
			.map(stall -> stall.manufacturer)
			.distinct()
			// Generate pairs (manufacturer, bestStall)
			.map(manufacturer -> bestStallPair(manufacturer))
			// Remove manufacturers that have no best stall
			.filter(p -> p.stallName != null)
			// Accumulate all Pairs to a map
			.collect(Collectors.toMap(p -> p.manufacturerName, p -> p.stallName));
	}	

	private Pair bestStallPair(String manufacturer) {
		String bestStall = stalls.stream()
				// Grab a stream of all candidates for this manufacturer
				.filter(stall -> !parasiteStalls.contains(stall) && stall.manufacturer.equals(manufacturer))
				// Choose the best one
				.sorted()		// Note that .sorted() requires my custom implementation
				.findFirst()	// of compareTo(:) in the Stall class!
				// Save its name...
				.map(stall -> stall.name)
				// ... or null if it doesn't exist.
				.orElse(null);

		return new Pair(manufacturer, bestStall);
	}
	
	private static class Stall implements Comparable<Stall> {
		String name, manufacturer;
		float averageEggs, foodQuantity;

		public Stall(String name, float averageEggs, float foodQuantity, String manufacturer) {
			this.name = name;
			this.averageEggs = averageEggs;
			this.foodQuantity = foodQuantity;
			this.manufacturer = manufacturer;
		}

		public Stall(String name) {
			this.name = name;
			this.averageEggs = 0;
			this.foodQuantity = 0;
		}

		@Override
		public boolean equals(Object obj) {
			if (!(obj instanceof Stall))
				return false;
			// Two objects are "identical" if their name is the same!
			return name.equals(((Stall) obj).name);
		}

		@Override
		public int hashCode() {
			// Two objects are "identical" if their name is the same!
			return Objects.hash(name);
		}

		@Override
		public int compareTo(Stall o) {
			// Sort in decreasing efficiency
			return Float.compare(o.averageEggs / o.foodQuantity, this.averageEggs / this.foodQuantity);
		}
	}

	private static class Pair {
		String stallName, manufacturerName;

		public Pair(String manufacturerName, String stallName) {
			this.stallName = stallName;
			this.manufacturerName = manufacturerName;
		}
	}

}

Good Classes

IntStream (for 1,2,3,...)

Collectors (for collecting)

Collections (sort, min, max)

Best of luck for your exams!