Home Java Java-tips Oop OOP Tutorial [first draft]

Ask Questions?

View Latest Questions

Advertisement


 
 

OOP Tutorial [first draft]
Posted on: July 26, 2006 at 12:00 AM
Object-Oriented Design (OOD) and Object-Oriented Programming (OOP)

Java: OOP Tutorial [first draft]

Table of contents

  1. Introduction
  2. Student - Data
  3. Student - Constructor
  4. TimeOfDay - Data+Constructor
  5. TimeOfDay - Validity checking
  6. TimeOfDay - Bulletproofing
  7. TimeOfDay - toString

Some thoughts and plans for next draft

  • The last page is missing, which is a reimplementation of TimeOfDay using only time in seconds to show the complete independence of the implementation from the interface. I've delayed on this because I'm thinking of changing from TimeOfDay to Appointment.
  • Some topics should be moved out-of-line, eg, toString, constructor overloading, this, @Override, ...
  • A better example than TimeOfDay might be Appointment, which would have a hour, minute, and duration in minutes. A isOverlap method would be a good choice then, as well at a more motivated implementation of Comparable.

1. Introduction

Object-Oriented Design (OOD) and Object-Oriented Programming (OOP)

An important part of Object-Oriented Design is to represent "things", sometimes called business objects, in your problem by classes and objects of these classes. Classes in the design process are often described by their "behavior", "responsibilities", or "messages" they pass. These are all ways describing the methods that are defined in a class. The data that is stored in classes is usually not emphasized in the design process.

In contrast to this, Object-Oriented Programming is concerned with implementing classes - writing the code. At the programming level you must be concerned with the data as well as the methods that constitute a class.

These notes are about programming and Java language features necessary for OOP, and do not discuss much about design. Ultimately you will be more concerned with the methods than the data, but it is generally easier to understand classes by starting with data and working up to methods. These notes start with data and ultimately show how methods can be used to completely hide the data implementation.

Class containing only data

Classes are most often used to group a number of related, named data values. For example, if you are storing information about students, you need their name, id, etc. A class describes the names and types of the data. For example, the following is the proposed definition of a class for a student. Follow the tutorial and you'll see how this can be improved, but it shows some of the basic ideas. See 2. Data - Student for an introduction to defining the classes and creating objects from them.

public class Student1 {
    public String firstName;
    public String lastName;
    public int    id;
}

Class containing only methods

Some classes contain no data, only static methods work. Every application that you write has at least one static method in it, main. If you're not already familiar with these methods, see the Static Method Tutorial.

Class containing both data and methods = Object Oriented Programming

Grouping related data with methods lets you not only model the values associated with business objects, but also the their behavior.

A class is a type

Classes are usually given the name of a noun, and contain the data associated with that noun. Some predefined Java classes that you have used are String, which contains the characters in the string, JOptionPane, which contains the information necessary to produce a JOptionPane on the screen, etc. These system classes are about programming, but the classes you write will be mostly be about your problem domain where they represent business objects (Student, TimeOfDay, Order, ...).

The class which contains main is often an exception to the noun-naming convention, and is typically named after the problem you are trying to solve.

Problem domain classes

To solve your problem, you need to define classes to represent your data. Often, the data is not a simple primitive value. For example, if you are writing a program that does something with university information, you may define classes like Student, (class names start with an uppercase character), Course, etc. Each of these classes defines attributes, for example, the student name, etc.

Misc Notes

  • An earlier term, Abstract Data Type (ADT) has largely been replaced by class.
  • Not all classes are used to represent business objects. Some are used to serve as infrastructure to facilitate the programming process or solve other parts of the problem (eg, the GUI). The Java designers use the OO model extensively, which sometimes results in awkward constructions (eg, listeners) rather than adding language features.

2. Student - Data

Defining only data in a class

Let's define a class that represents information about a student. Typically there is one class definition per file, so this would be stored in Student1.java. There are going to be several iterations on this class,

// File   : oop/dataclass/Student1.java
// Purpose: Information about a student.

public class Student1 {
    public String firstName; // First name
    public String lastName;  // Last name
    public int    id;        // Student id
}

Use new to create a new object

This class definition tells which data a class can hold (three different values), and is a description of what must be created when the new operation is performed to create a new object.

Student1 tatiana;
tatiana = new Student1();   // Create Student1 object with new.

Default constructor creates object with default field values

To create a new object, write new followed by the name of the class (eg, Student1), followed by parentheses. Later we'll see how to define our own constructor with parameters.

Access public instance variables with dot notation

