Java SE 11 Programmer I (1Z0-815)

1 Understanding Java Technology and environment

1.1 Describe Java Technology and the Java development

1.2 Identify key features of the Java language

2 Working With Java Primitive Data Types and String APIs

2.1 Declare and initialize variables (including casting and promoting primitive data types)

  • Identifier

    • May only contain letters, numbers, $, or _
    • Mustn't start with a number
    • Mustn't be a single _ (Java 9+), but _ can still be used as part of an identifier.
    • Mustn't be a reserved word
  • Integer literals

    • integer literal falling into the scope of a certain type can be assigned without a cast

      • byte: -128 -> 127
      • short: -32768 -> 32767
      • char: 0 -> 65535
      • int: -2147483648 -> 2147483647
      • long: any other integer literal, requiring L/l suffix
      byte b = 100;       // 100 < 127                  OK
      byte b1 = 128;      // 128 > 127                  Compiler error
      short s = 'A';      // 'A' < 32767                OK
      short s1 = '艺';    // 33402 ('艺') > 32767       Compiler error
    • integer literal prefix

      • Hex: 0x
      • Oct: 0
      • Bin: 0b
      • Underscore in numerical literals (opens in a new tab)
        • An underscore _ can be placed in any numeric literal, between any two digits, so long as it is not at the beginning, the end, or next to a decimal point ..
        • Underscores can even be placed next to each other.
  • Primitive Conversion

    • Narrowing conversion
      • Explicit
    • Widening conversion
      • Implicit
      • Most preserve precision
      • Some lose precision
        • int -> float
        • long -> float
        • long -> double
  • Unboxing will throw NPE if the boxed value is null.

2.2 Identify the scope of variables

  • local variable
    • Must be initialized before being accessed
      • If declared and initialized on different lines, there can't be statement in between trying to read the variable, which would cause compiler error.

2.3 Use local variable type inference

  • var is not a keyword but a reserved type name. This means that code that uses var as a variable, method, or package name will not be affected. Therefore, var can continue to be used as a variable name, method name, package name, but not a type declaration.
  • var is not allowed in a compound declaration
    • Compiler error: var fall2 = 2, autumn2 = 2;
  • var cannot be assigned to null upon declaration.

2.4 Create and manipulate Strings

  • java.lang.String.intern returns a String from the string pool.
  • java.lang.String.concat / + returns a new String object.
  • java.lang.String.lastIndexOf search backwards, by default from the end
    • int lastIndexOf(String str, int fromIndex) from the specified index
  • java.lang.String.repeat (opens in a new tab)
  • java.lang.String.indexOf search forwards, by default from the start, namely zero index.
    • if fromIndex is specified, it would start search from that position.
  • String.strip (Java 11+)
    • strip is Unicode whitespace aware, whereas the existing method trim removes any space which is less than or equal to (\u0020)

2.5 Manipulate data using the StringBuilder class and its methods

  • StringBuilder class uses a dynamic array to hold the string similar to an ArrayList.
  • toString creates a new String object.
  • StringBuilder.substring and String.substring throw runtime exception when:
    1. beginIndex is negative
    2. endIndex is negative
    3. beginIndex is larger than endIndex
    4. endIndex is larger than string length
  • StringBuilder.substring and String.substring return empty string when beginIndex equals to endIndex.
  • StringBuilder implements java.lang.Comparable.compareTo (11+)
  • append can specify start and enbd index for the target sequence to be appended.
  • StringBuilder doesn't override equals method, therefore its equals is equivalent to ==.

