taylorialcom/ ObjectOriented

User Defined Classes

Creating your own custom classes.

Introduction

Java is an object oriented (OO) programming language. The key idea behind object oriented programming is to identify objects that need to be represented in your program and then group all of the data associated with the object in a blob of bytes. In addition, we can define behavior that will operate on the the blob of data. This allows us to simplify our programs because all of the code associated with defining and managing the objects can be placed in a separate source file. Only the implementer(s) of the class need to worry about the details of how the objects are stored (what attributes are required) and manipulated (the implementation of methods defining desired behavior).

Object Free Messiness

Suppose we want a program that asks the user to enter two complex numbers and displays the result of multiplying the two complex numbers together. It might look something like this:

public static void main(String[] args) {
    Scanner in = new Scanner(System.in);

    // Get first number
    System.out.println("Enter a complex number in the form: 3.0 + 4.3i");
    // Read line of text entered by user
    String line = in.nextLine();
    // Create a scanner object of what the user entered, but don't include
    // the "i" at the end
    Scanner parser = new Scanner(line.substring(0, line.length()-1));
    // Convert the real part to a double
    double realOne = parser.nextDouble();
    // Read in and ignore the plus symbol
    parser.next();
    // Convert the imaginary part to a double
    double imagOne = parser.nextDouble();

    // Get second number
    System.out.println("Enter a complex number in the form: 3.0 + 4.3i");
    line = in.nextLine();
    parser = new Scanner(line.substring(0, line.length()-1));
    double realTwo = parser.nextDouble();
    parser.next();
    double imagTwo = parser.nextDouble();

    // Calculate result of multiplying two numbers
    double realAnswer = realOne * realTwo - imagOne * imagTwo;
    double imagAnswer = realOne * imagTwo + imagOne * realTwo;

    System.out.println("(" + realOne + " + " + imagOne + "i) * (" 
                       + realTwo + " + " + imagTwo + "i) = ("
                       + realAnswer + " + " + imagAnswer + "i)");
}

This produces a result that looks like this:

Enter a complex number in the form: 3.0 + 4.3i
1.0 + 3.0i
Enter a complex number in the form: 3.0 + 4.3i
2.0 + 2.0i
(1.0 + 3.0i) * (2.0 + 2.0i) = (-4.0 + 8.0i)

Object Oriented Clarity

This code above looks pretty complicated because our main method is required to manage all of the intricate details of complex numbers. The rest of this page will focus on developing a Complex class that will define all of the code specific to managing complex numbers. When completed, it will allow us to simplify our program so that it looks like:

public static void main(String[] args) {
    Scanner in = new Scanner(System.in);
  
    // Get first number
    System.out.println("Enter a complex number in the form: 3.0 + 4.3i");
    Complex c1 = new Complex(in.nextLine());
  
    // Get second number
    System.out.println("Enter a complex number in the form: 3.0 + 4.3i");
    Complex c2 = new Complex(in.nextLine());
  
    // Calculate result of multiplying two numbers
    Complex answer = c1.times(c2);
  
    System.out.println(c1 + " * " + c2 + " = " + answer);
}

In fact, the code is so much cleaner that we don't really need to include the comments. The intent of the code is so much clearer:

public static void main(String[] args) {
    Scanner in = new Scanner(System.in);
  
    System.out.println("Enter a complex number in the form: 3.0 + 4.3i");
    Complex c1 = new Complex(in.nextLine());
    System.out.println("Enter a complex number in the form: 3.0 + 4.3i");
    Complex c2 = new Complex(in.nextLine());
  
    System.out.println(c1 + " * " + c2 + " = " + c1.times(c2));
}

Defining Custom Classes

A user defined class should be in a separate file that has the same name as the name of the class (the following would be stored in a file called Complex.java). A class is defined as follows:

public class Complex {
    // Details of the class implementation go here
}

Object Attributes

We can declare attributes for each object from the class as follows:

public class Complex {
    private double real;
    private double imag;

    // ...
}

Whenever we create an object from the Complex class, the object will have two object variables: real and imag (both **double**s) that can be used to store the value of a complex number. If we were to create a Complex object like this:

public static void main(String[] args) {
    Complex c1 = new Complex();
}

here's what it would look like in memory:

Memory Footprint for a Complex Object and Reference

Constructors

Right now, we don't have a way to set the values of the object's attributes. One way to do this would be to implement a constructor that accepts real and imaginary values for the object that is about to be created. The implementation would take the values passed in and assign them to the object's attributes:

public class Complex {
    private double real;
    private double imag;

