Relevant Reading Material

Nested Classes

The Java Language allows the definition of a class within a class. A nested class is a member of the enclosing class, and much like other kinds of members of a class there are static and non-static inner classes.

  1. Static Nested Classes. Use static in their definition and have access to the static members of the enclosing class. Static Nested Classes interacts with its enclosing class in the same manner as a top-level class.

  2. Inner Classes. An Inner Class is associated with an instance of the enclosing class and has direct access to the enclosing class' fields and methods. Inner classes can not define any static members.

Static Nested Classes

Behave the same as top-level classes. It is a way to group related classes together.

class A {

    //  code for A

    static class B {
        //  code for B
    }

}

To access static nested classes we need to use the name of the outer class, much like accessing a static member.

  • A.B b = new A.B();

Inner Classes

Objects of inner classes exist within an instance of the outer class.

class X {

    //  code for X

    class Y {
        //  code for Y
    }
}
  • An instance of Y can only exist within an instance of X.

  • To access an inner class we need to do so through an instance of the outer class, much like accessing an objects members

X x = new X();

X.Y y = x.new Y();

Shadowing

When creating inner classes things get a little involved when we use the same variable names as the outer class.

  • Shadowing, inner most declaration hides the outer most declaration.

The Java Language provides syntax to allow developers to access variables at different scope levels.

public class ShadowTest {

  public int x = 0;

  class FirstLevel {

    public int x = 1;

    void methodInFirstLevel(int x) {
      System.out.println("x = " + x);                                     // argument x
      System.out.println("this.x = " + this.x);                           // FirstLevel.x
      System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);     // ShadowTest.x
    }
  }

  public static void main(String args) {
    ShadowTest st = new ShadowTest();
    ShadowTest.FirstLevel fl = st.new FirstLevel();
    fl.methodInFirstLevel(23);
  }
}

Iterator example again

Previously we had DDList implement Iterable<T> and the code implemented Iterable by using the instance of DDList. This implementation choice made it difficult to iterate over a DDList because the iterator was altering the underlying DDList object. Here is the code again

public class CellR<E> implements DDList<E>, Iterator<E> {

  private E val;
  private CellR<E> previous;
  private CellR<E> next;


  public CellR() {}

  public CellR(CellR<E> cell, E element) {}

  @Override
  public Iterator<E> iterator() {
    return this;
  }

  @Override
  public E next() {
    E result = this.getVal();
    this.remove(this.getVal());
    return result;
  }

  @Override
  public boolean hasNext() {
    return !isEmpty();
  }

  public E getVal() {}

  public void setVal(E val) {}

  public CellR<E> getPrevious() {}

  public void setPrevious(CellR<E> previous) {}

  public CellR<E> getNext() {}

  public void setNext(CellR<E> next) {}

  @Override
  public Boolean isEmpty() {}

  @Override
  public Integer size() {}

  @Override
  public void add(E element) {}

  @Override
  public void remove(E element) {}

  private boolean hasPrevious() {}

  private boolean hasNextCell() {}

  @Override
  public E getElementAt(Integer index) {}

  @Override
  public int hashCode() {}

  @Override
  public boolean equals(Object obj) {}

  @Override
  public String toString() {}

}

We could use an inner class to define a custom iterator implementation that will have access to CellR data.

public class CellR<E> implements DDList<E>, Iterator<E> {

  private E val;
  private CellR<E> previous;
  private CellR<E> next;


  public CellR() {}

  public CellR(CellR<E> cell, E element) {}

  /**
    * Inner Class Iterator
    */
  private class DDListIterator implements Iterator<E> {

    private Integer index;

    public DDListIterator() {
      this.index = 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean hasNext() {
      return this.index != size();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public E next() {
      E result = getElementAt(this.index);
      this.index = this.index + 1;
      return result;

    }
  }

  @Override
  public Iterator<E> iterator() {
    return this.new DDListIterator();
  }

  // For Iterator<E>
  @Override
  public E next() {
    E result = this.getVal();
    this.remove(this.getVal());
    return result;
  }

  @Override
  public boolean hasNext() {
    return !isEmpty();
  }

  public E getVal() {}

  public void setVal(E val) {}

  public CellR<E> getPrevious() {}

  public void setPrevious(CellR<E> previous) {}

  public CellR<E> getNext() {}

  public void setNext(CellR<E> next) {}

  @Override
  public Boolean isEmpty() {}

  @Override
  public Integer size() {}

  @Override
  public void add(E element) {}

  @Override
  public void remove(E element) {}

  private boolean hasPrevious() {}

  private boolean hasNextCell() {}

  @Override
  public E getElementAt(Integer index) {}

  @Override
  public int hashCode() {}

  @Override
  public boolean equals(Object obj) {}

  @Override
  public String toString() {}

}

We can now iterate over our list without altering the underlying DDList object.

public class Main {

