CISC 3115
Modern Programming Techniques
Lecture 3
Classes Part II


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
}

Classes as Types

Objects (Instances)

Creating (Instances) 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

Primitive Types and Reference Types

References, Objects, Aliases

Constructors - Initializing Objects

Default Constructors

Constructors with Arguments

Constructor Overloading

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

Constructor Overloading and this

The toString Method

Most classes have a method named toString defined by their designers.

== and the equals Method

Adding a main Method

A main method is often added to a class whose execution will either 'show-off' the behavior of the class, or test the class (basically the same thing).

public class Counter {
	public Counter() {val = 0;}		
	
	public void up() {val++;}
	public void down() {val--;}
	public int get() {return val;}
	
	public String toString() {return val+"";}	// The String concat, '+', converts integers to String automatically
	
	private int val;
	
	public static void main(String [] args) {
		Counter c = new Counter();
		System.out.println("After initialization: " + c.get());
		System.out.println("After initialization, using toString()" + c.toString());
		// The String concat, '+', calls an object's toString automatically
		System.out.println("After initialization, using toString() implicitly" + c);
		
		System.out.println("going up!");
		for (int i = 0; i < 10; i++) {
			c.up();
			System.out.println("The counter after " + i + " up(s) is now: " + c);
		}
		
		System.out.println("down we go!");
		for (int i = 0; i < 10; i++) {
			c.down();
			System.out.println("The counter after " + i + " down(s) is now: " + c);
		}
	}
}
	

More often than not, this illustrative main is placed in its own class:

class CounterApp {
	public static void main(String [] args) {
		Counter c = new Counter();
		System.out.println("After initialization: " + c.get());
		System.out.println("After initialization, using toString()" + c.toString());
		// The String concat, '+', calls an object's toString automatically
		System.out.println("After initialization, using toString() implicitly" + c);
		
		System.out.println("going up!");
		for (int i = 0; i < 10; i++) {
			c.up();
			System.out.println("The counter after " + i + " up(s) is now: " + c);
		}
		
		System.out.println("down we go!");
		for (int i = 0; i < 10; i++) {
			c.down();
			System.out.println("The counter after " + i + " down(s) is now: " + c);
		}
	}
}

Composition — Objects as Instance Variables

Consequences of Composition

Composition & Constructors

When using composition, there is often a chaining in the constructors similar to that of toString and equals
class Name {
	Name(String last, String first) {…}

	…

	private String last, first;

class Person {
	Person(int age, Name name) {
		this.age = age;
		this.name = name;
	}

	Person(int age, String last, String first) {this(age, new Name(last, first));}

	…

