Objects and Classes

The Importance of Code Encapsulation

Simple programs can generally get by with just declaring a small number of variables; manipulating primitive data types, like numbers and characters; and dealing with the console for input and output.

However, when a more complicated task must be performed by the program, or when a rich graphical user interface is desired, things can easily get hairy for the programmer. Without a way to organize a large number of variables required and/or the relationships between them, errors inevitably start to creep into the code.

Often, without a way to separate the pieces of the code that deal with unrelated components, complex inter-dependencies can easily be created that have two very bad effects. First, attempting to fix one error can often create another one somewhere else. Second, it becomes very hard to re-use code from one project to another -- which leads to an inefficient development process.

Because of this, modern programming languages allow the programmer to encapsulate unrelated components of their programs into what one might call software "black boxes".

The Levels of Abstraction in Software Design

Programming in this way leads to certain natural levels of abstraction. As a metaphor for this, consider playing with LEGOs.

At one extreme, there are the folks that design and manufacture the individual LEGO pieces. They spend their time thinking about what pieces might be useful to the child LEGO-builder, and where little knobs and recesses should be placed on these pieces so that they can connect to one another. The programming equivalent of these folks at the LEGO factory would be the Systems Programmer. Systems programmers are extremely interested in the inner mechanisms of computers, both on the hardware side and software side, but generally are not directly involved with the end user of any one program.

In the middle, there are the children that use the LEGO pieces to build things. They don't care about how plastic is molded to create each shape, or why one piece has more knobs than another. They simply build things out of the LEGO pieces they have -- trucks, helicopters, houses, little people, and so on... These children are the analog to the Application Programmers.

Some children might decide to cannibalize components from one LEGO sculpture to build another. For example, one might pull a couple of wheel-assemblies from some previously-made LEGO monster trucks, re-use an old LEGO rocket as a laser gun, and throw in a pre-built LEGO person and a platform to create a LEGO moon buggy. Children that build in this way can be equated to application programmers too -- just ones that work at a higher level of abstraction.

Lastly, you have the children that aren't interested in building LEGO toys, but rather, in playing with LEGO toys. These children are the analog of the End Users, who don't care how Excel or Angry Birds or any other software application was made -- but rather, just want to be able to use such software for their intended purposes.

So, at each level in the software design hierarchy, you have people/programmers creating objects to be used by people on the next level up.

The Difference Between an Object and a Class

Many applications today are developed using what is called Object-oriented Programming (i.e., programming using objects).

For us, it is important to understand the distinction between an object and a class within the Java language.

An object is an entity that you can manipulate in your program. It consists of data fields (called "instance variables") that define the state of an object, and methods (called "instance methods") that provide instructions that accesses or modifies the state of an object

A class is a construct that (generally) defines objects of the same type -- that is to say, a set of objects with the same properties and set of actions they can perform. To speak metaphorically, if the transmission in my Dodge Dakota is an object, you might think of the blueprints for that transmission as the corresponding class. In this sense, every object created (or "instantiated") in a program is tied to some class.


A class can often be thought of as a blueprint for an object

Classes include definitions for all of the instance fields of an object (i.e., the "properties" of the associated objects), and definitions for all of the instance methods associated with an object (i.e., the "actions" that can be taken by the associated objects)

A Simple Example: The Circle Class

Suppose we want to write a program that will at some point need to find the area of various circles. We decide to create a Circle class to encapsulate those parts of our code so related.

Different circles can have different radii, and the length of a circle's radius certainly affects the area of that circle. As such, our Circle class should have a instance field for the size of the radius (perhaps simply called radius).

We will, of course, need to be able to calculate the area of each circle. Consequently, our Circle class should also define a instance method, perhaps called getArea() to this end.

There is a third thing this Circle class must do. Such a class must provide a special type of method, known as a constructor, which is used to construct and initialize objects from that class.

Below is how such a class might be coded:

public class Circle{
   double radius;    //Data field that stores
                     //the radius of the circle

   Circle() {        //This is a constructor that
      radius = 1.0;  //gives the circle created
   }                 //a default radius of 1.0

   Circle(double newRadius) {  //This is an alternate
      radius = newRadius;      //constructor that 
   }                           //gives the circle 
                               //created a radius of
                               //newRadius

   double getArea() {                   //this is the
      return 3.14159 * radius * radius; //method that
   }                                    //finds the 
                                        //area of the 
                                        //circle
}

