Main
Introduction
Message Board
Tutorials
Chapter 1
Chapter 2
Chapter 3
Chapter 4
Chapter 5
Chapter 6
About
Contact Us
III. Object-Oriented Programming

Introduction

As we've said before, Object-Oriented Programming (OOP) took and extended the concepts of procedural programming to a new level. It also incorporated into itself the basic concept of data structures.

In this chapter, we'll talk about the many aspects of OOP and their uses. In the next chapter, we will discuss focus on advanced uses of data structures.

A. Crash Course of Objects in Java

What are objects?

Once again, OOP stands for Object-Oriented Programming, and so naturally it revolves around the concept of "objects." But what is an object in computer science? An object is something that can store data and also perform certain processes with data. The things the object can do are described in its methods, which we've discussed earlier. The data an object stores are generally called fields or data members.

For example, let's consider an object "Song" which depicts a song. The following figure indicates possible methods and fields of Song.

The fields of Song are basic variables that describe the object like title, author, length, etc. The methods of Song are play and stop, which are operations the object can perform.

Implementing Objects

So how do you write code for an object? This is where the concept of a class comes into hand. A class is code that describes an object. You might recall that when we wrote programs last section, in the beginning of the code we have a line public class Something with the "Something" being the name of the program. Does that make Something a class? It does, and in fact, a program in Java is a class. When the program is compiled and executed, the main method of the class is automatically called.

This is what a class might look like in Java.

class TwoNumClass {
	
    // declaration of fields
    private int int1;
    private int int2;
	
    // methods
    public int GetSumInts ()
    {
        return int1+int2;
    }

    public void SetNumOne (int a)
    {
        int1 = a;
    }

    public int GetNumOne ()
    {
        return int1;
    }

	public void SetNumTwo (int a)
    {
        int2 = a;
    }

    public int GetNumTwo ()
    {
        return int2;
    }

}

The first part of the class contains the declaration of its fields. Note that we put the word keyword private in each declaration. This is done so that when an outside class tries to access an object described by our class, it will not be able to directly alter or even access the value of the fields. This is a common practice, but if you don't want this type of behavior, you can either not specifically put in any modifier or use public or friendly, which we will describe in more detail later.

But then how does one modify the private fields' values when they use the object? That's why there are methods like NumOne above that serve to set the value of the fields.

Furthermore, to allow for retrieval of the fields' values, we have methods like GetNumOne above that return the value of the private field.

Creating Objects

Once you've written a class, you can use it as the prototype to create objects. There are two steps to this. First, you define a variable as an object. Then, you create a new object in memory and have your variable point to it.

The definition step is:

(modifiers) <Object type> <Object name>;

The creation step is:

<Object name> = new <Object type>(arguments);

However, these two steps are generally put together for simplicity.

(modifiers) <Object type> <Object name> = new <Object type>(arguments);

Thus, if we want to create an object that we described in our TwoNumClass earlier, we can say:

TwoNumClass object1 = new TwoNumClass();

In technical terms, we have just created an instance or object of the class TwoNumClass. Now we can access the methods of the class. This is done using the "dot operator" which basically means a plain dot put after the name of the object variable. Then we follow the dot with the name of the method we want to call or the field we want to access (however in this particular example, we cannot access the fields as they were set as private in the class). For example, say we want to set the values of the two numbers. We call the SetNumOne and SetNumTwo methods. Then we can use GetNumOne and GetNumTwo to access the values. Lastly, we try out the GetSumInts method. Note that for all the methods we have to put the empty parenthesis. When testing, remember to put this code in the main method of whatever class your program corresponds to and the class definitions outside of that class.

object1.SetNumOne(12);
object1.SetNumTwo(21);

System.out.println("1st number is "+object1.GetNumOne());
System.out.println("1st number is "+object1.GetNumTwo());
System.out.println("The sum is "+object1.GetSumInts());

Constructors

It is desirable to be able to set the initial values of important fields right when an instance of the class is declared. This can be done with a special type of method called a constructor. When you make a constructor, you give it the same name as the class. You would usually set the parameters so that the user can pass in the values of the important fields. Then inside, you put in the initializations. For example, we could implement a constructor so that we can specify the initial values of int1 and int2 whenever we create an instance of TwoNumClass.

// TwoNumClass constructor
TwoNumClass(int a, int b) // note: you should NOT 
                          // put in a return type
{
  int1 = a;
  int2 = b;
}