3 Working with Java Arrays

  • API
    • array.length is the capacity of the array, not the actual number of elements in the array
      • For comparison, Collection.size() returns the actual number of elements in the collection, all add, remove methods and clear will change size. Passing null with add and remove will change size too.
    • Arrays.compare (Java 9+)
      • null comes first.
    • Arrays.mismatch (Java 9+)
      • Return the index of the first mismatch between the two arrays, otherwise -1.
    • Array does not override equal method, so Arrays.equals should be used to compare equality of two arrays, while using equals method on array instances has the same effect as identity equality.
    • ArrayStoreException: Array keeps type information in runtime, for reference type, it works when a subclass object is assigned to a cell of a superclass type, but an ArrayStoreException would be thrown if the types are not compatible.
    • Arrays.binarySearch works the same way as with Java Collections.
    • Arrays.asList
      • Only allowed modifications are set and sort, others causing compiler errors
      • Modifications to the list will reflect in the array and vice versa, as the array is used to back the list.
    • List.of
      • No modification allowed, all generating compiler errors
    • <T> T[] list.toArray(T[] a)
      • If the array's length is smaller than the list's size, the list's size will be used for copying and a new array will be created instead to store the contents.
      • If the array's length is greater than or equal to the list's size, the array will be used for copying, with spaces if any padded with null.
    • Arrays.equals
      • Object[] a, int aFromIndex, int aToIndex, Object[] b, int bFromIndex, int bToIndex
    • Arrays.sort
      • Can specify a range to sort by providing start and end index.

3.1 Declare, instantiate, initialize and use a one-dimensional array

  • Array initializer such as int[] a = {1} can only be used upon variable declaration.

    int[] c = {1, 2, 3};          // Correct
    // or
    int[] c = new int[]{1, 2, 3}; // Correct
    // or
    int[] c;
    c = new int[]{1, 2, 3};       // Correct
     
    int[] c;
    c = {1, 2, 3};                // Compiler error
  • int ids[], types; gives us an int array called ids and an int called types.

  • Array reference can be assigned to another array of its parent type.

    String[] strs = {"abc", "def"};
    Object[] objs = strs;
     
    Integer[] ints = {1, 2, 3};
    Number[] nums = ints;

3.2 Declare, instantiate, initialize and use a two-dimensional array

  • Multi-dimensional array is essentially an array of array pointers.

  • For multi-dimensional array, the length of first dimension is required. Lengths of other dimensions could be omitted or zero.

  • int[][] a = new int[3][]; creates an array of three null pointers.

  • int[][] a = new int[3][2]; creates an array of three array pointers, each of which points to a newly created array with specified elements of initial value ([0, 0] in this case). Memory space will not be allocated without the length of the corresponding dimension.

  • If the lengths of higher dimensions are specified, corresponding memory space will be allocated. If not, the pointers to higher dimensions will remain null. In either case, they can be modified and assigned a new pointer to an array. Their lengths will be decided during runtime by the real contents of the arrays.

    Object[][][] objs = new Object[3][0][0];
    objs[0] = new Object[5][];
    objs[0][0] = new Object[7];
    objs[0][1] = new Object[8];
    objs[0][2] = new Object[6];
    objs[0][3] = new Object[9];
    objs[0][4] = new Object[10];
    objs[1] = new Object[2][];
    objs[1][0] = new Object[21];
    objs[1][1] = new Object[22];
    objs[2] = new Object[3][];
    objs[2][0] = new Object[31];
    objs[2][1] = new Object[32];
    objs[2][2] = new Object[33];
  • If first dimension has a length, second and third can leave it out, or second could have the length, while third leaves it out. But it cannot be that second leaves it out and third has a length.

4 Creating and Using Methods

