Skip to main content

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

All information submitted is secure.

  • Close [x]

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

  • Close [x]

Introduction to Java programming, Part 2: Constructs for real-world applications

More-advanced Java language features

J Steven Perry, Principal Consultant, Makoto Consulting Group, Inc.
Photo of J Steven Perry
J. Steven Perry is a software developer, architect, and general Java nut who has been developing software professionally since 1991. His professional interests range from the inner workings of the JVM to UML modeling and everything in between. Steve has a passion for writing and mentoring; he is the author of Java Management Extensions (O'Reilly), Log4j (O'Reilly), and the IBM developerWorks articles "Joda-Time" and OpenID for Java Web applications." In his spare time, he hangs out with his three kids, rides his bike, and teaches yoga.

Summary:  In Part 1 of this tutorial, professional Java™ programmer J. Steven Perry introduced the Java language syntax and libraries you need to write simple Java applications. Part 2, still geared toward developers new to Java application development, introduces the more-sophisticated programming constructs required for building complex, real-world Java applications. Topics covered include exception handling, inheritance and abstraction, regular expressions, generics, Java I/O, and Java serialization.

View more content in this series

Date:  19 Aug 2010
Level:  Introductory PDF:  A4 and Letter (904 KB | 53 pages)Get Adobe® Reader®

Comments:  

Next steps with objects

Part 1 of this tutorial left off with a Person object that was reasonably useful, but not as useful as it could be. Here you will begin learning about techniques to enhance an object like Person, starting with the following techniques:

  • Overloading methods
  • Overriding methods
  • Comparing one object with another
  • Making your code easier to debug

Overloading methods

When you create two methods with the same name but with different argument lists (that is, different numbers or types of parameters), you have an overloaded method. Overloaded methods are always in the same class. At run time, the Java Runtime Environment (JRE; also known as the Java runtime) decides which variation of your overloaded method to call based on the arguments that have been passed to it.

Suppose that Person needs a couple of methods to print an audit of its current state. I'll call those methods printAudit(). Paste the overloaded method in Listing 1 into the Eclipse editor view:


Listing 1. printAudit(): An overloaded method


public void printAudit(StringBuilder buffer) {
  buffer.append("Name="); buffer.append(getName());
  buffer.append(","); buffer.append("Age="); buffer.append(getAge());
  buffer.append(","); buffer.append("Height="); buffer.append(getHeight());
  buffer.append(","); buffer.append("Weight="); buffer.append(getWeight());
  buffer.append(","); buffer.append("EyeColor="); buffer.append(getEyeColor());
  buffer.append(","); buffer.append("Gender="); buffer.append(getGender());
}
public void printAudit(Logger l) {
  StringBuilder sb = new StringBuilder();
  printAudit(sb);
  l.info(sb.toString());
}

In this case, you have two overloaded versions of printAudit(), and one actually uses the other. By providing two versions, you give the caller a choice of how to print an audit of the class. Depending on the parameters that are passed, the Java runtime will call the correct method.

Two rules of method overloading

Remember these two important rules when using overloaded methods:

  • You can't overload a method just by changing its return type.
  • You can't have two methods with the same parameter list.

If you violate these rules, the compiler will give you an error.


Overriding methods

When a subclass of another class provides its own implementation of a method defined on a parent class, that's called method overriding. In order to see how method overriding is useful, you need to do some work on your Employee class. Once you have it set up, I'll be able to show you where method overriding comes in handy.

Employee: A subclass of Person

Recall from Part 1 of this tutorial that Employee might be a subclass (or child) of Person that has some additional attributes:

  • Taxpayer identification number
  • Employee number
  • Hire date
  • Salary

To declare such a class in a file called Employee.java, right-click the com.makotogroup.intro package in Eclipse. Choose New > Class..., and the New Java Class dialog box will open, as shown in Figure 1:


Figure 1. New Java Class dialog
New Java Class dialog in Project Explorer.

Type Employee as the name of the class and Person as its superclass, then click Finish. You will see the Employee class in an edit window. You don't explicitly need to declare a constructor, but go ahead and implement both constructors anyway. First, make sure the Employee class edit window has the focus, then go to Source > Generate Constructors from Superclass..., and you'll see a dialog that looks like Figure 2:


Figure 2. Generate Constructors from Superclass dialog
The project path to create a constructor.