This code has to be put inside of the class, usually where the methods are for convenience. The constructor can only be used when you create a new object. That is, you cannot call it again later just like any method to change the values of the fields. The following is an example of the correct use of our constructor.

TwoNumClass object2 = new TwoNumClass(10,20);

You can set more than one constructors in the same class, to correspond with different parameters. For example, you might have one that just sets the first number to allow for just one integer in the parameter. If you do not make any constructor, Java automatically creates a default one for you that won't do anything special. However, once you have set just one constructor, if the user entered in no parameters or a different set of parameters, your program will not run correctly as Java no longer provides a default constructor and tries to run your constructor but with non-matching parameters. Therefore, you must create your own default constructor with empty parantheses for the parameters.

To simplify coding for additional constructors, sometimes the this method is used. Putting this in the beginning of a constructor with appropriate arguments will call another constructor you put in your class. For example,

// All the TwoNumClass constructors
TwoNumClass()
{
  this(0,0); // everything set to 0 if no arguments
}
TwoNumClass(int a)
{
  this(a,0); // if 1 arg. is provided, set to 1st no.
}
TwoNumClass(int a, int b) // original constructor
{
  int1 = a;
  int2 = b;
}

A Revisit of Scope

Now that you know about classes, there is a lot of stuff to uncover that we have previously avoided. Remember the "modifiers" public, private, and static that always lurked in declarations? What exactly do they mean?

  • A public method or data member is assessible by any outside class using the object.
  • A private method or data member is assessible solely by methods of the same class.
  • A static method or data member is one that is shared among all created instances of a the class. That is, say you created two objects of the same class; the two will share the same static methods and data members. static methods and data members can be both public or private.

