tvata CISC 3115 — Lecture 2 — Classes

CISC 3115
Modern Programming Techniques
Lecture 2
Classes I: Object Validity, Encapsulation, Constructors


The BankAccount Class Again

class BankAccount {
    public void deposit(int amount) {balance += amount;}	// no static

    public void withdraw(int amount) {		// no static
        if (status.equals("CLOSED")) {
            System.out.println("Withdrawal denied: account is CLOSED");
            return;
        }
        balance -= amount;
    }

    public void print()  {
        System.out.println("Account #: " + accountNumber);
        System.out.println("Owner: " + owner);
        System.out.println("Balance: " + balance + " cents");
        System.out.println("Status: " + status);
        System.out.println("Opened: " + openedDate);
    }

    int accountNumber;
    String owner;
    int balance;
    String status;
    String openedDate;
}

import java.util.*;
import java.util.Scanner;

public class BankAccountApp {

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

        BankAccount account = new BankAccount();

        account.accountNumber = readAccountNumber(in);
        account.owner = readOwner(in);
        account.balance = readBalance(in);
        account.status = readStatus(in);
        account.openedDate = readOpenedDate(in);

        account.deposit(5000);
        account.withdraw(2000);

        account.print();
    }

    // ----- Input helpers -----

    static int readAccountNumber(Scanner in) {
        System.out.print("Enter account number: ");
        return in.nextInt();
    }

    static String readOwner(Scanner in) {
        System.out.print("Enter owner name: ");
        return in.next();
    }

    static int readBalance(Scanner in) {
        System.out.print("Enter starting balance (cents): ");
        return in.nextInt();
    }

    static String readStatus(Scanner in) {
        System.out.print("Enter status (ACTIVE/CLOSED): ");
        return in.next();
    }

    static String readOpenedDate(Scanner in) {
        System.out.print("Enter opened date: ");
        return in.next();
    }
}

Terminology

Guaranteeing a Valid Object After Creation

However, this approach opens a gap in the integrity of our object:

BankAccount account = new BankAccount();
account.print();
account.deposit(5000);

account.accountNumber = readAccountNumber(in);
account.owner = readOwner(in);

We need some way to guarantee the object has been properly initialized before any action can be taken on it.

Valid State Through Initialization

Let's first look at another relatively simple class definition; one for a counter whose value can be incremented and decremented:
public class Counter {
	up() {val++;}					// Behavior
	down() {val--;}					 

	private int val = 0;					// State
}

Constructors and Object Initialization

Another Example: A Name Class

Consider a class consisting of a first and last name.
public class Name {

	String getFormal() {return last + ", " + first;}
	String getStandardl() {return first + " " + last;}
	String getInitials() {return first.substring(0, 1) + "," + last.substring(0, 1);}

	String first, last;					// State
}

We need something other than Initialization at the Point of Declaration to guarantee a valid object upon creation

Constructors

Our solution comes from providing a method that initializes the object's instance variables to valid values that are supplied by the creator of the object. Here is an exaple applie to our Name class:
public class Name {
	Name(String l, String f) {
		last = l;
		first = f;
	}
dd

	String getFormal() {return last + ", " + first;}
	String getStandardl() {return first + " " + last;}
	String getInitials() {return first.substring(0, 1) + "," + last.substring(0, 1);}

