Relevant Reading Material

Chapters From How to Design Classes (HtDC)

  • Chapter 4

  • Chapter 5

  • Chapter 6

  • Intermezzo 1

  • Chapter 8

  • Chapter 9

  • Chapter 10

  • Chapter 11

Evaluation order DrRacket and Java

In DrRacket we had a set of rules that dictate how an expression gets evaluated. We used the Stepper in order to visualize these steps for an expression under evaluation.

Java has its own evaluation rules. We will start with a core set of evaluation rules and add to this set as the class progresses. All popular IDEs also provide a debugger that allows us to step through the execution of a Java program. For now you can think of a debugger to be similar to the DrRacket stepper. As we progress through the course we’ll see more on debugging and some of the difference between a debugger and the DrRacket Stepper.

Itemizations

Let’s consider the design of a chess game. We would like to capture the pieces of a chess board and the chess board as data. Our chess game will also have a graphical user interfaces so we need to capture any information that we might need in order to draw the pieces on our chessboard.

We will focus on the information for chess pieces first excluding movement.

Chess has the following pieces

  • Pawn

  • Rook

  • Knight

  • Bishop

  • Queen and

  • King

Each player is given a collection of these pieces. The collection consists of

  • 8 Pawns

  • 2 Rooks

  • 2 Knights

  • 2 Bishops

  • 1 Queen and

  • 1 King

The two collections have different colours, one player has white and the other black.

One possible way to capture these pieces of information in Racket could be the following

;; A Colour is one of
;; - 'white
;; - 'black

;; A Piece is one of
;; - Pawn
;; - Rook
;; - Knight
;; - Bishop
;; - Queen
;; - King


(define-struct pawn (loc colour))
;; A Pawn is (make-pawn Posn Colour)
;; INTERP: represents a pawn with its colour and location

(define-struct rook (loc colour))
;; A Rook is (make-rook Posn Colour)
;; INTERP: represents a rook with its colour and location

(define-struct knight (loc colour))
;; A Knight is (make-knight Posn Colour)
;; INTERP: represents a knight with its colour and location

(define-struct bishop (loc colour))
;; A Bishop is (make-bishop Posn Colour)
;; INTERP: represents a bishop with its colour and location

(define-struct queen (loc colour))
;; A Queen is (make-queen Posn Colour)
;; INTERP: represents a queen with its colour and location

(define-struct king (loc colour))
;; A King is (make-king Posn Colour)
;; INTERP: represents a king with its colour and location

In Racket an itemization Data Definition groups together data that served a specific role in our design. The name of the itemization Data Definition was used in our signatures and Data Definitions but did not really have a presence in our code/evaluation.

Interfaces in Java

Java provides Interfaces. A Java Interface is much like a class but

  • There is no constructor! We cannot directly create an instance of an interface.

  • Everything in the interface is public by default.

  • There are no fields in interfaces.

  • You can only have method signatures in interfaces. [1]

  • A class implements an interface by explicitly declaring in its class header the name of the interface using the special keyword implements. A class can implement as many interfaces as it wants.

  • Any class that claims to implement an interface must provide code for each signature in the interface.

Think of an interface as a way to provide a view on your objects that shows only the relevant methods that can be used on your objects from this view.

Back to our chess game now, we will capture itemizations in Java as interfaces and thus Piece is going to be an interface. Each of the itemization elements of the data definition, i.e., Pawn, Rook etc. will implement our interface, i.e., Piece

piece union

Notice the new style of arrow that we have between Piece and all of the kinds of pieces. The empty-head arrow in UML denotes a new relantionship between classes. We tend to call this relationship is-a.

Using the class King we read this arrow as follows

A King is a Piece.

We will see this in action shortly. The phrase conveys the fact that we can use Piece as a compile-time type for a variable and we can assign any object that implements the interface Piece.

Here is some of the code

Colour.java
/**
 * Represents the colour of a pieces in a Chess game.
 */
public interface Colour {
}
White.java
/**
 * Created by therapon on 5/16/16.
 */
public class White implements Colour {
}
Black.java
/**
 * Created by therapon on 5/16/16.
 */
public class Black implements Colour {

}
Piece.java
/**
 * Represents a piece in our chess game.
 *
 */
