Relevant Reading Material

Chapters From How to Design Classes (HtDC)

  • Chapter 1

  • Chapter 2

  • Chapter 3

Shift your thinking

In CS5001 we had data and functions that operate on data. Typically we would

  1. design our data and create examples (instances)

  2. design functions that take these data as inputs and return data as outputs

Data and functions in Racket are two independent entities. The dependency between which data is manipulated by which function(s) is defined by the signatures of our functions.

With Object Orientation data and functions (called methods) are packaged together into a class. So a class has data and methods. These methods have access to the data inside the class. The dependency between which data is manipulated by which method is defined in two parts, the signatures of the methods and the location of the methods.

Examples are instance of classes (called objects) and these objects hold inside of them data and methods. We thus talk about the methods that an object understands (or has available). Computation is then composed by creating objects and asking them to evaluate (run) their methods.

Values in Java

A value can be

  1. Given as an argument.

  2. Returned as a result of a method call.

  3. Stored

In Racket we had Number String Symbol Images etc. These were data definitions that are either provided to us by Racket or data definitions that we defined as comments. In Java what we called data definitions are captured as types and they are not comments but rather part of the Java language.

A value in Java is associated with a type. For now you can think of a type as a category that indicates the operations that the value understands.

Java has two kinds of types

  1. Primitive types (start with a lower case letter)

    • e.g., int, bool, float …​

  2. Reference types (start with a capital case letter)

    • e.g., Object, LinkedList, String, Integer …​

For now your code is not allowed to use primitive types. Your code should only use reference types.

From define-struct to class

In Racket, we were able to create new values made up of a finite set of slots. The slots could then store other values. Here is an example in Racket that captures information for a Person, specifically the first and second name of a person as well as their age.

(define-struct person (first second age))
;; A Person is (make-person String String PosInt)
;; INTERP: represents a person with their first, and second name
;;         and their age

Informally, we can draw a parallel to Java’s classes.

public class Person {  
(1)
    private String first;      
    private String second;(2)
    private Integer age;

}
1 We define a class using the keyword class. The keyword public is called a modifier and modifies the visibility of this class. Visibility here refers to the ability of code in other classes/files to "see" (use/refer to) this class
2 Slots from Racket are called fields in Java. In Java however we must specify the type and the name of the field. Informally the type maps to the Data Definition names that we used in Racket struct Data Definitions. The modifier private designates that this field is private to this class and can be accessed only from code that is within this class definition.

The keywords public and private will be explained in detail later in the course. For now your code should follow the rule

  • All classes are public

  • All fields are private

Constructors and selectors

In Racket a define-struct generated a set of functions that we used to create, access and test a value created from this structure.

;;;; CONSTRUCTOR
;;; make-person: String String PosInt -> Person


;;;; SELECTORS
;;; person-first : Person -> String

;;; person-second : Person -> String

;;; person-age : Person -> PosInt


;;;; Predicate
;;; person? : Any -> Boolean

In Racket we referred to operations as functions, in Java operations are referred to as methods.

Java does not automatically generate these operations on your data, In Java we need to code these operations. Java programmers also use a different naming convention for methods. Here is the mapping

Racket Java

Constructor

Constructor

Selector

Getter

Here is the updated Person class with a constructor and getters for each field. We will look at each new element in turn.

public class Person {

    private String first;
    private String second;
    private Integer age;

    /**
     * Instantiates a new person given its first and second name and
     * their age.
     *
     * @param first the first name of the person
     * @param second the second name of the person
     * @param age the age of the person
     */
    Person(String first, String second, Integer age){
        this.first = first;
        this.second = second;
        this.age = age;  // years
    }

    /**
     * Gets the person's first name.
     *
     * @return the first name of the person
     */
    public String getFirst() {
        return first;
    }

    /**
     * Gets the person's second name.
     *
     * @return the second name of the person
     */
    public String getSecond() {
        return second;
    }

    /**
     * Gets the person's age.
     *
     * @return the age of the person.
     */
    public Integer getAge() {
        return age;
    }
}

Let’s look at teach newly added code segment in turn.

Constructor

public class Person {

    private String first;
    private String second;
    private Integer age;

