Java SE 11 Programmer II (IZ0-816)

1 Java Fundamentals

1.1 Create and use final classes

1.2 Create and use inner, nested and anonymous classes

  • member inner class

    • Can be declared public, private, or protected or use default access
    • Can extend any class and implement interfaces
    • Can be abstract or final
    • Can access members of the outer class including private members by <OuterClassName>.this.<memberName>
    • Instead of new operator, use <instance of outer class>.new
    • Compiled bytecode file is named as <OuterClassName>$<InnerClassName>.class
    • Multiple level of member inner classes can be nested
    • When used outside the outer class, its reference type must be refered to as <OuterClassName>.<InnerClassName>
  • local inner class

    • Can only be class
    • Cannot be interface or enum
    • Can be abstract or final
    • Like a local variable, cannot be accessed outside of the method
    • Like a local variable, cannot have access modifier
    • Cannot be declared static
    • Can have static final fields, but not static only fields
    • Cannot have static initialization blocks
    • Cannot have static methods
    • Cannot have static nested class
    • Cannot have inner interface or enum
    • Can access members of the enclosing class
    • Can only access final or effectively final local variables of the enclosing method
  • anonymous inner class

    • Must extend an existing class or implement an existing interface
    • Can be converted to a lambda expression when implementing a functional interface
    • Can have static fields
    • Cannot have static initialization blocks
    • Cannot have static methods
    • Cannot have static nested class
    • Cannot have inner interface or enum
    • Can access members of the enclosing class
    • Can only access final or effectively final local variables of the enclosing method
    • Can define new methods the same as other inner classes.
  • static nested class

    • member level static class
    • Can be referenced without enclosing type
    • Cannot access members of the enclosing class without an instance
    • Enclosing class can access its static members by using class name, even private ones
    • Can be imported by either import or import static