public interface Piece {
}
Pawn.java
/**
 * Created by therapon on 5/16/16.
 */
public class Pawn implements Piece {

    private Posn loc;
    private Colour colour;

    public Pawn(Posn loc, Colour colour) {
        this.colour = colour;
        this.loc = loc;
    }

    /**
     * Getter for property 'loc'.
     *
     * @return Value for property 'loc'.
     */
    public Posn getLoc() {
        return this.loc;
    }

    /**
     * Getter for property 'colour'.
     *
     * @return Value for property 'colour'.
     */
    public Colour getColour() {
        return this.colour;
    }
}
Rook.java
/**
 * Created by therapon on 5/16/16.
 */
public class Rook implements Piece {

    private Posn loc;
    private Colour colour;

    public Rook(Posn loc, Colour colour) {
        this.loc = loc;
        this.colour = colour;
    }

    /**
     * Getter for property 'loc'.
     *
     * @return Value for property 'loc'.
     */
    public Posn getLoc() {
        return this.loc;
    }

    /**
     * Getter for property 'colour'.
     *
     * @return Value for property 'colour'.
     */
    public Colour getColour() {
        return this.colour;
    }
}

And here is a test for Pawn. We will add a test to verify Colour soon.

PawnTest.java
import org.junit.Assert;

public class PawnTest {
    private Pawn p1;

    @org.junit.Before
    public void setUp() throws Exception {
        this.p1 = new Pawn(new Posn(1,1), new White());
    }

    @org.junit.Test
    public void getLoc() throws Exception {
        Assert.assertTrue(samePosn(this.p1.getLoc(), new Posn(1,1)));

    }

    private Boolean samePosn(Posn loc, Posn posn) {
        return loc.getX().equals(posn.getX()) && loc.getY().equals(posn.getY());

    }

}

A Java interface is a valid type for a field/variable. The possible values that we can set to a field/variable whose type is the name of an Interface are all the instances of classes that implement that interface.

PieceTest.java
import org.junit.Before;
import org.junit.Test;


public class PieceTest {

    private Piece pawn;
    private Piece rook;
    private Piece bishop;
    private Piece knight;
    private Piece queen;
    private Piece king;
    private Posn p1;
    private Colour black;
    private Colour white;

    @Before
    public void setUp() throws Exception{
        this.p1 = new Posn(1, 1);
        this.black = new Black();
        this.white = new White();

        this.pawn = new Pawn(this.p1, this.black);    (1)
        this.rook = new Rook(this.p1, this.white);
        this.bishop = new Bishop(this.p1, this.black);
        this.knight = new Knight(this.p1, this.white);
        this.queen = new Queen(this.p1, this.black);
        this.king = new King(this.p1, this.white);
    }

    @Test
    public void testPiece(){
        this.pawn.getLoc();      (2)
    }
}
1 Observe that the instance we are creating on the right hand side of the = is a Pawn while the field we are assigning to this.pawn was given the type Piece. Given that Pawn implements Piece this is a valid assignment.
2 This line gives a compile time error
PieceTest.java:30: error: cannot find symbol
        this.pawn.getLoc();
                 ^
  symbol:   method getLoc()
  location: variable pawn of type Piece

The compiler complains that there is no getLoc() method defined for the field pawn which has the compile-time type Piece. The reason is that Piece as an interface provides no method signatures. Even though we know that we assigned the value new Pawn(this.p1, this.black) to the field pawn and that Pawn does have a method called getLoc() the compiler only checks against the compile-time type which in this case is the interface Piece in order to find a method getLoc() and not against the runtime-type Pawn.

The compiler only considers information that is available at compile time.

Adding methods on our Piece Itemization

Recall in Racket our functions took as input a value of our itemization and then using a cond statement detected which one of the valid values for this itemization we have and delegated to the appropriate function.

For example if we wanted to have get the location of a Piece

;;;; Signature
;; piece-posn: Piece -> Posn
;;;; Purpose
;; Given a piece returns the piece's location

(define (piece-posn piece)
  (cond
    [(pawn? piece) (pawn-loc piece)]
    [(rook? piece) (rook-loc piece)]
    [(knight? piece) (knight-loc piece)]
    [(bishop? piece) (bishop-loc piece)]
    [(queen? piece) (queen-loc piece)]
    [(king? piece) (king-loc piece)]))

