Object Oriented Concepts in Java – Part 2

Object Oriented Programming (OOP) is a powerful force in the landscape of software development today. Some say (quite correctly, in my opinion) that the development efforts behind large software projects such as Microsoft Office simply would not be possible to manage without the modularity and code reuse made possible by today’s object oriented languages. Others just prefer OOP because it pays better and it’s more fun!

Whatever your reasons for learning the principles of OOP, Java is an ideal language in which to get your feet wet. It’s friendly enough to beginners that you shouldn’t be overwhelmed by complicated syntax if you’re comfortable with basic programming concepts, and yet it is a truly complete, object oriented language, unlike many other Web development languages such as Perl, PHP, and ASP, which merely provide object oriented features.

This article is both the fourth in SitePoint’s series on developing dynamic Web sites with Java, and the second of two parts that focus on teaching you what you need to know about Object Oriented Programming to take full advantage of what the Java language has to offer. If you have not read the previous articles in this series, I would definitely recommend backing up and starting from the beginning, as the concepts presented here rely on your knowledge of all that has come before.

In Part One, we looked at the basic concepts of classes, Objects, properties, and methods. We developed a simple class called Tree as well as a program to instantiate a few Trees and test out their height property and their grow method. After a brief look at inheritance, where we developed a subclass of Tree called CoconutTree, we looked at the issues you will face when copying and comparing Objects in Java. In Part Two, we’ll pick up right where we left off by learning some more advanced types of methods and properties. We’ll learn how these may be used to design better classes that exhibit some important features of good object oriented software design. In addition, we’ll have a look at some advanced concepts in class design, and I’ll provide an explanation of class packages, which let you organize your classes into groups.

With all the formalities out of the way, let’s get started!

Passing Parameters and Returning Values

Most of the methods we have looked at so far have been of a special type. Here is a declaration of one such method, the pickNut method for the CoconutTree class that we developed in Part One:

  public void pickNut() {  
   numNuts = numNuts – 1;  
 }

What makes this, and the other methods we have looked at so far, special is the fact that it doesn’t require any parameters, nor does it return a value. As you’ll come to discover as we look at more practical examples of Java classes later in this series, most methods do one or both of these.

Parameters are pieces of information that must be provided when invoking a function to completely specify the action to be taken. For example, if we wanted to make the pickNut method above more powerful, we could give it a parameter to indicate the number of nuts to be picked:

  public void pickNut(int numberToPick) {  
   numNuts = numNuts – numberToPick;  
 }

In this new version of the pickNut method, we have specified that the function takes an integer (int) parameter, the value of which is stored in a variable called numberToPick. The code of the method then uses it as the number to be subtracted from the numNuts property. Thus, we can now pick as many nuts as we want from a CoconutTree with a single invocation of the pickNut method. Here are a few sample invocations of pickNut:

CoconutTree ct = new CoconutTree(); // New tree  
 
// Presumably we grow a few nuts first...  
 
ct.pickNut(1); // Picks one nut  
ct.pickNut(5); // picks five nuts  
ct.pickNut(0); // Doesn't do anything  
 
int nuts = 10;  
ct.pickNut(nuts); // Picks ten nuts  
 
ct.pickNut(-1); // Picks -1 nut (??)

As this last line demonstrates, there is a problem with this method. Since it accepts any integer as the number of nuts to be picked, there is nothing stopping a program that uses it to pick a negative number of nuts. Looking at the code of our method, this would actually just add more nuts to the tree, but we should not allow operations on our object that do not make sense in the real world. Another operation that would not make sense would be to pick more nuts than there are available for picking on the tree! With the existing code, this would result in our tree reporting a negative number of nuts — hardly a realistic situation.

These two problems reveal an important issue when designing methods that require parameters. You should always make sure that the value passed to a method makes sense before using it. Even if you’re only planning on using a class in your own programs, it’s surprising easy to forget what values will and will not cause problems when you aren’t checking the values automatically.