1.3 Create and use enumerations

  • enum

    • enum is essentially a class with enumerated, predefined class instances, which can have normal class elements, even abstract method.

    • enum literals must be on the first line of the body, and if there are any other content in the enum, the last literal must be followed by semi-colon.

    • enum literals are actually instances of the enum created beforehand, therefore they are static by nature.

    • enum constructors must be and are private implicitly.

    • enum can implement interfaces but can't extend any other class or enum.

    • Best for creating singleton objects intended for reuse. While enum could have more behaviors, its data is meant to be read-only.

      enum Tiger {
          SIBERIAN(1), BENGAL(2);
       
          public static final int LEVEL = 9;
       
          private final double length;
       
          Tiger(double length) {
              this.length = length;
          }
       
          public double getLength() {
              return length;
          }
       
          public static void t() {
              System.out.println("t");
          }
       
          private static void p() {
              System.out.println("p");
          }
       
          public void u() {
              System.out.println("p");
          }
       
          private void m() {
              System.out.println("p");
          }
       
          private static class Cub {
              public static void print() {
                  System.out.println("length: " + SIBERIAN.length);
              }
          }
      }
    • A enum constant can define an anonymous class. This anonymous class extends the enum type itself, and if the enum type has any method defined, it can be overridden in the anonymous class.

      enum Animal {
      DOG("happy") {
          @Override
          void communicate() {
              bark();
          }
       
          private void bark() {
              System.out.println("woof");
          }
       
          @Override
          public String toString() {
              return this.name;
          }
      },
      CAT("aloof") {
          @Override
          void communicate() {
              meow();
          }
       
          private void meow() {
              System.out.println("meow");
          }
       
          @Override
          public String toString() {
              return this.name;
          }
      };
       
      protected final String name;        // Compiler error if private
       
      abstract void communicate();
       
      public abstract String toString();
       
      Animal(String n) {
          this.name = n;
      }

2 Java Interfaces

  • In class definition, extends must go before implement.

    class Test1 implements Foo extends Bar {}     // Compiler error
    class Test2 extends Bar implements Foo {}     // OK

2.1 Create and use interfaces with default methods

  • default methods are implicitly public.

  • can only be instance methods

  • can be overridden in subclasses

  • Cannot override java.lang.Object methods

  • Multiple inheritance

    • a class implementing 2 or more interfaces with 2 or more methods of the same signature, at least one of which is a default method, must resolve the conflict by overriding the method. This only applies to when one of those methods is default method.
    interface Q18_A {
        void m();
    }
     
    interface Q18_B {
        default void m() {
            System.out.println("B");
        }
    }
     
    abstract class Q18_C implements Q18_A, Q18_B {
        @Override
        public void m() {         // Compiler error if the method is not overridden to resolve the conflict, and this cannot be delegated to subclasses.
        }
    }
     
    class D extends Q18_C {
        @Override
        public void m() {
            System.out.println("D");
        }
    }
    • Multiple methods from different types with the same signature but different access level must be overridden in the first common descendant, and the access level must be the least restrictive one among the methods with the same signature.

      abstract class Animal {
          abstract void fly();
      }
       
      interface Flyable {
          void fly();
      }
       
      public class Bird extends Animal implements Flyable {
          @Override
          public void fly() {
              System.out.println("fly");
          }
      }
    • Multiple methods from different types with the same signature but incompatible return types must be overridden in the first common descendant, and the overriding method must have a covariant return type that accommodate all incompatible types.

      abstract class Animal {
          abstract List<? extends Object> fly();
      }
       
      interface Flyable {
          List<String> fly();
      }
       
      public class Bird extends Animal implements Flyable {
          @Override
          public List<String> fly() {
              System.out.println("fly");
              return null;
          }
      }
  • With the same signature, in subclasses, non abstract method would take precedence over default methods

    @Test
    public void a() {
        Child child = new Child();
        child.m1();         // Print "C"
    }
     
    class C {
        public void m1() {
            System.out.println("C");
        }
    }
     
    interface I {
        default void m1() {
            System.out.println("I");
        }
    }
     
    class Child extends C implements I {
    }
  • static and instance methods cannot have the same signature, and all instance methods are included: abstract, default and private ones, in both interface and class.

  • When referring to an overridden method in a super-interface, we must use the format <super interface name>.super.<method name>.

    interface Q46_Foo {
        default void m() {
            System.out.println("Foo");
        }
    }
     
    interface Q46_Bar extends Q46_Foo {
        default void m() {
            Q46_Foo.super.m();            // <super interface name>.super.<method name>
        }
    }

2.2 Create and use interfaces with private methods

  • can be static methods
  • can be instance methods
  • static method must be invoked on containing interface only.

3 Functional Interface and Lambda Expressions

3.1 Define and write functional interfaces

  • The only difference between a FunctionalInterface and a regular interface is there can be only one abstract method in a FunctionalInterface.
  • Any interface with only one abstract method is considered effectively a FunctionalInterface.
    • abstract methods do not include inherited non-abstract instance methods from Object, such as String toString().
    • abstract methods include those inherited from super interfaces.

3.2 Create and use lambda expressions including statement lambdas, local-variable for lambda parameters

  • When using lambda expression to implement a FunctionalInterface, the referencing variable must have an explicit type instead of var, otherwise type inference would fail.

  • Any local variable, method parameter, or exception parameter used but not declared in a lambda expression must either be declared final or be effectively final, or a compile-time error occurs where the use is attempted, effectively final meaning the variable cannot be assigned twice in the enclosing block.

  • Variables of lambda expression cannot have the same name as enclosing local variable.

  • Limitations of var used in lambda expression

    (var s1, s2) -> s1 + s2           // Compiler error, all variables must have types or none of them have types
    (var s1, String s2) -> s1 + s2    // Compiler error, var cannot be mixed with explicit types
  • For a lambda returning void, an expression returning value as lambda body is allowed by the compiler. The return value of the expression is simply ignored.

    Consumer<String> c = s -> s.toLowerCase();      // OK, result of s.toLowerCase() is ignored

4 Built-in Functional Interfaces

4.1 Use interfaces from the java.util.function package

4.2 Use core functional interfaces including Predicate, Consumer, Function and Supplier

  • Predicate<T>

    • boolean test(T t)
  • Consumer<T>

    • accept(T t)
  • Function<T, R>

    • R apply(T t)
  • Supplier<T>

    • T get()

4.3 Use primitive and binary variations of base interfaces of java.util.function package

5 Migration to a Modular Application

5.1 Migrate the application developed using a Java version prior to SE 9 to SE 11 including top-down and bottom-up migration, splitting a Java SE 8 application into modules for migration

  • A modular JAR placed on classpath is considered as a plain-old JAR.

  • Unnamed Module

    • All classes of all JARs (modular or not) on the classpath becomes part of an unnamed module, and there's only one unamed module.
    • The unnamed module exports all of its packages.
    • The unnamed module reads all other modules.
    • If a package is defined in both a named module and the unnamed module then the package in the unnamed module is ignored.
    • An unamed module can refer to a named module, but a named module cannot declare a dependence upon an unnamed module, as it cannot be referenced in module-info.java.
  • Automatic Module

    • To enable a unnamed module to be able to be referred to, the unnamed module can be converted to an automatic module by placing it on the module path rather than the class path.
    • The name of an automatic module is derived from that of the JAR file.
    • Automatic module abides by modularization rules and forbids duplicate packages on the module path, therefore packages with the same name can only remain on the class path.
    • Able to read unamed module

5.2 Use jdeps to determine dependencies and identify ways to address the cyclic dependencies

  • output: <package> -> <package> <module/JAR>
    • <module/JAR> would display not found if it cannot be found on the class path.

6 Concurrency

6.1 Create worker threads using Runnable, Callable and use an ExecutorService to concurrently execute tasks

  • java.lang.Thread

    • Started by calling start() not run()
  • java.util.concurrent.Executor

    • void execute(Runnable command)
  • java.util.concurrent.Executors

    • API

      • Single thread executor: Executors.newSingleThreadExecutor()
      • Scheduled thread executor: Executors.newScheduledThreadPool(int corePoolSize)
      • Cached thread executor: Executors.newCachedThreadPool()
      • Fixed number of threads executor: Executors.newFixedThreadPool(int corePoolSize)
  • java.util.concurrent.ExecutorService

    • API

      MethodDescription
      <T> Future<T> submit(Callable<T> task)
      <T> Future<T> submit(Runnable task, T result)
      Future<?> Future<T> submit(Runnable task)
      void shutdown()No new tasks will be accepted. Invocation has no additional effect if already shut down.
      boolean isShutdown()
      boolean isTerminated()
      List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)Wait for all tasks to complete
      T invokeAny(Collection<? extends Callable<T>> tasks)Wait for any task to complete
    • Tasks are not guaranteed to be executed in the order they are submitted to ExecutorService.

  • java.util.concurrent.ScheduledExecutorService

    • API
      MethodDescription
      <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)supports Callable<V>
      ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)period calculated starting from initialDelay
      ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)delay is between the termination of one execution and the commencement of the next
  • java.util.concurrent.Callable

    • Difference with java.lang.Runnable

      • Return value
      • Exception declaration
      java.util.concurrent.Callable:  V call() throws Exception;
      java.lang.Runnable:             void run();
  • java.util.concurrent.Callable

    • Difference with java.util.function.Supplier

      • Exception declaration
      java.util.concurrent.Callable:  V call() throws Exception;
      java.util.function.Supplier:    T get();

6.2 Use java.util.concurrent collections and classes including CyclicBarrier and CopyOnWriteArrayList

  • LinkedBlockingQueue

    • A BlockingQueue does not accept null elements. Implementations throw NullPointerException on attempts to add, put or offer a null. A null is used as a sentinel value to indicate failure of poll operations.

    • All queuing methods are thread-safe, but bulk Collection operations such as addAll are not necessarily thread-safe.

    • Blocking operations

      • put(e)
      • take()
  • CyclicBarrier

    • Once the specified number of threads have each called await(), the barrier is released and all threads can continue.
    • await() is a blocking method, it will wait until the barrier is released.
    • Once the barrier is released, calling await() won't have any effect.
    • Make sure that you set the number of available threads to be at least as large as your CyclicBarrier limit value.
    • CyclicBarrier can be reused by new threads after all current threads are released.
  • CopyOnWriteArrayList

    • iterator()

      • Any Iterator established prior to a modification will not see the changes, but instead it will iterate over the original elements prior to the modification.
      • The returned Iterator does not support the remove.