The instance variables (firstName, lastName, ..) name data which is stored in each object. Another term for instance variable is field. All public fields can be referenced using dot notation (later we'll see some better ways to set and access fields). To reference the field, write the object name, then a dot, then the field name.

Student1 tatiana;

//... Create new Student1 object with new.
tatiana = new Student1();
tatiana.firstName = "Tatiana";
tatiana.lastName  = "Johnson";
tatiana.id        = 9950842;

JOptionPane.showMessageDialog(null, "One student is named: "
     + tatiana.lastName + ", " + tatiana.firstName);

Awkward? If the above example seems like an awkward way to store information about a student, you're right. The intention of the above example is simply to show the mechanics of classes. We'll eventually get to examples that show real advantages.

The class definition is a template for creating objects

The class defines which fields an object has in it, but you need to use new to create a new object from the class by allocating memory and assigning a default value to each field. A little later you will learn how to write a constructor to control the initialization.

It's not very interesting to create only one object from a class, so the following example creates a couple of them.

  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 
//  File  : oop/dataclass/TestStudent1.java

import javax.swing.*;

public class TestStudent1 {
    public static void main(String[] args) {
        Student1 tatiana;
        Student1 pupil;

        //... Create new Student1 object with new.
        tatiana = new Student1();
        tatiana.firstName = "Tatiana";
        tatiana.lastName  = "Johnson";
        tatiana.id        = 9950842;

        //... Create another Student1 object.
        pupil = new Student1();
        pupil.firstName = JOptionPane.showInputDialog(null, "First name");
        pupil.lastName  = JOptionPane.showInputDialog(null, "Last name");
        pupil.id        = Integer.parseInt(JOptionPane.showInputDialog(null, "ID"));

        JOptionPane.showMessageDialog(null, "One student is named: "
             + tatiana.lastName + ", " + tatiana.firstName
             + "\n and another is named: "
             + pupil.lastName + ", " + pupil.firstName);
    }
}

3. Student - Constructor

Add a constructor for better initialization

Avoid bad initializations. One problem with the Student1 class is that the user has to explicitly initialize all of the fields. This requires some extra typing, but more importantly, it is error-prone. If we forget to initialize a field, the default value could have bad consequences.

Convenience. Defining a constructor makes creation of an object a little easier to write, altho this is not the main advantage.

Syntax. A constructor is similar to a method, it's called like a method and returns. But you don't specify a return value and the name must be the same as the class name. Here is the Student1 class rewritten with a constructor which does nothing, which is exactly the same as the default constructor which is automatically created for every class.

Student2 example with a constructor

The example below shows how to define a constructor, altho this one doesn't do any more than the default constructor that Java automatically generates.

// File   : oop/dataclass/Student2.java
// Purpose: Information about a student. Defines constructor.

public class Student2 {
    public String firstName; // First name
    public String lastName;  // Last name
    public int    id;        // Student id
    
    //======================================== constructor
    public Student2(String fn, String ln, int idnum) {
        firstName = fn;
        lastName  = ln;
        id        = idnum;
    }
}

Similar to method. A constructor is similar to a method: it is called, it has a body of Java statements, and it returns. The most notable differences are in the header, which does NOT have a return type, and the name, which must be the same as the class name.

Using the constructor

Here is the first program rewritten to use the above class definition with a constructor.

  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 
// File   : oop/dataclass/TestStudent2.java
// Purpose: Tests Student2 constructor.

import javax.swing.*;

public class TestStudent2 {
    public static void main(String[] args) {
        Student2 tatiana;
        Student2 pupil;

        //... Create new Student2 object with new.
        tatiana = new Student2("Tatiana", "Johnson", 9950842);

        //... Create another Student2 object.
        String first = JOptionPane.showInputDialog(null, "First name");
        String last  = JOptionPane.showInputDialog(null, "Last name");
        int    studID= Integer.parseInt(JOptionPane.showInputDialog(null, "ID"));
        pupil = new Student2(first, last, studID);

        JOptionPane.showMessageDialog(null, "One student is named: "
             + tatiana.lastName + ", " + tatiana.firstName
             + "\n and another is named: "
             + pupil.lastName + ", " + pupil.firstName);
    }
}

If a constructor is defined, no default constructor is created

if you define a constructor in a class, the Java compiler no longer defines the default constructor. All object creation therefore must use an explicitly defined constructor.

This means that the constructor can be sure all fields are defined with legal values. We're not going to extend the Student2 class any further to test for legal values, but we will in the next example.

For the moment we'll leave the Student class, and move to something different to show the same ideas.

4. TimeOfDay - Data + Constructor

TimeOfDay1 Example

Let's say that we want to represent a time of day necessary for recording when an appointment is scheduled. It won't represent the day, only the time as hour and minute. Here's a reasonable start using a 24 hour clock (00:00 to 23:59).

TimeOfDay1.java

  1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
// File   : oop/timeofday/TimeOfDay1.java
// Purpose: A 24 hour time-of-day class to demo intro OOP concepts.
// Author : Fred Swartz
// Date   : 2005-05-30
// Issues : No validity check.

public class TimeOfDay1 {
    public int hour;
    public int minute;
}

Test program

  1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
// File   : oop/timeofday/TimeTest1.java
// Purpose: Test the TimeOfDay1 class..
// Author : Fred Swartz
// Date   : 2005-05-04

import javax.swing.*;

public class TimeTest1 {
    public static void main(String[] args) {

        //... Create a new TimeOfDay object.
        TimeOfDay1 now = new TimeOfDay1();

        //... Set the fields.
        now.hour   = 10;
        now.minute = 32;

        //... Display it.
        JOptionPane.showMessageDialog(null, now.hour + ":" + now.minute);
    }
}

TimeOfDay1c.java - with constructor

  1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
// File   : oop/timeofday/TimeOfDay1c.java
// Purpose: A time-of-day class with constructor.
// Author : Fred Swartz
// Date   : 2005-05-04

public class TimeOfDay1c {
    public int hour;
    public int minute;

    //=================================== constructor
    public TimeOfDay1c(int h, int m) {
        hour   = h;
        minute = m;
    }
}

Test program using constructor

  1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
// File   : oop/timeofday/TimeTest1c.java
// Purpose: Test the TimeOfDay1c class..
// Author : Fred Swartz
// Date   : 2005-05-04

import javax.swing.*;

public class TimeTest1c {
    public static void main(String[] args) {

        //... Create a TimeOfDay1c object for 10:30 in the morning.
        TimeOfDay1c now = new TimeOfDay1c(10,30);

        //... Display it.
        JOptionPane.showMessageDialog(null, now.hour + ":" + now.minute);
    }
}

Overloading constructors

It's possible to overload constructors - define multiple constructors which differ in number or types of parameters. For example, exact hours are common, so an additional constructor could be defined which takes only the hour parameter.

. . .
    //=================================== constructor
    public TimeOfDay1c(int h) {
        hour   = h;
        minute = 0;   // Always set to zero.
    }

Questions

  1. What would happen if you called the following constructor?
    TimeOfDay1c dentistAppointment = new TimeOfDay1c();
  2. What would the value of the two fields be after this constructor call?
    TimeOfDay1c wakeup = new TimeOfDay1c(6);

Misc notes

  • Should discuss calling one constructor from another (this, first statement).

5. TimeOfDay - Validity Checking

A constructor can ensure good values

By defining a constructor, all TimeOfDay objects must be created with your constructor. This is good because it gives you total control over how the parameters are used, but most importantly it can prevent the creation of illegal TimeOfDay values.

What to do if there are errors. Errors can be grouped into two general classes:

  • User errors. If the user makes an error, you should tell them about what they did wrong, how to correct it, what you did about it, etc.
  • Programmer errors. On these you should be very unforgiving. Programmer errors should be discovered during testing, and should never make it into the final program. There are several ways to handle errors (see Error processing), but the simplest is to use the assert statement (see Assertions) to stop execution if an illegal situation is encountered.

Crash please. The TimeOfDay class is called by a programmer, not the user. If illegal values are passed to the constructor, it's because the programmer isn't doing their job, so the program should be stopped immediately.

The program below should crash because an illegal number of minutes is passed to the second call on the constructor. If you have assertion checking enabled, as you always should, the result should be an error message something like the following.

Exception in thread "main" java.lang.AssertionError: TimeOfDay: Bad minute 99
        at TimeOfDay2a.<init>(TimeOfDay2a.java:17)
        at TimeTest2a.main(TimeTest2a.java:13)

TimeOfDay class

  1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
// File   : oop/timeofday/TimeOfDay2a.java
// Purpose: A 24 hour time-of-day class to demo intro OOP concepts.
// Author : Fred Swartz
// Date   : 2005-05-30
// Issues : Validity checking with assert.

public class TimeOfDay2a {
    //========================================= instance variables
    public int hour;
    public int minute;

    //================================================ constructor
    public TimeOfDay2a(int h, int m) {
        //... Check values for validity.
        assert h >= 0 && h <= 23 : "TimeOfDay: Bad hour   " + h;
        assert m >= 0 && m <= 59 : "TimeOfDay: Bad minute " + m;

        hour   = h;
        minute = m;
    }
}

Test program source

The following program should not run. If assertions are turned on, then the second call to the constructor should produce an AssertionError.

  1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
// File   : oop/timeofday/TimeTest2a.java
// Purpose: Test the TimeOfDay2 class assertion check.
// Author : Fred Swartz
// Date   : 2005-04-30
// Issues : When you run this test, it should produce an error.

import javax.swing.*;

public class TimeTest2a {
    public static void main(String[] args) {

        TimeOfDay2a now   = new TimeOfDay2a(8 , 35); // OK
        TimeOfDay2a never = new TimeOfDay2a(14, 99); // ILLEGAL VALUE.

        JOptionPane.showMessageDialog(null,
                "You should never see this message."
                +"\nTurn assertions on when you run this program.");
    }
}

6. TimeOfDay - Bulletproofing with private, getters, and setters

Bad Practice - Excessive Coupling

Risk. Public instance variables put the integrity of objects at great risk. Nothing prevents the following bad statements from being executed.

TimeOfDay3 goodTime = new TimeOfDay3(11, 11, 11);
goodTime.minute = 99;  // AVOIDS CONSTRUCTOR VALIDITY CHECK

But who would do that? Of course no programmer would intentionally set the value to something illegal like this, but there could be several reasons to mistakenly set it. Here are two examples.

goodTime.minute = userValue;  // FAILED TO CHECK INPUT VALUE FIRST.
goodTime.minute++;            // WHAT HAPPENS AFTER 59?

Solution - Private instance variables, public methods to change them

Private. The key to making classes safer and easier to change is to declare instance variables private. Constructors and methods in the same class can use the variables, but no one outside the class can see them. However, if they're private, something must be done so that whoever is using the class can get and set the values. This is sometimes done with so called getter and setter methods.

Getters and Setters

Naming. It's common to write public methods that allow getting and setting instance variables. The Java convention is to begin such method names with "get" or "set" followed by the attribute that should be obtained or set..

Getters and setters can be evil. Don't simply write getter and setter methods for every private instance variable -- altho these can filter out bad values, they make your code inflexibly dependent on a particular implementation. Getter and setter methods do not even need to correspond to specific fields -- they provide a user interface to your class, but internally you are free to make changes. We'll make such a change to the TimeOfDay class shortly.

It seems awkward to write these, and some languages have made this either automatic or easier to do. There is controversy about how easy it should be go create getters and setters, because they shouldn't exist for all instance variables. For Java just accept that you will have to type these definitions. It's really only a couple of minutes of work.

A note about instance variable names - myConvention

When you start working with methods that have more code in them, you will find that it isn't always obvious whether a variable is a parameter, local variable, or instance variable. Because instance variables have such a distinct meaning, it is not uncommon to prefix their names with something special.

Variations on the instance variable name "hour"

    hour     Instance variable looks like other variables.  Common.
    m_hour   A C++ convention for "member" variables.
    mHour    Another C++ convention.
    fHour    The "f" denotes "field" variable.
    _hour    Common.  Legal altho it violates Java naming conventions.
    myHour   Less common, but I use it in many programs.

I highly recommend naming instance variables differently than local variables. When you collaborate with others on a project there will often be a naming convention which you must use. Therefore be flexible on this topic.

TimeOfDay3

Here's the TimeOfDay class, rewritten with private instance variables and getters and setters.

  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 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
// File   : oop/timeofday/TimeOfDay3.java
// Purpose: A 24 hour time-of-day class to demo intro OOP concepts.
// Author : Fred Swartz
// Date   : 2005-05-30
// Issues : Make variables private, rename them, add getters and setters

public class TimeOfDay3 {
    //========================================= instance variables
    private int myHour;
    private int myMinute;

    //================================================ constructor
    public TimeOfDay3(int h, int m) {
        //... Check values for validity.
        assert h >= 0 && h <= 23 : "TimeOfDay: Bad hour   " + h;
        assert m >= 0 && m <= 59 : "TimeOfDay: Bad minute " + m;

        myHour   = h;
        myMinute = m;
    }

    //================================================== getHour
    public int getHour() {
        return myHour;
    }

    //================================================== setHour
    public void setHour(int h) {
        assert h >= 0 && h <= 23 : "TimeOfDay: Bad hour   " + h;
        myHour = h;
    }

    //================================================ getMinute
    public int getMinute() {
        return myMinute;
    }

    //================================================ setMinute
    public void setMinute(int m) {
        assert m >= 0 && m <= 59 : "TimeOfDay: Bad minute " + m;
        myMinute = m;
    }
}

Test program

  1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
// File   : oop/timeofday/TimeTest3.java
// Purpose: Test the TimeOfDay3 class..
// Author : Fred Swartz
// Date   : 2005-05-30

import javax.swing.*;

public class TimeTest3 {
    public static void main(String[] args) {

        TimeOfDay3 then = new TimeOfDay3(8, 35);
        TimeOfDay3 now  = new TimeOfDay3(14, 5);

        //... Print the hours and minutes of the times.
        JOptionPane.showMessageDialog(null,
            "From " + then.getHour() + ":" + then.getMinute()
          + " to " + now.getHour() + ":" + now.getMinute());

        // THE FOLLOWING WOULD BE ILLEGAL
        // now.myHour = 99;   // Can't reference private field.
    }
}

Misc Notes

  • Java uses the terms getter / setter, but you may also see the terms accessor / mutator, which come from C++.

Programming exercises

  1. Make the Student class (see 3. Student - Constructor) robust with private instance variables, getters and setters, and checks for legal values (eg, name strings must not be null or the empty string and the id must be in a certain range).

7. toString - TimeOfDay

The toString instance method

Just as the constructor accesses the instance variables (fields), you can also write methods that use the fields. A common method to write is toString, which returns a string representation of an object. This is convenient for displaying the value of an object.

String concatenation of objects. If you try to convert an arbitrary object to a String, as when you concatenate anything with a String, Java calls the object's toString method. If you didn't define this method, then your class inherited toString from the Object class. Object's version of toString doesn't produce anything very useful -- it gives the name of the class and the hexadecimal location in memory, for example, "TimeOfDay@82ba41".

TimeOfDay2 with toString method

This class is the same as in the previous example, except that the toString method has been defined.

  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 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
// File   : oop/timeofday/TimeOfDay2.java
// Purpose: A 24 hour time-of-day class to demo intro OOP concepts.
// Author : Fred Swartz
// Date   : 2005-05-30
// Issues : Add toString method.

public class TimeOfDay2 {
    //========================================= instance variables
    private int myHour;
    private int myMinute;

    //================================================ constructor
    public TimeOfDay2(int h, int m) {
        //... Check values for validity.
        assert h >= 0 && h <= 23 : "TimeOfDay: Bad hour   " + h;
        assert m >= 0 && m <= 59 : "TimeOfDay: Bad minute " + m;

        myHour   = h;
        myMinute = m;
    }

    //================================================== getHour
    public int getHour() {
        return myHour;
    }

    //================================================== setHour
    public void setHour(int h) {
        assert h >= 0 && h <= 23 : "TimeOfDay: Bad hour   " + h;
        myHour = h;
    }

    //================================================ getMinute
    public int getMinute() {
        return myMinute;
    }

    //================================================ setMinute
    public void setMinute(int m) {
        assert m >= 0 && m <= 59 : "TimeOfDay: Bad minute " + m;
        myMinute = m;
    }

    //================================================= toString
    public String toString() {
        return myHour + ":" + myMinute;
    }
}

Test program using toString

Note how convenient the output is.

  1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
// File   : oop/timeofday/TimeTest2.java
// Purpose: Test the TimeOfDay2 class.
//          toString called automatically for concatenation.
// Author : Fred Swartz
// Date   : 2005-04-30

import javax.swing.*;

public class TimeTest2 {
    public static void main(String[] args) {

        TimeOfDay2 then = new TimeOfDay2(8, 35);
        TimeOfDay2 now  = new TimeOfDay2(14, 5);

        JOptionPane.showMessageDialog(null, "From " + then + " to " + now);
    }
}

Programming exercises

Enhance the TimeOfDay3 class by writing some of the following methods.

  1. Two digit times We normally expect to see time values expressed with two digits, but the version of toString above formats numbers less than 10 as single digits, eg 12:7 altho we would expect to see 12:07. Change toString to do this.
  2. isAM() Write a method, isAM, that returns a boolean true if the time is in the AM, and false in the PM. Assume that 12:00 is PM.
  3. to12HString(). Write a method, to12HString, that returns the time as a string in 12-hour form, appending AM or PM as appropriate. For example, for the time 22:37 would be returned as "10:37 AM". There are three cases:
    • Before 12 is AM.
    • Between 12 and 13 is PM, but written with 12.
    • 13 and after are PM with 12 subtracted from the hour.
  4. incHour() is a void method that adds one (increments) to the hour field by 1. If the hour goes over 23, it should wrap around to 0.
  5. incMinute() is a void method that adds one (increments) to the minute field by 1. If the minutes go over 59, the "carry" should go into the hour field (by calling on incHour naturally.
Advertisement


DMCA.com