The following modified version of pickNut checks the parameter to make sure that it is not negative, and that it is no larger than the number of nuts on the tree:

  public void pickNut(int numberToPick) {  
   if (numberToPick < 0) return; // Cannot pick negative number  
   if (numberToPick > numNuts) return; // Not enough nuts  
   numNuts = numNuts – numberToPick;  
 }

The return command immediately terminates the method. Thus, the operation of picking the nuts (subtracting from the numNuts property) will only occur if both of the conditions in the if statements are false. This ensures that our two constraints are met before we allow the picking operation to go ahead.

One problem still remains, here. How can the code that invokes the pickNut method know whether the picking operation was successful? After all, if the picking operation fails because one of the constraints was not satisfied, we don’t want our program to carry on as if it was able to pick the nuts. To resolve this issue, we must once again alter pickNut; this time, we will make it return a value:

  public boolean pickNut(int numberToPick) {  
   if (numberToPick < 0) return false;  
   if (numberToPick > numNuts) return false;  
   numNuts = numNuts – numberToPick;  
   return true;  
 }

Not only can methods receive parameter values when they are invoked, but they can also send a value back by specifying the value as part of the return command. In this new version of the code, we have replaced the word void in the method declaration with the word boolean. This indicates that the function will return a Boolean (true/false) value when it terminates. In this particular case, we have elected to return true if the picking operation succeeded, and false if it failed for any reason. This allows us to structure the code of the program that invokes the method as follows:

if (!ct.pickNut(10)) {  
 System.out.println("Error: Could not pick 10 nuts!");  
 System.exit();  
}  
nutsInHand = nutsInHand + 10;

The condition of the if statement calls pickNut with a parameter value of 10, and then checks its return value to see if it’s false (note the ! operator). If it is, an error message is printed out. The System.exit method then terminates the program immediately, which is a reasonable response to an unexpected error. Otherwise, the program proceeds as usual.

Another common use for return values is to create methods that perform some common calculation and return the result for the program to use. There will be plenty more examples in the rest of this series for you to learn from.

Class Packages

For much of Part One, we worked with a class called Tree. Now, while Tree isn’t an especially original name for a class, it fits the class perfectly. The problem is that the same name might fit another class just as well, and you’ll have a naming conflict on your hands. Such conflicts aren’t too serious when you get to write all your own classes; however, when you need to bring in a set of classes that someone else wrote for use in your program, things can get messy.

Consider, for example, what would happen if you’ve designed all of the classes to handle the logic for a program that will track the sales of buttons for clothing. In such a case it would be natural to have a class called Button, but then your boss tells you he or she wants a nice, graphical user interface for the program. To your dismay, you find that the class built into Java for creating buttons on user interfaces is called (you guessed it) Button. How can this conflict be resolved without having to go back through your code and change every reference to your Button class?

Class packages to the rescue! Java provides class packages (usually called just ‘packages’) as a way of grouping together classes according to their purpose, the company that wrote them, or whatever other criteria you like. As long as you ensure that your Button class is not in the same package as Java’s built-in Button class, you can use both classes in your program without any conflicts arising.

By default, classes you create reside in the default package, an unnamed package where all classes that are not assigned packages go. For most of your programs it is safe to leave your classes in the default package. All of Java’s built-in classes as well as most of the classes you will find available on the Internet and from other software vendors are grouped into packages, so you usually don’t have to worry about your classes’ names clashing with those of other classes in the default package.

You will want to group your classes into packages if you intend to reuse them in future projects (where new class names may clash with those you want to reuse), or if you want to distribute them for use by other developers (where their class names may clash with your own). To place your class in a package, you simply have to give the name of the package on a line at the top of your file. The convention is to use "com." followed by the name of your company as your package name. For example, classes that we develop at SitePoint.com are grouped in the com.sitepoint package by adding the following line to the top of our java files:

package com.sitepoint;