  /**
   * @param args
   */
  public static void main(String[] args) {
    DDList<Integer> l1 = DDList.Empty();
    l1.add(1);
    l1.add(2);
    l1.add(3);
    l1.add(4);

    Iterator<Integer> it = l1.iterator();
    while (it.hasNext()) {
      System.out.println(it.next());
    }

    for (Integer ele : l1) {
      System.out.println(ele);
    }
  }

}

Generates

1
2
3
4
1
2
3
4

Anonymous Classes

Another feature of the Java Language is anonymous classes; a class defined in place.

public class CellR<E> implements DDList<E>, Iterator<E> {

  private E val;
  private CellR<E> previous;
  private CellR<E> next;


  public CellR() {}

  public CellR(CellR<E> cell, E element) {}

  /**
    * Inner Class Iterator
    */
  private class DDListIterator implements Iterator<E> {

    private Integer index;

    public DDListIterator() {
      this.index = 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean hasNext() {
      return this.index != size();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public E next() {
      E result = getElementAt(this.index);
      this.index = this.index + 1;
      return result;

    }
  }

  @Override
  public Iterator<E> iterator() {
    //return this.new DDListIterator();
      return  new Iterator<E>() {
          Integer index = 0; // initializer, anonymous classes cannot have constructors

          /**
           * {@inheritDoc}
           */
          @Override
          public boolean hasNext() {
                  return this.index != size();
          }

          /**
           * {@inheritDoc}
           */
          @Override
          public E next() {
                  E result = getElementAt(this.index);
                  this.index = this.index + 1;
                  return result;

          }

      }; // NOTE THE SEMICOLON HERE!
  }

  // For Iterator<E>
  @Override
  public E next() {
          E result = this.getVal();
          this.remove(this.getVal());
          return result;
  }

  @Override
  public boolean hasNext() {
          return !isEmpty();
  }

  public E getVal() {}

  public void setVal(E val) {}

  public CellR<E> getPrevious() {}

  public void setPrevious(CellR<E> previous) {}

  public CellR<E> getNext() {}

  public void setNext(CellR<E> next) {}

  @Override
  public Boolean isEmpty() {}

  @Override
  public Integer size() {}

  @Override
  public void add(E element) {}

  @Override
  public void remove(E element) {}

  private boolean hasPrevious() {}

  private boolean hasNextCell() {}

  @Override
  public E getElementAt(Integer index) {}

  @Override
  public int hashCode() {}

  @Override
  public boolean equals(Object obj) {}

  @Override
  public String toString() {}

}

Scoping

With inner and anonymous classes there are rules concerning what is accessible from the inner to the outer class. In order to talk about what is captured and shadowed we need to first explain what final and effectively final means

Final

The Java Language provides a modifier key called final. We can use final on

  • classes - this class cannot be extended (subclassed).

  • methods - this method cannot be overridden or hidden in subclasses.

  • variable - this variable can only be initialized once and cannot be re-assigned.

Effectively final has no syntax. Java 8 calls a variable effectively final if

  1. the variable is not declared final

  2. the code initializes the variable only once and cannot be re-assigned.

Scoping rules

  • Anonymous/Inner classes have access to the members of the enclosing class.

  • Anonymous/Inner classes have access local variables in its enclosing scope that are final or effectively final

  • Anonymous/Inner classes can shadow any other declaration in the enclosing scope that have the same name.

Lambda Expressions

Recall our example using List and sorting methods from Collections

public class StudentMain {

  public static void main(String[] args) {
    Student s1 = new Student("A", "A", 98);
    Student s2 = new Student("B", "A", 100);
    Student s3 = new Student("C", "A", 72);
    Student s4 = new Student("D", "A", 10);
    Student s5 = new Student("E", "A", 66);
    Student s6 = new Student("E", "X", 89);
    Student s7 = new Student("A", "B", 90);
    Student s8 = new Student("A", "C", 76);
    Student s9 = new Student("A", "D", 67);

    List<Student> course = new ArrayList<>();

    course.add(s1);
    course.add(s2);
    course.add(s3);
    course.add(s4);
    course.add(s5);
    course.add(s6);
    course.add(s7);
    course.add(s8);
    course.add(s9);

    System.out.println("Original Class");
    System.out.println(course);

    Collections.sort(course);
    System.out.println("Sorted Class with compareTo");
    System.out.println(course);

    GradeComparator gradeComparator = new GradeComparator();
    Collections.sort(course, gradeComparator);

    System.out.println("Sorted Class with GradeComparator");
    System.out.println(course);

    SurnameComparator surnameComparator = new SurnameComparator();
    Collections.sort(course, surnameComparator);

    System.out.println("Sorted Class with SurnameComparator");
    System.out.println(course);


  }
}

We created an interface for each sorting criterion and implemented our interface appropriately. We could have implemented Comparator using an anonymous class as well.

Java 8 provides a new feature called lambda expressions. Comparator has the annotation @FunctionalInterface. This annotation is not code but rather an informative annotation that indicates that this type has one (and only one) abstract method.

Types that have one and only one abstract method (default methods do not count) are called functional interfaces in Java 8.

public class StudentMain {