	private Name name;
	private int age;
}

static — Class Members

Scoping Rules

Within the methods of a class, one can access the private variables of another instance of the same class scope: the places in a program where a variable can be referenced without qualification (i.e., just using the variable's name)
class C {
	void f1() {
		int i;
		…
		System.out.println(i);
		…
	}

	void f2(int i) {
		…
		System.out.println(i);
		…
	}

	void f3() {
		…
		System.out.println(i);
		…
	}

	int i;
}
		

An interesting Example of Scoping Rules in Action — Another Use of this

Here is a variation on the Name constructor:
class Name {
	Name(String last, String first) {
		…	
	}
	String last, first;
}

Data Access Within the Class

Scope of Fields (Class/Instance Methods/Variables/Constants)

A Color Class

A Counter class

class Counter {
	void up() {...}
	void down() {...}

	int val;
}
	

A Window class

class Window {
	void move(int newX, int newY) {...}
	void resize(int new width, int newHeight) {...}
	void setFgColor(Color newFgColor) {...}
	Color getFgColor() {...}
	void setBgColor(Color newBgColor) {...}
	Color getBgColor() {...}
	void insert(String newText, int where) {...}
	
	int width, height;
	int x, y;
	Color fgColor, bgColor;
	String text;
}
				
  • An object can thus be thought of as a programming mechanism that 'binds' data together with the methods that operate upon that data

    Arrays of Objects

    As mentioned above, a class definition introduces a new type into the system, whose name is that of the class. Just as we would with any other type, we may have occasion to declare an array of instances of a class.

    Declaring an Array of Objects

    Creating an Array of Objects

    Analogous to creating an object of a class, creating an array object involves the new operator:

    String [] stringArr = new String[20];	// Creates an array of 20 String references and assign the 
    								//	reference to the array object to stringArr
    Name [] nameArr = new Name[100];		// Creates an array of 100 Name references and assigns the 
    								//	reference to the array object to nameArr
    

    Populating the Array with Instances of the Class

    Once the array reference has been assigned a reference to an array object, the elements (which are only reference locations) of the array can be populated by creating instances of the class and assigning their references to the elements of the array:

    final int CAPACITY = 100;
    Name [] names = new Name[CAPACITY];
    
    names[0] = new Name("Gerald", "Weiss");
    names[1] = new Name("David", "Arnow");
    names[2] = new Name("Yedidyah", "Langsam");
    …
    

    or more typically, the contents of the array would be read in from a file using a read method, which reads the data from the file, creates the object (which is initialized via the constructor being passed the data), and returns its reference:

    final int CAPACITY = 100;
    Name [] names = new Name[CAPACITY];
    int size = 0;
    
    Scanner scanner = new Scanner(new File("names.text"));
    
    Name name = Name.read(scanner);		
    while (name != null) {
    	if (size >= CAPACITY) {
    		System.out.println("Too many names in file -- increase the size of your array");
    		System.exit(1);
    	}
    	names[size++] = name;
    	name = Name.read(scanner);		
    }
    

    Working With Arrays

    Once the array has been populated, you use it as you would any other array:
    void print(Name [] names, int size) {
    	for (int i = 0; i < size, i++)
    		System.out.println(names[i] + (i < size-1 ? ", " : ""));
    }
    
    boolean contains(Name [] names, int size, Name name) {
    	for (int i = 0; i < size, i++)
    		if (names[i].equals(name)) return true;
    	return false;
    }
    

    Arrays as Instance Variables (Arrays in Objects)

    We've seen that a class can be used as the element type of an array. We can also have an array be an instance variable of an object.
    class FootballTeam {
    	…
    	private Player [] offensiveSquad = new Player[11];
    	private Player [] defensiveSquad = new Player[11];
    	private Player [] specialSquad = new Player[3];
    }
    

    class Arr {
    	…
    	private int [] arr = new int[100];
    	private int size = 0;
    }
    

    An Array Class

    Ability to create a class that encapsulates (contains as private data and thus protects) the data needed to maintain an array: This is our first example of a container or collection class.

    We lose some things by moving from an array to a class:

    We gain quite a bit, though

    Basic Class Design

    A Taxonomy of Classes

    Inner Classes

    class Phonebook {
    	static class PhonebookEntry {
    		…
    	}
    	…
    }
    

    Mutable vs Immutable Classes

    Some classes are deliberately designed so that their objects — once initialized — cannot be modified

    Immutable Classes

    class ImmutableColor {
    	public ImmutableColor(int r, int g, int b) {
    		this.r = r;
    		this.g = g;
    		this.b = b;
    	}
    	…
    	public ImmutableColor lighter() {
    		if (r < 255 && g < 255 && b < 255) 
    			return new ImmutableColor(r+1, g+1, b+1)
    		else
    			return null;	
    	}
    
    	private final int r, g, b;
    }
    
    Notes

    Mutable Classes

    class MutableColor {
    	public MutableColor(int r, int g, int b) {
    		this.r = r;
    		this.g = g;
    		this.b = b;
    	}
    	…
    	public MutableColor makeLighter() {
    		if (r < 255 && g < 255 && b < 255)  {
    			r++;
    			g++;
    			b++
    			return this;
    		}
    		else
    			return null;	
    	}
    
    	private int r, g, b;
    }
    
    Notes

    Criteria for Mutability/Immutability

    Files used in this Lecture