    public Complex(double r, double i) {
        real = r;
        imag = i;
    }
}
public static void main(String[] args) {
    Complex c1 = new Complex(2.0, 1.0);
    System.out.println(c1);
}

When the object is instantiated (created), space is reserved in memory to hold the object and then the constructor initializes the attributes of the object. Now the memory footprint for the instantiated object looks like this:

Memory Footprint for 2.0 + 1.0i

The output of the program is:

packageName.Complex@6ddf073d

where packageName is the name of the package containing the Complex class.

The toString() Method

We can customize the way the object displays itself by implementing the toString() method. The toString() method returns a string representation of the object:

public class Complex {
    private double real;
    private double imag;

    public Complex(double r, double i) {
        real = r;
      imag = i;
    }

    public String toString() {
        return "(" + real + " + " + imag + "i)";
    }
}

Now when we run the program, we see the following displayed:

(2.0 + 1.0i)

Adding Complex Numbers Together

Now that we can create Complex objects we can define other behaviors (or functionality) for objects from the class. For example, suppose we wanted to add two complex numbers together like this:

public static void main(String[] args) {
    Complex c1 = new Complex(2.0, 1.0);
    Complex c2 = new Complex(3.0, 8.0);
    Complex sum = c1.plus(c2);
    System.out.println(sum);
}

Here we intend for sum to contain the result of adding c1 and c2 together. We can define the plus() method in the Complex class. The method accepts a Complex object as a parameter.

public class Complex {
    // ...

    public Complex plus(Complex addend) {
        Complex sum = new Complex(real + addend.real, imag + addend.imag);
        return sum;
    }
}

In the previous paragraph, I said that the plus() method accepts a Complex object as a parameter. That's really a lie (an oversimplification). What it really accepts is a reference to a Complex object. Let's take a moment to visualize what is really going on in memory:

Memory Footprint for Adding Two Complex Objects
Figure 1: Memory Footprint for Adding Two Complex Objects

The three references c1, c2, and sum are accessible in main() and the first two complex numbers were instantiated from main() (all shown in black). The three references this, addend, and sum are accessible in the plus() method and the complex number storing the result of adding the other two numbers together was instantiated from the plus() method (all shown in red).

Java keyword this

In the above diagram the reference this magically appeared. The this keyword is available whenever you are in an object method and always points to the object that was used to call the method. In the example above, that was c1. Making use of the this keyword is often optional. For example, we could rewrite the plus() method as follows and it will function exactly the same way as before:

    public Complex plus(Complex addend) {
        return new Complex(this.real + addend.real, this.imag + addend.imag);
    }

If this is not present, the compiler first looks for a match to a local variable, and if none is present, it will then look for a match to an object attribute. We could mess up the original plus() method by creating a local variable called real.

    public Complex plus(Complex addend) {
        double real = 0.0;
        return new Complex(real + addend.real, imag + addend.imag);
    }

This is a problem because the real variable used when creating the Complex object will be the local variable (containing 0.0) instead of the value stored in the attribute of the object. By using this we can clarify that we mean the attribute of the object instead of the local variable. As a result, this implementation (although having a useless declaration of a local variable) will produce the desired result:

    public Complex plus(Complex addend) {
        double real = 0.0;
        return new Complex(this.real + addend.real, this.imag + addend.imag);
    }

Knowing this, we can improve the readability of our constructor by using more descriptive labels for the parameters accepted by the constructor (real instead of r and imag instead of i):

    public Complex(double real, double imag) {
        this.real = real;
        this.imag = imag;
    }

Multiple Constructors

There may be times when we'd like to take a real number and convert it into a Complex object. We could do that as follows:

double number = -2.0;
Complex c1 = new Complex(number, 0.0);

It might be nice to make it a little easier for users of our Complex class to do this. Instead of the above, we could simplify it slightly by allowing them to say:

double number = -2.0;
Complex c1 = new Complex(number);

In order to do this, we would need to create another constructor. This constructor accepts only one argument and sets the imaginary component to zero:

    public Complex(double real) {
        this.real = real;
        this.imag = 0.0;
    }

Calling Another Constructor

In an effort to minimize the amount of repeated code, we can have one constructor call another constructor. This allows us to do all of the initialization work in one place instead of having to repeat it in multiple places. Rewriting the above constructor as follows accomplishes this.

    public Complex(double real) {
        this(real, 0.0);
    }