Be aware that when a class that resides in a package is compiled, the class file will be placed in a directory based on the name of the package. For example, compiling Button.java that belongs to package com.sitepoint creates the Button.class file in the com/sitepoint/ subdirectory of the current directory. To run or otherwise make use of a class in such a package, you should refer to it as if it were in the directory that contains the com subdirectory. So, to run the com.sitepoint.MyProgram class, you should go to the directory containing com (which contains sitepoint, which in turn contains the MyProgram.class file) and type:

C:javadev> java com.sitepoint.MyProgram

Java will automatically look for com/sitepoint/MyProgram.class.

As it turns out, the Button class built into Java is actually in the java.awt package, which also contains all of the other classes for creating basic graphical user interfaces in Java (AWT stands for Abstract Windowing Toolkit, in case you were wondering). Thus, the fully qualified name of Java’s Button class is java.awt.Button. To make use of this class without your program thinking that you’re referring to your own Button class, you can use this full name instead. For example:

// Create a Java Button   
java.awt.Button b = new java.awt.Button();

In fact, Java requires that you use the full name of any class that is not in the same package as the current class!

But what if your program doesn’t have a Button class to clash with the one built into Java? Spelling out the full class name every time means a lot of extra typing. To save yourself this annoyance, you can import the class into the current package by putting the following line at the top of your .java file (just below the package line, if any):

import java.awt.Button;

Once it’s imported, you can use the class by its short name (Button) as if it were part of the same package as your class.

Another convenient feature allows you to import an entire class package into the current package. This comes in handy again when creating user interfaces for your program, because to create a decent interface you might easily have to use a dozen or more classes from the java.awt package, and listing each by name on a separate import line can become as tedious as typing the full name of the class in your code. To import the entire java.awt package for use in a class without having to type their full names, you can add the following line to the top of the file:

import java.awt.*;

The java.lang package, which contains all the most basic classes of the Java language (e.g. the System class, which we have been using in the form of the System.out.println() method to display text on the screen), is automatically imported into every Java file automatically.

Before you can import or use the fully qualified name of a class to access it from another package, it must be declared public. Classes, by default, are only available for use by code in the same package. Obviously, this is not a desirable limitation if you are planning to distribute your code for use by others, or reuse your classes in multiple projects; therefore, any class that you foresee being useful to code outside of the class’ package should be made public. Doing this is as simple as adding the keyword public to the very start of the class declaration. For example, it would make sense to declare our Tree class public:

package com.sitepoint;   
 
public class Tree {  
 ...  
}

The same goes for CoconutTree:

package com.sitepoint;   
 
public class CoconutTree extends Tree {  
 ...  
}

Access Control Modifiers

As we have just seen, a class can be made public to allow code outside of its package to make use of it. Class members, which include properties and methods, can be similarly modified to control access to them. Like classes, members have a default access setting that restricts access to code within the same package. Consider the following sample declarations:

  int numNuts = 0;    
   
 boolean pickNut(int numberToPick) {    
   ...    
 }

Even if the class is declared public, the value of the above numNuts property (assuming it is declared as the property of an Object class) may only be accessed or modified by code in the same package. Similarly, the pickNut method shown above may only be invoked by code in the same package as the class.

As with classes, properties and methods may be declared public:

  public int numNuts = 0;    
   
 public boolean pickNut(int numberToPick) {    
   ...    
 }

Any code (from any class in any package) can access and modify a public property value or invoke a public method.

Properties and methods have two additional access settings that classes do not have. The first is private:

  private int numNuts = 0;    
   
 private boolean pickNut(int numberToPick) {    
   ...    
 }

Only code in the same class can access private properties and methods. If you want to store information in an object that is only useful to the object itself or other objects of the same class, or if you want to restrict access to the information (as we’ll see in the next section, this is part of good class design!), then you should use a private property for it. Likewise, methods that perform internal calculations or are otherwise not useful to other classes should be declared private.

The final access setting that methods and properties may take is protected:

  protected int numNuts = 0;    
   
 protected boolean pickNut(int numberToPick) {    
   ...    
 }

The protected mode is very similar to the default mode, in that it refuses access to code outside of the class’ package, but it introduces one exception: subclasses of the current class (i.e. classes that extend this class) may also access protected members.

