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 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.
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 a union Data Definition grouped together data that served a specific role in our design. The name of the union Data Definition was used in our signatures and Data Definitions but did not really have a presence in the source code.
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
. -
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 information for that view on this object.
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
Here is some of the code Rook
/**
* Represents the colour of a pieces in a Chess game.
*/
public interface Colour {
}
/**
* Created by therapon on 5/16/16.
*/
public class White implements Colour {
}
/**
* Created by therapon on 5/16/16.
*/
public class Black implements Colour {
}
/**
* Represents a piece in our chess game.
*
*/
public interface Piece {
}
/**
* 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;
}
}
/**
* 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.
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.
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 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 code in the interface Piece
in order to find a method getLoc
and not
against 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 an 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
-
add a signature to the interface that captures our itemization and
-
to each class that maps to an element of the itemization provide an implementation of this signature
In order to be use the methods getLoc
and getColour
we need to
-
Add the signatures of these methods in
Piece
-
Ensure that all classes that implement
Piece
provide an implementation for each signature inPiece
.-
The signatures have to match exactly!
-
Let’s update Piece
to add the signatures that we want to use for all Piece
instances.
/**
* 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
Now our code compiles!
Java Evaluation
Whiteboard!
Abstracting commonalities using inheritance
Observe that each of the classes that implement Piece
have two fields
-
loc
-
colour
Java provides a mechanism called inheritance that allows classes to inherit propertied 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
-
public
fields -
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.
/**
* 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;
}
}
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 . We can use protected for fields and/or methods. A field/method
that is marked as protected designates that |
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
/**
* 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
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
.
/**
* 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. |
/**
* 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
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.
-
There are elements of each shape that are common, the pin.
-
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.
-
We would like to move a shape on the canvas in the x-direction
-
We would like to move a shape on the canvas in the y-direction
-
We would like to calculate the area of a shape
-
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.
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 Shape(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
-
Use the inherit method as is and call it on itself.
-
It can override the method in order to provide it’s own implementation. An override however must satisfy the following conditions
-
The method signature must be the same
-
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 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
isShape
-
the runtime type (or dynamic type) of
s1
isCircle
. -
the compile time type (or static type) of
s2
isAbstractShape
-
the runtime type (or dynamic type) of
s2
isCircle
.
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
interface 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 typeT
-
look at the class
T
and see if the method is defined in classT
, override or not. If the method is defined inT
run it ono
-
else if the method is not defined in
T
, grabT
's immediate superclass (lets called thatS1
), if the method is defined inS1
, override or not, run it ono
-
else if the method is not defined is
S1
, grabS1
's immediate superclass (lets called thatS2
), if … -
we terminate once we reach the class
Object
. IfObject
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".
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
TBD |