The above implementation instructs the compiler to generate code that will call the two-argument constructor passing the real value that was passed to this constructor and 0.0 for the imaginary component. In this particular example, the reduction in the amount of duplicate code is minimal, but this can have a more significant impact when implementing constructors for more complicated classes. It's a good idea to get in the habit of making calls to alternate constructors when possible.

One thing to keep in mind though, is that a call to another constructor must be the first thing you do in the constructor.

Constructor Accepting a String

Way back at the top of this page, I created a program that asked the user to enter two complex numbers and then displayed the result of multiplying those numbers together. The code passed the user input into a constructor for the Complex class that accepted a String. That constructor is responsible for extracting the real and imaginary components of the complex number from the string and then initializing the real/imag attributes to the appropriate values.

    public Complex(String num) {
        Scanner parser = new Scanner(num.substring(0, num.length()-1));
        real = parser.nextDouble();
        parser.next();
        imag = parser.nextDouble();
    }

This assumes that the string will have the appropriate form: "R + Ii" where R and I are **double**s (positive or negative). Later we'll look at a more robust implementation.

In this case, we can't call the two argument constructor to initialize the object's attributes because we need to parse the string to figure out what the values should be. Since the call to another constructor must be the first operation, calling it is not an option for us here.

Almost There

Let's take a look back at the original program we were trying to write:

public static void main(String[] args) {
    Scanner in = new Scanner(System.in);
  
    System.out.println("Enter a complex number in the form: 3.0 + 4.3i");
    Complex c1 = new Complex(in.nextLine());
    System.out.println("Enter a complex number in the form: 3.0 + 4.3i");
    Complex c2 = new Complex(in.nextLine());
  
    System.out.println(c1 + " * " + c2 + " = " + c1.times(c2));
}

We can do everything here except the times() call in the last print statement. The times() method is very similar to the plus() method; it just does a different calculation:

    public Complex times(Complex multiplicand) {
        return new Complex(this.real * multiplicand.real - imag * multiplicand.imag, 
            imag * multiplicand.real + real * multiplicand.imag);
    }

I'm going to leave the implementation of minus() as a exercise for now, but I'm confident you can write it, right?

Going Nuts

Suppose one of you classmates is a complex number guru, and she explains to you that complex number gurus insist that the-one-and-only-true-way to talk about complex numbers is using polar coordinates.1 As a result, you need to make it possible for complex numbers to be displayed using a polar coordinate system. We'd like to run the same program, but have the interaction with the user look like this:

Enter a complex number in the form: 3.0 + 4.3i
2.0 | 45.0
Enter a complex number in the form: 3.0 + 4.3i
8 | 90.0
(2.0 | 45.0) * (8.0 | 90.0) = (16.0 | -45.00000000000001)

Okay, we should switch the prompts to suggest the appropriate form for entering the complex numbers, but I'm going to ignore that for now.

In order for us to be able to accept the polar form input, we need to modify the constructor that accepts a String so that it recognizes polar coordinates:

    public Complex(String num) {
        if(num.contains("|")) {
            // In polar form, e.g., "1 | 45"
            Scanner parser = new Scanner(num);
            double magnitude = parser.nextDouble();
            parser.next();
            double angle = Math.toRadians(parser.nextDouble());
            real = magnitude * Math.cos(angle);
            imag = magnitude * Math.sin(angle);
        } else {
            // In cartesian form, e.g., "2.0 + 17.2i"
            Scanner parser = new Scanner(num.substring(0, num.length()-1));
            real = parser.nextDouble();
            parser.next();
            imag = parser.nextDouble();
        }
    }

In addition, we need to modify the way toString() works:

    public String toString(){
        return "(" + getMagnitude() + " | " + getAngle() + ")";
    }

    public double getMagnitude() {
        return Math.sqrt(Math.pow(real, 2) + imag*imag);
    }

    public double getAngle() {
        return Math.toDegrees(Math.atan(imag/real));
    }

You'll notice a couple of additional methods here. I've decided to make them public because users of the Complex class may be interested in getting the magnitude or angle of the object.

Class Attributes/Methods

The change we made to the toString() method forces the string representation of the object to be in polar coordinates, and we no longer have the ability to get a string representation in cartesian coordinates. It would be nice if we could switch between both ways of representing these objects. (Just in case the self-proclaimed complex number guru turns out to be a crack-pot.)

We'll do this by introducing an additional attribute for the Complex class and two methods to control its value:

public class Complex {
    // ...

    private static boolean isPolar = false;

    public static void setPolar() {
        isPolar = true;
    }

    public static void setCartesian() {
        isPolar = false;
    }

    // ...
}