Check both constructors (as shown in Figure 2), and click OK. Eclipse will generate the constructors for you. You should now have an Employee class like the one in Listing 2:


Listing 2. The new, improved Employee class

package com.makotogroup.intro;

public class Employee extends Person {

  public Employee() {
    super();
    // TODO Auto-generated constructor stub
  }

  public Employee(String name, int age, int height, int weight,
                  String eyeColor, String gender) {
    super(name, age, height, weight, eyeColor, gender);
    // TODO Auto-generated constructor stub
  }

}

Employee inherits from Person

Employee inherits the attributes and behavior of its parent, Person, and also has some of its own, as you can see in Listing 3:


Listing 3. The Employee class with Person's attributes

package com.makotogroup.intro;


import java.math.BigDecimal;

public class Employee extends Person {

  private String taxpayerIdentificationNumber;
  private String employeeNumber;
  private BigDecimal salary;

  public Employee() {
    super();
  }
  public String getTaxpayerIdentificationNumber() {
    return taxpayerIdentificationNumber;
  }
  public void setTaxpayerIdentificationNumber(String taxpayerIdentificationNumber) {
    this.taxpayerIdentificationNumber = taxpayerIdentificationNumber;
  }
  // Other getter/setters...
}

Method overriding: printAudit()

Now, as promised, you're ready for an exercise in overriding methods. You'll override the printAudit() method (see Listing 1) that you used to format the current state of a Person instance. Employee inherits that behavior from Person, and if you instantiate Employee, set its attributes, and invoke one of the overloads of printAudit(), the call will succeed. However, the audit that is produced won't fully represent an Employee. The problem is that it cannot format the attributes specific to an Employee, because Person doesn't know about them.

The solution is to override the overload of printAudit() that takes a StringBuilder as a parameter and add code to print the attributes specific to Employee.

To do this in your Eclipse IDE, go to Source > Override/Implement Methods..., and you'll see a dialog box that looks like Figure 3:


Figure 3. Override/Implement Methods dialog
Override/Implement Methods dialog box.

Select the StringBuilder overload of printAudit, as shown in Figure 3, and click OK. Eclipse will generate the method stub for you, and then you can just fill in the rest, like so:

@Override
public void printAudit(StringBuilder buffer) {
  // Call the superclass version of this method first to get its attribute values
  super.printAudit(buffer);
  // Now format this instance's values
  buffer.append("TaxpayerIdentificationNumber=");
    buffer.append(getTaxpayerIdentificationNumber());
  buffer.append(","); buffer.append("EmployeeNumber=");
    buffer.append(getEmployeeNumber());
  buffer.append(","); buffer.append("Salary=");
    buffer.append(getSalary().setScale(2).toPlainString());
}

Notice the call to super.printAudit(). What you're doing here is asking the (Person) superclass to exhibit its behavior for printAudit(), and then you augment it with Employee-type printAudit() behavior.

The call to super.printAudit() doesn't need to be first; it just seemed like a good idea to print those attributes first. In fact, you don't need to call super.printAudit() at all. If you don't call it, you must either format the attributes from Person yourself (in the Employee.printAudit() method), or exclude them altogether. Making the call to super.printAudit(), in this case, is easier.


Class members