4.1 Create methods and constructors with arguments and return values

  • Method signature

    1. method name
    2. parameter list
  • Varargs

    • Must be the last parameter

    • Only one is allowed if any

    • Considered the same type as array in parameter list

      class Parent {
          void t1(int[] a1, int... a2) {
          }
      }
       
      class Child extends Parent {
          @Override
          void t1(int[] a1, int[] a2) {         // Compiles
          }
      }
  • Constructors

    • When there is no constructor defined in a class, Java compiler will generate a default no-argument constructor with a super() in it, and the access modifier of the constructor is the same as the class. This would require parent having a no-argument constructor defined.

    • The Java compiler automatically inserts a call of super() to all constructors whose first statement is not super() or this().

    • If the parent class doesn't define a no-argument constructor, an explicit call to a parent constructor in the child constructor is required.

    • this() or super() must be the first statement of a constructor body, therefore only one of them is allowed in one constructor.

    • A constructor cannot call itself recursively.

    • If a parent class only has private constructors, only an inner class is able to extend it, because the child class definition must be part of the parent class to be able to call private parent constructors.

      class Parent {
      }
       
      class Child extends Parent {
          Child() {                           // if not present, generated implicitly, Line 1
              super();                        //                                       Line 2
          }                                   //                                       Line 3
      }
      // or
      class Child extends Parent {
          Child() {
              super();                        // if first line of constructor is not a call
                                              // to a parent constructor, generated implicitly
          }
      }

4.2 Create and invoke overloaded methods

  • Multiple overloaded method

    • Argument doesn't down cast.

    • As few conversion as possible

      @Test
      public void t() {
          Q26 test = new Q26();
          test.printMessage(1);   // print "Argument is long"
      }
       
      void printMessage(short argument) {
          System.out.println("Argument is short");
      }
       
      void printMessage(Integer argument) {
          System.out.println("Argument is Integer");
      }
       
      void printMessage(long argument) {
          System.out.println("Argument is long");
      }
    • Argument's reference type, not its runtime type, is used to match method parameter type

      @Test
      public void t() {
          Plant p = new Plant();
          Plant f1 = new Flower();
          Flower f2 = new Flower();
          Collection c = new ArrayList<>();
          p.print(c);                 // Plant C
          f1.print(c);                // Flower C
          f2.print(c);                // Flower C
          List d = new ArrayList<>();
          p.print(d);                 // Plant C
          f1.print(d);                // Flower C
          f2.print(d);                // Flower L
      }
       
      class Plant {
          void print(Collection c) {
              System.out.println("Plant C");
          }
      }
       
      class Flower extends Plant {
          void print(Collection c) {
              System.out.println("Flower C");
          }
       
          void print(List m) {
              System.out.println("Flower L");
          }
      }

4.3 Apply the static keyword to methods and fields

  • static variable

    • A static variable can be accessed via an instance of the class, even if the instance is null.

      Koala k = new Koala();
      System.out.println(k.count); // k is a Koala
      k = null;
      System.out.println(k.count); // k is still a Koala
    • If parent and child class have static variables with the same name, it would cause hiding, regardless of access modifier, type of the variables.

  • static method

    • Mustn't reference any non-static field or method such as this or super.
  • Inheritance

    • All static methods or fields that are accessible are not inherited by subclasses, and they are simply accessible
      • with a child referencing type or instance
      • without a referencing type inside a child type
    • Even if the referencing type is child class or object, they would still refer to the parent's version. Subclasses do not have a copy of their own.
    • static methods can be hidden in the child class, but still determined by the referencing type.

5 Reusing Implementations Through Inheritance

5.1 Create and use subclasses and superclasses

  • instance methods & fields

    • All instance methods that are accessible are inherited by subclasses.
      • If overriden, child's version would be used, otherwise parent's version would be used.
    • All instance fields that are accessible are inherited by subclasses.
      • When having the same type and name, fields in parent class are hidden. The version to be used is determined by the referencing type.
      • If not hidden, subclasses would have their own copy of the fields. They would be used no matter the referencing type is parent class or child class.

5.2 Create and extend abstract classes

  • abstract classes may have zero or more abstract methods and concrete methods.
  • Non abstract classes mustn't have abstract methods.
  • It is a compile-time error if a class is declared both final and abstract.
  • abstract classes can have constructors, and they can only be called when the abstract class is being initialized through a subclass.
  • If an abstract class does not provide a constructor, the compiler will still automatically insert a default no-argument constructor.
  • A abstract method cannot be marked as private or final.
  • static method cannot be marked as abstract.

