Classes and Objects

So far, we've been working with classes (such as the String class), but we've not designed any of our own. Before we do, it's important to try and understand the difference between a Class and an Object

Classes

A class is in someways like a blueprint, it defines how something should be, and how it should work, and potentially how the thing should be put together. The following 3 attributes of a class can be defined by these requirements:

Properties
How the thing should be
Methods
How the thing should work
Constructors
How the thing should be 'set up'
We will investigate these in more detail shortly

Objects

An object is also known as an 'instance of a class'. Essentially it is like the finished article that has been built from a blueprint.

Some context

We have worked with the String class so far, a string class has the three attributes defined above:

Example class - Odometer

Lets say we wanted to represent an odometer on a car. In its simplest form it should be able to tell us how far the car has travelled. We would therefore need some form of property to keep track of that value (for example a float). Not only that, but we would need to be able to update the odometer with a value that represented the distance travelled since it was last updated - this would be a method.

We would start by creating a class - in IntelliJ this can be done on the menu by choosing File - New - Java Class. The filename and class name should be the same: in this case 'Odometer', and we will place it in the same folder as our Main class (so it goes in the same package). IntelliJ will give us the following syntax:

public class Odometer {

}

In order to keep track of the distance travelled we will need some sort of variable. Clearly this value is not one we want somebody to be able to change (other than to increase in value in line with the car's actual mileage), so we will mark this with the private keyword as follows

class Odometer {
    private double totalMilesTravelled;    
}
The totalMilesTravelled variable unique to every odometer (otherwise all cars would show the same mileage). For this reason we would refer to it as an instance variable (ivar for short). Another term used to describe the same thing is a member variable.

Next we need to provide a way to access this value. Convention in Java dictates that we create a method which is prefixed get, to access a property, so in this case it will be getTotalMilesTravelled(). All this method will do is return the private value totalMilesTravelled, as follows:

public double getTotalMilesTravelled(){
    return totalMilesTravelled;
}

Our class is getting there, but there's still no way to update the totalMilesTravelled, and we can't risk making that variable public. The solution is to write a method which will take a distance travelled as follows:

public void addMileage(double miles){
    totalMilesTravelled += miles;
}

Finally, we want to create a way to construct the odometer. This basically allows us to the set the value of any properties in the class when an instance of it is created. A 'constructor' may or may not take parameters, and we can have multiple constructors to handle multiple setup options. For our odometer, the default constructor would set the totalMilesTravelled to 0 (this would be used in most cases when the Odometer is installed in a new car). The code for a constructor is a method which has the same name as the class - in this case it would appear as follows:

public Odometer(){
    totalMilesTravelled = 0;
}
We could add an additional constructor which would be used if the Odometer was a replacement one, and needed to specify a start mileage as follows:
public Odometer(double startMileage){
    totalMilesTravelled = startMileage;
}

Full class code

Our complete class would appear as follows:

package uk.ac.chester;
public class Odometer {
    
    private double totalMilesTravelled;

    public double getTotalMilesTravelled(){
        return totalMilesTravelled;
    }
    public Odometer(){
        totalMilesTravelled = 0;
    }
    public Odometer(double startMileage){
        totalMilesTravelled = startMileage;
    }
    
    public void addMileage(double miles){
        totalMilesTravelled += miles;
    }    
}

Creating an object (an instance of the class)

Just as a reminder, we create an instance of a class in a similar way to the other objects we've created. The following code creates a new instance of odometer, registers some miles travelled, retrieves and prints the total mileage, registered another value of miles travelled and retrives and prints the total again.

Odometer odo = new Odometer();
odo.addMileage(123.4);
System.out.print("Mileage so far: ");
System.out.println(odo.getTotalMilesTravelled()); //123.4
odo.addMileage(56.5);
System.out.print("Mileage now: ");
System.out.println(odo.getTotalMilesTravelled()); //179.9

Accessor methods and properties

We have already created one accessor method - getTotalMilesTravelled, however the Odometer class doesn't have any variables that we would set the value of directly, so we have not had to write a method to 'set' a property. You already know that the convention is to prefix the name of the method that gets a value with get, and so methods that set a property value start with set. Consider the simplified class below to represent an Office

public class Office {
    private String location;
    private String occupant;
    
    public Office(String officeLocation) {
        //office must exist so cannot be created without a location
        location = officeLocation;
    }
    
    public void setOccupant (String newOccupant){
        occupant = newOccupant;
    }
    
    public String getOccupant(){
        return occupant;
    }
    
    public String getLocation(){
        return location;
    }
}
The value of the occupant variable would be retrieved with the getOccupant() method, and set with the setOccupant() method. These methods are commonly referred to as 'getter and setters'.

Note also how the name of the occupant is named newOccupant - this is to avoid it clashing with the name of the variable. (An alternative is to use the keyword this to refer to the variable (e.g. this.occupant) - in this case the parameter can have the same name)

The combination of a private variable, coupled with public accessor methods, means that we have created a property

Accessors (getters) for boolean values

One final thing to know about accessors is that the 'getter' for boolean accessors typically uses the prefix has or is to make code more readable. For example, in the above Office class if we had a telephone property the accessor would likely be hasTelephone(), whereas if we had an airConditioned property, the getter would be isAirConditioned(). In both cases, the 'setter' would continue to use the set prefix, e.g. setAirConditioned(true)

Avoding public variables and making use of encapsulation

You may be wondering why, when we want to read and write to an instance variable, we don't just make the variable public (given the effort that is required to create getters and setters). There are a couple of key reasons for this. Firstly, because some variables will only be read or written to publically, we have to acknowldge the need for getter and setters. If we were to use a combination of public variables and getters and setters, we would end up with some variables being set directly, and some using a method, and needing to keep track of each. This makes things more difficult for the developer.

The second reason comes down to the concept of encapsulation. Encapsulation says that "Details not pertinent to the use of the object should be hidden from all other objects" (Weisfeld, 2013, p.21)Weisfeld, M. 2013. The Object-Oriented Thought Process (4th ed.). Upper Saddle River, NJ: Addison-Wesley, so in the Odometer example, the ability to manually change the mileage, is restricted, for good reason - we don't want our car to be 'clocked'! In more complex classes, ensuring that varables are correctly configured as private, get-only, set-only, or read/write is very important, to prevent 'accidental damage' to objects.

Task - Odometer

  1. Create a version of the odometer class in a new command line application, using the code above.
  2. In the main method create a new instance of the odometer class, and call the addMileage method twice (for example with values of 2.25 and 17.75), verifying that it works (total miles should be 20.0)
  3. Add additional functionality to the Odometer class so that it can also act as a trip computer (this provides the driver with an additional display of miles travelled which can be reset by the driver at will)
  4. Add a series of unit tests, to test both sets of functionality - the odometer and the trip computer

Task - meeting

  1. In a new Command line application, Create a class to represent a meeting, called 'Meeting'
  2. The class should be able to store data relating to: Location, Start Date, Start Time, Duration and notes relating to the meeting. You should determine the data types to be used for each of these properties. You may need to carryout some further research.
  3. Add functionality to the class that will:
    1. extend a meeting by a given duration
    2. reschedule the meeting to take place after a specified number of days
  4. Write unit tests to ensure that your meeting class behaves as expected

Advanced Task

Calendar events (such as meetings) are typically more complex, in fact there is a specifcation (iCalendar) which determines the features of an text-based interchangeable file format.

  1. Carry out research to determine the minimum requirements for an iCalendar (.ics) file.
  2. Create a class to store this data
  3. Add a method to the class which will return the relevant information as a String in the .ics format
  4. Modify the programme (but not the Calendar class however) so the String output by the Calendar class text output can be saved to a text file with the extension .ics
  5. Import that text file into a calendar programme (such as outlook) and check it appears in the calendar

Very advanced task

Create a constructor for the calendar class, which will take the text from a .ics file.