6.3 Write thread-safe code

6.4 Identify threading problems such as deadlocks and livelocks

  • Avoid acquiring multiple locks. If you want to acquire multiple locks, make sure that they are acquired in the same order everywhere to avoid deadlocks.
  • livelock: Consider two threads t1 and t2. Assume that thread t1 makes a change and thread t2 undoes that change. When both the threads t1 and t2 work, it will appear as though lots of work is getting done, but no progress is made.
  • lock starvation: occurs when low-priority threads starve for a long time trying to obtain the lock
  • race condition: occurs when two threads try to complete a task at the same time, which should have been completed sequentially.

7 I/O (Fundamentals and NIO2)

7.1 Read data from and write console and file data using I/O Streams

  • Console

    • System.out and System.err are PrintStream objects.
  • java.io.File

    • File[] listFiles()
      • returning all files and directories if the given File object is a directory
      • returning null if the given File object is a file

7.2 Use I/O Streams to read and write files

  • OutputStream

    • representing an output stream of bytes

    • BufferedInputStream

      • BufferedInputStream(InputStream in)
      • BufferedInputStream(InputStream in, int bufferSize)
    • ObjectInputStream

      • ObjectInputStream(InputStream in) throws IOException
    • FileInputStream

      • can only accept a file, not a InputStream

      • FileInputStream(String name) throws FileNotFoundException

      • FileInputStream(File file) throws FileNotFoundException

    • PrintStream

      • most methods don't throw IOException, including the common ones:
        • write
        • append
        • flush
        • close
      • Using boolean checkError() method to check if an IOException has occurred.
      • format and printf have identical behaviors.
  • Writer

    • writing to character streams

    • PrintWriter

      • PrintWriter(Writer out)

      • PrintWriter(Writer out, boolean autoFlush)

      • PrintWriter(OutputStream out)

      • PrintWriter(OutputStream out, boolean autoFlush)

      • PrintWriter(OutputStream out, boolean autoFlush, Charset charset)

      • PrintWriter(String fileName)

      • PrintWriter(Charset charset, File file)

      • PrintWriter(String fileName, String csn)

      • PrintWriter(String fileName, Charset charset)

      • PrintWriter(File file)

      • PrintWriter(File file, String csn)

      • PrintWriter(File file, Charset charset)

      • most methods don't throw IOException, including the common ones:

        • write
        • append
        • flush
        • close
      • Using boolean checkError() method to check if an IOException has occurred.

      • format and printf have identical behaviors.

7.3 Read and write objects by using serialization

  • java.io.Serializable

    • In order to serialize objects using the java.io API, the class they belong to must implement the java.io.Serializable interface.
    • Any class, abstract, concrete, or final, can be marked Serializable.
    • java.io.Serializable is a marker interface that has no method.
    • The purpose of implementing the Serializable interface is to inform any process attempting to serialize the object that you have taken the proper steps to make the object serializable, which involves making sure that the classes of all instance variables within the object are also marked Serializable.
    • A process attempting to serialize an object will throw a NotSerializableException if the class or one of its contained classes does not properly implement the Serializable interface.
    • Use transient keyword to mark instance variables that you don't want to serialize.
    • Recommended but not mandatory, serialVersionUID is used for class schema migration. A static field serialVersionUID is used to identify uniquely a version of the class, so that it knows whether the serialized data for an object will match the instance variable in the current version of the class. Without a serialVersionUID field, any change to a class will make previously serialized versions unreadable.
    • serialVersionUID field should be updated anytime the instance variables in the class are changed.
    • null objects can be serialized and deserialized, therefore using instanceof for every serialization and deserialization process.
    • When you deserialize an object, the constructor of the serialized class is not called. Java calls the no-arg constructor for the first nonserializable ancestor class, skipping the constructors of any serialized class in between.
    • If The first non-serializable ancestor class does not have an accessible no-arg constructor, an InvalidClassException will be thrown during serialization and deserialization process.
    • Because of not being a part of a class instance, any static variables or initialization blocks or constructors are ignored during the serialization and deserialization process.
  • ObjectInputStream

    • readObject() returns an Object, therefore a cast is usually needed to restore the instance.
  • ObjectOutputStream

    • writeObject(Object o) requires a Serializable object, otherise it throws a NotSerializableException.

