ADVANCED JAVA STUDY GUIDE

Inheritance. Inheritance should represent an is-a relationship. This notion is formalized by Liskov's substitution principle: if S is a subtype of T, then objects of type S can be used wheverever objects of type T are expected, without altering desirable properties of the program.

Methods are invoked via dynamic dispatch: obj.foo() invokes the foo() method of whatever class the object referenced by obj has at runtime.

Generics. Generics promote type-safe code reuse by allowing classes and methods to be parameterized by type variables that can be instantiated to different reference types. In this context, type safe means that the compiler checks for type mismatch errors at compile time rather than runtime (contrasting with the alternative approach that acheives code reuse by casting everything from/to Object).

Interfaces. An interface is a set of related methods. To implement an interface, a class must provide definitions for all methods in the interface and must be declared to implement the interface using the implements keyword.

The name of an interface can be used as a reference type, and object of any class that implements that interface can be implicitly cast to that reference type. This enables implementations of algorithms and data structures to work for any class that implements the interface (e.g., Array.sort() can sort an array of objects of any type T that implements the Comparable<T> interface).

Like classes, interfaces can be parameterized by generics (e.g., Iterator<Item>). A class may implement multiple interfaces (e.g., a class can be both Iterable and Comparable).

Iterators. Iterator and Iterable are two particular interfaces, which are important for traversing over collections.

  1. An Iterator<Item> is a representation of a position in a sequence of Item objects. An Iterator must be able to (1) determine whether it is at the last position in the sequence (hasNext()), and (2) return the next item in the sequence and advance its position (next()).
  2. An Iterable<Item> is collection of Item objects over which we are able to iterate. An Iterable must provide a way to get an Iterator for the sequence (iterator()).

Java features language support for iterators in the form of the for-each loop: for (int i : queueOfIntegers). Using this syntactic form is good style, but is not magic: any for-each loop can be written as an ordinary (for or while) loop by directly calling the iterator(), next(), and hasNext() methods.

Recommended Problems

C level

  1. Write a class Pair that implements the following API:
            public static class Pair<T, U> {
                // Construct a new pair (x, y)
                public Pair(T x, U y)
                    first = x;
                    second = y;
                }
    
                // returns the first item of the pair
                public T first()
    
                // returns the second item of the pair
                public U second()
    
                // returns a string representation of the pair
                public String toString()
            }
         
    Note that Pair is parameterized over two type variables, T and U.
  2. Consider the following code snippet:
          Stack<Integer> s = new Stack<>();
          for (int i = 0; i < 10; i++) {
              s.push(i);
          }       
          for (int elt : s) {
              StdOut.print(elt + ", ");
          }
        
    When executed, this code prints 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, . Rewrite the second loop so that it does not print the trailing comma.

B level

  1. Textbook 1.2.13
  2. Textbook 1.2.14

A level

  1. Using the Pair class above, complete the following implementation of the PairIterator<T, U> class, such that the following code outputs
    (0, 0), (0,1), (0, 2), (1, 0), (1, 1), (1, 2), (2,0), (2,1), (2,2)
        import edu.princeton.cs.algs4.Queue;
        import edu.princeton.cs.algs4.StdOut;
        import java.util.Iterator;
        import java.util.NoSuchElementException;
    
        public static class PairIterator<T, U> implements ... {
    
            // construct a new iterator over all pairs consisting of an item
            // in x and and item in y
            public PairIterator(Iterable<T> x, Iterable<U> y) { ... }
    
            // is there a next element in the sequence?
            public boolean hasNext() { ... }
    
            // retrieve the next element in the sequence
            public Pair<T, U> next() { ... }
    
            public static void main(String[] args) {
              Queue<Integer> xs = new Queue<>();
              for (int i = 0; i < 3; i++) {
                  xs.add(i);
              }
              Iterator<Pair<Integer, Integer>> it = new PairIterator<>(xs, xs);
              while (it.hasNext()) {
                  StdOut.print(it.next());
                  if (it.hasNext()) StdOut.print(", ");
              }
              StdOut.println();
        }