With Java since we captured our itemization as an Interface we need to

  1. add a signature to the interface that captures our itemization and

  2. to each class that maps to an element of the itemization provide an implementation of this signature. The code we add to each class that implements our Interface is the same as the code in our helper functions in Racket.

In order to be use the methods getLoc() and getColour() we need to

  1. Add the signatures of these methods in Piece

  2. Ensure that all classes that implement Piece provide an implementation for each signature in Piece.

    1. The signatures have to match exactly!

Let’s update Piece to add the signatures that we want to use for all Piece instances.

Piece.java
/**
 * Represents a piece in our chess game.
 *
 */
public interface Piece {

    /**
     * Get the location of this piece
     * @return  the current location of this piece
     */
    Posn getLoc();

    /**
     * Get the colour of this piece
     * @return this piece's colour
     */
    Colour getColour();
}

Another way to think about how the Racket implementation maps to the Java implementation is that the cond is done by Java during evaluation, and we need to add the corresponding Java code that maps to the right hand side of the cond -clause to the appropriate class.

And here is our updated UML Class Diagram

piece union interface sigs

Now our code compiles!

Java Evaluation

Whiteboard!

Abstracting commonalities using inheritance

Observe that each of the classes that implement Piece have two fields

  1. loc

  2. colour

Java provides a mechanism called inheritance that allows classes to inherit properties from their parents.

Inheritance in Java is a relationship between classes. The meaning of inheritance in Java is close to the meaning of the word inheritance in the English in that one class derives characteristics from another.

In Java the characteristics that are derived are

  1. public fields

  2. public methods.

private characteristics (methods/fields) of a class are not inherited.

So we could create a new class that will contains the common fields and methods across all classes that implement Piece. The purpose of this class is to contain all the common fields and methods across pieces. It is an abstraction (think of it as an incomplete segment) that we would like all concrete Piece implementation to share. As such we do not want to create instances of this incomplete segment.

Java allows us to define classes that are abstract in nature and cannot be instantiated.

AbstractPiece.java
/**
 * Created by therapon on 5/16/16.
 */
public abstract class AbstractPiece {        (1)

    protected Posn loc;             (2)
    protected Colour colour;

    public AbstractPiece(Posn loc, Colour colour) {    (3)
        this.colour = colour;
        this.loc = loc;
    }

    /**
     * Getter for property 'loc'.
     *
     * @return Value for property 'loc'.
     */
    public Posn getLoc() {
        return this.loc;
    }

    /**
     * Getter for property 'colour'.
     *
     * @return Value for property 'colour'.
     */
    public Colour getColour() {
        return this.colour;
    }

}

Protected modifier

We can use protected for fields and/or methods. A field/method that is marked as protected designates that

  1. The code in the enclosing class has access to the field/method.

  2. Any class that inherits from this class will obtain protected fields and methods and will have access to them

  3. Any other class that does not inherit from this class does not have access to protected fields/methods.

1 An abstract class is declared by using the keyword abstract in the class definition header
2 protected is another modifier like public and private.
3 Abstract classes can have constructors inside their definition. These constructors cannot be used to create an instance of the abstract class. They are used by subclasses to create the segments of the abstract class that they inherit and then fill in the remaining fields (if there are any).

By declaring a class abstract Java will not allow us to create instance of the class directly, e.g., you cannot use new on an abstract class. It is essentially "placeholder" with some common behaviour and/or some method signatures that we want all subclasses to inherit and/or implement.

Here is the updated implementation of Queen

Queen.java
/**
 * Created by therapon on 5/16/16.
 */
public class Queen extends AbstractPiece implements Piece {  (1)


    public Queen(Posn loc, Colour colour) {
        super(loc,colour);              (2)
    }


}
1 To define an inheritance relationship to another class we need to specify our parent class in the class definition header using the keyword extends.
2 In the constructor we can use the keyword super to refer to our parent class' constructor. We can also use super to refer to one of our parents methods, e.g., super.getLoc() calls the method in our parent class which is AbstractPiece.
In Java a class can only extend one other class and no more!

And here is our update UML Class Diagram

piece union interface absclass

