Lecture 1: Data Definitions, Classes and Testing in Java
Design of simple classes and classes that contain objects in another class.
Overview
Data vs. Information, and the need for structured data
Simple data
Simple classes: Person
Containments: Book and Person
Examples and Testing with JUnit
Visual representation: class diagrams
Do Now!
A Do Now! exercise is strongly recommended for you to do now, without reading ahead in the lecture notes. You should think through the question being asked, and then see if your understanding matches the concepts the lecture notes suggest.
Exercise
An Exercise is strongly recommended for you to do, now or when finished reading these notes. These exercises may suggest further applications of the concepts in the lecture notes.
1.1 Representing information by structured data
Information is everywhere: “it is 75 degrees Farenheit outside”, “Providence, RI is 65 miles away from Boston, MA”, “the book How to Design Programs was written by Matthias Felleisen”, etc. As humans, we can read those sentences and make sense of them: we understand the information they are trying to convey, and can use that information in other settings. But how can we cause a computer to do the same?
Computers do not work with information: they work with data, which is a way to represent information that makes it “convenient” to use.
1.2 Types of data
Numbers (e.g. 2, -10, 3.1415, etc.)
Booleans (e.g. true and false)
Strings (e.g. "CS 5004", "This is a great course")
Images (e.g. photographs)
Data composed of other data (e.g. book with name, author and publisher)
Unions (i.e. one of several forms of data)
1.2.1 Data in Python
When we represent data in Python, we do not include any information about what type of data is being represented. Often we include a purpose statement (i.e. a comment) to explain what information a given datum was supposed to represent. For example we define a variable to represent temperature as follows:
# A number representing the temperature in degrees Farenheit outside_temp = 75
Although the comment clarifies for us what outside_temp should represent (a number), there is no way to enforce it. Thus a variable could represent anything and it wouldn’t be a problem, until we attempted to use it in an invalid way.
1.2.2 Data in Java
When we declare an identifier in Java, we must specify the type of the data that it will represent. Java uses this to detect these type errors even before we run our program.
Booleans
boolean isDaytime = true; boolean inNewYork = false;
Numbers
int ten = 10;
double pointSix = 0.6; float pointFour = 0.4f;
Characters
char letterA = 'a'; char uppercaseA = 'A';
char singleQuote = '\'';
Strings
String courseName = "CS 5004";
1.3 Compound Data
The above types are useful if the information to be represented is a single unit. What if the information to be presented consists of several components? For example, consider the representation of a person. A person has a first name, a last name and a year of birth.
1.3.1 Python: Take 1
One way to represent it in Python is to use a dictionary. In order to facilitate creating new persons, we write a function that creates and returns such a dictionary
#create a dictionary that represents a person def createPerson(firstName,lastName,yearOfBirth): """ This function takes in the various attributes for a person and returns a dictionary with keys for first name, last name and year of birth with their respective values equal to whatever was passed to this function. """ record = {'firstName':firstName, 'lastName':lastName, 'yearOfBirth':yearOfBirth} return record
We can then use it to create a person, as follows:
#create an instance that represents a person named John Doe john = createPerson("John", "Doe", 1945)
We can write functions to extract different attributes of a person. Each of these functions accept a person (which is a dictionary object) and return its relevant attribute.
#def getFirstName(person): """ function that returns the first name of a person """ return person['firstName'] def getLastName(person): """ function that returns the last name of a person """ return person['lastName'] def getYearOfBirth(person): """ function that returns the year of birth of a person """ return person['yearOfBirth']
A unit test can be used to illustrate how this design will be used. We can use Python’s unittest module for this purpose.
import unittest class PersonTests(unittest.TestCase): def setUp(self): #create an instance that represents a person named John Doe self.john = createPerson("John", "Doe", 1945) #create an instance of elvis presley self.elvis = createPerson("Elvis", "Presley", 1935) def test_firstname(self): self.assertEquals("John", getFirstName(self.john)) self.assertEquals("Elvis", getFirstName(self.elvis)) def test_lastname(self): self.assertEquals("Doe", getLastName(self.john)) self.assertEquals("Presley", getLastName(self.elvis)) def test_yearofbirth(self): self.assertEquals(1945, getYearOfBirth(self.john)) self.assertEquals(1935, getYearOfBirth(self.elvis))
1.3.2 Python: Take 2
In the above design, the data and functions were separate from each other (note how the functions are called in the test by passing the data on which they operate). As an alternative, we can combine the data and related functions into one entity that represents a person. In this design, what we call a person contains both information as well as operations. In Python we can represent this using classes:
class Person: # a structure that represents a person """ This class represents a person. A person has the following attributes: first name, last name, year of birth. """ def __init__(self,firstName,lastName,yearOfBirth): #to instantiate a person self.firstName = firstName self.lastName = lastName self.yearOfBirth = yearOfBirth def getFirstName(self): """ function for a person to return their first name """ return self.firstName def getLastName(self): """ function for a person to return their last name """ return self.lastName def getYearOfBirth(self): """ function for a person to return its year of birth """ return self.yearOfBirth
Specifically we create a class Person. The attributes of a person are stored as attributes of this class, rather than contents in a dictionary. Its __init__ function is used to construct person objects. The class also contains functions that return the values of its various attributes.
Using this design is illustrated with tests as follows:
import unittest class OOPersonTests(unittest.TestCase): def setUp(self): #create an instance that represents a person named John Doe self.john = Person("John", "Doe", 1945) #create an instance of elvis presley self.elvis = Person("Elvis", "Presley", 1935) def test_firstname(self): self.assertEquals("John", self.john.getFirstName()) self.assertEquals("Elvis", self.elvis.getFirstName()) def test_lastname(self): self.assertEquals("Doe", self.john.getLastName()) self.assertEquals("Presley", self.elvis.getLastName()) def test_yearofbirth(self): self.assertEquals(1945, self.john.getYearOfBirth()) self.assertEquals(1935, self.elvis.getYearOfBirth())
1.3.3 Difference in Design
Both designs above achieve the same outcomes: representation of a person and getting the values of its attributes. But they model the problem in fundamentally different ways. Specifically note how we get the first name of a person. In the first design we do it as getFirstName(self.john). This can be characterized as “Function getFirstName, take this data self.john and return ITS first name”. In the second design, we do it as self.john.getFirstName(). This can be characterized as “Object self.john, get YOUR first name”.
The first design is functional in nature. In this type of design the data and the functions that operate on them are two separate entities in a program. The second design is object-oriented. In this type of design, the data is combined with the functions operating on it in a single unit. This unit gets ownership of its operations (note the its vs. your in the above characterization). In the example above, we first create a class, which can be thought of as a template for this single unit (i.e. the Person class is a template for a person). We then create an instance of this class (called an object), that represents a specific person.
1.4 Java
Similar to Design 2 above we describe compound data in Java using a class.
/** * This class represents a person The person has a first name, last name and an * year of birth */ class Person { String firstName; String lastName; int yearOfBirth; Person(String firstName, String lastName, int yearOfBirth) { this.firstName = firstName; this.lastName = lastName; this.yearOfBirth = yearOfBirth; } String getFirstName() { return this.firstName; } String getLastName() { return this.lastName; } public int getYearOfBirth() { return this.yearOfBirth; } }
This class contains two things.
We declare explicitly the fields of our classes with their types. They represent the attributes of a person.
We define constructors for the classes, similar to Python. The constructor must have the same name as that of the class, followed by a parenthesized list of argument types and names that (for now) mimic exactly the field definitions, then a block (enclosed in curly braces) of initialization statements. The job of the constructor is to initialize the fields to the provided values. Since we chose to give the arguments the same names as the fields of the object, we use the this keyword to disambiguate. yearOfBirth refers to the argument passed to the constructor and this.yearOfBirth refers to the yearOfBirth field of this object.
Java calls functions “methods”. The Person defines methods to get the values of its various attributes.
Conventions:
- Naming: class names in Java always are written in TitleCase, and field names are always written in camelCase. Primitive type names, like int and boolean, are lowercase.
Do Now!
Why do you think String is capitalized? Comments: a multi-line comment can be enclosed between /* and */ as shown. A single-line comment starts with //
If we name the class Person then it is saved in a file Person.java. This allows us to divide our code in meaningful chunks, although one-class-per-file is not strictly enforced.
1.5 Testing in Java
We will use a Java-based framework called JUnit to test our Person class.
import static org.junit.Assert.assertEquals; import org.junit.Before; import org.junit.Test; /** * A JUnit test class for the Person class */ public class PersonTest { private Person john; private Person elvis; @Before public void setUp() { john = new Person("John", "Doe", 1945); elvis = new Person("Elvis", "Presley", 1935); } @Test public void testFirst() { assertEquals("John", john.getFirstName()); assertEquals("Elvis", elvis.getFirstName()); } @Test public void testSecond() { assertEquals("Doe", john.getLastName()); assertEquals("Presley", elvis.getLastName()); } @Test public void testYearOfBirth() { assertEquals(1945, john.getYearOfBirth()); assertEquals(1935, elvis.getYearOfBirth()); } }
We see that all the tests are enclosed with a PersonTest class. Each test becomes a method within this class.
The above class contains several new features:
The import statements at the top makes it more convenient to use some JUnit methods, such as assertEquals.
The class declares fields that will be used in creating objects and testing them.
The setUp method creates an example Person object.
Each test method is identified with the @Test annotation. This tells JUnit which methods are tests and which ones are merely helpers.
Each test method does not return anything. This simply means that this method completes a task comprised of a sequence of operations, and the result of these operations is implicit in these steps (i.e. the result is not explicitly returned by it).
Each test method uses an assertEquals method. The assertEquals method is one of several methods provided by JUnit for verification (all of them begin with assert). Every meaningful test must have at least one such assert method.
This JUnit test class executes in the following manner:
The method annotated with @Before is executed before each method annotated with @Test.
By default, a method annotated with @Test passes. If the expected and actual values of an assertEquals method are equal to each other then the program moves on to the next statement in the method. If they are not equal, the method aborts and the test is said to fail.
Irrespective of the result of a test method (pass or fail) it moves on to the next test method, until all the test methods are run exactly once.
1.6 private and public: What are they?
Given a Java object obj its field somefield can be directly accessed as obj.somefield. This also applies to methods defined inside it. Indeed the method testFirst() can be rewritten as:
@Test public void testFirst() { assertEquals("John", john.firstName); assertEquals("Elvis", elvis.firstName); }
When we have many classes and objects, allowing one object direct access to another object’s fields like this creates problems and confusion. Java allows us to restrict access to a field or a method of an object. This is done through access modifiers. If a field of a class is denoted as private then that field can be directly accessed only by the methods of that class, nothing else. Similarly if a method of a class is denoted as private then that method can be called only from other methods within that class. At the other extreme, making a field (method) public makes it accessible (callable) using an object of this class from any method of any other class.
Think of an ATM. It allows you to withdraw cash, deposit checks and check balance. There are parts of it that you can access and use (e.g. the keypad on the ATM). However there are parts that you cannot access (e.g. the mechanical parts that dispense notes), and with good reason! In this way the bank can rest assured that no customer has unauthorized access to the machine, while you can use the machine without ever knowing how it works. This is exactly what we must accomplish when we design objects: expose only necessary functionality and hide others. This is called information hiding.
Make all fields private.
Make all constructors public.
Make a method public only if you can think of at least one situation where the method must be called from outside this class. Else make it private
Applying these rules to the Person class creates the following:
/** * This class represents a person The person has a first name, last name and an * year of birth */ class Person { private String firstName; private String lastName; private int yearOfBirth; public Person(String firstName, String lastName, int yearOfBirth) { this.firstName = firstName; this.lastName = lastName; this.yearOfBirth = yearOfBirth; } public String getFirstName() { return this.firstName; } public String getLastName() { return this.lastName; } public int getYearOfBirth() { return this.yearOfBirth; } }
Do Now!
Try to justify the use of each public and private in the PersonTest class
1.7 Commenting and Documentation
It is a really good idea to write comments explaining your design and purpose. This allows you and anybody other using your code to understand what it is doing, how to use it and how it has been designed and implemented.
It is a good idea to abide by the following conventions:
Above the class definition, explain in 1-2 sentences what this class represents. This explanation should include both semantic details (e.g. what the class represents from the problem statement) and technical details (e.g. useful for a fellow designer/programmer). Also mention any details that a user of this class may need to know to use it appropriately.
Before each method (including the constructor and getters) write the purpose statement for the method. Also include a list of any arguments along with what they represent, and what the method returns.
Within the method body mention any details that you think are relevant to what that method is doing.
A good rule of thumb is to assume that the audience for your comments is not you, but other designers/programmers who will use your code. If the language is such that only you can understand it fully (because you implemented it) revise the comments.
1.7.1 Documentation
Documentation is critical to making code understandable, readable and usable. As programmers we are both producers and consumers of documentation. Any good code base comes with copious and helpful documentation. For example “official” Java documentation is available online. You can read about the String class that we used above here.
Java comes with an in-built tool that helps you generate such html web pages from documentation written in your Java code. This tool is called Javadoc. This tool “reads” your comments and converts them into html. You can use specific formatting commands within your comments to facilitate generating pleasing documentation. For example here is the Person class with Javadoc-style comments.
/** * This class represents a person The person has a first * name, last name and a year of birth */ public class Person { private String firstName; private String lastName; private int yearOfBirth; /** * Constructs a Person object and initializes it * to the given first name, last name and year of birth * @param firstName the first name of this person * @param lastName the last name of this person * @param yearOfBirth the year of birth of this person */ public Person(String firstName, String lastName, int yearOfBirth) { this.firstName = firstName; this.lastName = lastName; this.yearOfBirth = yearOfBirth; } /** * Get the first name of this person * @return the first name of this person */ public String getFirstName() { return this.firstName; } /** * Return the last name of this person * @return the last name of this person */ public String getLastName() { return this.lastName; } /** * Return the year of birth of this person * @return the year of birth of this person */ public int getYearOfBirth() { return this.yearOfBirth; } }
All comments beginning with /** are Javadoc-style comments. These are the only comments that the Javadoc tool will read.
Every line of such comments begins with * followed by a space.
@param denotes information about a method argument.
@return denotes information about whatever the method returns.
Note again that since everything is within Java comments, this documentation does not affect your Java source code in any way. We will see how to use Javadoc to produce html documentation from this file in the lab.
We strongly recommend developing the habit of writing comments in Javadoc style to facilitate creating neat documentation.
Note that Javadoc-style documentation is “public-facing”. That is, this documentation is meant for others, not just yourself.
1.8 Authors and Books
Let us now represent a book. The book has a title, a single author and a price. Since the author is a person, we can use the Person class for this purpose.
/** * This class represents a book. A book has a title, an * author and a price. */ public class Book { /** The title of this Book. */ private String title; /** The author for this Book. */ private Person author; /** The price for this Book. */ private float price; /** * Construct a Book object that has the * provided title, author and price * * @param title the title to be given to this book * @param author the author to be given to this book * @param price the price to be assigned to this book */ public Book(String title, Person author, float price) { this.title = title; this.author = author; this.price = price; } /** * Return the title of this book * @return the title of this book */ public String getTitle() { return this.title; } /** * Return the price of this book * @return the price of this book */ public float getPrice() { return this.price; } /** * Return the author of this object * @return the author of this object as a @link{Person} */ public Person getAuthor() { return this.author; } }
The book has an author: this relationship is reflected above as the Book class has a Person object within it. This can be visually represented as follows:
This is called a class diagram, and is very useful in creating and understanding design visually. + denotes public, - denotes private, and the arrow denotes the has-a relationship.
Exercise
Write a JUnit class that creates examples of books and tests whether its fields have the expected values.