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
-
Data Analysis
-
Data Examples
-
Template
-
-
Signature
-
Purpose
-
Examples
-
Function Definition
-
Tests
-
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
;;;; 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
-
Java classes that you are going to create
-
Fields for each class that you need
-
Methods for each class that you need
-
Dependencies between classes; arrows that show relationships between classes.
For example recall the Author
example from Lab1
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
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.,
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
-
A one sentence description of what this method does.
-
Any
WHERE
clauses that the method requires -
Any
INVARIANTS
,HALTING MEASURES
,TERMINATION ARGUMENTS
that the method requires -
A one line comment for each formal argument using
@param
-
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
-
public
andprotected
fields -
public
andprotected
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.
-
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
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.
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
-
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.
-
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
isShape
-
the dynamic type (or runtime type) of
s1
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
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 typeT
-
look at the class
T
and see if the method is defined in classT
, override or not, run it ono
-
else grab
T
's immediate superclass (lets called thatS1
), if the method is defined inS1
, override or not, run it ono
-
else grab
S1
'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.
-
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
-
the method is inside an
abstract
class -
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
-
inherit all
public
andprotected
abstract methods -
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
-
the subclass
S
to also be abstract -
all subclasses of
S
must either-
implement all abstract methods of
S
, or, -
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.
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));
}
}