7.4 Use the Path interface to operate on file and directory paths

  • Optional Arguments

    • LinkOption.NOFOLLOW_LINKS
    • FileVisitOption.FOLLOW_LINKS
    • StandardCopyOption.COPY_ATTRIBUTES
    • StandardCopyOption.REPLACE_EXISTING
    • StandardCopyOption.ATOMIC_MOVE
  • java.nio.file.Paths

    • static Path get(String first, String... more)

      • equivalent to static Path.of
      • first is a relative path: it would be relative to working directory.
      • first could contain directory separator.
      • more would be appended to first to concatenate a path separated by directory separator of the current OS.
  • java.nio.file.Path

    • static of(String first, String... more)

      • equivalent to static Paths.get
    • boolean equals(Object other)

      • If the given object is not a Path, or is a Path associated with a different FileSystem, then this method returns false. This method does not access the file system and the file is not required to exist. Where required, the isSameFile method may be used to check if two paths locate the same file.
    • Path getParent()

      • Return the concatenated name elements from the second to last to the farthest
      • Doesn't check the file system
      Path.of("a", "b", "c").getParent()      // "a\b"
      Path.of("log", "..").getParent()        // "log"
      Path.of("/").getParent()                // null
      Path.of(".").getParent()                // null
    • int getNameCount()

      • Returns the number of name elements in the path, or 0 if this path only represents a root component.
    • Path getName(int index)

      • The element that is closest to the root in the directory hierarchy has index 0.
    • Path subpath(int beginIndex, int endIndex)

      • Returns a relative path, and the returned Path object has the name elements that begin at beginIndex and extend to the element at index endIndex - 1.
    • Path relativize(Path other)

      • Basically it means how to reach other path from the invoking path, similar to using cd command in command line.

        var path1 = Path.of("a", "b");
        var path2 = Path.of("c", "d");
        System.out.println(path1.relativize(path2));   // "..\..\c\d"
      • Doesn't check the file system.

      • If both path values are relative, then the relativize() method computes the paths as if they are in the same current working directory.

      • If both path values are absolute, then the method computes the relative path from one absolute location to another, regardless of the current working directory.

      • It will throw an IllegalArgumentException, if one path is relative path, and the other is an absolute path.

      • It will throw an IllegalArgumentException, when on Windows-based systems, both absolute paths are not under the same drive.

    • Path resolve(Path other)

      • Intended for joining two Paths

      • Doesn't check the file system.

      • Doesn't normalize path.

      • If an absolute path is provided as input to the method, such as path1.resolve(path2), then path1 would be ignored and a copy of path2 would be returned.

    • Path resolveSibling(Path other)

      • Resolve other path against the current path's parent.
    • Path normalize()

      • Doesn't check the file system.

      • Removing redundancies such as . or .. notation from a Path

      • Doesn't convert a relative path into an absolute path.

    • Path toAbsolutePath()

      • Doesn't check the file system.

      • If this path is already absolute then this method simply returns this path.

      • If this path is relative then this method returns this path relative to the current directory.

    • Path toRealPath(LinkOption... options) throws IOException

      • Cleaning up a Path and check the file system to locate the file/directory.

      • Converting a relative Path to an absolute one, similar to toAbsolutePath()

      • It throws an IOException when the file does not exist.

      • Implicitly calling normalize() on the resulting aboslute Path

7.5 Use the Files class to check, delete, copy or move a file or directory

  • Symbolic files

    • In POSIX-compliant operating systems, they are generally treated as an alias for the target.
  • Files API

    • boolean Files.exists(Path p, LinkOption... options)

    • boolean Files.isSameFile(Path p1, Path p2)

      1. If both Path objects are equal then this method returns true.

      2. If the two Path objects are associated with different providers then this method returns false.

      3. Checking the file system to see if both Path objects locate the same file.

    • Path Files.createDirectories(Path p, FileAttribute<?> attrs)

      • Creating a directory several levels deep when one or more of the parent directories might not yet exist, with all non-existent directories created along the way
      • Doesn't throw exception when directory path already exists, but if the specified path is a file, an exception would be thrown.
    • Path Files.createDirectory(Path p, FileAttribute<?> attrs)

      • Creating one single directory, assuming all parent directories exist beforehand
    • long File.copy(Path source, Path target, CopyOption... options) throws IOException

    • long File.copy(InputStream in, Path target, CopyOption... options) throws IOException

    • long File.copy(Path source, OutputStream out) throws IOException

      • All three copy methods throws IOException.

      • Directory copies are shallow rather than deep, meaning that files and subdirectories within the directory are not copied.

      • By default, copying files and directories will traverse symbolic links, although it will not overwrite a file or directory if it already exists, nor will it copy file attributes. These behaviors can be altered by providing the additional CopyOption....

    • Path Files.move(Path source, Path target, CopyOption... options) throws IOException

      • By default, the move() method will follow links, throw an exception if the file already exists, and not perform an atomic move. These behaviors can be altered by providing the additional CopyOption....

      • The method can be applied to non-empty directories only if they are on the same underlying drive. While moving an empty directory across a drive is supported, moving a non-empty directory across a drive will throw an NIO.2 DirectoryNotEmptyException.

      • Moving always preserves the metadata even if the COPY_ATTRIBUTES flag is not set.

    • void Files.delete(Path path) throws IOException

      • Deleting a file or empty directory within the file system

      • Throwing IOException

        • if the path represents a non-empty directory, the operation will throw the runtime DirectoryNotEmptyException
      • If the target of the path is a symbol link, then the symbolic link will be deleted, not the target of the link.

    • boolean Files.deleteIfExists(Path path) throws IOException

      • Almost identical to the delete(Path) method, except that it will not throw an exception if the file or directory does not exist, but instead it will return a boolean value of false.
    • BufferedReader Files.newBufferedReader(Path path) throws IOException

    • BufferedReader Files.newBufferedReader(Path path, Charset cs) throws IOException

    • List<String> Files.readAllLines(Path path) throws IOException

      • The entire file is read when readAllLines() is called, with the resulting String array storing all of the contents of the file in memory at once. Therefore, if the file is significantly large, you may encounter an OutOfMemoryError trying to load all of it into memory.
    • boolean Files.isDirectory(Path path, LinkOption... options)

    • boolean Files.isRegularFile(Path path, LinkOption... options)

      • By default, symbolic links are followed and the file attribute of the final target of the link is read.
      • If the target doesn't exist, return false, instead of throwing an IOException.
    • boolean Files.isSymbolicLink(Path path)

      • If the target doesn't exist, return false, instead of throwing an IOException.
    • boolean Files.isHidden(Path path) throws IOException

    • boolean Files.isReadable(Path path)

    • boolean Files.isExecutable(Path path)

    • <A extends BasicFileAttributes> A Files.readAttributes(Path path, Class<A> type, LinkOption... options)

    • <V extends FileAttributeView> V Files.getFileAttributeView(Path path, Class<V> type, LinkOption... options)

      • A lot of other methods in Files rely on these two under the hood for getting file attributes.

      • BasicFileAttributeView and its subclasses can be used to modify various file attributes.

    • Path Files.writeString​(Path path, CharSequence csq, OpenOption... options) throws IOException

      • Write a CharSequence to a file, creating the file if it doesn't exist, overriding its content if the file exists.
  • Methods that by default follow symbolic links

    • boolean Files.exists(Path p, LinkOption... options)
    • boolean Files.isDirectory(Path path, LinkOption... options)
    • boolean Files.isRegularFile(Path path, LinkOption... options)
    • <A extends BasicFileAttributes> A Files.readAttributes(Path path, Class<A> type, LinkOption... options)
    • <V extends FileAttributeView> V Files.getFileAttributeView(Path path, Class<V> type, LinkOption... options)

