On this page:
Overview
1.1 Representing information by structured data
1.2 Types of data
1.2.1 Data in Python
1.2.2 Data in Java
Booleans
Numbers
Characters
Strings
1.3 Compound Data
1.3.1 Python:   Take 1
1.3.2 Python:   Take 2
1.3.3 Difference in Design
1.4 Java
1.5 Testing in Java
1.6 private and public:   What are they?
1.7 Commenting and Documentation
1.7.1 Documentation
1.8 Authors and Books
7.7

Lecture 1: Data Definitions, Classes and Testing in Java

Design of simple classes and classes that contain objects in another class.

Related files:
  person.py     OOperson.py     Person.java     Book.java     PersonTest.java  

Overview

Note: throughout these lecture notes, you’ll see the following:

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

Data may come in several forms:
  • 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

In Java, Boolean values are written true and false, and the name of the type is boolean.
boolean isDaytime = true;
boolean inNewYork = false;

Numbers

Java has several types for numbers. Integral numbers are written as follows:
int ten = 10;
There are several other types for integral numbers like byte, short and long that different from int only in the range of numbers that they can represent.

Decimal numbers are written as follows:
double pointSix = 0.6;
float pointFour = 0.4f;
While both represent decimal numbers, double can store numbers in a bigger range and with greater precision than float. The “f” in “0.4f” is used to specify explicitly that we mean a “float” and not a “double”.

Characters

Java has a type char that can be used to represent single characters (letters, punctuation, special symbols, etc.).
char letterA = 'a';
char uppercaseA = 'A';
A single character is written in single quotes. But what if we wanted to represent the single-quote character itself? We use a special character ‘\’ to tell Java to take the character immediately following it literally.
char singleQuote = '\'';

Strings

Strings in Java have the String type.
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.

Conventions:

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:

This JUnit test class executes in the following manner:

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.

How does one decide whether to make something private or public? The following rules are useful and apply well in most cases:
  • 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:

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;
}
}

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.