We have been exposed to using both primitive (int, double, boolean, char) and reference (Scanner, String, ArrayList) type variables. Until now, we have worked with a limited knowledge of reference type variables. Object oriented programming is the process of creating your own reference type variables.
What is an Object?
An object is a representation of any real world thing. For example, civil engineers working on simulating traffic flow might need to represent cars and traffic lights. Game designers might need to represent missals and attackers. Cell phone developers often need to reference dates, times and locations (GPS coordinates). Each of these items would be an object in Java and we would develop classes for each of them.
Class vs. Object
The words class and object are often thrown around in Java. The difference is subtle, but important. An object is an instance of a class. Think of it this way – My wife loves to bake. Most often, it is cakes or cookies. We have two storage bins of cake pans and cookie cutters, some for each holiday. So, when she makes her famous lemon cookies at Christmas, she often uses a snowman cookie cutter. My kids then decorate each snowman in their own way using color sugar. When each snowman cookie comes out of the cutter, they are identical. In this case, the cookie cutter is the class and the snowmen cookies are the objects.
Another way to look at it can be found in construction. When a new neighborhood is built, there is a set of blueprints that the construction company must follow to build the houses. Each house that is built by these blueprints is built the same way. Which is the class, the blueprint or the house? You guessed it, the blueprint, or the plan, is the class. And, each house is an instance of the blueprint (also known as an object).
In Java, the class is the code file that creates the properties and actions that the object will need. The properties are stored as variables and the actions are written as methods. The object is then the named variable of the class type. For example, in the statement Car myCar = new Car(). The class is car and the object is myCar.
Designing Object
Objects have two main parts – properties and actions. Properties qualities that an object possesses and actions are things that the object can do. The designer of a class needs to determine what properties and actions the objects of the class will need to perform the necessary jobs. In the case of the car object that the civil engineers need, we might store properties of make, model, year, color, and number of passengers. Meanwhile, each car will need to move forward, backward, stop and turn (both right and left).
We often use something called UML (Unified Modeling Language) to display the design of a class. UML is a comprehensive set of rules for designing and modeling, but will only use a subset of this information. In a basic UML diagram, we will need to state the variables with types (properties) and the methods with return types and parameters (actions). We will also need to denote whether each of these items are private or public (to be discussed later).
So, for our car class, the resulting UML diagram looks as follows:
Car
String Color; String make; String model; int year; int numPassengers;
When we write the code for a class, we DO NOT create a main. A class is code that should not run by itself. From here on out, when you are asked to make a program, use a main, otherwise, it is a class with no main. So, to code our Car class we would enter the following:
/**
* The car class represents a car for a traffic simulation.
* @author mengel
* @version 2/6/12
*/
public class Car {
//instance variables
private String color;
private String make;
private String model;
private int year;
private int numPassengers;
//instance methods
/**
* Moves the car forward dist
* @param dist the distance to move forward
*/
public void forward(double dist){ //to be coded later }
/**
* Moves the car backward dist
* @param dist the distance to move backward
*/
public void backward(double dist){ //to be coded later }
/**
* turns the car right or left
* @param degrees the amount of degrees to turn
*/
public void turn(int degrees){ //to be coded later }
/**
* stops the car
*/
public void stop(){//to be coded later }
}
Instantiating Objects & Accessing Variables
When we declare a variable in java, we give it a name and type:
int age;
String name;
Declaring a reference type variable is the same:
Scanner reader;
ArrayList <String> list;
Car myCar;
When we initialize a variable, we give it a starting value:
age = 4;
name=”Jim”;
However, when we initialize a reference type variable, we use the keyword new. This helps to call a method that initializes ALL of the objects variables. This process is called instantiation (creating an instance).
reader = new Scanner(System.in);
list = new ArrayList <String> ();
myCar = new Car();
To access the variables (or methods) of an object, once it has been instantiated, we use dot (.) notation. Dot notation will allow us to both set and get the values of the variable:
Constructors are public instance methods of a class used to initialize the instance variables. A constructor takes the same name as the class and does not have a return type(not even void). The constructor is called following the keyword, new, at the time of instantiation.
Default constructors for objects are created by Java. A default constructor takes no parameters. In a Java supplied default constructor, all Strings are set to '' and all numbers are set to 0. Once we specify any other constructor, we must also explicitly specify our default constructor. Using a default constructor, however, means that we then have to set each variables using its setter, if available.
We often overload a constructor to allow us to send in some, all, or no values for the instance variables. The key to overloading a constructor is that each version must have a different number/type of parameters.
So, our Dog class, with overloaded constructors may look as follows:
public Dog() //default
{
this.name='';
this.cage=0;
this.sound='bark';
this.customer=null;
}
public Dog(String name)
{
this.name=name;
this.cage=0;
this.sound='bark';
this.customer=null;
}
If we were to overload the constructor several more times, we would quickly begin to find the process tedious, as each constructor requires four lines of code to initialize each variable. If we extrapolate that to a larger class with say, 10 varibles, and wrote ten constructors, that would be one hundred lines of code just for constructors. So, the faster way to implement overloaded constructors requires writing a constructor that takes all of the values as parameters and using the keyword this to call it. Similar to the way this.name refers to the instance variable name, this by itself refers to the instance constructor.
public Dog(String name, int cage, String sound, Customer customer){
this.name=name;
this.cage=cage;
this.sound=sound;
this.customer=customer;
}
public Dog() //default{
this('', 0, 'bark', null); //calls the full constructor and sends in default values
}
public Dog(String name){
this(name, 0, 'bark', null); //calls the full constructor with a mixture of default and parameter values
}
Our next issue is error checking. In writing setters we were so careful to ensure that the incoming data was valid, thus eliminating the need to test it throughout the rest of the program. We could add error checking to the constructors as well, but that would require copying and pasting code (which means that there must be a better way). So, if we take the full constructor and call the setters (that already have the error checking) we can use the result to decide if it needs a default value. Look at the following example:
public Dog(String name, int cage, String sound, Customer customer){
this.name=name;
if(!setCage(cage)) cage=0;
this.sound=sound;
this.customer=customer;
}
public Dog() //default{
this('', 0, 'bark', null); //calls the full constructor and sends in default values
}
public Dog(String name){
this(name, 0, 'bark', null); //calls the full constructor with a mixture of default and parameter values
}
It is important to note that the job of a constructor is to initialize the values of EVERY instance variable. So, if the setter fails, the constructor MUST assign the variable to a default value.
The toString Method
Similar to the problem of writing multiple lines of code each time we want to create an object, we currently have to write just as many lines of code to output an object, each time calling the appropriate getter. In a program that outputs an object with 5 variables ten times, that is 50 lines of code. Obviously, we could write a method in the program that would take in the object and output it to be more efficient. Even better is to write the output method INSIDE the object itself. That way, any program that uses the object will have one line access to outputting the information. The other benefit is that since it is an instance method, it has direct access to the variables without the need for getters.
By default, every object has a toString method and that method is called when the object is output using System.out.print(). So, the following lines of code are equivalent:
System.out.println(obj.toString());
AND
System.out.println(obj);
Unfortunately, the default output for an object is its address in memory. To change this behavior, we must override the toString method. Overriding a method means to write the method exactly as it is written in its parent class (more about this in Inheritance). So, when writing a toString method, we must always use the following method signature:
public String toString()
Here is an example of the Dog class toString method:
public String toString(){
return 'Name:'+name+'
Cage:'+cage+'
Sound'+sound+'
Customer:'+customer;
}
Similar to how a constructor is required to set all of the instance variables, a toString method should show all of the instance variables. This is called the state of the object.
Get & Set
Encapsulation
Instance, or object, variables are declared immediately following the class declaration in a program. This makes the variables global, thus they can be referenced anywhere inside the class. It is important that class variable declarations pay attention to encapsulation.
Encapsulation is the idea that a class should manage/protect it's own information. We declare all variables in a class as private so that outside programs can't alter their values without being checked in a method. This is helpful for two reasons:
It allows programmers to check values that are being assigned to make sure that they are in an acceptable range.
It allows programmers to change their mind about acceptable ranges without effecting code that references the objects by other programs.
Other visibility modifiers include public (all can see) and protected (sub classes can see).
/**
* Class ClassName
* Represents a general class and all of its properties
* @author M. Engel
* @version 2005.9.11
*/
public class ClassName
{
//list all variables here
private int quantity;
private double taxRate;
private String id;
//list all methods here
}
Getters/Setters
In order to encapsulate and protect the private instance variables in our classes, we have made them private. This means that users of our classes do not have direct access to them (object.varName will not work). However, making variables private also means that we do not have access to see them (System.out.println(object.varName) will not work) or to alter their values after creation (object.varName=value; will not work).
For this reason, we need to create accessor (or getter) methods and mutator (or setter) methods in our classes. For each instance variable in a class, the designer decided if getter and setters are appropriate. In some cases, there may be variables whose values are determined by calculations and should not be set, or values that other methods need to access behind the scenes that users should not be able to access.
If we were writing a dog class to be used in a Veterinary Hospital, we may have the following setup:
public class Dog {
private String name;
private int cage;
}
When a Dog comes into the hospital and is entered into the system, its name is entered. While our users should be able to access the dog's name, they should not be able to change it. So the name variable will have a getter but not a setter. On the other hand, we will also assign the dog a cage. It may be that during the dog's stay, it needs to be moved to another cage. So, the cage variable will need both a setter and a getter. Also, remember that part of the reason for encapsulation is to verify the values of our instance variables. So, since the vet's office only has 20 cages, we will limit the incoming values in the setter to be between 1 and 20.
public class Dog {
private String name;
private int cage;
public String getName(){
return name;
}
public int getCage(){
return cage;
}
public void setCage(int c){
if(c>=1 && c<=20)
cage=c;
else
cage=1;
}
}
*Note the use of the words 'set' and 'get' in the method names to make it clear what the purpose of the method is.
Advanced OOP
.equals
Objects are reference type variables and therefore store the memory address of the object. So, if we try to compare two objects for equality using ==, the compiler will compare their memory addresses for equality instead of their values. For this reason, a .equals method should be provided when writing a class. The class designer should decide what makes two objects of the same class equal. For example, two Circles may be equal if they have the same center and radius OR two MusicNotes may be equal if they are the same pitch AND duration.
The signature of all .equals methods should be public boolean equals(Object o). Note that the method takes an Object parameter. Inside the method, that parameter will need to be cast to the class type. Before casting, the instanceof operator needs to be used to make sure that o is an instance of the class in which the code is being written.
public boolean equals(Object o){
if( o instanceof Dog){
Dog d = (Dog)o;
return this.getName().equals(d.getName()) && this.getCage()==d.getCage();
}
return false; //was not an instance of the Dog class
}
.compareTo
For the same reasons that we cannot use == to compare equality, we cannot use > or < for ordering. So, a .compareTo method has to be written for ordering. The compareTo method returns an int: positive if the object is greater than the parameter, 0 if they are equal and negative if the object is less than the parameter. A typical condition of the compareTo method is that two objects deemed equal by .equals should be deemed equal by compareTo. Finally, the compareTo method allows the class type to be the parameter, as opposed to the Object type.
public int compareTo(Dog d){
//order dogs by cage
return this.getCage() - d.getCage();
}
Other Methods
Classes often require other methods besides getters, setters, constructers, equals and compareTo. This is what makes the class useful in a program. Examples:
A Car class might need methods for stop(), turn() and drive().
A TrafficLight class might need a method called change() that is designed to set the color in the proper order.
A MusicNote class might need a method called play() that plays the note.
Feel free to write whatever methods are needed to make your classes useful.
Static Keyword
The word static means unchanging. In java, static is used to describe methods that do not access/alter/change private instance variables. For example, the Math class has a variety of methods that take input and alter that input, without storing any information in private instance variables. These Math methods are therefore static and called from the class name. The String methods, however, require a String type variable on which to operate. So, the String methods are mostly non-static and are called from a variable name.
The static keyword can also be applied to variables within a class. When creating class constants, that users may want to access outside of a class, they should be declared as static. For example, Math.PI is a class constant with a value of 3.1415.... Static variables that are not constants, are known as class variables and are not treated as private values of an object. They instead stay with the class. An example of their use is to create a self incrementing id number that can be automatically assigned to objects when created.
public class Computer{
private static int id=1;
private int myId;
private String model;
public Computer()
{
model='Dell';
myId=id;
id++;
}
}
Using the code above:
Computer c1=new Computer(); //has an id of 1
Computer c2=new Computer(); //has an id of 2
//the static id variable is currently set to 3