5.3 Enable polymorphism by overriding methods

  • final methods

    • final instance methods cannot be overridden.
    • static methods and private instance methods can be final, but it's redundant and has no effect.
  • private methods

    • parent private method with the same name in child class is not considered overridden, therefore when invoked on parent type reference, parent's version would be invoked.

5.4 Utilize polymorphism to cast and call methods, differentiating object type versus reference type

  • Compiler check for type cast and instanceof is based on deduction.

    • Fail if
      • Target is an unrelated class
      • Target is an unrelated interface while the class itself is also final
    • Pass if
      • Target is an unrelated interface while the class itself is not final
  • Type cast

    • A class can only be directly cast from a type from its ancestry.

    • This can be worked around by assigning to a common ancestor for transition. This will pass compiler check, but causing ClassCastException in runtime.

      // cast to an unrelated class
      Long x = (Long) new RuntimeException();         // Compiler error
       
      Object a = new RuntimeException();
      Long d = (Long) a;                              // Compiler OK, ClassCastException in runtime
       
      // a non-final class cast to an unrelated interface
      Runnable r = (Runnable) new RuntimeException(); // Compiler OK, ClassCastException in runtime
       
      // a final class cast to an unrelated interface
      Runnable r = (Runnable) new String();           // Compiler error
  • instanceof

    BigDecimal tickets = BigDecimal.ZERO;
    if (tickets instanceof Collections) {   // Compiler error when target is an unrelated class
    }
     
    BigDecimal t = BigDecimal.ZERO;
    if (t instanceof List) {                // Compiler OK when target is an unrelated interface, and the class is not final
    }
     
    String s = "";
    if (s instanceof List) {                // Compiler error when target is an unrelated interface, and the class is final
    }

5.5 Distinguish overloading, overriding, and hiding

  • Overloading

    • Same name, different parameters

    • When child and parent types are both present, choosing the closest one without promoting.

    • When neither of two types can be determined to be closer to the argument, compiler error will occur saying ambiguous method

    • Primitives and their wrapper classes are considered different types, and invocation will choose the closest type without boxing or unboxing

      void autobox(int p) {
          System.out.println("autobox int");
      }
       
      void autobox(Integer p) {
          System.out.println("autobox Integer");
      }
    • Due to type erasure, parameters of the different generic types are considered the same.

    • Only one conversion is allowed

      public class TooManyConversions {
          public static void play(Long l) {}
          public static void play(Long... l) {}
          public static void main(String[] args) {
              play(4); // DOES NOT COMPILE
              play(4L); // calls the Long version
          }
      }
  • Overriding

    • Suppose both methods have the same signature

      • instance -> static: compilation error (methods with same signature)
      • static -> instance: compilation error (methods with same signature)
      • instance -> instance: overriding
      • static -> static: hiding
    • The method in the child class must have the same signature as the method in the parent class.

    • The method in the child class cannot be more restrictive than the method in the parent class.

      • eg: protected -> public is allowed, not public to protected.
    • The method in the child class may not throw a checked exception that is new or broader than the class of any exception thrown in the parent class method.

      • In other words, checked exceptions thrown by the child must be able to be covered by those thrown by the parent.
      • eg: parent: IOException, SQLException; child: FileNotFoundException
    • If the method returns a value, its type must be the same or a subclass of the type return by the method in the parent class, known as covariant return types.

    • Autoboxing types and primitive types are not considered covariant return types to each other.

    • Methods with generic type parameters must be overridden with the exact same generic type.

      class Parent {
          void test(List<String> args) {
              System.out.println("Parent test");
          }
      }
       
      class Child extends Parent {
          @Override
          void test(List<String> args) {           // If it's not String, there'd be a compiler error
              System.out.println("Child test");
          }
      }
    • Methods whose return value is generic can be overridden with a return value of subtype with the exact same generic parameter type, considered covariant.

      class Parent {
          List<String> test(List<String> args) {
              System.out.println("Parent test");
              return Collections.emptyList();
          }
      }
       
      class Child extends Parent {
          @Override
          ArrayList<String> test(List<String> args) {
              System.out.println("Child test");
              return new ArrayList<>();
          }
      }
    • Implementing a method is essentially overriding a method when the parent version is abstract. Therefore all the rules about overriding are applicable.

    • abstract class extending a concrete class can override a concrete method with an abstract method.

  • Hiding

    • Must meet the same criteria of overriding

    • Only happens to static methods.

    • If the static method is invoked inside of the same class without a reference variable, the static method in the same class would be used.

    • If the static method is invoked with a variable, its invocation would depend on the type of the variable.

    • If both parent and child have a static method with the same signature, calling this method with an instance, the invoked method will be determined by the type of the variable.

      @Test
      public void m() {
          Child c = new Child();
          c.t1();                 // print "Child"
          Parent p = new Child();
          p.t1();                 // print "Parent"
      }
       
      class Parent {
          static void t1() {
              System.out.println("Parent");
          }
      }
       
      class Child extends Parent {
          static void t1() {
              System.out.println("Child");
          }
      }
    • The version of the overridden method that gets invoked is the one in the subclass. The version of the hidden method that gets invoked depends on whether it is invoked from the superclass or the subclass.