You'll notice that all of these are declared with the static modifier. Such a declaration indicates that the attribute/method is not associated with a specific object but is associated with the class instead. As a result, you do not need a Complex object in order to call a static method and all Complex objects share the same attribute value.

We can now modify the toString() implementation so that it generates a string in polar form if isPolar is true; otherwise, it will generate a string in cartesian form:

    public String toString(){
        String answer = "(" + real + " + " + imag + "i)";
        if(isPolar) {
            answer = "(" + getMagnitude() + " | " + getAngle() + ")";
        }
        return answer;
    }

Now we'll add a line to our program that calls setPolar() in order to set our preference for the toString() method to produce strings representing the objects in polar coordinates.

public static void main(String[] args) {
    Scanner in = new Scanner(System.in);

    Complex.setPolar();
  
    System.out.println("Enter a complex number in the form: 3.0 + 4.3i");
    Complex c1 = new Complex(in.nextLine());
    System.out.println("Enter a complex number in the form: 3.0 + 4.3i");
    Complex c2 = new Complex(in.nextLine());
  
    System.out.println(c1 + " * " + c2 + " = " + c1.times(c2));
}

Because isPolar() is associated with the class, we can call it using the name of the class instead of the name of an object from the class. This sets the class attribute isPolar to true and, since all objects share that same value, all calls to the toString() method will generate strings in polar form (until Complex.setCartesian() is called). Running our program now will produce the following interaction:

Enter a complex number in the form: 3.0 + 4.3i
2.0 | 45.0
Enter a complex number in the form: 3.0 + 4.3i
8.0 | 90
(2.0 | 45.0) * (8.0 | 90.0) = (16.0 | -45.00000000000001)

Using Polar Coordinates for dividedBy()

Even though your annoying classmate forced us to do all this work related to polar coordinates, there is a bright side: it will make it easier for us to implement the dividedBy() method since division of complex numbers is easier when the numbers are represented in polar form.

    public Complex dividedBy(Complex divisor) {
        double magnitude = getMagnitude() / divisor.getMagnitude();
        double angle = Math.toRadians(getAngle() - divisor.getAngle());
        return new Complex(magnitude * Math.cos(angle), magnitude * Math.sin(angle));
    }

Alternatively, we could make use of our constructor that accepts a string to create the new Complex object:

    public Complex dividedBy(Complex divisor) {
        double magnitude = getMagnitude() / divisor.getMagnitude();
        double angle = getAngle() - divisor.getAngle();
        return new Complex(magnitude + " | " + angle);
    }

A Brief Retrospective

At the top of this page, we started with a non-OO approach. This program was complicated and not very robust. For example, if the user entered -2.3i for the complex number, the program wouldn't work. We were able to simplify the code significantly by re-writing it using an OO approach and a class to represent complex numbers.

Our first iteration of the Complex class would also get confused if the user entered -2.3i for one of the complex numbers; however, by improving the implementation of the Complex class (see code below), our program can now handle such an input.

This is a big deal! The program got better without requiring any changes to my code. I just needed to convince the developer of my Complex class to make improvements.

Putting it All Together

The complete Complex class is shown below. I've made a few additional improvements:

/*
 * Course: CS1011-011
 * Fall 2018
 * Tutorial on User-Built Classes
 * Name: Dr. Chris Taylor
 * Created: 8/4/2018
 */

package us.msoe.cs1011;

/**
 * Represents complex numbers.  Objects from the class are immutable,
 * i.e., their value cannot change once they are created.
 * @author taylor
 * @version 2018.8.4
 */
public class Complex {

    /**
     * Real component of the complex number
     */
    private final double real;

    /**
     * Imaginary component of the complex number
     */
    private final double imag;

    /**
     * Determines whether the string representation of the complex number
     * will be in cartesian or polar coordinates
     */
    private static boolean isPolar = false;


    /**
     * Default constructor of a complex number with real and
     * imaginary components of zero
     */
    public Complex() {
        this(0.0);
    }


    /**
     * Constructor of a complex number with an imaginary component of zero
     * @param real The value of the real component
     */
    public Complex(double real) {
        this(real, 0.0);
    }


    /**
     * Constructor for a fully specified complex number
     * @param real The value of the real component
     * @param imag The value of the imaginary component
     */
    public Complex(double real, double imag) {
        this.real = real;
        this.imag = imag;
    }