Given that all the code that implements the signatures found in Piece are in AbstractPieces we could move the implements Piece declaration to AbstractPiece and all of our classes that extend AbstractPiece will still implement Piece.

AbstractPiece.java
/**
 * Created by therapon on 5/16/16.
 */
public abstract class AbstractPiece implements Piece {        (1)

    protected Posn loc;
    protected Colour colour;

    public AbstractPiece(Posn loc, Colour colour) {
        this.colour = colour;
        this.loc = loc;
    }

    /**
     *  Getter for property 'loc'.
     *
     *  @return Value for property 'loc'.
     *                     */
    public Posn getLoc() {
        return loc;
    }

    /**
     * Getter for property 'colour'.
     *
     * @return Value for property 'colour'.
     *                     */
    public Colour getColour() {
        return colour;
    }

}
1 AbstractPiece now implements Piece and through inheritance so will all the classes that extends AbstractPiece. Notice that AbstractPiece does not provide implementations for the signatures defined in Piece. We will talk about abstract classes extensively in our next lecture.
King.java
/**
 * Created by therapon on 5/16/16.
 */
public class King extends AbstractPiece  {


    public King(Posn loc, Colour colour) {
        super(loc, colour);
    }

}

And our updated UML Class Diagram again

piece union interface inh abs

Java Evaluation again

Whiteboard!

Shapes Example

Let’s try another example. We would like to design a program that allows us to manipulate shapes on a canvas.

A Shape, for now, can be one of

  • a circle with a radius and a pin (its center)

  • a square with a pin (the top left corner) and a side

  • a rectangle with a pin (the top left corner) a width and a height

This is an example of an itemization.

We can make some observations about the data for each shape.

  1. There are elements of each shape that are common, the pin.

  2. There are element of each shape that are uncommon (or special) to each individual shape, e.g., radius for circle

Lets look at the operations that we want for each shape.

  1. We would like to move a shape on the canvas in the x-direction

  2. We would like to move a shape on the canvas in the y-direction

  3. We would like to calculate the area of a shape

  4. We would like to calculate the circumference of a shape

In Racket

Recall what we had to do for itemizations in Racket?

;; A Shape is one of
;; - (make-circle Posn PosInt)
;; - (make-square Posn PosInt)
;; - (make-rectangle Posn PosInt PosInt)


;; shape-area: Shape -> Number
(define (shape-area shape)
  (cond
    [(circle? shape) (circle-area shape)]
    [(square? shape) (square-area shape)]
    [(rectangle? shape) (rectangle-area shape)]))


;; circle-area: Circle -> Number
(define (circle-area circle)
   (* pi (sqr (circle-radius))))

;; square-area: Square -> Number
(define (square-area square)
  (sqr (square-side square)))


;; rectangle-area: Rectangle -> Number
(define (rectangle-area rectangle)
  (* (rectangle-width rectangle)
     (rectangle-height rectangle)))

In Java

We can use inheritance for the common parts of our data so that we define a pin in one location in our code and have all the specific shapes that use a pin inherit that field.

shape

Fields and constructors in the presence of inheritance

The class Circle has access to radius but it also has access to pin. The same argument goes for each subclass.

The Java language provides syntax to refer to you parent. In Java the special keyword super refers to your parent.

In order to create a new Circle instance we need to provide a constructor method like before. Now however we need to create our constructor to accept a value for radius and a value for pin.

public abstract class AbstractShape implements Shape {
    protected Posn pin;

    public AbstractShape(Posn pin) {
        this.point = point;
    }
}

public class Circle extends AbstractShape { (1)
    private Integer radius;

    public Circle(Posn pin, Integer radius) {
        super(pin);
        this.radius = radius;
    }
}
1 The Java keyword extends specifies that Circle inherits from Shape. We say that Circle is a subclass of Shape and Shape is the superclass of Circle.

In Java you can only have 1 superclass. This property is called single inheritance. A superclass however can have more than one subclasses.

The statement super(pin) tells Java to call the parent (or superclass) constructor in Shape passing pin as the argument to Shape 's constructor. This will setup pin correctly and then we proceed to set the fields found in Circle.

In Java calling the superclass constructor must be the first line in the subclass constructor.

Methods in the presence of inheritance

Java applies similar rules to methods when it comes to inheritance and which methods are derived or not.