6 Handling Exceptions

6.1 Describe the advantages of Exception handling and differentiate among checked, unchecked exceptions, and Errors

  • Any checked exceptions cannot be caught without invoked methods declaring one.

    • Exception can still be caught without declared, as it's a super class of RuntimeException as well.
  • Errors are essentially unchecked exceptions that extend the Error class, thrown by the JVM, and can but shouldn't be handled or declared.

6.2 Create try-catch blocks and determine how exceptions alter program flow

  • try must be followed by either a catch or a finally block.

  • For multiple catch blocks, at most one catch block will run, and it will be the first catch block that can handle it.

  • Exception types in multi-catch must be disjoint.

  • Exception variable of multi-catch is final, so cannot be reassigned.

  • Exception variable of catch is not final, but its type is fixed, therefore casting is required for narrowing conversion.

      try {
          System.exit(0);
      } finally {
          System.out.print("Never going to get here"); // Not printed
      }
  • finally decides execution flow

    • finaly gets run almost always, but doesn't get executed when JVM exits.
    • Throwing exceptions in finally will cause it unfinished.
    • If try throws an exception that is not compatible with any catch clause, and finally block also throws an exception, the one thrown by try block will be discarded.
    • If catch block and finally block throw an exception respectively, the one thrown by catch block will be discarded.
    • If try, catch and finally all return a value, finally's return value will override those of try and catch. In other words, the return statements in try and catch are redundant when finally returns a value.
    • JLS - Execution of try-finally and try-catch-finally (opens in a new tab)
  • try-with-resources

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

    • All variables used as try-with-resources resource must be final or effectively final, no matter where they are declared.

    • Declared resource variables are local to the try block, so 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.

    • An already defined final or effectively final AutoCloseable variable can be used in try-with-resources statement. (9+)

      // New and improved try-with-resources statement in JDK 9
      try (resource1;
            resource2) {
          // Use of resource1 and resource 2.
      }

6.3 Create and invoke a method that throws an exception

  • A method that declares an exception (checked or not) isn't required to throw one.
  • But catch requires the exceptions declared on the invoked method get handled.
  • Thrown checked exceptions must be either caught or declared on method signature.
  • Classes listed in the throws part of a method declaration must extend java.lang.Throwable.
  • The checked exceptions handled by catch block(s) must match the checked exceptions declared on the method, making sure all checked exceptions possibly thrown by the method would be handled should they ever happen. Also handling checked exception not declared on the method is not allowed.
  • catch on java.lang.Exception is permitted without invocations declaring any checked exceptions.

