slanted W3C logo

Day 11 — Object Creation 2

In today's lecture we look at how to create an object.

Constructors

A class that allows a client to create objects will usually provide one or more constructors that allows the client to create an object with the desired state.

A constructor looks similar to a method but there are several important differences. First, the name of a constructor is the same as the name of the class. The statement

Fraction f = new Fraction(5, 3);

uses the constructor from the Fraction class to create a Fraction object with a numerator of 5 and a denominator of 3.

The constructor is responsible for initializing the state of the object based on the arguments provided by the client.

Constructors

Second, a constructor does not return a value (not even void). If you look in the API you will see that the constructor declarations look like:

public Fraction()
public Fraction(long numerator, long denominator)

Constructors

Third, as a client, you always use new with a constructor, and you never include the class name or an object reference in front of the constructor.

// good
Fraction f = new Fraction(5, 3);

// good
f.multiply(new Fraction(7, 6));

// legal, but useless?
new Fraction(1, 10);

// error; missing new
Fraction e1 = Fraction(5, 3);

// error; do not use class name before constructor
Fraction e2 = new Fraction.Fraction(5, 3);

// error; do not use object reference before constructor
Fraction e3 = new f.Fraction(5, 3);

// ok, package name before constructor is allowed
Fraction k = new type.lib.Fraction(5, 3);

The Default Constructor

Some classes define a constructor with no parameters; such a constructor is called a default constructor. The purpose of a default constructor is to allow a client to create an object without having to specify its initial state; instead, an object will be created with a default state (as defined by the implementer of the class).

The statement

Fraction f = new Fraction();

creates a Fraction object with a numerator of 0 and a denominator of 1 (look in the API), and assigns the value of the memory address of the object to the reference f.

==

== is Java's equality operator.

import java.io.PrintStream;

public class EqualityExample1
{
   public static void main(String[] args)
   {
      PrintStream output = System.out;
      int x = 0;
      int y = 1 / 2;
      boolean eq = x == y;
      output.printf("x and y have the same value : %b%n", eq);
   }
}

x == y compares the values of x and y.

==

== behaves the same with primitive types and reference types; it compares the values of the variables. The states of the objects that the variables refer to do not affect the result of the comparison.

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

public class EqualityExample2
{
   public static void main(String[] args)
   {
      PrintStream output = System.out;
      
      Fraction f = new Fraction(3, 5);
      Fraction g = new Fraction(11, 13);
      Fraction h = f;
      Fraction i = new Fraction(3, 5);

      output.printf("f and g have the same value : %b%n", f == g);
      output.printf("g and h have the same value : %b%n", g == h);
      output.printf("f and h have the same value : %b%n", f == h);
      output.printf("f and i have the same value : %b%n", f == i);
   }
}

The above example prints:

f and g have the same value : false
g and h have the same value : false
f and h have the same value : true
f and i have the same value : false

equals

If you want the state of the objects to factor into the comparison, you must use the equals method. Every object in Java has a method named equals, but you must read the API to discover how equality is determined.

The Fraction class API says that two Fraction object are equal if their numerators and denominators are the same (in reduced form).

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

public class EqualsExample1
{
   public static void main(String[] args)
   {
      PrintStream output = System.out;
      
      Fraction f = new Fraction(3, 5);
      Fraction g = new Fraction(11, 13);
      Fraction h = f;
      Fraction i = new Fraction(6, 10);

      boolean fEqualsG = f.equals(g);
      boolean gEqualsH = g.equals(h);

      output.printf("f equals g : %b%n", fEqualsG);
      output.printf("g equals h : %b%n", gEqualsH);
      output.printf("f equals h : %b%n", f.equals(h));
      output.printf("f equals i : %b%n", f.equals(i));
   }
}

The above example prints:

f equals g : false
g equals h : false
f equals h : true
f equals i : true

toString

Every object in Java has a method named toString. The purpose of the toString method is to provide a textual description of the object.

You must read the API to discover exactly what the textual representation of the object is, but often it will be related to the state of the object.

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

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

      output.print("f is : ");
      output.println(f.toString());
      output.printf("f is : %s%n", f.toString());

      output.print("g is : ");
      output.println(g);
      output.printf("g is : %s%n", g);
   }
}

The above example prints:

f is : 3/5
f is : 3/5
g is : 11/13
g is : 11/13

Notice that the various print-like methods of PrintStream do not require the client to manually call toString.

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 (more precisely, the state of the object does not change as far as the client can tell).

A Fraction object has a numerator and a denominator. The client can ask for the values of the numerator and denominator 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();

      output.printf("numer == %d and denom == %d%n", numer, denom);
   }
}

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);
   }
}

You should look at the Fraction API and decide if the methods add, divide, multiply, pow, and subtract are mutators.

Self Check

  1. Try to work through Lab 4 in the textbook (page 162—168).