7.6 Use the Stream API with Files

  • Stream<Path> walk(Path start, int maxDepth, FileVisitOption... options) throws IOException

    • Uses depth-first searching with a default maximum depth of Integer.MAX_VALUE.

    • maxDepth with a value of 0 indicates the current path record itself.

    • When you create a Stream<Path> object using Files.walk(), the contents of the directory have not yet been traversed.

    • walk() will not traverse symbolic links by default.

  • Stream<Path> find(Path start, int maxDepth, BiPredicate<Path, BasicFileAttributes> matcher, FileVisitOption... options)

    • maxDepth must be >= 0, and 0 means the starting directory itself, and 1 means the items under the starting directory.
  • Stream<Path> list(Path dir) throws IOException

    • The listing is not recursive, only entries in the given directory.

    • similar to File[] listFiles(), but returns a lazily populated stream.

  • Stream<String> lines(Path path) throws IOException

    • Unlike Files.readAllLines()'s eager processing, this one returns a lazily processed Stream.

8 Database Applications with JDBC

  • JDBC interfaces

    • Driver
    • Connection
    • Statement
    • ResultSet

8.1 Connect to databases using JDBC URLs and DriverManager

  • JDBC URL

    • The first piece is always the same. It is the protocol jdbc. The second part is the name of the database such as derby, mysql, or postgres. The third part is “the rest of it,” which is a database-specific format. Colons separate the three parts.
  • Starting with JDBC 4.0, driver implementations were required to provide the name of the class implementing Driver in a text file named java.sql.Driver in the directory META-INF/services.

var url = "jdbc:h2:mem:ocp;DB_CLOSE_DELAY=-1";
var query = "SHOW SCHEMAS";
try (var conn = DriverManager.getConnection(url);
      var stmt = conn.createStatement();
      var rs = stmt.executeQuery(query)) {
    while (rs.next())
        System.out.println(rs.getString(1));
} catch (SQLException e) {
    e.printStackTrace();
}
  • JDBC resources should be closed in the reverse order from that in which they were opened.

8.2 Use PreparedStatement to perform CRUD operations

  • Connection

    • Statement createStatement() throws SQLException

    • Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException

      • resultSetType

        • int ResultSet.TYPE_FORWARD_ONLY
          • default option
          • cannot navigate backwards
        • int ResultSet.TYPE_SCROLL_INSENSITIVE
          • can navigate both backwards and forwards
          • static view of the ResultSet in spite of any change in the table after the query was made
        • int ResultSet.TYPE_SCROLL_SENSITIVE
          • can navigate both backwards and forwards
          • ongoing update to the ResultSet after the query was made to reflect the latest change
          • rarely used due to insufficient support from implementations, downgrade to TYPE_SCROLL_INSENSITIVE when unavailable
      • resultSetConcurrency

        • int ResultSet.CONCUR_READ_ONLY
          • default option
          • unable to update via ResultSet
        • int ResultSet.CONCUR_UPDATABLE
          • able to update via ResultSet
          • rarely used due to insufficient support from implementations, downgrade to CONCUR_READ_ONLY when unavailable
  • Statement

    • boolean execute(String sql) throws SQLException

      • Returns true if the first result is a ResultSet object; false if it is an update count or there are no results.
    • ResultSet executeQuery(String sql) throws SQLException

      • Returns a ResultSet object.
    • int executeUpdate(String sql) throws SQLException

      • Returns updated row count.
    • A Statement automatically closes the open ResultSet when another SQL statement is run on the same Statement object.

  • ResultSet

    • boolean next() throws SQLException

      • A ResultSet cursor is initially positioned before the first row; the first call to the method next makes the first row the current row.
      • Returns true if the new current row is valid; false if there are no more rows.
      • When a call to the next method returns false, the cursor is positioned after the last row.
      • Column index of get methods starts from 1.
    • boolean absolute(int row) throws SQLException

      • The number of the row to which the cursor should move. A value of zero indicates that the cursor will be positioned before the first row.
      • If the given row number is negative, the cursor moves to an absolute row position with respect to the end of the result set.
    • void beforeFirst() throws SQLException

    • void afterLast() throws SQLException

8.3 Use PreparedStatement and CallableStatement APIs to perform database operations

  • PreparedStatement

    • storing a precompiled SQL statement, therefore when the PreparedStatement is executed, the DBMS can just run the PreparedStatement SQL statement without having to compile it first
    • able to be supplied with parameters, index starting from 1, and placeholder being ?
    • can be reused
    • PreparedStatement prepareStatement(String sql) throws SQLException
  • CallableStatement

    • subclass of PreparedStatement intended for executing SQL stored procedures
    • in addition to being able to receive parameter values by index, supporting receiving parameter values by name, which must be identical with the name defined in the stored procedure
    • parameter placeholders composed of IN, OUT and INOUT parameters.
    • IN parameters are input parameters, and set methods are used to supply values.
    • OUT parameters are return values of the stored procedure, and get methods are used to retrieve OUT parameters.
    • All OUT parameters must be registered before a stored procedure is executed.
    • INOUT parameters can serve both purposes, therefore they must be both set with values as input and registered as output before calling the stored procedure.
    • CallableStatement prepareCall(String sql) throws SQLException

9 Annotations

9.1 Describe the purpose of annotations and typical usage patterns

  • Information for the compiler — Annotations can be used by the compiler to detect errors or suppress warnings.
  • Compile-time and deployment-time processing — Software tools can process annotation information to generate code, XML files, and so forth.
  • Runtime processing — Some annotations are available to be examined at runtime.

