slanted W3C logo

Day 09 — Using Objects

Fraction Constructors

Fraction()
Construct a default fraction with numerator equal to 0, denominator equal to 1, and separator equal to '/'.

Fraction(long numerator, long denominator)
Construct a fraction with the passed numerator and denominator and a '/' separator.

Fraction(Fraction fraction)
Construct a copy of the passed Fraction.

Fraction(long numerator, long denominator, char separator)
Construct a fraction with the passed numerator, denominator, and separator.

The Copy Constructor

Fraction(Fraction fraction)
Construct a copy of the passed Fraction.

Some Java classes have a copy constructor that lets the client create a new object with the same state as another object. You can recognize a copy constructor by the fact that it has only one parameter of the same type as the class it is defined in.

      Fraction f = new Fraction(1, 2);

      // create a new object using copy constructor
      Fraction fcopy = new Fraction(f);

Memory Diagram

0
 
  ¦
  main
 
f ⇒ 100 600
fcopy ⇒ 104 640
  ¦
500 Fraction class
numerator ⇒ 504
denominator ⇒ 520
 
  ¦
600 Fraction object
numerator ⇒ 604 1
denominator ⇒ 620 2
  ¦
640 Fraction object
numerator ⇒ 644 1
denominator ⇒ 660 2

Constructor with Separator char

Fraction(long numerator, long denominator, char separator)
Construct a fraction with the passed numerator, denominator, and separator.

Another attribute of a Fraction object is the separator character used for printing. The code

Fraction f = new Fraction(1, 2, ':');
System.out.println(f);

will print:

1:2

Destroying an Object

When an object is no longer needed it should be destroyed so that the virtual machine can recover whatever memory the object was using.

Java does not provide a mechanism for explicitly destroying objects; thus, the client is not normally concerned about destroying objects that are no longer needed.

Instead, Java reclaims memory occupied by unused objects automatically via garbage collection.

Unused Objects

How does the garbage collector know when an object is no longer needed? When there are no reference variables referring to the object.

// create a Fraction object and refer to it
Fraction f = new Fraction(1, 2);

// create another Fraction object and refer to it
f = new Fraction(3, 4);

Notice that two Fraction objects have been created, but only the second is referenced. The first Fraction object is now eligible to be garbage collected.

null

In some cases, the client may want to indicate that a reference variable refers to nothing. The keyword null is used in such cases.

// create a Fraction and refer to it
Fraction f = new Fraction(1, 2);

/*
  Some wonderful code here that never
  causes a new reference to the object
  f refers to...
*/

f = null;

By assigning null to f the original Fraction object can now be garbage collected.

If you try to use a null reference you will get a runtime error.

==

== is Java's equality operator. It returns a value of type boolean.

There are only two boolean values: true and false.

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.

!=

The operator != is the not-equal-to operator. It returns a value of type boolean.

import java.io.PrintStream;

public class EqualityExample2
{
   public static void main(String[] args)
   {
      PrintStream output = System.out;
      int x = 0;
      int y = 1;
      boolean eq = x != y;
      output.printf("x and y have different values : %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 EqualityExample3
{
   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 same value? : %b%n", f == g);
      output.printf("g and h same value? : %b%n", g == h);
      output.printf("f and h same value? : %b%n", f == h);
      output.printf("f and i 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

not equals

It is often more natural to check if the state of two objects are different or not equal.

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

// are states not equal?
boolean notEqual = !f.equals(g);

System.out.print("fractions are different: ");
System.out.println(notEqual);

The operator ! inverts the value of a boolean.

Equality

If you want to compare two primitive values for equality then you must use == or !=.

If you want to compare the state of two objects for equality then you must use equals.

If you want to check if two reference variables refer to the same object then you must use == or !=.

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 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.

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

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