slanted W3C logo

Day 10 — Recap and Miscellaneous

In today's lecture we review the concepts of reference equality, object equality, accessors, and mutators. We also look at the consequences of badly designed class, and exactly what static means for attributes of a class.

Accessor Methods

Accessor methods let the client ask for information about the object's state. An important feature of an accessor method is that invoking it does not change the state of the object.

A Fraction object has a numerator and a denominator. The client can ask for the values of the numerator, denominator, and separator using accessor methods.

import java.io.PrintStream;
import type.lib.Fraction;

public class AccessorExample1
{
   public static void main(String[] args)
   {
      PrintStream output = System.out;
      
      Fraction f = new Fraction(3, 5);
      
      long numer = f.getNumerator();
      long denom = f.getDenominator();
      char sep = f.getSeparator();

      output.printf("numer: %d   denom: %d   sep: %c%n",
                    numer, denom, sep);
   }
}

The name of an accessor often starts with get, but not every method with a name that starts with get is an accessor.

If the accessor returns a boolean value then its name usually starts with is.

Mutators

Some objects allow the client to change the state of the object. Methods that change the state of the object are called mutators.

The Fraction class allows the client to change the values of the numerator and denominator.

import java.io.PrintStream;
import type.lib.Fraction;

public class MutatorExample1
{
   public static void main(String[] args)
   {
      PrintStream output = System.out;
      
      Fraction f = new Fraction(3, 5);

      output.printf("f is %s%n", f);

      f.setNumerator(17);
      output.printf("f is now %s%n", f);

      f.setDenominator(11);
      output.printf("f is now %s%n", f);

      f.setFraction(4, 9);
      output.printf("f is now %s%n", f);
   }
}

Mutator Contracts

Allowing a client to change the state of an object has serious implications because of the possibility that the client may (unknowingly or maliciously) put the object into an invalid state. Examples (all for illustration puposes only):

The contract of a mutator method states what happens when a mutator method is invoked.

Contract with Precondition

A mutator method contract with a precondition places all responsibility for invalid arguments on the client (as you already know).

If the client passes an invalid argument to such a method then anything can happen.

No Java Standard Library method uses this approach.

Contract with boolean Return

A mutator method contract can indicate that the method returns a boolean value to indicate if the method invocation was successful.

Consider the type.lib.Fraction method setSeparator:

public boolean setSeparator(char newSeparator)
A mutator of the separator of this fraction. The separator must not be a letter or a digit.

Parameters:
newSeparator - the new separator character.

Returns:
true if the change was made (i.e. if the passed parameter is neither a letter nor a digit), and return false otherwise.

The client is responsible for checking the return value to ensure that the method was invoked successfully.

Contract with Exception

A mutator method contract can indicate that the method throws an exception if the client passes an invalid argument to the method.

The class java.lang.StringBuilder is very similar to java.lang.String except that StringBuilder has mutator methods and String does not. Consider its method setLength:

public void setLength(int newLength)
Sets the length of the character sequence...
The newLength argument must be greater than or equal to 0.

Parameters:
newLength - the new length.

Throws:
IndexOutOfBoundsException if the newLength argument is negative.

There is no way for a client to ignore an exception.

Recap: Accessors and Mutators

An accessor is a method that lets the client read the value of an attribute.

A mutator is a method that lets the client write the value of an attribute.

By providing accessor and mutator methods, the class implementer can hide all of the attributes from the client (make them private). From the client's point of view, this is a good thing because the client can use the class without knowing the details of how it is implemented (as long as the method contracts are obeyed).

Recap: Reference vs. Object Equality

Suppose you have two reference variables:

Fraction f;
Fraction g;
/*
   Some code that assigns values to f and g...
*/

Then

boolean sameObject = (f == g);

is true if and only f and g refer to the same object.

boolean differentButSimilarObject = f.equals(g);

is true if and only if the state of the objects referred to by f and g are the same (as defined by the implementer of the class).

Quiz 1

What does the following code fragment output?

Fraction f = new Fraction(1, 2);
Fraction g = new Fraction(3, 4);