	String first, last;					// State
}
and here is a sample usage:
Name name = new Name("Weiss", "Gerald");	// Upon return, the Name object is properly initialized
System.out.println(name.getFormal();		// Weiss, Gerald
System.out.println(name.getStandard();		// Gerald Weiss
System.out.println(name.getiInitials();		// G.W.
(As an aside, there's nothing that prevents us from creating nonsensical Names as long as we supply a first and last String to the constructor:
Name name = new Name("", "**!!");
we will deal with this later.)

Returning to Our BankAccount

Here is a constructor analogous to the one for Name. i.e., one argument per instance variable:
class BankAccount {
	BankAccount(int initId, String initOwner, int initBalance, String initiStatus, String initOpenedDate) {
		id = initId;
		owner = initOwner;
		balance = initBalance;
		status = initStatus;
		openedDate = initOpenedDate;
	}

	public void deposit(int amount) {balance += amount;}	// no static

	public void withdraw(int amount) {		// no static
		if (status.equals("CLOSED")) {
			System.out.println("Withdrawal denied: account is CLOSED");
			return;
		}
		balance -= amount;
	}

	public void print()  {
		System.out.println("Account #: " + accountNumber);
		System.out.println("Owner: " + owner);
		System.out.println("Balance: " + balance + " cents");
		System.out.println("Status: " + status);
		System.out.println("Opened: " + openedDate);
	}

	int accountNumber;
	String owner;
	int balance;
	String status;
	String openedDate;
}

Here is some code to create a BankAccount object and have it initialized with caller-supplied values:

BankAccount account = new BankAccount(10010, "Weiss", 0, "ACTIVE", 01/01/2026");

Maintaining the Validity of the Object (Encapsulation)

Constructors now provide a way of ensuring we can creating a valid object of a class that will be available to the caller as soon as the object is created:
Name name = new Name("Weiss", "Gerald");

BankAccount account = new BankAccount("Weiss");
… the question now becomes how do we maintain that validity:
name.last = "";

account.status = "CLOSED";		// 'unofficial' change of status		

account.balance += 10000;	// 'rogue' deposit of $100
The problem is that the object has no control over how its state is modified., i.e., the user is able to modify the object's state directly, bypassing any of the rules of the class (such as status or overdraft checking)

We solve this by giving the class exclusive control over access to its internal state. This principle is known as encapsulation and is enforced through access control.

Access Control

BankAccount Again

class BankAccount {
	public BankAccount(String initOwner) {
		owner = initOwner;		// supplied by object creator
		id = next available account number can't code this yet
	}

	public void deposit(int amount) {balance += amount;}	// no static

	public void withdraw(int amount) {		// no static
		if (status.equals("CLOSED")) {
			System.out.println("Withdrawal denied: account is CLOSED");
			return;
		}
		balance -= amount;
	}

	public int getBalance() {return balance;}

	public void print()  {
		System.out.println("Account #: " + accountNumber);
		System.out.println("Owner: " + owner);
		System.out.println("Balance: " + balance + " cents");
		System.out.println("Status: " + status);
		System.out.println("Opened: " + openedDate);
	}

	private int accountNumber;
	private String owner;
	private int balance = 0;;
	private String status = "ACTIVE";
	private openedDate = new java.util.Date() + "";	// converts Date value for 'now' to a String
}
class BankAccount {
	public BankAccount(String initOwner, int initBalance, String initiStatus, String initOpenedDate) {
		id = initId;
		owner = initOwner;
		balance = initBalance;
		status = initStatus;
		openedDate = initOpenedDate;
	}

	public void deposit(int amount) {balance += amount;}	// no static

	public void withdraw(int amount) {		// no static
		if (status.equals("CLOSED")) {
			System.out.println("Withdrawal denied: account is CLOSED");
			return;
		}
		balance -= amount;
	}

	public int getBalance() {return balance;}

	public void print()  {
		System.out.println("Account #: " + accountNumber);
		System.out.println("Owner: " + owner);
		System.out.println("Balance: " + balance + " cents");
		System.out.println("Status: " + status);
		System.out.println("Opened: " + openedDate);
	}

	private int accountNumber;
	private String owner;
	private int balance;
	private String status;
	private String openedDate;
}

Constructor Overloading

In many situations, the semantics of a class may provide several ways to create an object; often they are related in some manner.

Rational: A Class for Rational Numbers

A rational number is one that can be expressed using integer numerator and denominator (i.e., the ratio between two integers). o

Leveraging/Delegation

We will often encounter situations involving leveraging and delegation.

Another Example: A Name Class with a Middle Name

Not all names have a middle name.
class Name {
    // Primary constructor
    Name(String f, String m, String l) {
        first = f;
        middle = m;
        last = l;
    }

    // Convenience constructor
    Name(String f, String l) {
        this(f, "", l);   // delegate to the primary constructor
    }

    private String first;
    private String middle;
    private String last;
}

Default Constructors

A Window Class

Here is a class modelling a graphic window:
class Window {
    public Window() {
        width = 800;
        height = 600;
        x = 100;
        y = 100;
        title = "Untitled";
        color = Color.GRAY;
        opacity = 1.0;
    }

    private int width;
    private int height;
    private int x;
    private int y;
    private String title;
    private Color color;
    private double opacity;
}

Switch — Another Example

class Switch {
    public Switch() {
        on = false;
    }

    public void flip() {
        on = !on;
    }

    private boolean on;
}

Another Flavor of Default Constructor

Sometimes default constructors arose as a result of a sequence of constructor argument reductions:

Back To Rational

  • While Rational() is a default constructor by virtue of it having no arguments, it arose as a result of a natural delegation chain (numerator and denominator, numerator only, nothing) rather than a decision on the part of the class designer as to how to provide a reasonably-initialized default object. Finally, a default constructor is appropriate only when the class can establish a valid object without any information from the caller. That rules out:

    Revisiting the Counter Class

    Sometimes, which motivation is at play is not so clear:
    class Counter {
        public Counter(int initialValue) {
            value = initialValue;
        }
    
        public Counter() {
            this(0);
        }
    
        private int value;
    }
    
    The default constructor here straddles BOTH motivations:

    Parameter Names for Constructors

    Parameters to constructors typically initialize corresponding instance variables. Up until now we assigned the parameter as different name to avoid shadowing

    One Last Item: The Implicit Default Constructor

    Files Used in This Lecture