  public static void main(String[] args) {
    Student s1 = new Student("A", "A", 98);
    Student s2 = new Student("B", "A", 100);
    Student s3 = new Student("C", "A", 72);
    Student s4 = new Student("D", "A", 10);
    Student s5 = new Student("E", "A", 66);
    Student s6 = new Student("E", "X", 89);
    Student s7 = new Student("A", "B", 90);
    Student s8 = new Student("A", "C", 76);
    Student s9 = new Student("A", "D", 67);

    List<Student> course = new ArrayList<>();

    course.add(s1);
    course.add(s2);
    course.add(s3);
    course.add(s4);
    course.add(s5);
    course.add(s6);
    course.add(s7);
    course.add(s8);
    course.add(s9);

    System.out.println("Original Class");
    System.out.println(course);

    Collections.sort(course);
    System.out.println("Sorted Class with compareTo");
    System.out.println(course);

    GradeComparator gradeComparator = new GradeComparator();
    Collections.sort(course, gradeComparator);

    System.out.println("Sorted Class with GradeComparator");
    System.out.println(course);

    SurnameComparator surnameComparator = new SurnameComparator();
    Collections.sort(course, surnameComparator);

    System.out.println("Sorted Class with SurnameComparator");
    System.out.println(course);

    Comparator<Student> gC = (Student o1, Student o2) -> {
      return o2.getGrade().compareTo(o1.getGrade());
    };

    System.out.println("Lambda 1");
    Collections.sort(course, gC);
    System.out.println(course);
    Collections.shuffle(course);

    System.out.println("Lambda 2 Inline");
    course.sort((o1, o2) -> {
      return o1.getFirst().compareTo(o1.getFirst());
    });
    System.out.println(course);

    System.out.println("Lambda 3 Inline");
    Collections.shuffle(course);
    Collections.sort(course, (o1, o2) -> {
      return o2.getGrade().compareTo(o1.getGrade());
    });
    System.out.println(course);
  }

}

Method references

Sometimes expression that we would like to capture as a lambda expression is already defined as a named method in a class. Java 8 provides syntax that allows us to refer to a method whether is is a static or an instance method.

  • Class::staticMethod - is a method reference to the static method staticMethod in the class Class

  • o::m - is a method reference to the method m found on the instance o

  • String::compareToIgnoreCase) - refers to an instance method compareToIgnoreCase found in String. Note that the method compareToIgnoreCase takes 1 argument. The code will pass 2 arguments to the reference String::compareToIgnoreCase, e.g., a and b and will then execute a.compareToIgnoreCase(b). This is equivalent to (String a, String b) → { return a.compareToIgnoreCase(b);}

  • Student::new - a reference to the constructor

Aggregate Operations

Java 8 uses Stream<T> interface to create a sequence of elements for sequential and parallel processing.

Aggregate operations are operations that work on streams. A sequence of aggregate operations is called a pipeline.

  System.out.println("Print all students with grade > 60");
    course.stream().filter(s -> s.getGrade() > 60).forEach(s -> System.out.println(s));
 System.out.println("Add 10 to all grades > 60");
    course.stream().filter(s -> s.getGrade() > 60).map(s -> {
      s.setGrade(s.getGrade() + 10);
      return s;
    }).forEach(s -> System.out.println(s.toString()));

Separation of Concerns

The best way to understand and implement a system is by breaking it into smaller pieces.

  1. Each piece has a reason for its existence.

  2. Each piece has a clear definition of how it can be used.

  3. Each pieces does one job and it does it well.

    • Single responsibility principle - A class should have one and one job.

    • Open Close principle - open to extension closed for modification.

    • Liskov substitution principle - subtypes honor the properties set by their supertypes.

    • Interface segregation principle - clients should never be forced to implement/depend on interfaces/methods they do not use.

    • Dependency Inversion principle - depend on abstractions not concrete implementations when you can.

Design Patterns

A collection of well thought out and well documented solutions to commonly found problems.

  • Creational Patterns - deal with constructing new instances

  • Structural Patterns - deal with relationships between objects

  • Behavioural Patterns - deal with communication patterns between objects

Singleton

Factory

Composite

Template Method

Visitors