A couple of things should be noted about the class definition above.

First, note that there are two constructors for the class, both named in agreement with the name of the class. Constructors must always share the same name as their class. This is how Java knows that they are constructors.

Multiple constructors are differentiated by the program based on the arguments they require. Note, the first constructor Circle() requires no arguments, while the second constructor Circle(double newRadius) requires one double parameter.

Also, notice the absence of a return type in the two constructors shown. Constructors never have a return type -- not even void.

It is possible for a class to be declared without a constructor. In this case, a no-argument default constructor with an empty body (i.e., one that doesn't do much) gets implicitly declared in the class.

Another thing that you should notice is the absence of a main() method. There need only be one main() method in a program (as this method just tells the Java Virtual Machine where to start executing code). Consequently, if your program consists of more than one class, some (most) of your classes may not have a main() method. (Even though most classes used don't require a main method, often programmers give each class they write a main method anyways, using it to test the class and make sure that it does what is claims it does.)

Declaring and Creating Objects

Once we have the class defined, we can create a reference variable to an object of that class much like we create a reference variable to a String or array. In the case of our Circle class, we can now create a reference variable (named myCircle) to a specific circle with the following:

Circle myCircle;  

Remember, declaring a reference variable is different than creating the item referenced by that variable. To create a new object, we invoke one of the class constructors for the class in question, using the new operator. As an example, to declare and create two new circle objects, we might use the following code:

Circle myFirstCircle;     //declares the refVar myFirstCircle
Circle mySecondCircle;    //declares the refVar mySecondCircle

myFirstCircle = new Circle();     //creates a circle object with
                                  //the first constructor of the 
                                  //Circle class.  Recall, this 
                                  //produces a circle with a 
                                  //default radius of 1.0

mySecondCircle = new Circle(5.0); //creates a circle object with
                                  //the second constructor of 
                                  //the Circle class, producing
                                  //a circle of radius 5.0

As we have done with other data types in the past, we can do the declaration and creation of an object in a single step, if we wish:

Circle myThirdCircle = new Circle(7.0); //declares and creates
                                        //a circle of radius 7.0

Accessing Objects

Referencing an Object's Data

To reference an instance field of an object, we simply append the instance field's name preceded by a "." to the end of the variable that references the object in question, as shown below:

objectRefVar.someInstanceField

So, for example, to print the radius of a circle object named myCircle, we use:

System.out.println( "the radius is " + myCircle.radius );

Invoking a Method Attached to an Object

Similarly, to invoke an instance method attached to an object, we use the following syntax:

objectRefVar.someInstanceMethodName(arguments)

As an example, we could print the area of a circle object named myCircle with:

System.out.println( "The area is " + myCircle.getArea() );

Primitive Data Types vs. Object Data Types

Understand that variables of a particular class type store references to the object of that class -- not the object itself.

This differs from variables of primitive data types (i.e., boolean, byte, short, int, long, float, double, and char) which store the actual value in question.

This difference directly impacts how we should conduct a number of common tasks like copying variables or passing variables to methods.

For example, consider the following code:

int n1;   //this is a variable that stores a primitive data type value
int n2;   //this is also a variable that stores a primitive data type value

Circle c1 = new Circle();  //this is a "reference variable" which "points to" 
                           //or "references" an object of type Circle
Circle c2 = new Circle();  //this is also a "reference variable" which "points to" 
                           //or "references" an object of type Circle

n1 = 5;   
n2 = 6;   

n1 = n2;    //n1 now has the value of n2, which is stored independently of n1

n1 = 4;     //only the value of n1 has changed -- n2 is still 6

System.out.println(n2);    //prints "6"

c1.radius = 5;   
c2.radius = 6;

c1 = c2;             //c1 and c2 now point to the same Circle object, 
                     //the object to which c1 used to point is now 
                     //inaccessible (it is "garbage") as nothing points to it

c1.radius = 4;       //this changes the radius of the single object to 
                     //which both c1 and c2 point

System.out.println(c2.radius);    //prints "4"

Don't Reference Variables and Methods of Uninitialized Objects

Suppose we declare a reference variable for a particular object, but forget to assign it (i.e., "point it towards") some object? What happens when we try to access some data field or method of that object? We get a compilation error, of course!

For example,

Circle c;
double r = c.getRadius();    //Compilation Error

Instance Fields of Primitive and Reference Type and Default Values

We've seen how objects can store data in their instance fields. It is important to note that we are not restricted to primitive data types in this capacity. The instance fields themselves can be reference variables to other objects. For example, the following Student class contains an instance field name of the String type -- and String is a REFERENCE TYPE (recall it is a class).

public class Student {
   String name;            //name has default value null
   int age;                //age has default value 0
   boolean isMathMajor     //isMathMajor has default value false (sniff!)
   char gender;            //gender has default value (char) 0
}

Note each of the instance fields below does not need to be initialized -- they have default values. This is true in general. Instance fields of numeric types have a default value of 0. Instance fields of type boolean have a default value of false. Instance fields of type char have a default value of (char) 0. And instance fields which are of reference type have a default value of null, which you can think of as a "reference to nowhere".

We can see this if we add to the class above the Main class shown below:

public class Main {
    public static void main(String[] args) {
        Student student = new Student();
        System.out.println("name " + student.name);
        System.out.println("age? " + student.age);
        System.out.println("isScienceMajor? " + student.isScienceMajor);
        System.out.println("gender? " + student.gender);
    }
}

This produces the following output:

name null
age? 0
isScienceMajor? false 
gender?

More on Default Values (or the lack thereof)

Warning! Java assigns no default value to local variables inside a method.


So the following code will produce a compilation error...

public class Test {
   public static void main(String[] args) {
      int x;    // x has no default value
      String y;   // y has no default value
      System.out.println("x is " + x);     //compilation error: x was not initialized
      System.out.println("y is " + y);     //compilation error: y was not initialized
   }
}

Different Categories of Variables
(Instance Fields, Parameters, and Local Variables)

As the above suggests, not all variables are created equal. There are essentially three types of variables you will encounter in a Java program:

  1. Instance fields, which belong to a particular object (i.e., one "instance" of a class);
  2. Local variables, which belong to a method; and
  3. Parameters, which are variables supplied to a method

Examples of all of these types can be found in the following class definition:


public class Circle{
  double radius;                                  //radius is an instance field

  Circle() {        
     radius = 1.0;  
  }                 

  Circle(double newRadius) {                       //newRadius is a parameter
     radius = newRadius;       
  }                                                                  

  double getArea() {  
     double area;                                   //area is a local variable
     area = 3.14159 * radius * radius; 
     return area;
  }                                    
}

Implicit and Explicit Parameters

There is one parameter for every method of an object that is supplied automatically to the method, regardless of what appears in the method signature (it can even be empty) -- a reference to the object itself! This motivates the following classification of parameters:

  1. An Explicit parameter
  2. The Implicit parameter

The this Keyword

Again, the this reference within a method of an object refers to the object itself. This is best explained by example. Consider again the Circle class defined by:

public class Circle{
  double radius;                                  

  Circle() {        
     radius = 1.0;  
  }                 

  Circle(double newRadius) {                       
     radius = newRadius;       
  }                                                                  

  double getArea() {  
     double area;                                   
     area = 3.14159 * radius * radius; 
     return area;
  }                                    
}

Note, in the getArea() method, we use the variable radius. Now suppose we later execute the following code:

Circle c1 = new Circle(5.0);
Circle c2 = new Circle(6.0);
System.out.println(c2.getArea());

When c2.getArea() is called in the third line, we shortly thereafter have the assignment

area = 3.14159 * radius * radius;

Which radius is this? There are two possibilities after all: c1.radius and c2.radius. Of course, we intend radius to mean the radius of the object that called the method: c2.radius.

When the method is called, the this reference is created and initialized to reference the object through which the method is invoked (in this case, c2). Then every reference to an instance variable (like radius) in the body of the method really stands for a reference to the instance variable of this object.

Given this, we could have replaced

area = 3.14159 * radius * radius;

with the following, and everything will still work great:

area = 3.14159 * this.radius * this.radius;

OK, I know what you are thinking -- why add the "this" at all? Doesn't this just make things more complicated?

Believe it or not, it can be very useful. Consider the following case where we are intending to set the instance variable "number" with a value supplied to a method named "setNumber()":

public class ThisTest {
   private int number;
   public void setNumber(int number) {
       number = number;                  //this is bad
                                         //which number is which?
   }
}

Here is the same method with the ambiguity removed:

public class ThisTest {
   private int number;
   public void setNumber(int number) {
       this.number = number;             //this is good -- "this.number" is the 
                                         //instance variable being
                                         ///set with the parameter named "number"
   }
}

The Life, Scope, and Initialization of Variables

The scope of a variable determines when the variable is created (i.e., when memory is set aside to contain the data stored in the variable) and when it becomes a candidate for destruction. This "destruction" consists of having its memory returned to the operating system for recycling and re-use and is called garbage collection.

How variables are initialized (whether they have a default value, or are assigned their first value another way) and the scope of a variable depends on what type of variable one is looking at -- the following table describes how these things differ from one type of variable to another:

  Lifetime / Scope Initialization
Instance Variables Instance variables are created when the corresponding object is constructed and die when object terminates. The scope of instance (and static variables, for that matter) is the entire class. They can be declared anywhere inside a class (but not in its methods). Instance variables can be initialized with default values or they may be explicitly initialized.
Local
Variables
Local variables are created when the associated method is invoked and die when that method exits. The scope of a local variable starts from its declaration, which must be in some method, and continues to the end of the block that contains the variable Local variables must be initialized explicitly before they can be used
Parameter Variables Parameter variables are created when the associated method is invoked and die when the method exits. The implicit parameter (i.e., "this") is initialized to be the object through which the method is invoked
Any explicit parameters are initialized to be the corresponding values supplied in the method call

Object-Oriented Programming (OOP), and the Java API

You have seen some of the basics of how to define new classes, but don't forget that with Java, you "stand on the shoulders of giants"! Many programmers before you have thought up all kinds of useful object that you can now employ in your own programs. Use these objects! Check the Java API library for the usage -- the required parameters and return values. There is a wealth of pre-built stuff there!

Instance Variables and Methods vs. Static Variables and Methods

Recall that instance variables belong to a specific object (or "instance") of a class. Similarly instance methods are invoked by a specific object or instance of a class.

There are other variables that belong not to any one object or instance of a class, but rather, they are shared by all the instances of the class. These are called static variables. Similarly, static methods belong to the entire class and not any one object. Static constants are final variables shared by all the instances of a class.

We can tell Java that a variable or method should be static by simply including the static modifier in their declaration. Here is our circle class again, with a couple of static items:

public class Circle{
  double radius;       
  static String definition = 
       "The locus of points a fixed distance from a given point."; 

  Circle() {        
     radius = 1.0;  
  }                 

 Circle(double newRadius) {                       
     radius = newRadius;       
  }                                                                  

 static double fractionOfArea(double radians) {
     return radians / ( 2.0 * Math.PI);
 }

  double getArea() {  
     double area;                                   
     area = 3.14159 * radius * radius; 
     return area;
  }                                    
}

Note, the "definition" of a circle given above is a general one that works for all circles -- so there is no reason to tie it to a particular object, so we make this variable static by including static in its declaration.

Likewise, the method for finding the fraction of the total area of a circle subtended by a central angle measured in radians does not depend on the radius of the circle in question. So again, there is no reason to tie this method to a particular object. As such, we make this method static by including static as a modifier for the method.

There are differences in how static vs. non-static methods are invoked:

We invoke non-static method (i.e., instance methods) by including a reference to the object to which they belong, while static methods and variables should instead be called using the class name as prefix, instead of the object reference. Consider the following example:

Circle c = new Circle(10);

System.out.println(c.radius);                        
//c.radius is an instance variable

System.out.println(c.getArea());                    
//c.getArea() is an instance method

System.out.println(Circle.definition);              
//Circle.definition is a static variable

System.out.println(Circle.fractionOfArea(Math.PI / 2.0));      
//Circle.fractionOfArea() is a static method
//Note: Math.PI is a static constant

If you are curious, the above produces the following output:

10.0
314.159
The locus of points a fixed distance from a given point.
0.25

Visibility, Accessor and Mutator Methods

By default, any constant, data field, or method can be accessed by any class in the same package -- but we can change that! All it takes is the presence of a visibility modifier in the declaration of each.

If we include the modifier public in the declaration of a class, data field, or method, then that class, data field, or method will be visible to any class in ANY package.

Alternatively, if we include the modifier private then the corresponding data or methods can be accessed ONLY by the declaring class.

Why would we care if other classes can or can't access the internal structure or classes we design (instance variables, methods, etc..)? Suppose you create a really useful class and everything about it -- all the variables and methods -- are public for all to see and access. If it is a useful class, other people start using it in their own programs. But many "hard-wire" your class into their programs -- having their programs directly modifying the instance fields and maybe even calling some smaller "helper methods" that you wrote to support the methods more pertinent to your class. A couple of years pass and you realize there are much more efficient ways to implement your class. The new implementation would be faster, and more powerful -- but you can't change anything about the internal structure of your existing class because it would break all of the programs that now rely on your class to do their work!

If we never reveal the internal structure of our class (i.e., if we make the variables and some other methods "private") and force all other programs to communicate with this class through well-defined channels (i.e., special "public" methods), then we avoid the above situation entirely. Good encapsulation allows us to swap out the whole engine of our "black-box" class and as long as we replace it with something that works in the same way (i.e., has the same "public" methods), nobody is the wiser. In this way programming advances can happen all along the chain -- from low-level classes to extremely complicated ones -- at the same time, without anybody messing anybody else up and forcing them to start back at square zero.

To this end, variables used in a class (i.e., instance variables, static variables) should always be private -- this protects the data they store and keeps the class easy to maintain. When we need to let other classes modify these variables, we provide them with "getter" and "setter" methods for doing so.

Let's look at a Circle class with all these considerations taken into account:

public class Circle{

  private double radius;    //this is a instance field - should always be private

  public Circle() {         //constructors need to be public to do their job!
     radius = 1.0;  
  }                 

  public Circle(double newRadius) {    //another constructor (always public)
     radius = newRadius;       
  }                                                                  
 
  public double getRadius() {   //this is called a "getter" (an accessor)
     return radius;                
  }

  public void setRadius(double newRadius) {  //this is a "setter" (a mutator)
      this.radius = newRadius;
  }

  private double radiusSquared() {  //this should only used by getArea() below
      return radius * radius        //so we make it private
  }
 
  double getArea() {    //this is also an accessor method, even though we
     double area;       //don't internally store the area in a separate variable
     area = Math.PI * radiusSquared; 
     return area;
  }                                    
}

Of course the programs that use a given class need some way to store, access, and/or change the data stored in objects of the class in question. This is where the accessor and mutator methods indicated above come into play...

Accessor methods only access the values of instance variables of an object without modifying them, while mutator methods modify the values of instance variables. The simple "getter" methods are a type of accessor method, and the simple "setter" methods are a type of mutator method.

We need to be particularly mindful about the consequences of methods that themselves return references to other objects -- for if a method returns a reference to a intance variable object, the internal structure of our class has not been completely encapsulated, as this reference variable could be used to modify an object of our class.

Immutable Objects and Classes

If the contents of an object can't be changed once the object is created, the object is called an imutable object and its class is called an immutable class

Note that a class with all private variables and without mutators is not necessarily immutable! Consider the following classes:

public class Student {
   private int id;
   private BirthDate birthdate;

   public Student(int ssn, int year, int month, int day) {
       id = ssn;
       birthDate = new BirthDate(year, month day);
   }
   
   public int getId()  {
       return id;
   }

   public BirthDate getBirthDate() {
       return birthDate;   //This is a not a mutator method, but is
                           //an accessor method that returns a reference
                           //to a mutable object
   }
}
public class BirthDate {
    private int year;
    private int month;
    private int day;

    public BirthDate( int newYear, int newMonth, int newDay) {
        year = newYear;
        month = newMonth;
        day = newDay;
    }

    public void setYear(int newYear) {
        year = newYear;
    }
}
public class Test {
    public static void main(String[] args) {
       Student student = new Student(111223333, 1970, 5, 3);
       BirthDate date = student.getBirthDate();
       date.setYear(2010);   //Now the student birth year is changed!
    }
}

For a class to be immutable, it must do ALL of the following:

Class Abstraction and Encapsulation

Class Abstraction is the idea of separating class implementation (i.e., how the data of a class is stored, what algorithms a class uses to manipulate its data or provide information to other classes) from the use of the class (i.e., accessing data from objects in the class, modifying the same, using methods of the object and so on...).

The creator of a class needs to provide a description of the class and let the user know how it can be used -- but the user of the class does NOT need to know how the class is implemented!

You can think of the class implementation as being a "black box" whose contents are hidden from its "clients". The only thing the client sees is the class contract (i.e., the signatures of public methods and public constants). The clients use the class through the class contract only.