CISC 3115
Modern Programming Techniques
Lecture 1
Classes I — The Basics


Overview

In this lecture we're going to present a series of applications that are similar to those developed in CISC 1115, but quite different inteir perspective. We'll develop them and discuss various issues as we go along.

In the Absence of Classes: 1115-Like Apps

A Baseline, Generic 1115-like App (App)

Write an app that maintains an integer value that is modified through adding and subtracting values read in from a file named action.text Each entry in the actions file consists of an action A (for add) or S (for subtract), followed by the amount to be added/subtracted from the value.

import java.io.File;
import java.util.Scanner;

class App {
     public static void main(String [] args) throws Exception {
          Scanner scanner = new Scanner(new File("actions.text"));

          int value = 0;

          while (scanner.hasNext()) {
               String action = scanner.next();
               int amount = scanner.nextInt();

               if (action.equals("A")) {
                    value += amount;
                    System.out.println("Value after add of " + amount + ": " + value);
               }   
               else if (action.equals("R")) {
                    value -= amount;
                    System.out.println("Value after removal of " + amount + ": " + value);
               }   
          }   
     }    
}

Notes

actions.text
A 200
S 50
S 70
A 10

Initial value: 0
Value after add of 200: 200
Value after subtraction of 50: 150
Value after subtraction of 70: 80
Value after add of 10: 90

Modelling 'Things': A Bank Account (01-BankAccount)

Borrowing from Above

o Let us take the above generic app and add specific semantics — that of a bank account — to it.
Write an app named BankAccount that contains two methods named deposit and withdraw, both of which accept a balance and an amount and returns the amount added-to/subtracted-from the balance respectively. The main method should illustrate the methods by reading in actions (deposit and withdrawal) from a while and apply them to the account.


import java.io.File;
import java.util.Scanner;

class BankAccountApp {
	public static void main(String [] args) throws Exception {
		Scanner scanner = new Scanner(new File("bank_actions.text"));

		int balance = 0;

		while (scanner.hasNext()) {
			String action = scanner.next();
			int amount;

			switch (action) {
				case "D":
					amount = scanner.nextInt();
					balance = deposit(balance, amount);
					System.out.println("Balance after deposit of $" + amount + ": " + balance);
					break;
				case "W":
					amount = scanner.nextInt();
					balance = withdraw(balance, amount);
					System.out.println("Balance after withdrawal of $" + amount + ": " + balance);
					break;
				default:
					System.out.println("*** Error *** unknown action: " + action);
			}
		}
	}	

	public static int deposit(int balance, int amount) {return balance + amount;}
	public static int withdraw(int balance, int amount) {return balance - amount;}
}