    /**
     * Constructor that accepts a string representation of a
     * complex number
     * @param num String representation of a complex number
     */
    public Complex(String num) {
        double real = 0.0;
        double imag = 0.0;
        num = num.replace("(", "").replace(")", "");
        if(num.contains("|")) {
            // In polar form, e.g., "1 | 45"
            Scanner parser = new Scanner(num);
            double magnitude = parser.nextDouble();
            parser.next();
            double angle = Math.toRadians(parser.nextDouble());
            real = magnitude * Math.cos(angle);
            imag = magnitude * Math.sin(angle);
        } else if(num.contains(" + ") || num.contains(" - ")) {
            // Both real and imaginary components present, e.g., "2.0 - 2.0i"
            Scanner parser = new Scanner(num.substring(0, num.length()-1));
            real = parser.nextDouble();
            parser.next();
            imag = parser.nextDouble();
            if(num.contains(" - ")) {
                imag = -imag;
            }
        } else if(num.contains("i")) { 
            // imaginary component only, e.g., "3.8i"
            Scanner parser = new Scanner(num.substring(0, num.length()-1));
            imag = parser.nextDouble();
        } else {
            // real component only, e.g., "-5.6"
            Scanner parser = new Scanner(num);
            real = parser.nextDouble();
        }
        this.real = real;
        this.imag = imag;
    }


    /**
     * The String representation of the complex number
     * This may be in cartesian or polar form depending on
     * the value of the class variable isPolar.
     * @see Object#toString()
     * @return String representation of the object
     */
    public String toString(){
        String answer;
        if(!isPolar) {
            if(this.imag == 0.0) {
                answer = Double.toString(real);
            } else if(this.real == 0.0) {
                answer = imag + "i";
            } else if(this.imag < 0.0) {
                answer = "(" + real + " - " + (-imag) + "i)";
            } else {
                answer = "(" + real + " + " + imag + "i)";
            }
        } else {
          answer = "(" + getMagnitude() + " | " + getAngle() + ")";
        }
        return answer;
    }


    /**
     * Calculates the sum of the object and a real value
     * @param addend The value to be added to the real component of the complex number
     * @return A new complex number containing the sum of the object and the specified
     * real component
     */
    public Complex plus(double addend) {
        return new Complex(this.real + addend, this.imag);
    }


    /**
     * Calculates the sum of two complex numbers
     * @param addend Number to be added
     * @return the result of the sum of two complex numbers
     */
    public Complex plus(Complex addend) {
        return new Complex(this.real + addend.real, this.imag + addend.imag);
    }


    /**
     * Calculates the difference of two complex numbers
     * @param subtrahend Number to be subtracted
     * @return the result of taking away the specified value from the object 
     */
    public Complex minus(Complex subtrahend) {
        return new Complex(real - subtrahend.real, imag - subtrahend.imag);
    }


    /**
     * Compares two complex numbers to see if they are equal
     * @param that The complex number to compare
     * @return true if the objects share the same value, otherwise false
     */
    public boolean equals(Complex that) {
        return this.real == that.real && this.imag == that.imag;
    }


    /**
     * Returns the magnitude of the complex number
     * @return the magnitude of the complex number
     */
    public double getMagnitude() {
        return Math.sqrt(Math.pow(real, 2) + imag*imag);
    }


    /**
     * Returns the angle of the complex number in degrees
     * @return the angle of the complex number in degrees
     */
    public double getAngle() {
        return Math.toDegrees(Math.atan(imag/real));
    }

    /**
     * Sets class preference so that complex numbers are represented in
     * polar coordinates
     */
    public static void setPolar() {
        isPolar = true;
    }


    /**
     * Sets class preference so that complex numbers are represented in
     * cartesian coordinates
     */
    public static void setCartesian() {
        isPolar = false;
    }


    /**
     * Calculates the product of two complex numbers
     * @param multiplicand Number to be multiplied
     * @return the result of the product of two complex numbers
     */
    public Complex times(Complex multiplicand) {
        return new Complex(this.real * multiplicand.real - imag * multiplicand.imag, 
                           imag * multiplicand.real + real * multiplicand.imag);
    }


    /**
     * Calculates the result of dividing the passed complex number
     * into the calling number
     * @param divisor Number to be used as the divisor
     * @return the result of the division
     */
    public Complex dividedBy(Complex divisor) {
        double magnitude = getMagnitude() / divisor.getMagnitude();
        double angle = Math.toRadians(getAngle() - divisor.getAngle());
        return new Complex(magnitude * Math.cos(angle), magnitude * Math.sin(angle));
    }

}
1

Of course, there is no truth to this, but arguing with her in the past has led to hours of silly arguments that usually end up debating the length of your ties... something you'd prefer to avoid.