    /**   
     * Instantiates a new person given its first and second name and(1)
     * their age.
     *
     * @param first the first name of the person 
     * @param second the second name of the person(2)
     * @param age the age of the person
     */
    public Person(String first, String second, Integer age){  
        this.first = first;  (3)
        this.second = second;(4)
        this.age = age;
    }
1 A multiline comment in Java starts with /* and ends with */. This is equivalent to Racket’s #| and |#. Java comments can be free from text but Java comments can also deal with HTML. The Javadoc tool can be used to read all comments in your source code and generate documentation. A single line comment in Java starts with //, equiavelent to Racket’s ;
2 Javadoc allows you to provide documentation for the arguments using the @param annotation, followed by the argument’s name and then a sentence describing what this argument is for.
3 This line starts the definition of the constructor method. Constructor methods are special methods. The name of a constructor method must be the same name as the class name, i.e., Person. Arguments are then provided between normal parenthesis separated by a comma. For each argument we need to specify its type a space and a name, i.e., String first.
4 The body of the constructor method is enclosed in curly parenthesis. While writing the body of the constructor method, if we want to refer to the fields of the class we must use the special keyword this. While inside the file of a class, if we want to access a field of the enclosing class we use this then a dot `. and then the name of the field.

The constructor method has the responsibility to check the values passed to it and assign the appropriate value to the appropriate field of the newly created value.

Here is another rule to follow for this course

  • All constructor methods are public

Getter

    /**
     * Gets the person's first name.
     *
     * @return the first name of the person (1)
     */
    public String getFirst() { (2)
        return first; (3)
    }
1 Javadoc provides the annotation @return that allows us to annotate and comment on the method’s return value.
2 For a normal method, the signature consists of a modifier public, the type of the value to be returned by the method String, the name of the method getFirst then the list of arguments enclosed in normal parenthesis. In this case there are no arguments. The modifier public here indicates that this method is available to be called from inside the class but also from other classes.
3 In the method body if we want to return a value back to the caller we must use the keyword return followed by the value to return or an expression that will be first evaluated to obtain a value and then return that result.

Yet another rule for this course

  • All methods are public

The rest of the methods that we added in Person follow the same pattern as the one for the method getFirst(); they return the value associated with a field. These methods are called getter methods because they "get the value of a field".

The names for getter methods in Java follow a naming convention, prepend the word get to the name of the field in CamelCase. We used CamelCase to write our Data Definition names in Racket.

Examples and Tests

In Racket we were able to create examples and tests. Our tests in Racket could use our examples and check using check-expect that we do in fact have the correct values inside our value. In Racket we could also use our interactions window to create some examples and test/inspect them.

(define-struct person (first second age))
;; A Person is (make-person String String PosInt)
;; INTERP: represents a person with their first, and second name
;;         and their age

(define JOHN (make-person "John" "Doe" 45))
(define MAGGIE (make-person "Maggie" "Flux" 23))

(check-expect (person-first JOHN) "John")
(check-expect (person-second JOHN) "Doe")
(check-expect (person-age JOHN) 45)

Java does not have an interactions window (yet!). Java also does not have a build-in check-expect. In order to write tests in Java we are going to use a library called JUnit to help as write and run tests.[1]

Examples and Tests in Java and JUnit are create in a separate class.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
public class PersonTest { (1) private Person john; private Person maggie; @org.junit.Before (2) public void setUp() throws Exception { (3) john = new Person("John", "Doe",45); (4) maggie = new Person("Maggie", "Flux", 23); } @org.junit.Test (5) public void getFirst() throws Exception { Assert.assertEquals(john.getFirst(), "John"); (6) Assert.assertEquals(maggie.getFirst(), "Maggie"); } @org.junit.Test public void getSecond() throws Exception { Assert.assertEquals(john.getSecond(), "Doe"); Assert.assertEquals(maggie.getSecond(), "Flux"); } @org.junit.Test public void getAge() throws Exception { Assert.assertEquals(john.getAge(), new Integer(45)); (7) Assert.assertEquals(maggie.getAge(), new Integer(23)); }
1 We create a new class that will contain all of our examples and tests for Person. The name for this new class is to append the word Test to the class name that we are going to test, in this case the class Person, giving us the final name PersonTest.
2 This is a Java annotation. Annotations are hints to the Java language and are used by JUnit to mark which methods are tests and which methods are to be called before/after we run our tests.
3 The method setup is marked to be executed before we run any tests. We use the setup method to create our examples and assign them to fields so that we can use them in our tests later
4 We are creating a value here that is the example for John, just like we did in Racket using define and we are assigning the value to the field john. This is similar to what we did in the constructor method for class Person.
5 This is another annotation from JUnit. org.junit.Test is used immediately above a method inside a test class to tag the method as a test that needs to be evaluated by JUnit.
6 JUnit provides a library for asserting (checking/verifying) that two values are the same.[2]. Assert.assertEquals() takes two arguments, much like check-expect, and checks that the two values are equal.
7 Observer that the second argument to Assert.assertEquals() is new Integer(45) and not 45. JUnit gets confused when it needs to deal with int and Integer types. [3]

You are free to organize your tests in any manner that you see fit. The typical pattern is to have a test method for each method in the class that is under testing.

Javadoc

Java provides a special tool that is responsible for reading your source code and generating HTML formatted documentation from your source codes comments. The tool is called Javadoc.

All of the Java libraries use Javadoc for their documentation.

    /** (1)
     * Gets the person's first name.
     *
     * @return the first name of the person (2)
     */
    public String getFirst() {
        return first;
    }
1 To start a Javadoc comment we must use /\**, notice the two stars!
2 Javadoc provides special annotations for

We can also use HTML tags inside our Javadoc comments.

Diagrams

As part of designing our data for a given problem we will be using a notation for drawing diagrams. The graphical notation that we will use is based on UML. [4]

We use diagrams before we write any code. Diagramming helps developers discuss their ideas on how they intend to capture information from as data. Recall the class Person, here is the class diagram for Person

person

To denote a class we use a rectangular shape with three sections, each section separated by a horizontal line:

  1. The first section holds the class name Person. The green circle with the character C in it is not necessary.

  2. The second section lists all fields and their types

    • we use - for private fields

    • we use + for public fields

  3. The third section lists all the methods and their signatures.[5]

    • we use - for private methods

    • we use + for public methods

Notice that in the class Person all fields are of a standard Java types String and Integer.

Class references and containment

As you already have seen in CS5001, data can be a lot more complex. Recall from Racket that we can have nested structures (ignore recursively nested structures for now).

Let’s consider the case of an online book store that would like to represent books. The online bookstore would like to store books and information about books, specifically

  • the title of the book

  • the author of the book (you may assume there is only one author)

  • the price of the book in dollars (you may assume it is always a rounded dollar amount)

The title of the book is essentially a string. The author of a book has a first and a last name. The price of the book is a rounded dollar amount, e.g., 5 (not 5.00 or 5.55).

Here one way that we can capture this information as data in Racket

(define-struct person (first second))
;; A Person is (make-person String String)
;; INTERP: represents a person with their first, and second name



(define-struct book (title author price))
;; A Book is (make-book String Person PosInt)
;; INTERP: represents a book with its title, author and price in
;;         dollars.


;;;; Examples

(define MILAN (make-person "Milan" "Kundera"))
(define ECO (make-person "Umberto" "Eco"))

(define ULB (make-book "The Unbearable Lightness of Being"
                       MILAN
                       9))

(define NOR (make-book "In the Name of the Rose"
                       ECO
                       10))

So let’s use a class diagram to figure out how we are going to capture these information about books for the online bookstore in Java using classes

bookstore

Observe how the arrow points from Book to Person to show that there is a containment relationship, i.e., a book contains an author. These are the same arrows we use to draw on top of Data Definitions in Racket. We typically draw the containment arrows for classes that are not provided by the Java language.

Going from the UML class diagram to code should be straightforward.

IDEs and tools can take UML class diagrams and generate code but also take Java code and generate UML class diagrams. DO NOT USE THESE TOOLS. The point of this course is to teach you how to design and code your solutions. Once you master the topics of the course then you can rely on automated tools.

Here is the code for Person and Book in Java. Each definition is written in a separate file.

/**
 * Represents an author in the online bookstore
 */
public class Person {

    private String first;
    private String second;

    /**
     * Creates a new person with the given first and last name.
     *
     * @param first the person's first name
     * @param second the person's last name
     */
    public Person(String first, String second) {
        this.first = first;
        this.second = second;
    }

    /**
     * Getter for property 'first'.
     *
     * @return Value for property 'first'.
     */
    public String getFirst() {
        return first;
    }

    /**
     * Getter for property 'second'.
     *
     * @return Value for property 'second'.
     */
    public String getSecond() {
        return second;
    }
}
/**
 * Represent a book in the online bookstore
 */
public class Book {

    private String title;
    private Person author;
    private Integer price;

    /**
     * Creates a new Book with the given title, author and price.
     *
     * @param title the book's title
     * @param author the book's author
     * @param price the book's price
     */
    public Book(String title, Person author, Integer price) {
        this.title = title;
        this.author = author;
        this.price = price;
    }

    /**
     * Getter for property 'title'.
     *
     * @return Value for property 'title'.
     */
    public String getTitle() {
        return title;
    }

    /**
     * Getter for property 'author'.
     *
     * @return Value for property 'author'.
     */
    public Person getAuthor() {
        return author;
    }

    /**
     * Getter for property 'price'.
     *
     * @return Value for property 'price'.
     */
    public Integer getPrice() {
        return price;
    }
}

Designing Classes

  1. Read the problem and identify pieces of information that are relevant for the task at hand. These will probably end up being fields in your classes.

  2. Draw a class diagram. You can do this with pen and paper or use a tool if you prefer. Critic your class diagram and ensure that you are capturing all the information relevant to the problem.

    • You have capture what is needed, nothing more nothing less. "The truth, the whole truth and nothing but the truth".

    • The structure of your data is dictated by the structure/relationships of the information as used in the problem.

  3. Translate your class diagram to Java code.

  4. Make examples and tests.

You have seen similar steps before. These are your new steps for the first element of the Design Recipe, Data Design and Data Definitions. With Java your Data Definitions are now code.


1. There are other testing libraries for Java, like TestNG, but for this course we will be using JUnit.
2. In Java equality is not as straigh forward as in Racket. We will cover equality in detail in the following weeks
3. Java introduced an automatic mechanism to turn int values to Integer values and vice versa called autoboxing. Unfortunately it does not always know which values to automatically alter from a primitive to a reference type. More on autoboxing later in the semester.
4. A good brief guide to UML is the book UML Distilled. You can also read this quick introduction to UML class diagrams.
5. Signatures in Java mean the method header that consists of the return type, the name of the method and the list of arguments as pairs of type and name separated by commas and enclosed in normal parenthesis.