Notes
  • Do the methods buy us anything?
  • Another Sort of 'Thing': A Fuel Tank

    Here is another version of the generic app with specific semantics; this time that of a vehicle's fuel tank.


    import java.io.File;
    import java.util.Scanner;
    
    class FuelTankApp {
    		Scanner scanner = new Scanner(new File("fueling_actions.text"));
    
    		int tank = 0;
    		System.out.println("Initial tank: " + tank + " gallons");
    
    		while (scanner.hasNext()) {
    			String action = scanner.next();
    			int gallons;
    
    			switch (action) {
    				case "F":
    					gallons = scanner.nextInt();
    					tank = fill(tank, gallons);
    					System.out.println("Tank after fueling " + gallons + ": " + tank + " gallons");
    					break;
    				case "C":
    					gallons = scanner.nextInt();
    					tank = consume(tank, gallons);
    					System.out.println("Tank after consuming " + gallons + ": " + tank + " gallons");
    					break;
    				default:
    					System.out.println("*** Error *** unknown action: " + action);
    			}
    		}
    	}	
    
    	public static int fill(int tank, int gallons) {return tank + gallons;} 
    	public static int consume(int tank, int gallons) {return tank - gallons;}
    }
    

    Notes


    Moving Related Methods to their Own Source Files; Separate Compilation

    We now extract the bank account-related methods and place them in their own class.
    class BankAccount {
    	public static int deposit(int balance, int amount) {return balance + amount;}
    	public static int withdraw(int balance, int amount) {return balance - amount;}
    }
    

    import java.io.File;
    import java.util.Scanner;
    
    class BankAccountApp {
    	public static void main(String [] args) throws Exception {
    		Scanner scanner = new Scanner(new File("bank_actions.text"));
    
    		int balance = 0;
    
    		while (scanner.hasNext()) {
    			String action = scanner.next();
    			int amount;
    
    			switch (action) {
    				case "D":
    					amount = scanner.nextInt();
    					balance = BankAccount.deposit(balance, amount);
    					System.out.println("Balance after deposit of $" + amount + ": " + balance);
    					break;
    				case "W":
    					amount = scanner.nextInt();
    					balance = BankAccount.withdraw(balance, amount);
    					System.out.println("Balance after withdrawal of $" + amount + ": " + balance);
    					break;
    				default:
    					System.out.println("*** Error *** unknown action: " + action);
    			}
    		}
    	}	
    }
    

    Notes

    Two 'Things' in the Same App

    In the above apps, main is busy dealing with the value of the 'thing' we are modelling. Now imaging an app which requires bank accounts AND fuel tanks. main would have to make sure it properly handles the two things, i.e., not sending a balance to a fillUp method, or a fuel tank level to a withdraw method.

    A more realistic scenario would be two different sorts of bank accounts:

    class SavingsAccount {
    	public static int deposit(int balance, int amount) {return balance + amount;}
    	public static int withdraw(int balance, int amount) {return (amount <= balance) ? balance - amount : balance;}
    }
    

    class CheckingAccount {
    	public static int deposit(int balance, int amount) {return balance + amount;}
    	public static int withdraw(int balance, int amount) {return balance - amount;}
    }
    

    import java.io.File;
    import java.util.Scanner;
    
    class BankAccountApp {
    	public static void main(String [] args) throws Exception {
    		Scanner scanner = new Scanner(new File("bank_actions.text"));
    
    		int 
    			savingsBalance = 0,
    			checkingBalance = 0;
    
    		while (scanner.hasNext()) {
    			String action = scanner.next();
    			int amount;
    
    			switch (action) {
    				case "SD":
    					amount = scanner.nextInt();
    					savingsBalance = SavingsAccount.deposit(savingsBalance, amount);
    					System.out.println("Balance after deposit of $" + amount + ": " + savingsBalance);
    					break;
    				case "SW":
    					amount = scanner.nextInt();
    					savingsBalance = SavingsAccount.withdraw(savingsBalance, amount);
    					System.out.println("Balance after withdrawal of $" + amount + ": " + savingsBalance);
    					break;
    				case "CD":
    					amount = scanner.nextInt();
    					checkingBalance = CheckingAccount.deposit(checkingBalance, amount);
    					System.out.println("Balance after deposit of $" + amount + ": " + checkingBalance);
    					break;
    				case "CW":
    					amount = scanner.nextInt();
    					checkingBalance = CheckingAccount.withdraw(checkingBalance, amount);
    					System.out.println("Balance after withdrawal of $" + amount + ": " + checkingBalance);
    					break;
    				default:
    					System.out.println("*** Error *** unknown action: " + action);
    			}
    		}
    	}	
    }
    

    Notes

    Let's Add One more Layer: An Array of BankAccounts

    Introducing an Array of 'BankAccount's raises all sorts of challenges and opportunities.

    import java.io.File;
    import java.util.Scanner;
    
    class BankAccountsApp {
    	public static void main(String [] args) throws Exception {
    		Scanner scanner = new Scanner(new File("bank_actions.text"));
    
    		final int CAPACITY = 100;
    		int size = 0;
    		int [] balances = new int[CAPACITY];
    		int [] ids = new int[CAPACITY];
    
    		while (scanner.hasNextInt()) {
    			int id = scanner.nextInt();
    			String action = scanner.next();
    			int amount;
    
    			int index = search(ids, size, id);
    			if (index == -1) {
    				size = add(ids, balances, size, id, CAPACITY);
    				index = size-1;
    				System.out.println("Added account " + id + " with balance of $0 at location " + index);
    			}
    
    			switch (action) {
    				case "D":
    					amount = scanner.nextInt();
    					balances[index] = BankAccount.deposit(balances[index], amount);
    					System.out.println("Balance of account " + id + " after deposit of $" + amount + ": $" + balances[index]);
    					break;
    				case "W":
    					amount = scanner.nextInt();
    					balances[index] = BankAccount.withdraw(balances[index], amount);
    					System.out.println("Balance of account " + id + " after withdrawal of $" + amount + ": $" + balances[index]);
    					break;
    				default:
    					System.out.println("*** Error *** unknown action: " + action);
    			}
    		}
    		System.out.println();
    		print(ids, balances, size);
    	}	
    
    	static int search(int [] ids, int size, int id) {
    		for (int i = 0; i *lt; size; i++) 
    			if (ids[i] == id) return i;
    		return -1;
    	}
    
    	static int add(int [] ids, int [] balances, int size, int id, int capacity) {
    		if (size == capacity) {
    			System.err.println("Capacity reached; make arrays larger");
    			System.exit(1);
    		}
    		ids[size] = id;
    		balances[size] = 0;
    		return size+1;
    	}
    		
    	static void print(int [] ids, int [] balances, int size) {
    		for (int i = 0; i *lt; size; i++)
    			System.out.println(ids[i] + ": $" + balances[i]);
    	}
    }
    

    Notes

    actions.text

    1001 D 200
    1030 D 100
    1030 W 50
    1001 W 70
    

    Added account 1001 with balance of $0 at location 0
    Balance of account 1001 after deposit of $200: $200
    Added account 1030 with balance of $0 at location 1
    Balance of account 1030 after deposit of $100: $100
    Balance of account 1030 after withdrawal of $50: $50
    Balance of account 1001 after withdrawal of $70: $130
    
    1001: $130
    1030: $50
    

    Classes: Overview From 10,000 Feet

    We are now going to do the same thing with the data of our 'thing' as we did with the methods that operate on it; i.e., place it into a separate class together with those methods. This combination of known as a class and the purpose of the class is to create objects that posses the behavior and characteristics, i.e, data (state) and methods (behavior), specified in the class definition.

    Here are the semantics of defining and using a class in a nutshell:

    public class BankAccount {
    	public void deposit(int amount) {balance += amount;}
    	public void withdraw(int amount) {balance -= amount;}
    
    	public int getBalance() {return balance;}
    
    	private int balance = 0;
    }
    

    Notes

    Here is the app using the class:

    import java.io.File;
    import java.util.Scanner;
    
    class BankAccountApp {
    	public static void main(String [] args) throws Exception {
    		Scanner scanner = new Scanner(new File("bank_actions.text"));
    
    		BankAccount account = new BankAccount();
    
    		while (scanner.hasNext()) {
    			String action = scanner.next();
    			int amount;
    
    			switch (action) {
    				case "D":
    					amount = scanner.nextInt();
    					account.deposit(amount);
    					System.out.println("Balance after deposit of $" + amount + ": " + account.getBalance());
    					break;
    				case "W":
    					amount = scanner.nextInt();
    					account.withdraw(amount);
    					System.out.println("Balance after withdrawal of $" + amount + ": " + account.getBalance());
    					break;
    				default:
    					System.out.println("*** Error *** unknown action: " + action);
    			}
    		}
    	}	
    }
    

    Notes
    We discussed above the issue of having more than one 'thing' in the same app, and presented it in the context of a savings and checking account. We now present the SavingsAccount and CheckingAccount examples using classes. We will also introduce the approach for printing out objects in general.

    class SavingsAccount {
    	public void deposit(int amount) {balance += amount;}
    	public void withdraw(int amount) {if (amount <= balance) balance -= amount;}
    	
    	public String getBalance() {return "$" + balance;}
    
    	private int balance = 0;
    }
    

    class CheckingAccount {
    	public void deposit(int amount) {balance += amount;}
    	public void withdraw(int amount) {balance -= amount;}
    	
    	public String getBalance() {return "$" + balance;}
    
    	private int balance = 0;
    }
    

    Notes

    import java.io.File;
    import java.util.Scanner;
    
    class BankAccountApp {
    	public static void main(String [] args) throws Exception {
    		Scanner scanner = new Scanner(new File("bank_actions.text"));
    
    		SavingsAccount savingsAccount = new SavingsAccount();
    		CheckingAccount checkingAccount = new CheckingAccount();
    
    		while (scanner.hasNext()) {
    			String action = scanner.next();
    			int amount;
    
    			switch (action) {
    				case "SD":
    					amount = scanner.nextInt();
    					savingsAccount.deposit(amount);
    					System.out.println("Balance after deposit of $" + amount + ": " + savingsAccount);
    					break;
    				case "SW":
    					amount = scanner.nextInt();
    					savingsAccount.withdraw(amount);
    					System.out.println("Balance after withdrawal of $" + amount + ": " + savingsAccount);
    					break;
    				case "CD":
    					amount = scanner.nextInt();
    					checkingAccount.deposit(amount);
    					System.out.println("Balance after deposit of $" + amount + ": " + checkingAccount);
    					break;
    				case "CW":
    					amount = scanner.nextInt();
    					checkingAccount.withdraw(amount);
    					System.out.println("Balance after withdrawal of $" + amount + ": " + checkingAccount);
    					break;
    				default:
    					System.out.println("*** Error *** unknown action: " + action);
    			}
    		}
    	}	
    }
    

    Notes

    Classes: The Details

    Introduction

    We've introduced the notion of a class. The basic idea is to bundle together logically/semantically-related variables and methods into a single entity that represents or models specific the desired behavior. One fundamental change is to take the principal values of our proposed object (i.e., entity) and place them at the class level rather than as local variables; in effect making them available to all the methods in the class (assuming we remove the static keyword from their header — see below). Most of the other changes are then a consequence of this change.

    Summarizing the introductory bank account example above:

    Revisiting 'Some Things to Think About'

    Terminology

    Responsibility-Driven Programming

    We want the object to be responsible for its own behavior and integrity.

    Data Access

    A consequence of an object being responsible for its own behavior is the need to control access to the object from the outside. This leads to the notion of data access specification — indicating what entities are allowed what sort of access to the state/behavior of the object.

    Class Definition

    Example: Defining a Class

    public class Name {
    	public String getFirst() {return first;}		// Behavior
    	public String  getLast() {return last;}
    	public void setFirst(String f) {first = f;}
    	public void setLast(String l) {last = l;}
    
    	private String first, last;					// State
    }
    

    public class Counter {
    	public up() {val++;}					// Behavior
    	public int getVal() {return val;}
    
    	private int val = 0;					// State
    }
    

    Notes

    Classes as Types

    Objects (Instances)

    Creating Instances (Objects) of a Class

    Example: Using a Class

    public static void main(String [] args) {
    	Name name1 = new Name();
    	name1.setLast("Arnow");
    	name1.setFirst("David");
    	System.out.println(name1.getFirst());
    	System.out.println(name1.getLast());
    	Name name2 = new Name();
    	name2.setLast("Weiss");
    	name2.setFirst("Gerald");
    	System.out.println(name2.getFirst());
    	System.out.println(name2.getLast());
    	Name name3 = name1;
    	System.out.println(name3.getFirst());
    	System.out.println(name3.getLast());
    }
    

    public static void main(String [] args) {
    	Counter c = new Counter();
    
    	for (int i = 1; i <= 10; i++)
    		c.up();
    
    	System.out.println(c);
    }
    

    The main Method as App

    Primitive Types and Reference Types

    References, Objects, Aliases

    Object Initialization

    Suppose we wish to be able to create a bank account with an initial non-zero balance, We could add a setter method and write:
    BankAccount account = new BankAccount();		
    account.setBalance(100);
    
    Notes
    There are several issues with the above approach: In this particular situation, one COULD avoid the need for a setter and instead perform a deposit after object creation:
    BankAccount account = new BankAccount();		
    account.setBalance(100);
    
    While this preserves encapsulation (i.e., we are still limited to performing a 'sanctioned' operation , i.e., deposit, rather than the 'free-for-all' setBalance), there are still some drawbacks to this approach: Note also, that we cannot provide this initial balance at the point of declaration as it will typically be different from object to object (one bank account may be opened with an initialize balance of $100; another with $500).

    Constructors — Initializing Objects

    Default Constructors

    Constructors with Arguments

    More often, however, object creation requires initialization that must be supplied at runtime

    Constructor Overloading

    One may wish to allow the user to create instances of a class in one of several ways.