The variables and methods you have on Person and Employee are instance variables and methods. To use them you either must instantiate the class you need or have a reference to the instance. Every object instance has variables and methods, and for each one the exact behavior (for example, what's generated by calling printAudit()) will be different, because it is based on the state of the object instance.

Classes themselves can also have variables and methods, which are called class members. You declare class members with the static keyword introduced in Part 1 of this tutorial. The differences between class members and instance members are:

  • Every instance of a class shares a single copy of a class variable.
  • You can call class methods on the class itself, without having an instance.
  • Instance methods can access class variables, but class methods cannot access instance variables.
  • Class methods can access only class variables.

Adding class variables and methods

When does it make sense to add class variables and methods? The best rule of thumb is to do so rarely, so that you don't overuse them. That said, it's a good idea to use class variables and methods:

  • To declare constants that any instance of the class can use (and whose value is fixed at development time).
  • To track "counters" of instances of the class.
  • On a class with utility methods that don't ever need an instance of the class (such as Logger.getLogger()).

Class variables

To create a class variable, use the static keyword when you declare it:

accessSpecifier static variableName [= initialValue];

Note: The square brackets here indicate that their contents are optional. They are not part of the declaration syntax.

The JRE creates space in memory to store each of a class's instance variables for every instance of that class. In contrast, the JRE creates only a single copy of each class variable, regardless of the number of instances. It does so the first time the class is loaded (that is, the first time it encounters the class in a program). All instances of the class will share that single copy of the variable. That makes class variables a good choice for constants that all instances should be able to use.

For example, you declared the Gender attribute of Person to be a String, but you didn't put any constraints around it. Listing 4 shows a common use of class variables:


Listing 4. Using class variables

public class Person {
//. . .
 public static final String GENDER_MALE = "MALE";
 public static final String GENDER_FEMALE = "FEMALE";
// . . .
 public static void main(String[] args) {
   Person p = new Person("Joe Q Author", 42, 173, 82, "Brown", GENDER_MALE);
 // . . .
 }
//. . .
}

Declaring constants

Typically, constants are:

  • Named in all uppercase.
  • Named as multiple words, separated by underscores.
  • Declared final (so that their values cannot be modified).
  • Declared with a public access specifier (so that they can be accessed by other classes that need to reference their values by name).

In Listing 4, to use the constant for MALE in the Person constructor call, you would simply reference its name. To use a constant outside of the class, you'd preface it with the name of the class where it was declared, like this:

String genderValue = Person.GENDER_MALE;


Class methods

If you've been following along since Part 1, you've already called the static method Logger.getLogger() several times — whenever you've retrieved a Logger instance to write some output to the console. Notice, though, that you didn't need an instance of Logger to do this; instead, you referenced the Logger class itself. This is the syntax for making a class method call. As with class variables, the static keyword identifies Logger (in this example) as a class method. Class methods are also sometimes called static methods for this reason.

Using class methods

Now you'll combine what you've learned about static variables and methods to create a static method on Employee.You'll declare a private static final variable to hold a Logger, which all instances will share, and which will be accessible by calling getLogger() on the Employee class. Listing 5 shows how:


Listing 5. Creating a class (or static) method

public class Employee extends Person {
 private static final Logger logger = Logger.getLogger(Employee.class.getName());
//. . .
 public static Logger getLogger() {
   return logger;

 }

}

Two important things are happening in Listing 5:

  • The Logger instance is declared with private access, so no class outside Employee can access the reference directly.
  • The Logger is initialized when the class is loaded; this is because you use the Java initializer syntax to give it a value.

To retrieve the Employee class's Logger object, you make the following call:

Logger employeeLogger = Employee.getLogger();


Comparing objects

The Java language provides two ways to compare objects:

  • The == operator
  • The equals() method

Comparing objects with ==

The == syntax compares objects for equality such that a == b returns true only if a and b have the same value. For objects, this means that the two refer to the same object instance. For primitives, it means that the values are identical. Consider the example in Listing 6:


Listing 6. Comparing objects with ==

int int1 = 1;
int int2 = 1;
l.info("Q: int1 == int2?           A: " + (int1 == int2));

Integer integer1 = Integer.valueOf(int1);
Integer integer2 = Integer.valueOf(int2);
l.info("Q: Integer1 == Integer2?   A: " + (integer1 == integer2));

integer1 = new Integer(int1);
integer2 = new Integer(int2);
l.info("Q: Integer1 == Integer2?   A: " + (integer1 == integer2));

Employee employee1 = new Employee();
Employee employee2 = new Employee();
l.info("Q: Employee1 == Employee2? A: " + (employee1 == employee2));

If you run the Listing 6 code inside Eclipse, the output should be:

Apr 19, 2010 5:30:10 AM com.makotogroup.intro.Employee main
INFO: Q: int1 == int2?           A: true
Apr 19, 2010 5:30:10 AM com.makotogroup.intro.Employee main
INFO: Q: Integer1 == Integer2?   A: true
Apr 19, 2010 5:30:10 AM com.makotogroup.intro.Employee main
INFO: Q: Integer1 == Integer2?   A: false
Apr 19, 2010 5:30:10 AM com.makotogroup.intro.Employee main
INFO: Q: Employee1 == Employee2? A: false

In the first case in Listing 6, the values of the primitives are the same, so the == operator returns true. In the second case, the Integer objects refer to the same instance, so again == returns true. In the third case, even though the Integer objects wrap the same value, == returns false because integer1 and integer2 refer to different objects. Based on this, it should be clear why employee1 == employee2 returns false.

Comparing objects with equals()

equals() is a method that every Java language object gets for free, because it is defined as an instance method of java.lang.Object (which every Java object inherits from).

You call equals() just as you would any other method:

a.equals(b);

This statement invokes the equals() method of object a, passing to it a reference to object b. By default a Java program would simply check to see if the two objects were the same using the == syntax. Because equals() is a method, however, it can be overridden. Consider the example from Listing 6, modified in Listing 7 to compare the two objects using equals():


Listing 7. Comparing objects with equals()

Logger l = Logger.getLogger(Employee.class.getName());

Integer integer1 = Integer.valueOf(1);
Integer integer2 = Integer.valueOf(1);
l.info("Q: integer1 == integer2?        A: " + (integer1 == integer2));
l.info("Q: integer1.equals(integer2)?   A: " + integer1.equals(integer2));

integer1 = new Integer(integer1);
integer2 = new Integer(integer2);
l.info("Q: integer1 == integer2?        A: " + (integer1 == integer2));
l.info("Q: integer1.equals(integer2)?   A: " + integer1.equals(integer2));

Employee employee1 = new Employee();
Employee employee2 = new Employee();
l.info("Q: employee1 == employee2 ? A: " + (employee1 == employee2));
*l.info("Q: employee1.equals(employee2) ? A : " + 
employee1.equals(employee2));*
Running this code produces:

Apr 19, 2010 5:43:53 AM com.makotogroup.intro.Employee main
INFO: Q: integer1 == integer2?        A: true
Apr 19, 2010 5:43:53 AM com.makotogroup.intro.Employee main
INFO: Q: integer1.equals(integer2)?   A: true
Apr 19, 2010 5:43:53 AM com.makotogroup.intro.Employee main
INFO: Q: integer1 == integer2?        A: false
Apr 19, 2010 5:43:53 AM com.makotogroup.intro.Employee main
INFO: Q: integer1.equals(integer2)?   A: true
Apr 19, 2010 5:43:53 AM com.makotogroup.intro.Employee main
INFO: Q: employee1 == employee2?      A: false
Apr 19, 2010 5:43:53 AM com.makotogroup.intro.Employee main
INFO: Q: employee1.equals(employee2)? A: false

A note about comparing Integers

In Listing 7, it should be no surprise that the equals() method of Integer returns true if == returns true; but notice what happens in the second case, where you create separate objects that both wrap the value 1: == returns false because integer1 and integer2 refer to different objects; but equals() returns true.

The writers of the JDK decided that for Integer, the meaning of equals() would be different from the default (which is to compare the object references to see if they refer to the same object), and would instead return true in cases in which the underlying int value is the same.

For Employee, you did not override equals(), so the default behavior (of using ==) returns what you would expect, given that employee1 and employee2 do in fact refer to different objects.

Basically, this means that for any object you write, you can define what equals() means as appropriate for the application you are writing.

Overriding equals()

You can define what equals() means to your application's objects by overriding the default behavior of Object.equals(). Again, you can use Eclipse to do this. Make sure Employee has the focus in your Eclipse IDE's Source window, then go to Source > Override/Implement Methods. The dialog box in Figure 4 will appear:


Figure 4. Override/Implement Methods dialog
The Override/Implement Methods dialog box in Eclipse.

You've used this dialog before, but in this case you want to implement the Object.equals() superclass method. So, find Object in the list, check the equals(Object) method, and click OK. Eclipse will generate the correct code and place it in your source file.

It makes sense that the two Employee objects are equal if the states of those objects are equal. That is, they're equal if their values — last name, first name, age — are the same.

Autogenerating equals()

Eclipse can generate an equals() method for you based on the instance variables (attributes) you've defined for a class. Because Employee is a subclass of Person, you'll first generate equals() for Person. In Eclipse's Project Explorer view, right-click Person and choose Generate hashCode() and equals() to bring up the dialog box shown in Figure 5:


Figure 5. Generate hashCode() and equals() dialog
The dialog to auto-generate equals()

Select all attributes (as shown in Figure 5) and click OK. Eclipse will generate an equals() method that looks like the one in Listing 8:


Listing 8. An equals() method generated by Eclipse

@Override
public boolean equals(Object obj) {
 if (this == obj)
   return true;
 if (obj == null)
   return false;
 if (getClass() != obj.getClass())
   return false;
 Person other = (Person) obj;
 if (age != other.age)
  return false;
 if (eyeColor == null) {
   if (other.eyeColor != null)
     return false;
 } else if (!eyeColor.equals(other.eyeColor))
   return false;
 if (gender == null) {
   if (other.gender != null)
     return false;
 } else if (!gender.equals(other.gender))
   return false;
 if (height != other.height)
   return false;
 if (name == null) {
   if (other.name != null)
     return false;
 } else if (!name.equals(other.name))
   return false;
 if (weight != other.weight)
   return false;
 return true;
}

Don't worry about hashCode() for now — you can keep it or delete it. The equals() method generated by Eclipse looks complicated, but what it does is pretty simple: if the object passed in is the same object as the one in Listing 8, then equals() will return true. If the object passed in is null, it will return false.

After that, the method checks to see if the Class objects are the same (meaning the passed-in object must be a Person object). If that's true, then each attribute value of the object passed in is checked to see if it matches value-for-value with the state of the given Person instance. If the attribute values are null (meaning missing) then the equals() will check as many as it can, and if those match, the objects will be considered equal. You may not want this behavior for every program, but it works for most purposes.

Exercise: Generate an equals() for Employee

Try following the steps in Autogenerating equals() to generate an equals() for Employee. Once you have your generated equals(), add the following code above it:

public static void main(String[] args) {
 Logger l = Logger.getLogger(Employee.class.getName());

 Employee employee1 = new Employee();
 employee1.setName("J Smith");
 Employee employee2 = new Employee();
 employee2.setName("J Smith");
 l.info("Q: employee1 == employee2?      A: " + (employee1 == employee2));
 l.info("Q: employee1.equals(employee2)? A: " + employee1.equals(employee2));
}

If you run the code, you should see the following output:

Apr 19, 2010 5:26:50 PM com.makotogroup.intro.Employee main
INFO: Q: employee1 == employee2?      A: false
Apr 19, 2010 5:26:50 PM com.makotogroup.intro.Employee main
INFO: Q: employee1.equals(employee2)? A: true

In this case, a match on Name alone was enough to convince equals() that the two objects were equal. Feel free to add more attributes to this example and see what you get.

Exercise: Override toString()

Remember the printAudit() method from the beginning of this section? If you thought it was working a little too hard, you were right! Formatting the state of an object into a String is such a common pattern that the designers of the Java language built it right into Object itself, in a method called (no surprise) toString(). The default implementation of toString() is not very useful, but every object has one. In this exercise, you'll make the default toString() a little more useful.

If you suspect that Eclipse can generate a toString() method for you, you are correct. Go back into your Project Explorer and right-click the Employee class, then choose Source > Generate toString().... You'll see a dialog box similar to the one in Figure 5. Choose all attributes and click OK. The code generated by Eclipse for Employee is shown in Listing 9:


Listing 9. A toString() method generated by Eclipse

@Override
public String toString() {
 return "Employee [employeeNumber=" + employeeNumber + ", salary=" + salary
 + ", taxpayerIdentificationNumber=" + taxpayerIdentificationNumber
 + "]";
}

The code Eclipse generates for toString doesn't include the superclass's toString() (Employee's superclass being Person). You can fix that in a flash, using Eclipse, with this override:

@Override
public String toString() {
 return super.toString() +
   "Employee [employeeNumber=" + employeeNumber + ", salary=" + salary
   + ", taxpayerIdentificationNumber=" + taxpayerIdentificationNumber
   + "]";
}

The addition of toString() makes printAudit() much simpler:

@Override
public void printAudit(StringBuilder buffer) {
 buffer.append(toString());
}

toString() now does the heavy lifting of formatting the object's current state, and you simply stuff what it returns into the StringBuilder and return.

I recommend always implementing toString() in your classes, if only for support purposes. It's virtually inevitable that at some point you'll want to see what an object's state is while your application is running, and toString() is a great hook for doing that.

2 of 14 | Previous | Next

Comments



static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=508383
TutorialTitle=Introduction to Java programming, Part 2: Constructs for real-world applications
publish-date=08192010
author1-email=steve@makotoconsulting.com
author1-email-cc=jaloi@us.ibm.com