Java 9/10: Small Enhancements

mug

Part 1: JShell, Collection Literals, Optional

JShell

Cursor Keys and Tab Completion

JShell Tips

Exercise 1

More about JShell

Collection Literals

Exercise 2

Exercise 2 Solution

Map Literals

Exercise 3

What does this code print?

Map<String, Integer> months = Map.of("Jan", 1, "Feb", 2, "Mar", 3, "Apr", 4, "May", 5, "Jun", 6,
   "Jul", 7, "Aug", 8, "Sep", 9, "Oct", 10, "Nov", 11, "Dec", 12);
System.out.println(map);
  1. {Jan=1, May=5, Aug=8, ..., Feb=2}
  2. {Apr=4, Aug=8, Dec=12, ... Sep=9}
  3. {Jan=1, Feb=2, Mar=3, ..., Dec=12}
  4. Nothing—the code does not compile.

Exercise 3 Solution

Elvis Operator (Almost)

Optional Refresher

Optional Cheat Sheet

Exercise 4

The call Runtime.version().build() returns an Optional<Integer>, the build number of the JDK. Given the declaration

Optional<Integer> build = Runtime.version().build();

which of the following code segments print the build number or "unknown" if the build number isn't present?

  1. build.ifPresentOrElse(System.out::println, "unknown");
  2. System.out.println(build.orElse("unknown"));
  3. System.out.println(build.get() == null ? "unknown" : build.get());
  4. All of the above.

Optional Code Smells

Optional Java 9 News

Optional Java 9 News

Exercise 5

Consider the method

public Optional<String> input(String prompt) {
   return Optional.ofNullable(JOptionPane.showInputDialog(prompt));
}

The method gets an input string or an empty Optional if the input was canceled by the user.

  1. Call input("How old are you?") and convert the result to an Optional<Integer> that is empty if the user provided no input or the string wasn't an integer.
  2. Give the user two chances to provide an input string.

Don't use the isPresent or get methods. Try not to use ifPresent either.

Exercise 5 Solution

input("How old are you?").
   filter(s -> s.matches("[0-9]+")).
   map(Integer::parseInt)

input("How old are you?").
   or(() -> input("Don't be shy! How old?")). 
   filter(s -> s.matches("[0-9]+")).
   map(Integer::parseInt)

// Note delayed execution of the second input

Part 2: Streams, I/O, Regex

Stream Refresher

Stream News

Files.lines demo

double time(Callable<?> task) throws Exception {
   long start = System.nanoTime();
   System.out.println(task.call());
   long end = System.nanoTime();
   return (end - start) * 1.0E-9;
}

Path path = Paths.get("/home/cay/Dropbox/presentations/2018/corejava9/gutenberg/crsto10.txt")

time(() -> Files.lines(path).filter(w -> w.matches(".*[Cc]ount.*")).count())

Exercise 6

In JShell, do this:

import java.time.*;
LocalDate from = LocalDate.of(2000, 1, 1);
LocalDate to =  LocalDate.of(3190, 7, 4);

Here is how you can work with these objects:

to.getDayOfMonth()
from.getDayOfWeek() == DayOfWeek.SATURDAY

Now make a stream of dates by calling:

from.datesUntil(to)

and find out how many of them are a “Friday the 13th”.

Hint: filter, count

What did you get?

Exercise 6 Solution

from.datesUntil(to).
   filter(d -> d.getDayOfWeek() == DayOfWeek.FRIDAY).
   filter(d -> d.getDayOfMonth() == 13).
   count()

Stream News

Exercise 7

Now let's look at another question about words. Given a particular last letter, what characters can be in such a word? For example, looking at all words ending with v, not a single one contains the letters j, q, w, or x.

  1. Read the words from /usr/share/dict/words.
  2. Group by last letter.
  3. Break each word into letters, using:
    public Stream<String> letters(String w) { return Stream.of(w.split("")); }
    
  4. Use flatMapping to flatten those streams in the same group.
  5. As before, collect to sets.
  6. Now look at all words ending in q. What letters can they contain?
  7. Now look at all words ending in f. Which letter is absent from all of them?

Exercise 7 Solution

public Stream<String> letters(String w) { return Stream.of(w.split("")); }

import java.util.stream.Collectors.*;

Path path = Paths.get("/usr/share/dict/words");
Map<String, Set<String>> result = Files.lines(path).
   collect(
      groupingBy(w -> w.substring(w.length() - 1),
         flatMapping(w -> letters(w),
            toSet())))

I/O Improvements

Exercise 8

In JShell, start with

import java.net.*;
import static java.nio.charset.StandardCharsets.*;

URL url = new URL("http://www.gutenberg.org/files/11/11-0.txt");
InputStream in = url.openStream();

Now we want to read the contents of this web page as a string.

There is no method (yet) for reading a string from a given input stream. So, read it into a byte array, and then convert it to a string, using the constructor

String(byte[] bytes, Charset encoding)

What is the length of the string that you get? Use StandardCharsets.UTF_8 as the encoding.

How many times does it contain “Alice”? (Hint: Scanner.tokens)

Regex Refresher

Regex Improvements

Exercise 9

As before, read the contents of “Alice in Wonderland” into a string:

import java.net.*;
import static java.nio.charset.StandardCharsets.*;