7 Creating a Simple Java Program

7.1 Create an executable Java program with a main class

7.2 Compile and run a Java program from the command line

7.3 Create and import packages

  • Multiple wildcard imports containing same class name would cause conflict, and therefore fully qualified class name reference is required.

  • Explicit import of class trumps wildcard import of classes with the same name.

    import foo.*;
    import bar.*;
    import bar.Greeting; // Comment this line would cause compiler error at call site
    import org.junit.Test;
     
    public class Q23 {
        @Test
        public void t() {
            Greeting greeting = new Greeting();
            System.out.println(greeting.say());
        }
    }

8 Using Operators and Decision Constructs

8.1 Use Java operators including the use of parentheses to override operator precedence

  • Operator order

    1. Compare Precedence
    2. Check Associativity
  • a++ generates bytecode:

    • ILOAD 1
    • IINC 1 1
      The old value is pushed to the operand stack, and if it is stored back to the variable array, the incremented value will get overridden, because the IINC instruction increment the variable without pushing it to the operand stack.
  • Unary plus/minus sign means positive/negative number, and it can be used before a variable

  • The result of an assignment is an expression in and of itself, equal to the value of the assignment.

  • null == null: true

  • Calling instanceof on the null literal or a null reference always returns false, and null instanceof null does not compile.

  • Compound assignment operators

  • Conditional operator

    • The conditional operator is syntactically right-associative (it groups right-to-left). (JLS 15.25 (opens in a new tab))

    • Second and third expressions can have different types, as long as the assignment type is compatible with both expressions.

      Number r = a > b ? 8L : 3.2F;
  • References

8.2 Use Java control statements including if, if/else, switch

  • if/else

    • A single pair of if/else can be considered as a single statement.
  • switch

    • The type of the expression can be Enum or any integer type (also their object wrapper) except long, and String.
    • The expression cannot be resolved to null, which would cause NPE.
    • var can be used for the expression, if it can be resolved to a supported type, but it cannot be used for a case expression.
    • Only the matched case gets executed, the order of cases does not matter when matching expression value.
    • case expression must be a compile-time constant, therefore known before runtime. Such as:
      • number / string literals
      • enum values
      • final variables of supported types

8.3 Create and use do/while, while, for and for each loops, including nested loops, use break and continue statements

  • For for, while or do/while or if, variables with duplicate name of local variable cannot be declared in those constructs.

  • Variables declared in those constructs cannot be accessed outside in the enclosing method.

  • for

    • variable can be any type.
    • If declared in for construct, all variables must be of the same type.
    • To have multiple variables of different types, they can be declared outside the for construct.

9 Describing and Using Objects and Classes

9.1 Declare and instantiate Java objects, and explain objects' lifecycles (including creation, dereferencing by reassignment, and garbage collection)

  • Initialization block

    • Order of execution upon creation of an object

      1. static initialization blocks
        • Run in the order of definition in the class
        • Only run once for the first time an instance is created, not necessarily an instance of the same class. It could be a subclass.
        • Parent first, child next, following the inheritance tree
      2. instance initialization blocks
        • Run in the order of definition in the class
        • Run every time an instance is created
      3. Constructor
        • Run the chain of constructors, starting from the java.lang.Object constructor
    • When parent and child both have all three initialization constructs

      1. parent static initialization block(s)
      2. child static initialization block(s)
      3. parent instance initialization block(s)
      4. parent constructor(s)
      5. child instance initialization block(s)
      6. child constructor(s)
    • When a field is initialized with an expression, this expression can be considered an initialization block.

    • During compilation, the body of each instance initialization block is copied to all the constructors in the order the block is declared. This copied code is placed right before the existing code in those constructors.

9.2 Define the structure of a Java class

  • A top-level class or interface can only be declared with public or package-private access.