9.2 Apply annotations to classes and methods

  • Applied to declarations

    • If there is just one element named value, then the name can be omitted
      • @SuppressWarnings(value = "unchecked") equals to @SuppressWarnings("unchecked")
    • If no elements, the parentheses can be omitted
  • Type Annotations (Java 8+)

    • Class instance creation expression:

      new @Interned MyObject();
    • Type cast:

      myString = (@NonNull String) str;
    • implements clause:

      class UnmodifiableList<T> implements
          @Readonly List<@Readonly T> { ... }
    • Thrown exception declaration:

      void monitorTemperature() throws
      @Critical TemperatureException { ... }

9.3 Describe commonly used annotations in the JDK

  • java.lang: annotations for common use

    • @Deprecated
    • @Override
    • @SuppressWarnings
    • @SafeVarargs
    • @FunctionalInterface
  • java.lang.annotation: meta-annotations

    • @Retention

    • @Documented

    • @Target

    • @Inherited

    • @Repeatable

      • An annotation to be used repeatedly must be marked with @Repeatable
      • Repeating annotations are stored in a container annotation, which is not used in code but intended for the compiler.
      • The containing annotation type must have a value element with an array type, and the component type of the array type must be the repeatable annotation type.
      import java.lang.annotation.Repeatable;
       
      // Repeatable Annotation
      @Repeatable(Schedules.class)            // Specifying containing Annotation type
      public @interface Schedule {
        String dayOfMonth() default "first";
        String dayOfWeek() default "Mon";
        int hour() default 12;
      }
       
      // Containing Annotation Type
      public @interface Schedules {
        Schedule[] value();                   // an array type of the repeatable Annotation
      }
       
      // Repeatable Annotation usage
      @Schedule(dayOfMonth="last")
      @Schedule(dayOfWeek="Fri", hour="23")
      public void doPeriodicCleanup() { ... }

9.4 Declare custom annotations

  • Annotation Type Elements (opens in a new tab)
    • The return type of a method declared in an annotation type must be one of the following, or a compile-time error occurs:
      • primitive
      • String
      • Class or an invocation of Class (§4.5)
      • enum
      • annotation
      • An array type whose component type is one of the preceding types
        • multi-dimentional array is not permitted.

10 Exception Handling and Assertions

10.1 Use the try-with-resources construct

  • try-with-resources can only have AutoCloseable variable declaration statements, separated by semi-colon if multiple.

  • As the variables are local to the try block, var can be used.

  • catch and finally block are both optional for try-with-resources.

  • A user defined finally block gets executed after the implict finally block.

  • Resources are closed after the try clause ends and before any catch / finally clauses.

  • Resources are closed in the reverse order from which they were created.

  • Checked exceptions thrown from variable declarations must be catched in corresponding catch blocks for try-with-resources.

  • If both Autocloseable resources' close method and try block throw exceptions, the ones from close methods would be suppressed, and the one gets thrown is called primary exception.

    • Suppressed exceptions can be accessed by calling getSuppressed() on the primary exception.
  • java.io.Closeable extends java.lang.AutoCloseable, throwing different exceptions.

    public interface Closeable extends AutoCloseable {
      void close() throws IOException;
    }
     
    public interface AutoCloseable {
      void close() throws Exception;
    }
  • When there are multiple AutoCloseables declared

    • If more than one of the close methods throw exceptions, only the first one will be the primary exception, the rest suppressed. The first resource to close is the last one declared in try statements.
    • Even if there are exceptions, all close methods would be run nonetheless. Program flow wouldn't be interrupted.

10.2 Create and use custom exception classes

10.3 Test invariants by using assertions

  • When an assertion evaluates to false, an AssertionError is thrown.

  • assert forms

    • assert Expression1

    • assert Expression1 : Expression2 ;

      • Expression1 is a boolean expression.
      • Expression2 is an expression that has a value (cannot be a method invocation returns void).
      • The String representation of Expression2 would be passed to AssertionError.
      • Expression1 can have optional enclosing parenthesis.
  • By default, Java ignores assertions

  • -enableassertions / -ea to enable assertions

    • java -enableassertions/-ea Rectangle
  • -ea:<package-name>... to enable assertions for specific package and its subpackages.

  • -ea:<class-name> to enable assertions for specific package

  • -da:<class-name> to disable a specific class

11 Generics and Collections

11.1 Use wrapper classes, autoboxing and autounboxing

  • Wrapper types can only be assigned with corresponding primitives and its own wrapper types.

    Double f = Integer.valueOf(43);     // Compiler error
  • Primitive types can be assigned with wrapper types that can be implicitly converted after unboxing.

    double d = Integer.valueOf(4);      // OK
  • When a primitive value is compared with an instance of a wrapper class, this instance is unboxed automatically.

  • valueOf() method of all integer wrapper types (Byte, Short, Integer, Long) cache values from -128 to 127, and Character caches values from 0 to 127

11.2 Create and use generic classes, methods with diamond notation and wildcards

  • super

    • super can only be used with wildcard, not to be used in type definition.

    • Used to define lower bound (mainly of method parameter), only super types of the given type can be accepted.

    • Set the lower bound, which can be any type that is a ancestor of the specified type.

    • <? super Number> means the set of all types extending Number, but which type it will be in runtime is unknown, therefore if it's a generic container, only elements is-a Number could be added into the container.

      @Test
      public void wildcardSuper1() {
          List<Object> o1 = List.of(new Object(), new Object());
          accept(o1);                                   // OK
          List<Serializable> o2 = List.of("abc", "def");
          accept(o2);                                   // OK
          List<Number> n1 = List.of(1, 2, 3, 4);
          accept(n1);                                   // OK
          List<Integer> n2 = List.of(1, 2, 3, 4);
          accept(n2);                                   // Compiler error
      }
       
      private void accept(List<? super Number> nums) {
          System.out.println(nums);
      }
       
      @Test
      public void wildcardSuper2() {
          // The following lines compile successfully.
          List<? super Number> nums = new ArrayList<>();
          nums.add(Short.valueOf("2"));
          nums.add(1);
          nums.add(1L);
          nums.add(1F);
          nums.add(1D);
      }
  • extends

    • When used with wildcard, it defines upper bound (mainly of return type), only sub types of the given type can be returned, therefore meeting is-a relationship.
    • When used with type parameter for generic type definition or generic method, it helps to restrict the captured type.
    • Set the upper bound, which can be any type that is a descendant of the specified type, but only one at a time.
    • For collection, not able to add element to it, as upper bound is set, while the specific class could be any descendent of the upper bound.
  • Wildcard

    • Stand for a set of types meeting the requirement, while a type parameter is used to indicate a specific type and can be captured.
    • Can be used in method parameters
    • Can be used as return type
    • Cannot be used to instantiate object directly.
  • Object[] is considered super class of other array type such as Integer[].

