Appendix A
From Java 6/7 to Java 8


Exam Objectives

Develop code that uses Java SE 8 collection improvements, including Collection.removeIf(), List.replaceAll(), Map.computeIfAbsent(), and Map.computeIfPresent() methods.
Develop code that uses String objects in the switch statement, binary literals, and numeric literals, including underscores in literals.
Use Lock, ReadWriteLock, and ReentrantLock classes in the java.util.concurrent.locks.
Format dates, numbers, and currency values for localization with the NumberFormat and DateFormat classes, including number and date format patterns.
Recursively access a directory tree by using the DirectoryStream and FileVisitor interfaces
Find a file by using the PathMatcher interface.
Observe the changes in a directory by using the WatchService interface.

New Methods on Collections

Java 8 brings some new helper methods for Collections to write less verbose code.

The first one is forEach() (we talked about it earlier):

default void forEach(Consumer<? super T> action)

That calls the accept() method on each element of the collection. So instead of having something like:

for(int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

Or:

for(String s : list) {
    System.out.println(s);
}

Now we can have it as (without creating a stream):

list.forEach(System.out::println);

Another one is removeif():

default boolean removeIf(Predicate<? super E> filter)

That iterates over the elements of the collection and removes an element if it matches the given predicate (if the implementation of the Collection doesn't support removal, an UnsupportedOperationException is thrown). So instead of having something like:

Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    if(s != null && s.length() < 3) {
        it.remove();
    }
}

Now we can have just:

list.removeIf( s -> s != null && s.length() < 3 );

We also have replaceAll():

default void replaceAll(UnaryOperator<E> operator)

That replaces each element of this list with the result of applying the operator to the element. So instead of having something like:

Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    it.set(s.toUpperCase());
}

Now we just have:

list.replaceAll(s -> s.toUpperCase());

New Methods on Maps

Java 8 also brings new helper methods to Maps. There's two interesting methods in particular.

One is computeIfAbsent():

default V computeIfAbsent(
        K key,
        Function<? super K,? extends V> mappingFunction)

This method calculates the value of the given key, and adds it to the map only if:

For example:

Map<String, String> letters = new HashMap<>();
letters.put("a", "a");
letters.put("b", null);

Function<String, String> func = k -> {
    if(k.startsWith("d")) return null;
    return k.toUpperCase();
};

// Won't update,already in map

letters.computeIfAbsent("a", func);
// Will update
 letters.computeIfAbsent("b", func);
// Will add
letters.computeIfAbsent("c", func);
// Won't add
letters.computeIfAbsent("d", func);
// Yes, there's also a forEach in Maps
letters.forEach( (key, value) ->
    System.out.format("%s-%s;", key, value)
);

The output:

a-a;b-B;c-C;

The other method is computeIfPresent():

default V computeIfPresent(
        K key,
        BiFunction<? super K,? super V,? extends V> remappingFunction)

This method updates the value of a given key only if:

If the key has a non-null associated and the function returns null, the key is removed from the map. For example:

Map<String, String> letters = new HashMap<>();
letters.put("a", "a");
letters.put("b", null);
letters.put("d", "d");

BiFunction<String, String, String> func = (k, v) -> {
    if(k.startsWith("d")) return null;
    return k.toUpperCase();
};

// Will update
letters.computeIfPresent("a", func);
// Won't update 
letters.computeIfPresent("b", func);
// Won't add 
letters.computeIfPresent("c", func);  
// Will remove
letters.computeIfPresent("d", func);  
letters.forEach( (key, value) ->
    System.out.format("%s-%s;", key, value)
);

In this case, the output will be:

a-A;b-null;

Switch statement

A switch statement is another way to represent an if-else statement. This is its syntax:

switch(expression) {
   case constant_value_1:
      statements;
   case constant_value_2:
      statements;
   default:
      statements;
}

The switch expression must evaluate to one of the following types:

Anything else, like a float, will generate a compiler error.

Here's an example of a switch statement:

String s = "Jack";
switch(s) {
   case "Mike":
      System.out.println("Good morning Mr. " + s);
      break;
   case "Laura":
      System.out.println("Good morning Mrs. " + s);
      break;
   default:
      System.out.println("Good morning " + s);
}

There are many things to take into account with a switch statement.

First, each case needs either a constant value or a final variable initialized at declaration. Otherwise, a compile time error will be generated:

final int goodFinal = 2;
final int badFinal;
badFinal = 3;
int var = 1;

switch(var) {
   case 1:               // OK
      var *= 2;
      break;
   case goodFinal:       // OK
      var++;
      break;
   case badFinal:        // Compiler error!
      var--;
}

It's not valid to have more than case with the same value:

final int goodFinal = 2;
int var = 1;

switch(var) {
   case 1:                // OK
      var *= 2;
      break;
   case 2:                // Compiler error!
      var++;
      break;
   case goodFinal:        // Compiler error!
      var--;
}

Besides, case labels are evaluated from top to down beginning from the first case constant that matches the switch expression. This means that all the subsequent statements will be executed until the end of the switch statement, or a break is found. For example:

int var = 1;

switch(var) {
   case 1:
      System.out.println("Shirt");
   case 2:
      System.out.println("Pants");
   case 3:
      System.out.println("Shoes");
}

Will output:

Shirt
Pants
Shoes

Because of this, we can have a switch statement like the following:

int var = 1;

switch(var) {
   case 1:
   case 2:
      System.out.println("Pants");
      break;
   case 3:
      System.out.println("Shoes");
}

Where, if var is 1 or 2, execution will fall through, and Pants will be printed.

Also, notice that default doesn't need a break because it's the last element of the switch statement. However, default can appear in any position, so watch for it.

Finally, it may be obvious, but switch statement can only test for equality. This is important when using a String expression because we have to take into account the meaning of object equality. In other words, this won't match:

String s = "a";

switch(s) {
   case "A": // Strings are case-sensitive, so this case won't match
   ...
}

Numeric Literals

In Java, integers numbers are by default of type int and floating-point numbers are by default of type double.

Integer numbers are the ones with types byte, short, int, and long and generally expressed in decimal base. However, they can also be expressed as hexadecimal or binary numbers.

Hexadecimal numbers consist of numbers 0 through 9 and letters A through F. To specify a hexadecimal number, you add the 0x or 0X prefix.

int hex = 0x2B; // The number 43

Binary numbers consist of the numbers 0 and 1. To specify a binary number, you add the 0b or 0B prefix.

int bin = 0b0010110; // The number 22

These numbers can be cast to float or double types, but we can't express these types as hexadecimal or binary numbers. For example:

float goodFloat = 0xF2;             // OK, number 242.0
double goodDouble = 0b11110110; ;   // OK, number 246.0
float badFloat = 0xF2f;             // Compilation error!
double badDouble = 0b11110110d; ;   // Compilation error!

From Java 7, you can place any number of underscores between the digits of numbers to separate groups of digits and improve readability.

You cannot put underscores in the following places:

Here are some examples:

int i = 34_765;             // OK
float f = 43.987_876;       // OK
short s = 0b0000____0101;   // OK
int i2 = 100_;              // Compilation error!
int i3 = 0_x1D;             // Compilation error!
int i4 = 0x_1D;             // Compilation error!
float f2 = 8945.40_f;       // Compilation error!
float f3 = 8945_.40f;       // Compilation error!

java.util.concurrent.locks

Soon...

Formatting dates, numbers, and currency values

Soon...

DirectoryStream and FileVisitor interfaces

Soon...

PathMatcher

Soon...

WatchService

Soon...