A subclass inherits all public and protected methods that are present in the superclass.

A subclass can

  1. Use the inherit method as is and call it on itself.

  2. It can override the method in order to provide it’s own implementation. An override however must satisfy the following conditions

    1. The method signature must be the exactly the same.

    2. Use the @Override annotation.

If you would like to call the overriden method, Java allows you to do so with super.<method-name>(<args>) where

  • <method-name> is the name of the method you want to call in the superclass.

  • <args> are any number of arguments (0 or more) that you want to pass to the superclass' method you are calling.

Inheritance, types and subtype polymorphism

Inheritance defines a relationship between a superclass and a subclass. This relationship affects how the Java compiler and the JVM treat instances of these classes.

Given that a subclass must have all the publicly available methods of its superclass (overridden or not) we can view a subclass as its superclass.

 Circle c1 = new Circle(new Point(10,20), 5);

The above line is valid Java code. We create a variable called c1 that can hold objects of type Circle and we assigned to c1 the object new Circle(new Point(10,20), 5).

 Shape s1 = new Circle(new Point(10,20), 5);
 AbstractShape s2 = new Circle(new Point(10,20), 5);

The preceding line is also valid Java. We create a new variable called s1 that can hold objects of type Shape and we assigned to s1 the object new Circle(new Point(10,20), 5). Some terminology here we say

  • the compile time type (or static type) of s1 is Shape

  • the runtime type (or dynamic type) of s1 is Circle.

  • the compile time type (or static type) of s2 is AbstractShape

  • the runtime type (or dynamic type) of s2 is Circle.

Conceptually you can think of this as, given that a Circle has the same methods and fields as a Shape (well maybe a bit more) then it is perfectly valid to use it as a Shape if I want to.

The Java compiler will treat s1 as a Shape in your code and will not allow you to call a method on s1 that is not found in the Shape interface or one of its superclasses/superinterfaces. Superclasses here refers to the parent, grandparent, grand-grandparent etc.

The Java compiler will treat c1 as a Circle in your code and will not allow you to call a method on c1 that is not found in the Circle class or one of its superclasses. Superclasses here refers to the parent, grandparent, grand-grandparent etc.

The JVM however has the following evaluation rule :

  • when looking up a method on an instance o that has runtime type T

    1. look at the class T and see if the method is defined in class T, override or not. If the method is defined in T run it on o

    2. else if the method is not defined in T, grab T 's immediate superclass (lets called that S1), if the method is defined in S1, override or not, run it on o

    3. else if the method is not defined is S1, grab S1 's immediate superclass (lets called that S2), if …​

    4. we terminate once we reach the class Object. If Object does not have the method then we throw an error. This error should never happen in a Java program that was build using the Java compiler.

The ability of one instance to be viewed/used as different types is called polymorphism — the ability to takes many "shapes"/"forms"/"views".

There are 3 kinds of polymorphism that we will explore in this class,

  1. Subtype polymorphism which is what we saw today

  2. Ad-hoc polymorphism (or overloading) which we will see soon

  3. Parametric polymorphism (or Generics in Java) which we will see later

In Java the cond is taken care of by the language due to the way we designed our classes. What we have to write is the helper functions of for shape-area and we have to write them inside the appropriate class.

public class Circle extends AbstractShape{
    // elided code

    public Double area() {
        return Math.PI * this.radius * this.radius;
    }

}

public class Square extends AbstractShape{
    // elided code

    public Double area() {
        return this.side * this.side;
    }

}

public class Rectangle extends AbstractShape{
    // elided code

    public Double area() {
        return this.width * this.height;
    }
}



public class ShapeTest {

    private Shape c1;
    private Shape s1;
    private Shape r1;

    @Before
    public void setUp() throws Exception {
        this.c1 = new Circle(new Point(10,20), 5);
        this.s1 = new Square(new Point(100,100), 20);
        this.r1 = new Rectangle(new Point(300,300), 30, 10);
    }


    @Test
    public void testArea() {
        Assert.assertEqual(this.c1.area(), new Double(78.53981633974483));
        Assert.assertEqual(this.s1.area(), new Double(400));
        Assert.assertEqual(this.r1.area(), new Double(300));
    }
}

Equality in Java