URL url = new URL("http://www.gutenberg.org/files/11/11-0.txt");
String text = new String(url.openStream().readAllBytes(), UTF_8);

Using the regular expression [0-9]+ and one of the new Java 9 features, and match all integers.

How many are there?

How do you replace them all with their hexadecimal value?

Exercise 9 Solutions

Pattern.compile("[0-9]+").matcher(text).results().count()

Pattern.compile("[0-9]+").matcher(text).results().map(MatchResult::group).toArray()

Pattern.compile("[0-9]+").matcher(text).replaceAll(m ->
   String.format("0x%x", Integer.parseInt(m.group())))

Part 3: Completable Futures, Processes, HTTPClient

CompletableFuture Refresher

CompletableFuture

Process API Refresher

Process Handles

Exercise 10

List all Java processes that are currently running.

Exercise 10 Solution

ProcessHandle.allProcesses().
   flatMap(h -> h.info().command().stream()).
   filter(c -> c.contains("java")).
   toArray()


ProcessHandle.allProcesses().
   filter(h -> h.info().command().map(c -> c.contains("java")).orElse(false)).
   map(ProcessHandle::pid).toArray()

Process Improvements

Exercise 11

Crazy calculator with jshell:

String expr = "6 * 7";
Process proc = new ProcessBuilder("jshell", "-q").start();
Writer out = new OutputStreamWriter(proc.getOutputStream());
out.write("System.out.println(" + expr + ");\n");
out.close();
String result = new String(proc.getInputStream().readAllBytes(), "UTF-8");

It's unsatisfactory that some cleanup is required.

Let's do something crazier instead. Can we call System.exit(expr) instead and retrieve the exit value in the onExit method?

In Java 9, this works if you change the JShell command slightly. Run

jshell --execution local

Otherwise JShell won't exit when the System.exit method is called.

In Java 10, don't call System.exit, but instead use /exit followed by the expression to be evaluated.

Exercise 11 Solution

String expr = "6 * 7";
Process proc = new ProcessBuilder("jshell", "-q").start();
proc.onExit().thenAccept(h -> System.out.println(h.exitValue()));
Writer out = new OutputStreamWriter(proc.getOutputStream());
out.write("/exit " + expr + "\n");
out.close();

HttpClient

HttpClient

jshell

jshell --add-modules jdk.incubator.httpclient
  
import jdk.incubator.http.*;
import java.net.*;

HttpClient client = HttpClient.newBuilder().build();
HttpRequest request = HttpRequest.newBuilder().
   uri(URI.create("http://www.gutenberg.org/files/11/11-0.txt")).
   GET().
   build();
client.sendAsync(request, HttpResponse.BodyHandler.asString()).
   thenAccept(response -> System.out.println(response.body()));

Part 4: Miscellaneous Improvements

Deprecation

Exercise 12

Here is a class Fred whose no-arg constructor throws an IOException:

class Fred {
   public Fred() throws IOException {
      throw new IOException("No Fred.properties");
   }
}

Can this method throw an IOException? Why or why not?

public Fred haveFred() throws ReflectiveOperationException {
   return Fred.class.newInstance();
}

Call the method. What happens?

Now try:

public Fred haveFred() throws ReflectiveOperationException {
   return Fred.class.getConstructor().newInstance();
}

What is different?

Minor Changes

Exercise 13

Write a utility method:

String string(InputStream in, Charset cs) throws IOException

Your method should read the contents of in as a string. Be sure to close the string even if an exception occurs.

What exception could occur?

Exercise 13 Solution

String string(InputStream in, Charset cs) throws IOException {
   try (in) {
      return new String(in.readAllBytes(), cs);
   }
}

InputStream makeUnhappy(InputStream in, int b) {
   return new InputStream() {
      public int read() throws IOException {
         int r = in.read();
         if (r == b) throw new IOException();
         return r;
      }
      public void close() throws IOException {
         System.out.println("Closing stream.");
         in.close();
      }
   };
}

string(makeUnhappy(Files.newInputStream(Paths.get("/usr/share/dict/words")), '*'), UTF_8)
string(makeUnhappy(new URL("http://www.gutenberg.org/files/11/11-0.txt").openStream(), '*'), UTF_8)

Java 10 News Flash - Local Type Inference

java9news

Version Numbers

Command-Line Argument Cleanup

Command-Line Argument Cleanup

Exercise 14

Try out those cleaned-up arguments. Which of these works?

  1. java --jar MyApp.jar
  2. jar -c -f=MyApp.jar *.class
  3. jar -cv -f MyApp.jar *.class
  4. jar cvf MyApp.jar *.class

Exercise 14 Solution

Propeller Head Stuff

Exercise 15

Look at this method:

private <T> T[] asArray(T... args) { return args; }

Now try this:

Optional<Integer>[] optInts = asArray(Optional.of(42), Optional.empty());
Object[] objects = optInts;
objects[0] = Optional.of("Java");
System.out.println(optInts[0].get() + 1);

This can't be good. objects[0] and optInts[0] are the same object.

Where does an exception occur?

Annotate asArray with @SafeVarargs. Does that solve the problem?

Is this method safe?

@SafeVarargs private <T> void printAll(T... args) {
   Stream.of(args).forEach(System.out::println);
}

My Favorite Features

apisearch