11.3 Describe the Collections Framework and use key collection interfaces

  • Set

    • retainAll: Only keeps the intersection with the given Collection in this Set
  • Map

    • merge

      MapM<String, String> map = new LinkedHashMap<>();
      BinaryOperator<String> operator = (s1, s2) -> null;
      map.put("John", "Teacher");
      map.merge("John", "Doctor", operator);    // Use remappingFunction to get a new value to replace "Doctor", because the new value is null, the entry is removed.
      map.merge("Jane", "Doctor", operator);    // As there is no value for "Jane", "Doctor" is associated with "Jane".
      System.out.println(map);                  // {Jane=Doctor}

11.4 Use Comparator and Comparable interfaces

11.5 Create and use convenience methods for collections

12 Java Stream API

12.1 Describe the Stream interface and pipelines

  • java.util.stream.Stream

    • static methods

      • infinite stream

        • generate: the supplier function determines what to be generated.
        • iterate
          • the initial element is given
          • the supplier function applies to the previous item to generate the current item.
      • finite stream

        • of: enumerated elements, equivalent to Arrays.stream.
    • instance methods

      • limit: ensure the stream outputs elements no more than the specified max size.

      • toArray

        • Object[] toArray()
        • <A> A[] toArray(IntFunction<A[]> generator): generator is a function accepting an int and returning an array
      • reduce

        • identity must meet the requirement that accumulator.apply(identity, t) == t
  • 3 primitive streams

    • java.util.stream.IntStream

    • java.util.stream.LongStream

      • static methods

        • range: start inclusive, end exclusinve
        • rangeClosed: start inclusive, end inclusinve
    • java.util.stream.DoubleStream

  • java.util.function.BooleanSupplier

    • A Supplier returns a boolean
  • Consumer

    • Two Consumers chained together is considered accepting the input separately.

12.2 Use lambda expressions and method references

13 Lambda Operations on Streams

13.1 Extract stream data using map, peek and flatMap methods

13.2 Search stream data using search findFirst, findAny, anyMatch, allMatch and noneMatch methods

  • noneMatch

    • When the Stream is empty, it returns true

13.3 Use the Optional class

  • java.util.stream.Optional

    • T get()

      • Throws NoSuchElementException when no value exists.
    • Optional.of(T value)

      • Throws NullPointerException when the parameter is null.
  • 3 primitive optional classes

    • java.util.stream.OptionalInt

      • int getAsInt()
    • java.util.stream.OptionalLong

      • long getAsLong()
    • java.util.stream.OptionalDouble

      • double getAsDouble()

13.4 Perform calculations using count, max, min, average and sum stream operations

  • Only primitive streams have average, sum methods.
  • OptionalDouble average();
  • All sum methods return their corresponding type.
  • count returns long.
  • max and min return Optional<T>

13.5 Sort a collection using lambda expressions

13.6 Use Collectors with streams, including the groupingBy and partitioningBy operations

  • The partitioningBy() operation always returns a map with two Boolean keys, even if there are no corresponding values. By contrast, groupingBy() returns only keys with associated values.

  • Stream.collect(supplier, accumulator, combiner)

    • The combiner function must fold the elements from the second result container into the first result container.

14 Services in a Modular Application

14.1 Describe the components of Services including directives

  • Service provider doesn't need to export the package of the implementation class.
  • Service provider requires the API package containing the interface.
// service provider
module com.mysql.jdbc {
    requires java.sql;
    requires org.slf4j;
    provides java.sql.Driver with com.mysql.jdbc.Driver;  // implementation
}
 
// service consumer
module java.sql {
    requires public java.logging;
    requires public java.xml;
    exports java.sql;
    exports javax.sql;
    exports javax.transaction.xa;
    uses java.sql.Driver;                                 // interface
}
  • If a JAR file defining an automatic module contains a folder META-INF/services, then each such entry is treated as if it were a corresponding provides clause in a hypothetical declaration of that module. An automatic module is considered to uses every available service.

14.2 Design a service type, load services using ServiceLoader, check for dependencies of the services including consumer and provider modules

  • JPMS

    • For a plain JAR, it must have META-INF/services directory to declare services, every file under that directory has a file name of the service interface's full name. The content of the file is the full name of the service provider class.
    • For a module JAR, it uses module-info.java to declare services.
    • compile-time error: if more than one provides directive in a module declaration specifies the same service.
    • compile-time error: if the with clause of a given provides directive specifies the same service provider more than once.
  • ServiceLoader API

    • class ServiceLoader<S> implements Iterable<S>, therefore it can be iterated directly.
    • The type parameter is the target service.
    • The implementation class isn't involved in the client code.
    ServiceLoader<Q12_MyService> service1 = ServiceLoader.load(Q12_MyService.class);
    Optional<Q12_MyService> service2 = ServiceLoader.load(Q12_MyService.class).findFirst();
    Iterator<Q12_MyService> service3 = ServiceLoader.load(Q12_MyService.class).iterator();
    Stream<ServiceLoader.Provider<Q12_MyService>> stream = ServiceLoader.load(Q12_MyService.class).stream();

15 Parallel Streams

15.1 Develop code that uses parallel streams

  • Stream pipelines may execute either sequentially or in parallel, determined when the terminal operation is initiated.