Distinguishing the situations in which each access control setting is appropriate takes a little experience. It’s tempting at first to just declare everything public to save yourself the trouble of worrying when something is accessible and when it isn’t. While this will certainly work, it is definitely not in the spirit of object oriented programming. Code that you plan to reuse or distribute, especially, will benefit from being assigned the most restrictive access control settings that are appropriate. One reason for this is illustrated in the following section.

Encapsulation with Accessors

Previously, we modified the pickNut method so that it would not accept too high a number, which would cause our CoconutTree to think it contained a negative number of coconuts. But there is a much simpler way to produce this unrealistic situation:

CoconutTree ct = new CoconutTree();     
ct.numNuts = -10;

How can we protect Object properties from being assigned values like this that don’t make sense? The solution is to make the properties themselves private, and only permit access to them using methods. Here is an updated version of our CoconutTree class that makes use of this technique:

1  package com.sitepoint;     
2    
3  public class CoconutTree extends Tree {    
4    private int numNuts = 0;      
5    
6    public void growNut() {    
7      numNuts = numNuts + 1;    
8    }    
9    
10   public boolean pickNut(int numToPick) {    
11     if (numToPick < 0) return false;    
12     if (numToPick > numNuts) return false;    
13     numNuts = numNuts – numToPick;    
14     return true;    
15   }    
16    
17   public int getNumNuts() {    
18     return numNuts;    
19   }    
20    
21   public boolean setNumNuts(int newNumNuts) {    
22     if (newNumNuts < 0) return false;    
23     numNuts = newNumNuts;    
24     return true;    
25   }    
26 }

As you can see on line 4, the numNuts property is now private, meaning that only code within this class is allowed to access it. The growNut and pickNut properties remain unchanged; they can continue to update the numNuts property directly (the constraints in pickNut ensure that the value of numNuts remains legal). Since we still want code to be able to determine the number of nuts in a tree, we have added a public getNumNuts method that simply returns the value of the numNuts property. As for setting the number of nuts, we have added a setNumNuts method that takes an integer value as a parameter. That value is checked to ensure that it is positive or zero (since we can’t have a negative number of nuts) and then sets the numNuts property to this new value.

These two new methods, getNumNuts and setNumNuts, are known as accessor methods; that is, they are methods used for accessing a property. Accessors are very typical of a well-designed object. Even in cases where any value is acceptable, you should make your objects’ properties private and provide accessor methods to access them. Doing this allows your programs to exhibit an important feature of object oriented programming called encapsulation.

Encapsulation means that the internal representation of an object is separated from the interface it presents to other objects in your program. In other words, a programmer that uses your class only needs to know what the public methods do, not how they work. The advantage is that you can change how your class works to improve performance or add new features without breaking any code that relies on the methods provided by your original class.

For example, if you decided you wanted to represent each coconut as an individual object of class Coconut instead of using a single integer variable to keep count, you could make the necessary changes and still have the same four methods as the implementation above. Old code that was written with the original interface in mind would continue to work as before, while new code could take advantage of the new features (which would of course be provided by new methods).

As an exercise, rewrite the Tree class so that it correctly encapsulates its height property.

Constructors

A constructor is a special type of method that is invoked automatically when an object is created. Constructors allow you to specify stating values for properties, and other such initialization details.

Consider once again our Tree class; specifically, the declaration of its height property (which should now be private and accompanied by accessor methods):

  private int height = 0;

It’s the "= 0" part that concerns us here. Why should all new trees be of height zero? Using a constructor, we can let users of this class specify the initial height of the tree. Here’s what it looks like:

  private int height;      
     
 public Tree(int height) {      
   if (height < 0) this.height = 0;      
   else this.height = height;      
 }

At first glance, this looks just like a normal method. There are two differences, however:

  • Constructors never return a value; thus, they don’t have a return type (void, int, boolean, etc.) in their declaration.
  • Constructors have the same name as the class they are used to initialize. Since we are writing the Tree class, its constructor must also be named Tree. By convention, this is the only case where a method name should be capitalized.

