Relevant Reading Material

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 core set of evaluation rules and add to these 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.

Design Recipe

  1. Data Analysis

    1. Data Examples

    2. Template

  2. Signature

  3. Purpose

  4. Examples

  5. Function Definition

  6. Tests

  7. Review

Nothing has changed about the Design Recipe. What has changed is the outcome of each step since we are now going to create Java programs instead of Racket programs.

Racket

design-recipe
;;;; Data Analysis and Definitions:
(define-struct student (last first teacher))
;; A student is a (make-student Symbol Symbol Symbol)
;; INTERP: represents a student's first and last name
;;         and the name of the their teacher

;;;; Data Examples:
;; (make-student 'Joe 'Doe 'Mary)
;; (make-student 'Mat 'Jones 'Fritz)

;;;; Template:
;; student-fn: Student -> ???
;; (define (student-fn a-student a-teacher)
;;   ... (student-last a-student) ...
;;   ... (student-first a-student) ...
;;   ... (student-teacher a-student) ...)



;;;; Signature: subst-teacher : Student Symbol -> Student

;;;; Purpose: to create a student structure with a new
;;;; teacher name if the teacher's name matches 'Fritz

;;;; Examples:
;; (subst-teacher (make-student 'Mat 'Jones 'Fritz) 'Elise)
;; =
;; (make-student 'Mat 'Jones 'Elise)
;; (subst-teacher (make-student 'Joe 'Doe 'Mary) 'Elise)
;; =
;; (make-student 'Joe 'Doe 'Mary)

;;;; Function Definition:
(define (subst-teacher a-student a-teacher)
  (cond
    [(symbol=? (student-teacher a-student) 'Fritz)
     (make-student (student-last a-student)
                   (student-first a-student)
                   a-teacher)]
    [else a-student]))

;;;; Tests:
(check-expect (subst-teacher (make-student 'Mat 'Jones 'Fritz) 'Elise)
              (make-student 'Mat 'Jones 'Elise))

(check-expect (subst-teacher (make-student 'Joe 'Doe 'Mary) 'Elise)
              (make-student 'Joe 'Doe 'Mary))

Java

Data Analysis

We are trying to map information to data. Instead of define-struct we are going to design Java classes. Create a UML class diagram to capture

  1. Java classes that you are going to create

  2. Fields for each class that you need

  3. Methods for each class that you need

  4. Dependencies between classes; arrows that show relationships between classes.

For example recall the Author example from Lab1

author

Data Examples

Java code that creates instance of your classes. This code ends up in your setup() method inside your Test class (e.g., AuthorTest, EmailTest, AddressTest).

Templates

The goal of the template it do decompose your data. At this point data in Java means a class. So we need a template for a class that, hopefully, all class methods can use as a starting point.

So lets take Author

author-template
public class Author {
    Person person;
    Email email;
    Address address;

    public Author(Person person, Email email, Address address) {
        this.person = person;
        this.email = email;
        this.address = address;
    }

    /**
      * Template
      *

          ... template() {

            ... this.person ...
            ... this.email  ...
            ... this.address ...

            ... this.hashcode() ...
            ... this.toString() ...

          }
      */

template() contains all the elements (fields and methods) that we can access.

Observer that the template above does not include equals(Object o). The template that we have above concerns only the Author class. This template has higher chances of being used for methods that do not take any argument.

Ideally we can write a different template for every possible argument e.g.,

author-template-arg
public class Author {
    Person person;
    Email email;
    Address address;

    public Author(Person person, Email email, Address address) {
        this.person = person;
        this.email = email;
        this.address = address;
    }

    /**
      * Template
      *

          ... template() {

            ... this.person ...
            ... this.email  ...
            ... this.address ...

            ... this.hashcode() ...
            ... this.toString() ...

          }


          ... template(Author a) {
             ... this.template() ...
             ... this.equals(a) ...

             ... a.getPerson() ...
             ... a.getEmail() ...
             ... a.getAddress() ...
             ... a.hashcode() ...
             ... a.toString() ...
             ... a.equals(this) ...
          }

Observe that tempalte(Author a) reuses template() because we are still within Author and have access to all (private, public and protected) fields and methods.

However, now we can add equals() since we can call the equals method with a as our argument.

Also, template(Author a) contains all the publicly available methods and fields of the argument a. In your IDE, this is all the elements that you see when you type a. and the autocomplete feature gives you a list of all accessible fields and methods of a.

Signature

The signature that we used to write for each Racket function is now embedded into the method signature for Java. The types that we have to include as part of the Java method’s signature are essentially the signature that we used to write as comments in Racket.

public class MyInt {

  private Integer val;

  public MyInt(Integer val) {
    this.val = val;
  }

  // signature : getVal() :  -> Integer
  public Integer getVal() {
    return this.val;
  }

 // signature : greaterThan: Integer -> Boolean
  public Boolean greaterThan(Integer other) {
    return this.val > other.getVal();
  }

We do have to remember however that Java methods are inside a class and we always has access to the this class. In Racket we had to pass a value of our struct as an argument, in Java we are already inside the instance of the class.

Another way to visualizing this property is to assume an invinisble first argument that has the type of the class that defines this method and it is called this and that Java auto-populates each call with the right object for the first argument called this.

e.g.,

public class MyInt {

  private Integer val;

  public MyInt(MyInt this, Integer val) {
    this.val = val;
  }

  // signature : getVal() : MyInt  -> Integer
  public Integer getVal(MyInt this) {
    return this.val;
  }

 // signature : greaterThan: MyInt Integer -> Boolean
  public Boolean greaterThan(MyInt this, Integer other) {
    return this.val > other.getVal();
  }

Purpose

Similar to Racket only in Java we need to write our purpose using Javadoc, which means that we have

  1. A one sentence description of what this method does.

  2. Any WHERE clauses that the method requires

  3. Any INVARIANTS, HALTING MEASURES, TERMINATION ARGUMENTS that the method requires

  4. A one line comment for each formal argument using @param

  5. A one line comment explaining the method’s return value using @return.

Examples

Examples in Java are written as part of the Javadoc comment for the method so that when we generate our documentation and provide it to clients they will have example uses for each method.

Function Definition (now Method Definition)

The name is misleading for Java since we do not have function but methods. So we will rename this step to Method Definition.

Tests

In Java our tests are found in a companion JUnit class. Instead of check-expect and its variants we use the JUnit Assert class along with its assertion methods.

Review

This step is the same. Go back and review each step of the Design Recipe.

Inheritance

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 and protected fields

  2. public and protected methods.

private characteristics of a class are not inherited.

As an example consider the notion of a shape.

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

Data Design

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 class Shape {
    protected Point pin;

    public Shape(Point pin) {
        this.point = point;
    }
}

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

    public Circle(Point 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 super class 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 same

    2. Use the @Override annotation.

You have already use override when you implemented hashcode(), equals(Object o) and toString().

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.

e.g.,

  public String toString() {
      return super.toString() + " some more information from the subclass";
  }

Inheritance, types and 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);

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 static type of s1 is Shape

  • the dynamic type (or runtime type) of s1 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 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 class or one of its superclasses. 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, run it on o

    2. else 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 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.

Diagram of evaluation that uses different static type and dynamic type.

Polymorphism is the ability of an object to take many "forms" or "types".

Abstract Classes and abstract methods

Java allows for the keyword abstract and we can use it on a class definition or a method.

Abstract class

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.

public abstract class Shape {
    protected Point pin;

    public Shape(Point pin) {
        this.point = point;
    }
}

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

    public Circle(Point pin, Integer radius) {
        super(pin);
        this.radius = radius;
    }
}

We can have constructors inside an abstract class like Shape we cannot however use new directly on Shape even though it has a constructor.

We can use the constructor in Shape in our Circle subclass to help us instantiate a Circle object and assign to the fields that Circle inherited from Shape

Abstract methods

When we use abstract on a method, then Java requires that

  1. the method is inside an abstract class

  2. the method provides a signature but no body

For example

public abstract class Shape {
    protected Point pin;

    public Shape(Point pin) {
        this.point = point;
    }

    abstract Double area();
}

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

    public Circle(Point pin, Integer radius) {
        super(pin);
        this.radius = radius;
    }
}

Compiling the preceding code will generate a compile time error that the class Circle does not implement the method area().

An abstract method can be thought of as a promise that all subclasses that extend the abstract class will

  1. inherit all public and protected abstract methods

  2. must provide implementations for this methods

We are essentially forcing our subclasses to provide these methods.

A subclass S can choose to inherit an abstract method and leave it abstract. This approach however forces

  1. the subclass S to also be abstract

  2. all subclasses of S must either

    1. implement all abstract methods of S, or,

    2. must themselves be abstract

Itemizations

Let’s go back to our shapes example. The shape example is an itemization. The first design that we will see for itemizations uses abstract classes and abstract methods.

shape itemization

Now we have to provide implementations for each abstract method inside each subclass.

Where is the cond or the 90 degree view

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 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 {
    // elided code

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

}

public class Square {
    // elided code

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

}

public class Rectangle {
    // 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));
    }
}