15.2 Implement decomposition and reduction with streams

16 Secure Coding in Java SE Application (opens in a new tab)

16.1 Prevent Denial of Service in Java applications

  • Guideline 1-1 / DOS-1: Beware of activities that may use disproportionate resources

  • Guideline 1-2 / DOS-2: Release resources in all cases

  • Guideline 1-3 / DOS-3: Resource limit checks should not suffer from integer overflow

16.2 Secure confidential information in Java application

  • Guideline 2-1 / CONFIDENTIAL-1: Purge sensitive information from exceptions

  • Guideline 2-2 / CONFIDENTIAL-2: Do not log highly sensitive information

  • Guideline 2-3 / CONFIDENTIAL-3: Consider purging highly sensitive from memory after use

16.3 Implement Data integrity guidelines- injections and inclusion and input validation

  • Guideline 3-1 / INJECT-1: Generate valid formatting

  • Guideline 3-2 / INJECT-2: Avoid dynamic SQL

  • Guideline 3-3 / INJECT-3: XML and HTML generation requires care

  • Guideline 3-4 / INJECT-4: Avoid any untrusted data on the command line

  • Guideline 3-5 / INJECT-5: Restrict XML inclusion

  • Guideline 3-6 / INJECT-6: Care with BMP files

  • Guideline 3-7 / INJECT-7: Disable HTML display in Swing components

  • Guideline 3-8 / INJECT-8: Take care interpreting untrusted code

  • Guideline 3-9 / INJECT-9: Prevent injection of exceptional floating point values

16.4 Prevent external attack of the code by limiting Accessibility and Extensibility, properly handling input validation, and mutability

  • Guideline 4-1 / EXTEND-1: Limit the accessibility of classes, interfaces, methods, and fields

  • Guideline 4-2 / EXTEND-2: Limit the accessibility of packages

  • Guideline 4-3 / EXTEND-3: Isolate unrelated code

  • Guideline 4-4 / EXTEND-4: Limit exposure of ClassLoader instances

  • Guideline 4-5 / EXTEND-5: Limit the extensibility of classes and methods

  • Guideline 4-6 / EXTEND-6: Understand how a superclass can affect subclass behavior

16.5 Securely constructing sensitive objects

  • Guideline 7-1 / OBJECT-1: Avoid exposing constructors of sensitive classes

  • Guideline 7-2 / OBJECT-2: Prevent the unauthorized construction of sensitive classes

  • Guideline 7-3 / OBJECT-3: Defend against partially initialized instances of non-final classes

  • Guideline 7-4 / OBJECT-4: Prevent constructors from calling methods that can be overridden

  • Guideline 7-5 / OBJECT-5: Defend against cloning of non-final classes

16.6 Secure Serialization and Deserialization

  • Guideline 8-1 / SERIAL-1: Avoid serialization for security-sensitive classes

  • Guideline 8-2 / SERIAL-2: Guard sensitive data during serialization

  • Guideline 8-3 / SERIAL-3: View deserialization the same as object construction

  • Guideline 8-4 / SERIAL-4: Duplicate the SecurityManager checks enforced in a class during serialization and deserialization

  • Guideline 8-5 / SERIAL-5: Understand the security permissions given to serialization and deserialization

  • Guideline 8-6 / SERIAL-6: Filter untrusted serial data

17 Localization

17.1 Use the Locale class

  • Locale must be in the format of en_US, with language lowercase and country uppercase.

  • Locale must have a language, but country/region is optional.

    var l1 = new Locale.Builder()
            .setLanguage("en")
            .setRegion("US")
            .build();
    var l2 = Locale.getDefault();
    var l3 = new Locale("en");
    var l4 = new Locale("en", "US");

17.2 Use resource bundles

  • Resource Bundle as a property file

    • Value must be String, using String ResourceBundle.getString(String key)
  • Resource Bundle as a Java class

    • A class with the same name that you would use for a property file. Only the extension is different.
    • Value can be any type, using Object ResourceBundle.getObject(String key) method.
    • Values of the properties can be created at runtime
    • For the same ResourceBundle, Java class file takes precedence over the property file, the resources in property file would be ignored.
  • How to use Resource Bundle

    • Locate the Resource Bundle

      • If a Resource Bundle with specified Locale is found

        • Both language and country are matched
        • Only language is matched
      • If a Resource Bundle with specified Locale is not found

        • It would fall back to default Locale.
    • Locate the property

      • Java isn’t required to get all of the keys from the same resource bundle. It can get them from any parent of the matching resource bundle.

      • When a Resource Bundle cannot be found, a RuntimeException java.util.MissingResourceException will be thrown.

        var fr = new Locale("fr", "CA");
        var b = ResourceBundle.getBundle("Dolphins", fr);
        var s = b.getString("name");
         
        // Property search order
        // 1. Dolphins_fr_CA.properties         Look for the bundle matching both language and country.
        // 2. Dolphins_fr.properties            If no match with both language and country is found, look for the bundle with matching language.
        // 3. Dolphins.properties               If no match with language is found, look for the bundle with base name only.
      • Look for the bundle matching both language and country

      • Matching Java class comes before the matching property file.

      • If no match with both language and country is found, look for the bundle with matching language.

      • If no match with language is found, look for the bundle with base name only.

17.3 Format messages, dates, and numbers with Java

  • NumberFormat

    • parse

      • Leading whitespace is not allowed, which causes a ParseException, a checked exception.

      • Any characters that follow the number in the string are simply ignored, so no exception is thrown.

        NumberFormat nf = NumberFormat.getInstance();
        String one = "456abc";
        String two = "-2.5165x10";
        String three = "x85.3";
        System.out.println(nf.parse(one)); // 456
        System.out.println(nf.parse(two)); // -2.5165
        System.out.println(nf.parse(three));// throws ParseException
    • DecimalFormat

      • Both the # and 0 characters in a pattern string denote a digit. The difference between them is that if the digit is missing, # is shown as absent while 0 is shown as 0.