output.println(f.getNumerator());
output.println(f == g);
output.println(f.equals(g));

Quiz 2

What line of code would you need to add to:

Fraction f = new Fraction(1, 2);
Fraction g = new Fraction(3, 4);

// your line of code here

output.println(f.getNumerator());
output.println(f == g);
output.println(f.equals(g));

to produce the following output?

3
true
true

Quiz 3

What line of code would you need to add to:

Fraction f = new Fraction(1, 2);
Fraction g = new Fraction(3, 4);

// your line of code here

output.println(f.getNumerator());
output.println(f == g);
output.println(f.equals(g));

to produce the following output?

3
false
true

Quiz 4

What line of code would you need to add to:

Fraction f = new Fraction(1, 2);
Fraction g = new Fraction(3, 4);

// your line of code here

output.println(f.getNumerator());
output.println(f == g);
output.println(f.equals(g));

to produce the following output?

1
false
true

Quiz 5

What line of code would you need to add to:

Fraction f = new Fraction(1, 2);
Fraction g = new Fraction(3, 4);

// your line of code here

output.println(f.getNumerator());
output.println(f == g);
output.println(f.equals(g));

to produce the following output?

6
false
false

The Consequences of public Attributes

public char separator

A character that separates the numerator denominator pair in the return of the toString() method. The default value is '/'.

import type.lib.Fraction;

public class SeparatorExample1
{
   public static void main(String[] args)
   {
      Fraction f = new Fraction();
      System.out.println(f.separator);
      System.out.println(f);

      f.separator = ':';
      System.out.println(f.separator);
      System.out.println(f);
   }
}

The Consequences of public Attributes

Because the attribute is public, the client can change the attribute value to something inappropriate (i.e. the client can put the object into an invalid state).

import type.lib.Fraction;

public class SeparatorExample2
{
   public static void main(String[] args)
   {
      Fraction f = new Fraction();
      f.separator = '0';
      System.out.println(f.separator);
      System.out.println(f);
   }
}

The Consequences of public Attributes

The Fraction class was intentionally implemented with a public attribute to illustrate the fact that public attributes allow a client to easily put an object into an invalid state.

If the client had used the setSeparator mutator, the Fraction object could have prevented itself from being put into an invalid state.

import type.lib.Fraction;

public class SeparatorExample3
{
   public static void main(String[] args)
   {
      Fraction f = new Fraction();
      boolean separatorOK = f.setSeparator('0');

      System.out.println(separatorOK);
      System.out.println(f.separator);
      System.out.println(f);
   }
}

The advantage of forcing the client to use a mutator method is that the method has a contract whereas an attribute can never have a contract.

static Attributes

If a class has a static attribute, then the class is responsible for storing the attribute (i.e. no object has its own version of the attribute).

public static boolean isQuoted

A flag that determines if the return of the toProperString() method is surrounded by quotes or not. The default value is true.

import java.io.PrintStream;
import type.lib.Fraction;

public class StaticExample1
{
   public static void main(String[] args)
   {
      PrintStream output = System.out;
      
      Fraction f = new Fraction(25, 8);
      Fraction g = new Fraction(100, 1);

      output.println(Fraction.isQuoted);
      output.println(f.toProperString());
      output.println(g.toProperString());
      output.println();

      Fraction.isQuoted = false;
      output.println(Fraction.isQuoted);
      output.println(f.toProperString());
      output.println(g.toProperString());
   }
}

static Attributes Memory Diagram

Notice that the class stores the static attribute isQuoted whereas the object stores the values for the numerator and denominator.

0
 
  ¦
  main
 
f ⇒ 100 600
f ⇒ 120 700
 
  ¦
500 Fraction class
numerator ⇒ 504
denominator ⇒ 520
isQuoted ⇒ 524 true
 
  ¦
600 Fraction object
numerator ⇒ 604 25
denominator ⇒ 620 8
 
  ¦
700 Fraction object
numerator ⇒ 704 100
denominator ⇒ 720 1
 

When the client sets the value of isQuoted to false the value stored at address 524 is changed to false.

See the discussion in Section 4.3.3 of the textbook.