OOP 7. Private data - TimeOfDay

Purpose of this lesson: New Java language features Good Practices

Bulletproofing with private, getters, and setters

To make reliable software, it's essential to protect the data in an object - insure that it is initialized, and prevent illegal or inconsistent values. The key to this is to declare the fields private.

Private data reduces complexity by preventing unintended coupling -- references to fields inside your class from other classes.

Bad Practice - Excessive Coupling

Risk. Public fields put the integrity of objects at great risk. Nothing prevents the following bad statements from being executed if the fields are public.

TimeOfDay3 goodTime = new TimeOfDay2a(11, 11);  // Legal values.
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 fields, public methods to get/set them

Private data. The key to making classes safer and easier to change is to declare fields private. Constructors and methods in the same class can use the field variables, but no one outside the class can see them. However, if they're private, something must be done to allow getting and setting the values. This is done with so called getter and setter methods.

Getters and Setters

Naming. If it's appropriate for the user of a class to get or set field values, the convention is to write public getter and setter methods. The Java convention is to begin such method name with "get" or "set" followed by the attribute that should be obtained or set.

Final parameters. A good case can be made for declaring setter parameters final. See Code Advice #12: Use final on constructor and setter parameters.

Getters and setters can be evil. Don't automatically write getter and setter methods for every private instance variable.

It seems awkward to write these, and some languages and IDEs 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.

Alternative to getters/setters

A principle of OOP is that you should ask an object to perform a service for you, not just give you data. If your class is a simple "value" class -- a class which just represents a simple value, it may be appropriate to have getters and setters. As a class becomes more complex, it's often more appropriate to supply methods which do the necessary computation rather than letting the user get the values and do the computation themselves.

Instance, not static, methods

Getter and setter methods are instance, not static, methods. Instance methods can reference instance variables, but static methods can not. Instance variables can only be referenced if there is an object (instance) that holds them, of course.

A note about field names - a _convention

When you read long methods it isn't always obvious whether a variable is a parameter, local variable, field (instance variable), or class (static) variable. Because instance variables have such a distinct meaning, many programmers find it useful to prefix field names with something special.

Variations on the instance variable name "hour"

_hour   Common, although it violates some Java style conventions. I use it frequently.
myHour   Less common, but you will see it sometimes.
hour   Instance variable looks like other variables. Common, but unhelpful.
m_hour   A C++ convention for "member" variables. Common in Java too.
mHour   Another common convention originating in C++.
fHour   The "f" denotes "field" variable.

Controversial. Like brace conventions, field prefixes are controversial. I highly recommend naming instance variables differently than local variables, but when you collaborate with others on a project there will often be a naming convention which you must use, or your instructor may insist on one style. Therefore be flexible on this topic.

Naming other kinds of variables. Some programmers use naming conventions for parameters (eg, start parameter names with "p") or class (static) variables (eg, start names with "c"). Because parameters are essentially local variables which occur in only a limited span of code, there isn't as much interest in distinguishing them. However, class variables are seriously different, and it's good practice to name them differently. Because they're relatively rare, there hasn't been as much controversy or consensus on naming conventions.

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 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
// File   : oop/timeofday/TimeOfDay3.java
// Purpose: A 24 hour time-of-day class to demo intro OOP concepts.
// Author : Fred Swartz - 2006-09-18 - Placed in the public domain.
// Issues : Makes variables private, adds getters and setters.
//          Can be improved by calling setters from constructor.

public class TimeOfDay3 {
    //========================================= instance variables
    private int _hour;
    private int _minute;

    //================================================ constructor
    public TimeOfDay3(int h, int m) {
        //... Check values for validity.
        if (h < 0 || h > 23 || m < 0 || m > 59) {
            throw new IllegalArgumentException(
                "TimeOfDay: Bad constructor value: " + h + ":" + m);
        }
        _hour   = h;
        _minute = m;
    }

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

    //================================================== setHour
    public void setHour(int h) {
        if (h < 0 || h > 23) {
            throw new IllegalArgumentException(
                "TimeOfDay setHour: Bad hour value: " + h);
        }
        _hour = h;
    }

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

    //================================================ setMinute
    public void setMinute(int m) {
        if (m < 0 || m > 59) {
            throw new IllegalArgumentException(
                "TimeOfDay setMinute: Bad minute value: " + m);
        }
        _minute = m;
    }
}

See one of the problems below for a way to improve this class.

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/TimeTest3.java
// Purpose: Test the TimeOfDay3 class..
// Author : Fred Swartz - 2006-09-18 - Placed in public domain.

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._Hour = 99;   // Can't reference private field.
    }
}

Terminology note

Java uses the terms getter and setter, but you may also see the C++ terms accessor and mutator.

Programming exercises

  1. Improvement. This class definition above is OK, but it could be improved by calling the setter methods from the constructor so that code isn't repeated. This code violates the DRY (Don't Repeat Yourself) principle. Rewrite the constructor with this improvement.
  2. 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).