In Java, the default (if you don't specify anything in particular) is public.

So when would you make something private? As said before, sometimes you want to make variables that contribute to the inner workings of your class invisible. Furthermore, if you leave your data members private and write public methods that allow people using the class to modify things, if you ever you change the object, you could continue to have the same methods work by just re-coding the methods to fit the new way your data's organized. Then the code written earlier to use the object can be reused with little or no modification.

It is important to know that static methods cannot access non-static variables or methods. That's why if you try to call a non-static method from main, you will get an error message. This is due to the fact that a static method is shared among all instances of a class, so if a static method were allowed to call a non-static variable, it would not know from which instance of the class the non-static variable should be taken. This concept is illustrated in this diagram:

But what are static variables and methods good for? First of all, you don't have to create an object of the class to use its static methods as they are not instance-dependent. All you have to do is put the class name followed by a period and the name of the static method or data to use it. Some of the utility classes provided by Java (such as those for math, etc.) code their methods as static so that you don't have to create an instance of the class to use them. All you would have to do is "include" the package containing that class in the beginning of your program.

Since static methods are not instance-dependent, how then could you initialize them? Besides the initialize as your create method that we usually use (e.g. static int num1=10;), for more complex problems, one can use a static "initialization block." Just put your initialization code within braces {} in your class with the keyword static in front of the braces. That way, all code within the braces will be marked as static initialization code and run before an instance is created (note: without putting the keyword static, you would have a regular initialization block that is run whenver an instance is created).

Class Methods/Variables vs. Instance Methods/Variables

Instead of "data members" or "fields," sometimes you hear people say "instance variables" or "class variables." While the two are frequently used interchangeable, there is actually a different between them. A class variable is a variable declared in the class (a data member) as static; that is, the variable is associated with the class, not a particular instance of the class. An instance variable, on the other hand, is a variable that is created and associated with a particular instance of the class. The same can be3 said about class methods and instance methods.

B. Encapsulation

One of the true powers of OOP comes from the concept of encapsulation. What is encapsulation? Encapsulation refers to the practice of writing code that use classes without knowing their inner workings. One would use solely the methods provided by the class, and if the the class is ever changed for improvement, the old code would still be supported so long as the programmers keep the original methods' names and parameter lists intact.

Encapsulations also allow for multiple ways to do the same thing. For example, remember our discussions of factorials earlier? We implemented a recursive and an iterative way to do the factorial. However, if we had just put the method in a factorial class and named the method Factorial regardless of which implementation the code is using, another person can just call the Factorial method and get the job done with. If we then later change the algorithm for the Factorial method, the other person can still use the same method call and argument.

If you have had some background in procedural programming, you might think: this sounds a lot like "modularity"! Modularity refers to the procedural programming concept of splitting a big task into modules that can be reused. Indeed, at first glance, encapsulation resembles the traditional modularity concept, but there is really a world of difference with the power of OOP.

First of all, you can have multiple instances of a single class, whereas in procedural programming you do not have an option to use a procedure more than one time simultaneously. This feature is can be very important. Also, classes are no longer limited to code that solely performs operations; they can contain multiple methods and data members and thus are at further from procedures.a step further from procedures.

In sum, encapsulation is one of the major basic principles of OOP--a very important one.

C. Inheritance

Another important principle of OOP is called "inheritance." But to understand inheritance, first you have to understand the concept of a class hierarchy--the representation of one class superseded by a base class. The following diagram illustrates a class hierarchy, using sample characters from Blizzard's 1998 hit game Starcraft.

There's a base class called Person with some common properties of all units (e.g. health, attack power, etc.). Then, there are three "subclasses"-- Terran, Protoss, and Zerg that extends the base class Person with additional properties unique to each race. For example, in the game, Zerg units continuously regenerate health points. This could be demonstrated by adding a method to the Zerg class that will do the regeneration calculations. Protoss units, on the other hand, have not only the regular health meter but also a unique "shield meter," and we can add fields for this in the Protoss class.

Then each race has their different units, each with different attacks and attack powers, and here we could set all those values and also add additional methods for any unique spells or things the unit can do.

In correct OOP terminology, Person is the superclass (also known as the base class and the parent class) of Terran, and Marine is the subclass (derived class/child class) of Terran and so forth.

But why make the superclasses? We do that to reduce work. That way we don't have to code and recode everything when we do the final classes for each unit. This is a very useful feature of OOP and is the basic concept behind inheritance.

How does one use inheritance in Java? You can use the extends keyword. When you declare subclass with extends to some superclass, the subclass will automatically inherit all the methods and fields of the superclass. Then, inside the subclass' code, you only put in the differences. You can add in more methods and fields, or you can put another version of a previously made method with the same name. All you have to do for that is just write the method with the same name and Java will automatically use the new version for the subclass. You can have more than one level of inheritance, like in our diagram, where Marine is a subclass of Terran which is a subclass of Person.

The following code shows a working example of inheritance. First we have to construct a Person class:

class Person
{
  // Instance Variables
  String Type;
  int TotalHealth;
  int CurHealth;
  int XPos;
  int YPos;
  int GdStrength;
  int AirStrength;
  
  // constructor, setting initial position
  public Person(int a, int b)
  {
    XPos=a;
    YPos=b;
  }

  public void GdAttack(Person a)
  {
    a.CurHealth -= GdStrength;
  }

  public void GdAttack(Protoss a)
  {
    if(a.CurShield>0) {
      a.CurShield -= GdStrength;
    }
    else {
      a.CurHealth -= GdStrength;
    }
  }
}

Several things of importance here. First of all, I have a list of (greatly simplified) fields common to all units, such as those related to health and attack strength. There are also the x and y positions, which are needed in a real game to denote where the units are on the screen.

For methods, we have only put in a crude ground attack method named GdAttack which will take off health away from the target object by the amount of the attacking object's strength.

You will notice that there are two GdAttack methods. The first one has parameter Person while the second has paramter Protoss. This is because in the game, a unit of type Protoss will actually have shield layer that is harmed before the actual health is reduced, and in the second GdAttack we reduce the CurShield field (which you will see in the Protoss class definition below).

If Java sees two methods of the same name in a class, but with a different signature (parameter data type, name, number, etc.), it will call the appropriate one according to what arguments were passed in during the call. In this case, if the parameter is simply of type Person and not the subclass Protoss, it will evaluate using the first method. This type of behavior is a hallmark of OOP and is called method overloading.

Alas, here are the rest of the classes, Protoss, Terran, Dragoon, and Marine. Please note the use of the super method in the subclasses' constructors, which will automatically call the constructor of the base class above it, using whatever parameters are passed in.

Protoss

class Protoss extends Person
{
  public Protoss(int a, int b)
  {
    super(a,b);
  }
  
  int TotalShield;
  int CurShield;

  public void RegenShield()
  {
    if (CurShield<=TotalShield) {
      CurShield++;
    }
  }
}

Terran

class Terran extends Person
{
  public Terran(int a, int b)
  {
    super(a,b);
  }
}

Dragoon

class Dragoon extends Protoss
{
  public Dragoon(int a, int b)
  {
    super(a,b);
  }
  {
    Type="Dragoon";
    TotalHealth=100;
    CurHealth=100;
    TotalShield=80;
    CurShield=80;
    GdStrength=20;
    AirStrength=20;
  }
}
Marine
class Marine extends Terran
{
  public Marine(int a, int b)
  {
    super(a,b);
  }

  {
    Type="Marine";
    TotalHealth=40;
    CurHealth=40;
    GdStrength=6;
    AirStrength=6;
  }
}

As you can see, we've added in the new fields of CurShield and TotalShield as well as a RegenShield method in the Protoss class. While this looks cool and demonstrates the theoretical uses of OOP programming, this is not always the ideal solution. For example, it is sometimes useful to create an array of the base class (e.g. Person[]) and then assign subclass objects (e.g. Dragoon) to the elements of the array. However, once you do this, you will not be able to access any additional methods of the subclass (e.g. RegenShield) as the object is of the superclass type (Person), which does not have those methods. Sometimes, there are ways to get around this, with what is called object type casting, but that is not a good way to handle this. As a result, many people find that it's actually better to include all the important fields and methods in the base class, regardless of whether all the subclasses use them. For those that don't, one can just set the values to 0. So for instance, we could have had the shield variables in Person, but just set them as 0 for non-Protoss classes.

Nevertheless, we have done things the more obvious way to demonstrate inheritance in a clear fashion.

Below is code used to test our classes.

public class sc
{
  public static void main(String args[])
  {
    Dragoon obj1 = new Dragoon(100,100);
    Marine obj2 = new Marine(101,101);
    System.out.println(obj1.Type+"'s position: 
                   "+obj1.XPos+", "+obj1.YPos);
				   
    System.out.println(obj2.Type+"'s position: 
	               "+obj2.XPos+", "+obj2.YPos);
				   
    System.out.println("obj2: "+obj2.CurHealth
	                    +"/"+obj2.TotalHealth);
						
    obj1.GdAttack(obj2);
    System.out.println("obj2: "+obj2.CurHealth
	                   +"/"+obj2.TotalHealth);
					   
    obj2.GdAttack(obj1);
    System.out.println("obj1: "+obj1.CurShield+
	                 "/"+obj1.TotalShield+", "+
                            obj1.CurHealth+"/"+
					         obj1.TotalHealth);
  }
}

This ends our discussion of inheritance.

D. Polymorphism

In our Person class, we saw two definitions of the GdAttack method with different signatures. We said that was called "method overloading." In OOP, there is a very important concept somewhat similar to method overloading called polymorphism. Polymorphism refers to the behavior we can observe by having the methods with the same name and signature in a superclass and its subclasses. This type of automation is the beauty behind polymorphism. It is particularly useful when you have many subclasses that you want to share a method with but each class has a different version of the method. Then, you might have variable of the superclass type that you assign a subclass to and call the method. Normally, the superclass method is called, but if you write the code properly, the subclass method is called (even though the variable itself is of the superclass type). That way, no matter which subclass type was assigned to the variable, the matching method will be called.

The following code demonstrates the concept behind polymorphism. It uses the Random class provided by Java. Random's NextInt() method will generate a random number within the range you provide as an argument. To use random, we have to import the java.util.Random library.

import java.util.Random;

class Student
{
  public void ShowName()
  {
  }
}

class Jack extends Student
{
  public void ShowName()
  {
    System.out.println("This is Jack");
  }
}


class Joe extends Student
{
  public void ShowName()
  {
    System.out.println("This is Joe");
  }
}

class Jill extends Student
{
  public void ShowName()
  {
    System.out.println("This is Jill");
  }
}

public class poly
{
  public static void main(String args[])
  {
    Student[] Student = {new Jack(), new Joe(), new Jill() };

    Student test;

    Random select = new Random();
    for (int i=0;i<5;i++)
    {
      test = Student[select.nextInt(Student.length)];
      test.ShowName();
    }
  }
}

Note the empty definition of ShowName in the base class Student. We have to do this for proper polymorphic behavior since test is of type Student and if Student has no definition for the method, an error will be generated.

If the code is executed properly, you will get 5 randomly picked names of the three choices.

This ends our discussion of Object-Oriented Programming. There are a few additional OOP concepts that we haven't covered, but many of them are Java-specific and we leave the task to learn them to the reader.

(Please note: All Starcraft-related names and terms usd in our examples in this section eare trademarked to Blizzard Entertainment and are not endorsed by Thinkquest nor Team C0120962)

(c) 2001 Thinkquest Team C0120962. All rights reserved.