9.3 Read or write to object fields

  • Fields

    • All static and instance fields declaration are processed before respective initialization blocks.

    • final static variables can be defined upon declaration or in any static initialization block and cannot be changed thereafter.

    • final instance variables can be defined upon declaration or in any initialization block or in any constructor. They are equivalent and cannot be changed thereafter.

    • A field's forward reference reading must have a prefix

      • static field

        • Can modify it if it's not final, or define it if it's final
        • Cannot read it before static initialization is finished
        • Must read it via Class reference
        class Child {
            static {
                count = 3;                                    // OK
                System.out.println(count);                    // Compiler error
                System.out.println(Child.count);              // OK
            }
         
            static int count;
        }
      • instance field

        • Can modify it if it's not final, or define it if it's final
        • Cannot read it before instance initialization is finished
        • Must read it via this reference
        class Child extends Parent {
            {
                count = 10;
                System.out.println(count);                    // Compiler error
                System.out.println(this.count);               // OK
            }
         
            int a = 2 + count;                                // Compiler error
            int b = 2 + this.count;                           // OK
            int count = 10;
        }
    • Field hiding

      • Only the type of the Primary expression, not the class of the actual object referred to at run time, is used in determining which field to use.

        class Parent {
            protected int number = 10;
            static int count = 12;
        }
         
        class Child extends Parent {
            protected int number = 25;
            static int count = 27;
        }
         
        public class Test {
            @Test
            public void t() {
                Child c = new Child();
                System.out.println(c.number);               // 25
                System.out.println(c.count);                // 27
                Parent p = c;
                System.out.println(p.number);               // 10
                System.out.println(p.count);                // 12
            }
        }
      • In the child class, the parent's hidden instance variable can be accessed using super

10 Applying Encapsulation

10.1 Apply access modifiers

10.2 Apply encapsulation principles to a class

11 Programming Abstractly Through Interfaces

11.1 Create and implement interfaces

  • interface is implicitly abstract.

  • interface can extend multiple interfaces.

  • interface doesn't inherit from java.lang.Object, when it has no direct superinterfaces, every public instance method in java.lang.Object will be implicitly declared in this interface.

  • abstract methods in interface and abstract class can be duplicate, abiding by the same rules of overriding, but they are considered compatible.

  • When implementing multiple interfaces with duplicate abstract methods, to resolve incompatibility

    • For declared checked exception, intersection of the exceptions declared on all compatible abstract methods can be declared on the concrete version of the method. If the intersection is none, then no checked exception can be declared.
    • For return type, intersection of the return types of all compatible abstract methods can be used as the return type of the concrete version of the method. If the intersection is none, the method would cause compiler error, therefore the intersection cannot be none.
  • Fields allowed in interface

    • all variables are public static final with the three keywords optional.
  • Methods allowed in interface

    • public abstract instance methods (public and abstract are both optional)
    • public static methods (public is optional)
    • private static methods
    • private instance methods
    • public default instance methods (public is optional, methods can be overridden in subclasses)
  • Implementing multiple interfaces

    1. Abstract and default methods or multiple default methods with the same name conflict with each other, causing compilation error.
    2. Only multiple abstract methods with the same name are allowed.
    3. If the conflicting default methods are overriden as an abstract or concrete method, there wouldn't be any compilation error.

11.2 Distinguish class inheritance from interface inheritance including abstract classes

11.3 Declare and use List and ArrayList instances

  • Arrays.binarySearch and Collections.binarySearch return (-(insertion point) - 1) when the target element cannot be found.
  • AbstractList overrides equals method, which returns true when two lists have the same elements in the same order.
  • Only java.util.List provides the sort method as a shortcut to Collections.sort. Neither java.util.Set nor java.util.Map has that.
  • java.util.List.of, as well as List.copyOf, forbids any modifications, all of which throwing UnsupportedOperationException