Java provides two mechanisms for checking equality between values.

  1. ==, the double equals is used to check

    1. equality between primitive types

    2. "memory equality", check whether the two references point to the same object in memory

  2. equals() is a method defined in the class Object that is inherited to all classes. The JVM expects developers to override the equals() method in order to define the notion of equality between objects of classes that they define.

Overriding the equals() method however imposes extra restrictions. These restrictions are spelled out in the equals() method documentation.

  1. reflexive - for a non-null reference value x, x.equals(x) returns true

  2. symmetric - for non-null reference values x and y, x.equals(y) returns true if and only if y.equals(x) returns true

  3. transitive - for non-null reference values x, y, and z,

    1. if x.equals(y) returns true and

    2. y.equals(z) returns true, then

    3. x.equals(z) must return true

  4. consistent - for non-null references x, y, multiple invocations of x.equals(y) should return the same result provided the data inside x and y has not been altered. [2]

  5. for any non-null reference value x x.equals(null) returns false

  6. the code needs to override hashCode as well in order to uphold the hashCode() method’s contract. The contract for hashCode() is spelled out in hashCode() 's documentation.

Here are the conditions for the hashCode() method’s contract

  1. for a non-null reference value x multiple invocations of x.hashCode() must return the same value provided the data inside x has not been altered. [2]

  2. for any two non-null reference values x, y

    1. if x.equals(y) returns true then

    2. x.hashCode() and y.hashCode() must return the same result

  3. for any two non-null reference values x, y

    1. if x.equals(y) returns false then

    2. it is prefered but not required that x.hashCode() and y.hashCode() return different/distinct results.

Let’s look at an example using our Posn class.

Posn.java
/**
 * Represents a Cartesian coordinate.
 *
 */
public class Posn {

    private Integer x;
    private Integer y;

    public Posn(Integer x, Integer y) {
        this.x = x;
        this.y = y;
    }

    /**
     * Getter for property 'x'.
     *
     * @return Value for property 'x'.
     */
    public Integer getX() {
        return this.x;
    }

    /**
     * Getter for property 'y'.
     *
     * @return Value for property 'y'.
     */
    public Integer getY() {
        return this.y;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object o) {  (1)
        if (this == o) return true;    (2)
        if (o == null || getClass() != o.getClass()) return false; (3)

        Posn posn = (Posn) o; (4)

        if (this.x != null ? !this.x.equals(posn.x) : posn.x != null) return false; (5)
        return this.y != null ? this.y.equals(posn.y) : posn.y == null; (6)

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        int result = this.x != null ? this.x.hashCode() : 0;  (7)
        result = 31 * result + (this.y != null ? this.y.hashCode() : 0); (8)
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return "Posn{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }
}

The strategy used to check for equality is recursive; we check each field in turn and since a field can be a reference type, we need to call its equals() method. There are many different implementations of equals(), we will go over this specific one here so that we have one example of how we could write an equals() method.

The strategy used for the implementation of hashCode() is also recursive; we use each field and get its hash code, we then compose all field hash codes together along with a prime number to get this object’s hash code.

1 Observe that the compile-time type for the argument o is Object not Posn.
2 We first check whether the argument passed is in fact the same exact object in memory as this object using ==.
3 We then check that if the argument o is null or the runtime-type of o [3] is not the same as the runtime-type of this then the two values cannot be equal.
4 If the runtime-types are the same then we cast--force the compile-time type of a variable to change to a new compile-time type-- o to be a Posn and give it a new name posn.
5 Now we check each field for equality, first x.
6 and then y. The ?: is the Java if-expression; an if statement that returns a value. The test is the code before ?, if that expression returns true then we evaluate the expression found between ? and :. If the test expression returns false then we evaluate the expression found after the :.
7 If a field, x in this case, is null then return 0 else grab its hash code by calling hashCode() on that object.
8 repeat to get the hash code for y, add the field hash code’s together and multiply by 31. [4]

1. Java 8 removes this restriction and we will learn more about interfaces later in the course
2. This condition will be more relevant once we cover mutation later in the semester.
3. getClass() is defined on all reference types and returns the value’s runtime-type.
4. It is impossible to have a computer generate a perfect hash function. Our hashing algorithm here can have collisions. The best we can do is to decrease the probability of a collision.