So dissecting this line by line, we are declaring a public constructor that takes a single parameter and assigns its value to an integer variable height. Note that this is not the object property height, as we shall see momentarily. The second line checks to see if the height variable is less than zero. If it is, we set the height property of the tree to zero (since we don’t want to allow negative tree heights). If not, we assign the value of the parameter to the property.

Notice that since we have a local variable called height, we must refer to the height property of the current object as this.height. this is a special variable in Java that always refers to the object in which the current code is executing. If this confuses you, you could instead name the constructor’s parameter something like newHeight. You’d then be able to refer to the Object property simply as height.

Since the Tree class now has a constructor with a parameter, you must specify a value for that parameter when creating new Trees:

Tree myTree = new Tree(10); // Initial height 10

Overloaded Methods

Sometimes it makes sense to have two different versions of the same method. For example, when we modified the pickNut method in the CoconutTree class to require a parameter that specified the number of nuts to pick, we lost the convenience of being able to pick a single nut by just calling pickNut(). Java actually lets you declare both versions of the method side by side and determines which one to use by the number and type of the parameters passed when the method is called. Methods that are declared with more than one version like this are called overloaded methods.

Here’s how to declare the two versions of the pickNut method:

  public boolean pickNut() {       
   if (numNuts == 0) return false;      
   numNuts = numNuts – 1;      
   return true;      
 }      
     
 public boolean pickNut(int numToPick) {      
   if (numToPick < 0) return false;      
   if (numToPick > numNuts) return false;      
   numNuts = numNuts – numToPick;      
   return true;      
 }

One way to save yourself some typing is to notice that pickNut() is actually just a special case of pickNut(int numToPick); that is, calling pickNut() is the same as calling pickNut(1), so you can implement pickNut() by simply making the equivalent call:

  public boolean pickNut() {       
   return pickNut(1);      
 }      
     
 public boolean pickNut(int numToPick) {      
   if (numToPick < 0) return false;      
   if (numToPick > numNuts) return false;      
   numNuts = numNuts – numToPick;      
   return true;      
 }

Not only does this save two lines of code, but if you ever change the way the pickNut method works, you only have to adjust one method instead of two.

Constructors can be overloaded in the same way as normal methods. If you miss the convenience of being able to create a new Tree of height zero, you can declare a second constructor that takes no parameters:

  private int height;       
     
 public Tree() {      
   this(0);      
 }      
     
 public Tree(int height) {      
   if (height < 0) this.height = 0;      
   else this.height = height;      
 }

Note that we have once again saved ourselves some typing by implementing the simpler method (Tree()) by invoking a special case of the more complex method (Tree(0)). In the case of a constructor, however, you call it by the special name this.

Advanced Inheritance: Overriding Methods

I covered inheritance in Part One of this article, but I left out one advanced issue for the sake of brevity that I’d like to cover now: overriding methods. As you know, an object of class CoconutTree inherits all of the features of the Tree class, on which it is based. Thus, CoconutTrees have grow methods just like Trees do.

But what if you wanted CoconutTrees to sprout new coconuts when they grew? Sure, you could call the growNut method every time you caused a CoconutTree to grow, but it would be nicer if you could treat Trees and CoconutTrees exactly the same way (i.e. call their grow method) and have them both do what they’re supposed to do when objects of their type grow.

To have the same method do something different in a subclass, you must override that method with a new definition in the subclass. Put simply, you can re-declare the grow method in the CoconutTree class to make it do something different! Here’s a new definition for grow that you can add to your CoconutTree class:

public void grow() {
height = height + 1;
growNut();
}

Simple, right? But what if you added new functionality to the grow method in the Tree class? How could you make sure that this was inherited by the CoconutTree class? Like in our discussion of overloaded methods, where we implemented a simple method by calling a special case of the more complicated method, we can implement a new definition for a method in a subclass by referring to its definition in the superclass:

  public void grow() {        
   super.grow();        
   growNut();        
 }

