03
-
3.0 Classes (Revisited)
- The difference between classes and objects can be confusing, however, it is simple. A class is the code the programmer creates and an object is what is created when the computer executes the code for the class, i.e. an object is a particular instance of a class. As an analogy, think of the relationship between a blueprint and a car.
- A blueprint is a template that holds all the details for creating a car, much like a class is a template that holds all the details (code) for creating an object. However, until the car has actually been constructed it does not exist; all that exists is the blueprint for creating the car. This is the same relationship between classes and objects, a class can be thought of as a template that holds all the information for an object, but until the code has been run (instantiated) the object does not exist. The object only comes into existence when the program runs the class and creates an object.
- Objects can be thought of as something of interest to a system/application. This allows developers to programmatically solve problems using a coding method focused on objects and their relationships. Objects in many cases represent “things” which exist in the real world. For example, a program that allows customers to buy books online will use objects representing customers, books and orders. A word processor program will deal with objects representing words and paragraphs. A computer game may deal with objects representing characters and scenes.
- Consequently, objects are characterised by states and behaviours. States are attributes (data) we know about the object, behaviours are procedures (methods) which can be performed by the object. For example:
- A door is an object.
- “Open” and “closed” are states.
- “Open door” and “close door” are behaviours.
- Objects can have a number of different types of relationship (association) with other objects. An OO application involves objects invoking methods of other objects to query/modify attributes.
- Example method types:
- Constructors; Getters (Accessors); Setters (Mutators);
- Other e.g. toString, add/remove to/from collection field
-
3.1 Inheritance
- Inheritance is when an object, or class, is based on another object. It allows reuse of code facilitated by creating specialised subclasses which add fields and/or methods and inherit features of their superclass, leading to class hierarchies and libraries. Reuse recognises that duplication of code involves unnecessary effort and complexity. Inheritance identifies classes which share common states and behaviours but differ in some aspects of state and/or behaviour e.g. specialised instances add state and behaviour. A superclass identifies commonality while subclasses add state and/or behaviour. A subclass has only one direct superclass while a superclass may have many subclasses.
- Since subclasses can also have subclasses a class hierarchy is formed. Often the top of the hierarchy is an abstract class which is a class which cannot be instantiated but identifies the commonality of all its subclasses. In Java the "Object" class is at the top of the hierarchy. A subclass can inherit state (fields) and behaviour (methods) from its superclass and may override (redefine) methods. If a method cannot be inherited, then it cannot be overridden. A subclass within the same package as the instance's superclass can override any superclass method that is not declared private or final. A subclass in a different package can only override the non-final methods declared public or protected.
- All Classes in the Java Platform are Descendants of Object
- Note above the equals() and toString() methods which we can override in class definitions. Also note the finalize() method which is used in garbage collection when an object instance can no longer be accessed by code and therefore its memory is reclaimed
-
3.2 Examples:
- Insurance App - Architecture
- Recall the CarInsuranceApp will simply create an instance of the controller class and request its run() method. The PolicyController class has a constructor which requests filename and type from the user and calls a load() method to construct the model i.e. a Policy object. The PolicyController run() method will display policy details and a menu allowing the user to select add or remove a named driver and private methods will implement the add and delete functionality.
- Example: Appointments App
- An application is required which will load a day’s appointment data from a specified file (either text or object) - customer name, appointment time and length. A menu should be displayed offering options to add and cancel an appointment. An extension to the application will handle promotion appointments where a promo code will earn a discount. The system will contain a collection of appointments which will be implemented using an ArrayList:
-
public class Appointment implements Serializable { private String customerName; private LocalTime appointmentTime; private int appointmentLength;
- Note the use of the Java class LocalTime to represent a time object. The class implements the Serializable interface to allow us to store an Appointment object directly into an object file and load it back into memory.
- As before we will use a controller object to implement the application functionality. The application class creates a controller object and requests its "run()" method.
-
public static void main(String[] args) { AppointmentApp appointmentApp = new AppointmentApp(); appointmentApp.run(); } public static void run() { AppointmentController appointmentController = new AppointmentController(); appointmentController.run(); }
- The controller object which will:
- load appointment data from a file (text/object)
- display appointment data
- display a menu and accept user choice
- add or cancel an appointment as required
- store appointment data to a file (text/object)
-
public class AppointmentController { private ArrayList<Appointment> appointments;
- The appointment app will handle a list of appointments for a specified date. An ArrayList is a satisfactory collection choice to hold those appointment objects. The controller constructor method will ask the user to specify the date and whether a new file needs to be created or an existing one loaded, which will do carried out by loading text files or objects.
-
public AppointmentController() { appointments = new ArrayList<>(); InputHelper inputHelper = new InputHelper(); String appointmentDate = inputHelper.readString("Enter Appointment Date (YYYYMMDD"); String fileName = "appointments" + appointmentDate; char c = inputHelper.readCharacter("Load existing File (Y/N)?"); if (c == 'Y') { char c1 = inputHelper.readCharacter("Load Text File (T) or Object File (O)?"); if (c1 == 'T') { fileName += ".txt"; loadAppointmentsFromTextFile(fileName); } else if (c1== 'O') { fileName += ".dat"; loadAppointmentsFromObjectFile(fileName); } }
- Example text file:
- "Lynn Kelly","10:00",30
- "Ciara O'Donnell","11:15",45
- "Louise Kelly","13:15",60,
- Note each appointment is stored on a separate line
- Loading the file involves:
- repetitively reading lines
- splitting the line into an array of Strings
- constructing an "Appointment" object
- adding the "Appointment" object to the ArrayList
- Loading from text file will be implemented in the same way as before using a delimited line-oriented approach with "split()" and "stripQuotes()" methods:
-
private void loadAppointmentsFromTextFile(String fileName) { char DELIMITER=','; try (BufferedReader br = new BufferedReader(new FileReader(fileName))) { String[] temp; String line = br.readLine(); while(line!=null){ temp=line.split(Character.toString(DELIMITER)); customerName = stripQuotes(temp[0]); localTimeStr = stripQuotes(temp[1]); appointmentTime = LocalTime.parse(localTimeStr); appointmentLength = Integer.parseInt(temp[2]); Appointment appointment = new Appointment(customerName, appointmentTime, appointmentLength); appointments.add(appointment); line = br.readLine(); } br.close();
- Loading from an object file is implemented in the same way as previously, i.e. the class must implement the Serializable interface; fortunately the ArrayList class also implements the Serializable interface:
-
public void loadAppointmentsFromObjectFile(String filename) { FileInputStream fin = new FileInputStream(filename); try (ObjectInputStream ois = new ObjectInputStream(fin)) { appointments = (ArrayList<Appointment>) ois.readObject(); } }
- The store methods are essentially the same as before. As the "ArrayList" and "Appointment" classes implement the "Serializable" interface we can simply write out the appointments attribute to the object file. However, the text file stores each appointment object in a new line so we require a for loop to store each appointment object in the "ArrayList" in turn:
-
try (PrintWriter output = new PrintWriter(fileName)) { for (Appointment appointment:appointments) { output.print(appointment.toString(DELIMITER)); } output.close(); }
Playlist App – Class Diagram Insurance App – Class Diagram : Appointment App – Class Diagram AppointmentApp Class:
-
3.3 Promo Appointments
- Let’s now consider the situation where some appointments utilise a promo code to gain a discount. A "PromoAppointment" will still have the same attributes as previously but will add attributes for the promo code and the discount i.e. a "PromoAppointment" is a specialized form of an "Appointment" adding state and, consequently, behaviour. To implement this we can create a "PromoAppointment" class which inherits from the "Appointment" class i.e. a subclass. This will inherit specified states and behaviours from its superclass and define the new attributes and methods to handle them, i.e. constructors, getters, setters, toString().
-
public class PromoAppointment extends Appointment implements Serializable { private String promoCode; private int discountPercentage;
- The extends specifier indicates the inheritance from the Appointment class. Note this class also has to implement the Serializable interface in order to be stored directly in an object file. Note we are required to specify the state and behaviour which the superclass – Appointment – will allow to be inherited. We use the protected access modifier instead of private where the subclass is to interpret that state and behaviour.
-
public class Appointment implements Serializable { protected String customerName; protected LocalTime appointmentTime; protected int appointmentLength;
- Constructors are required to create "PromoAppointment" objects when they are required. These constructors can make use of their superclass’s constructors using the "super()" method call:
-
public PromoAppointment() { super(); this.promoCode = null; this.discountPercentage = 0; } public PromoAppointment( String customerName, LocalTime appointmentTime, int appointmentLength, String promoCode, int discountPercentage) { super(customerName, appointmentTime, appointmentLength); this.promoCode = promoCode; this.discountPercentage = discountPercentage; }
- We require getters and setters for each of the new attributes e.g.
-
public String getPromoCode() { return this.promoCode; } public void setPromoCode(String promoCode) { this.promoCode = promoCode; }
- We also need a customised version of the toString() method. Again this can use the toString() method of its parent class using super.toString():
-
@Override public String toString() { return super.toString() + "Code: " + this.promoCode + " Discount: " + Integer.toString(this.discountPercentage) + "\n"; }
- We also need a version of the delimited toString() method for storing in a text file. Since a PromoAppointment class inherits attributes from the Appointment class then a PromoAppointment object will have five attributes:
-
@Override public String toString(char delimiter) { final char EOLN='\n'; final String QUOTE="\""; String str = QUOTE + this.customerName + QUOTE + delimiter + QUOTE + this.appointmentTime + QUOTE + delimiter + Integer.toString(this.appointmentLength) + delimiter + QUOTE + this.promoCode + QUOTE + delimiter + Integer.toString(this.discountPercentage) + EOLN; return str; }
- Let’s revisit the application and, in particular, the "AppointmentController" class which implements the functionality The appointments attribute does not need to change as a "PromoAppointment" object is still an "Appointment" object and thus can be stored in the ArrayList. Note the methods which require amending are:
- loadAppointmentsFromTextFile(), addAppointment()
- While the other methods do not require any change since a "PromoAppointment" is still an "Appointment":
- AppointmentController()
- storeAppointmentsToTextFile()
- loadAppointmentsFromObjectFile()
- storeAppointmentsToObjectFile()
- cancelAppointment()
- These all deal with appointments as whole objects and don’t need to distinguish between an "Appointment" object and a "PromoAppointment" object.
-
3.4 Appointments Text File Revisited
- "Lynn Kelly","10:00",30
- "Ciara O'Donnell","11:15",45,"Student",10
- "Louise Kelly","13:15",60,"Unemployed",5
- Note the latter two appointments are promo appointments and have 5 values while the first line represents an appointment with 3 values. Hence we can distinguish between the types of appointment by checking the number of delimited values in the line.
-
String[] temp; String line = br.readLine(); while(line!=null){ temp=line.split(Character.toString(DELIMITER)); customerName = stripQuotes(temp[0]); localTimeStr = stripQuotes(temp[1]); appointmentTime = LocalTime.parse(localTimeStr); appointmentLength = Integer.parseInt(temp[2]); if (temp.length==5){ // Promo … } else { … } line = br.readLine(); } if (temp.length==5){ // Promo String promoCode = stripQuotes(temp[3]); int discountPercentage = Integer.parseInt(temp[4]); PromoAppointment appointment = new PromoAppointment(customerName,appointmentTime, appointmentLength, promoCode, discountPercentage); appointments.add(appointment); } else { Appointment appointment = new Appointment(customerName, appointmentTime, appointmentLength); appointments.add(appointment); }
- All public and protected members of a parent class are inherited. A subclass can add fields and methods and can override methods of a parent by declaring a method with the same signature. Their constructor can use a parent constructor using super and a subclass can access private members of a parent through a public or protected method exists for accessing the private field. The instanceOf operator can be used to test the class of an object.
-
3.5 Access Modifiers & Class Variables and Methods
- Pure O-O enforces data encapsulation to ensure fields/methods are only available in the place where they are declared. Java is not a pure O-O languages and allows different levels of access for different elements of a class or different types of classes:
- public:
- A class is by default available to all other classes in the same package
- Using a public modifier make the class accessible to all classes in the application and should be avoided
- public methods are available to be executed on objects by the application/other objects
- private:
- Used to enforce information hiding/data encapsulation
- Fields are generally specified as private
- Methods may be specified as private if they are internal to a class
- protected:
- Used to specify allowable access to methods within the class and methods from a subclass declared in another package
- Access Levels
- A class definition defines the fields and methods of all its object instances. Sometimes you require a field/method which belongs to the class rather than a specific instance. A class variable/static field defines a field which is accessible and updateable by all object instances, it is Identified by the class name followed by the field name. A class method is a static method called using the class name rather an object variable. Constant definitions are static fields with an additional final specifier:
-
static final MAX_ITEMS = 10;
-
public class Bicycle { private int gear; private int speed; private static int numberOfBicycles = 0;
- The class variable is identified as: Bicycle.numberOfBicycles
-
public static int getNumberOfBicycles() { return numberOfBicycles; }
- Called using the class name: int noBikes = Bicycle.getNumberOfBicycles();
Modifier Class Package Subclass World public Y Y Y Y protected Y Y Y N no modifier Y Y N N private Y N N N