11.4 Understanding Lambda Expressions

  1. If there are braces, statements must be complete.

  2. Autoboxing doesn't work for Predicates, primitives must use primitive type specializations of java.util.function.Predicate such as java.util.function.IntPredicate.

  3. Parentheses around parameters are optional only when there is one parameter and its type is omitted.

  4. Lambdas can only reference effectively final enclosing local variable and method parameter. They must be effectively final throughout the whole method. Even modification after the invocation of lambda is not allowed.

  5. java.lang.FunctionalInterface annotation enforces the interface only has one abstract method.

  6. Lambda parameters cannot have the same name as enclosing local variable and method parameter.

  7. You can't use a lambda expression for a functional interface, if the method in the functional interface has type parameters. (opens in a new tab)

    // Correct
    @FunctionalInterface
    public interface PreparedStatementSetup<T> {
        void run(PreparedStatement ps, T movie);
    }
     
    // Would cause compiler error upon lambda usage, saying "Target method is generic."
    @FunctionalInterface
    public interface PreparedStatementSetup {
        <T> void run(PreparedStatement ps, T movie);
    }

12 Understanding Modules

  • JPMS enables fine-grained package access, as defined in module-info.java, that some packages are exposed publicly, while others are for internal use only.

12.1 Describe the Modular JDK

  • java.base module is available to all modules.

12.2 Declare modules and enable access between modules

  • The module-info file must be in the root directory of your module, along with all other root packages.

    • With Apache Maven's directory structure, this file would be placed under src/main/java.
  • The module name follows the naming rules for package names. It often includes periods ( . ) in its name.

  • module has no modifier.

  • A package is only allowed to be supplied by one module.

  • requires directive specifies that this module depends on another module.

  • The packages in a module are accessible to other modules only if the module explicitly exports them in module-info.java.

    • Exporting is necessary for other code to use the packages; it is not necessary to call the main() method at the command line, therefore main class still can be run.
  • module-info.java

    module <module a name> {
        exports <package name> [to <module name>, <module name>];
        requires <module b name>;
        requires transitive <module c name>;        // when module a is required by a module, module c will also be required
    }
    • exports can only export one package per statement, but the package can be exported to multiple modules.
    • exports module to a specific module will deny other modules' access to it, essentially a white list.
    • requires transitive essentially means requires the module along with its dependencies.
    • The same module cannot be repeated in two requires clause.

12.3 Describe how a modular project is compiled and run

  • Compiling

    • javac [options] <source files>

      • All paths are relative to the working directory.

      • [options]

        • Specify where to find application modules: -p <path> / --module-path <path>
        • Specify where to find user class files: -cp <path> / --class-path <path> / -classpath <path>
        • Specify where to place generated class files: -d <directory>
      • <source files>

        • <directories relative to the working directory>/*.java
    • Order matters for the list of <source files>

    • Packaging as a jar file

      • jar cvf <jar file> [-C <directory>] <class files>
        • -c create new archive
        • -v generate verbose output on standard output
        • -f specify archive file name
        • -C change to specified directory
        • . could be use to denote all files
  • Running

    • java -p (or --module-path) <path> -m <module name>/<main class>

      • Specify where to find application modules: -p (or --module-path) <path>
      • Specify which main class of which module to run: -m <module name>/<fully qualified main class>
        • slash is required, and both module name and fully qualified main class must be the same identifiers as used in Java code.
    • Running a module using java

    • java <main class> <arguments>

      • Quotes around arguments are not included.
      • java Test "1" "2" 3: arguments are {"1", "2", "3"}
  • Describing a module

    • java -p (or --module-path) <module directory path> -d (or --describe-module) <module name>
    • jar -f (or --file) <module jar file> -d (or --describe-module)
  • List available modules

    • java --list-modules lists the JDK’s set of modules.
    • java -p (or --module-path) <module directory path> --list-modules lists all modules under the module path, in addtion to the JDK modules.
  • View dependencies

    • jdeps -s (or -summary) --module-path <module directory path> <jar file>
  • Show module resolution

    • java --show-module-resolution -p (or --module-path) <module path> -d (or --describe-module) <module name>