The super.grow() line invokes the version of grow defined in the superclass, thus saving us from having to reinvent the wheel. This is especially handy when you are creating a class that extends a class for which you do not have the source code (e.g. a class file provided by another developer). By simply calling the superclass versions of the methods you are overriding, you can ensure that your objects aren’t losing any functionality.

Constructors may be overridden just like normal methods. Here’s a set of constructors for the CoconutTree class, along with the new declaration of the numNuts property without an initial value:

  private int numNuts;        
       
 public CoconutTree() {        
   super();        
   numNuts = 0;        
 }        
       
 public CoconutTree(int height) {        
   super(height);        
   numNuts = 0;        
 }        
       
 public CoconutTree(int height, int numNuts) {        
   super(height);        
   if (numNuts < 0) this.numNuts = 0;        
   else this.numNuts = numNuts;        
 }

The first two constructors override their equivalents in the Tree class, while the third is completely new. Notice that we call the constructor of the superclass as super(). All three of our constructors call a constructor in the superclass to ensure that we are not losing any functionality.

Static Members

If you went back and examined every code example we have seen so far, there should only remain one keyword that puzzles you. Surprisingly, it appears in the very first article in this series, and at the beginning of every Java program we have written so far:

  public static void main(String[] args) {

In case you didn’t spot it, the keyword in question is static. Both methods and properties can be declared static. Static members belong to the class instead of to Objects of that class. Before I explain why the main method is declared static, let’s look at a simpler case.

It might be useful to know the total number of Trees that had been created in our program. To this end, we could create a static property called totalTrees in the Tree class, and modify the constructor to increase its value by one every time a Tree was created. Then, using a static method called getTotalTrees, we could check the value at any time by calling Tree.getTotalTrees().

Here’s the code for the modified Tree class:

public class Tree {         
       
 private static int totalTrees = 0;          
 private int height;        
       
 public Tree() {        
   this(0);        
 }        
       
 public Tree(int height) {        
   totalTrees = totalTrees + 1;        
   if (height < 0) this.height = 0;        
   else this.height = height;        
 }        
       
 public static int getTotalTrees() {        
   return totalTrees;        
 }        
       
 ...        
}

Static members are useful in two main situations:

  • When you want to keep track of some information shared by all members of a class (as above).
  • When it doesn’t make sense to have more than one instance of a property or method.

The main function is an example of the latter case. Since it doesn’t make sense to have more than once instance of the main function (i.e. a program can only have one program), it is declared static.

Another example of a static member that we have seen is the out property of the System class (also known as System.out, we have used its println method on many occasions). Since there is only one system on which this program is running, it is represented by a class containing static properties (e.g. out) and methods (e.g. exit), rather than an instance of a class.

Don’t worry too much if you can’t quite wrap your head around the reasoning behind the use of static members in the program and the System classes. As long as you can grasp how the tree-counting example above keeps track of the total number of Tree objects created, you’re in good shape.

Summary

I wouldn’t blame you for feeling a little overwhelmed at this point. If you’re anything like me, your first impression of Object Oriented Programming (OOP) in Java is that it is extremely powerful, extremely complicated, and very different from anything you have worked with before.

Fortunately, you get used to it.

As you follow along through the rest of this series, the sometimes frightening concepts presented in this article will blend into a natural way of working with software components that model real-world things and work together in sensible ways to achieve the desired outcome. The more Java you see and write yourself, the more you’ll get used to this way of working, and the less you’ll want to return to the old way of doing things. Ultimately, Object Oriented Programming is more fun!

You now have an effectively complete grasp of the Java language itself. What remains for you to learn before you can put that knowledge to practical use is a set of classes. Java includes packages of classes for performing all sorts of tasks, from building graphical user interfaces to creating dynamic Web sites. In the next article in this series, I’ll introduce you to the classes related to building Java Servlets, the most basic component of Java-powered dynamic Web sites.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

No Reader comments

Comments on this post are closed.