Unit 13: Next steps with objects

Before you begin

This unit is part of the “Intro to Java programming” learning path. Although the concepts discussed in the individual units are standalone in nature, the hands-on component builds as you progress through the units, and I recommend that you review the prerequisites, setup, and unit details before proceeding.

Unit objectives

  • Learn about method overloading and overriding
  • Be able to compare one object with another
  • Understand how and when to use class variable and methods

Overloading methods

It’s time to return to the Person class. Person is reasonably useful at this point, but not as useful as it could be. You’ll start enhancing Person by overloading one of its 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. At runtime, the JRE decides which variation of your overloaded method to call, based on the arguments that were passed to it.

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

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());
}

You have two overloaded versions of printAudit(), and one even 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 calls the correct method.

Remember two important rules when you use overloaded methods:

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

If you violate these rules, the compiler gives you an error.

Overriding methods

When a subclass provides its own implementation of a method that’s defined on one of its parent classes, that’s called method overriding. To see how method overriding is useful, you need to do some work on an Employee class. Watch the following video to see how to set up the Employee class and perform method overriding in that class. After you watch, I’ll briefly recap those steps while giving you a closer look at the code.

The video also includes a demo of overriding the equals() method and autogenerating equals() and hashCode(), which I cover in detail in this unit’s “Comparing objects” section.

Employee: A subclass of Person

Recall from Unit 3: Object-oriented concepts and principles that an Employee class could be a subclass (or child) of Person that has additional attributes such as taxpayer identification number, employee number, hire date, and salary. You’ll declare the Employee class now and add some of those attributes in a moment.

To declare the Employee class, right-click the com.makotojava.intro package in Eclipse. Click New > Class… and, in the New Java Class dialog box, enter Employee as the name of the class and Person as its superclass. Click Finish, and you can see the Employee class code in an edit window.

You don’t explicitly need to declare a constructor, but go ahead and implement both constructors anyway. With the Employee class edit window having the focus, go to Source > Generate Constructors from Superclass…. In the Generate Constructors from Superclass dialog box, select both constructors and click OK. You now have an Employee class like the one in Listing 2.

Listing 2. The Employee class

package com.makotojava.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 as a child of Person

Employee inherits the attributes and behavior of its parent, Person. Add some attributes of Employee‘s own, as shown in lines 7 through 9 of Listing 3.

Listing 3. The Employee class with Person’s attributes

package com.makotojava.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...
}

Don’t forget to generate getters and setters for the new attributes, as you did for Person in Unit 5: Your first Java class.

Overriding the printAudit() method

Now 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. If you instantiate Employee, set its attributes, and call either of the overloads of printAudit(), the call succeeds. However, the audit that’s produced doesn’t fully represent an Employee. The printAudit() method can’t 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.

With Employee open in the editor window or selected in the Project Explorer view, go to Source > Override/Implement Methods…. In the Override/Implement Methods dialog box, select the StringBuilder overload of printAudit() and click OK. Eclipse generates the method stub for you, and then you can 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 format the attributes from Person yourself in the Employee.printAudit() method, or they won’t be included in the audit output.

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 will be the case if the two refer to the same object instance. For primitives, if the values are identical.

Suppose you generate a JUnit test for Employee (which you saw how to do in Unit 5: Your first Java class. The JUnit test is shown in Listing 4.

Listing 4. Comparing objects with ==

public class EmployeeTest {
  @Test
  public void test() {
    int int1 = 1;
    int int2 = 1;
    Logger l = Logger.getLogger(EmployeeTest.class.getName());
    
    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));
  }
}

Run the Listing 4 code inside Eclipse (select Employee in the Project Explorer view, then choose Run As > JUnit Test) to generate the following output:


Sep 18, 2015 5:09:56 PM com.makotojava.intro.EmployeeTest test
INFO: Q: int1 == int2?           A: true
Sep 18, 2015 5:09:56 PM com.makotojava.intro.EmployeeTest test
INFO: Q: Integer1 == Integer2?   A: true
Sep 18, 2015 5:09:56 PM com.makotojava.intro.EmployeeTest test
INFO: Q: Integer1 == Integer2?   A: false
Sep 18, 2015 5:09:56 PM com.makotojava.intro.EmployeeTest test
INFO: Q: Employee1 == Employee2? A: false

In the first case in Listing 4, 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. Think of == as a test for “same object instance.”

Comparing objects with equals()

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

You call equals() like this:

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 are the same by using the == syntax. Because equals() is a method, however, it can be overridden. Compare the JUnit test case in Listing 4 to the one in Listing 5 (which I’ve called anotherTest()), which uses equals() to compare the two objects.

Listing 5. Comparing objects with equals()

@Test
public void anotherTest() {
  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 the Listing 5 code produces this output:


Sep 19, 2015 10:11:57 AM com.makotojava.intro.EmployeeTest anotherTest
INFO: Q: integer1 == integer2 ? A: true
Sep 19, 2015 10:11:57 AM com.makotojava.intro.EmployeeTest anotherTest
INFO: Q: integer1.equals(integer2) ? A: true
Sep 19, 2015 10:11:57 AM com.makotojava.intro.EmployeeTest anotherTest
INFO: Q: integer1 == integer2 ? A: false
Sep 19, 2015 10:11:57 AM com.makotojava.intro.EmployeeTest anotherTest
INFO: Q: integer1.equals(integer2) ? A: true
Sep 19, 2015 10:11:57 AM com.makotojava.intro.EmployeeTest anotherTest
INFO: Q: employee1 == employee2 ? A: false
Sep 19, 2015 10:11:57 AM com.makotojava.intro.EmployeeTest anotherTest
INFO: Q: employee1.equals(employee2) ? A : false

A note about comparing Integers

In Listing 5, 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, as you recall, is to compare the object references to see if they refer to the same object). For Integer, equals() returns true in cases in which the underlying (boxed) int value is the same.

For Employee, you didn’t override equals(), so the default behavior (of using ==) returns what you’d expect, because employee1 and employee2 refer to different objects.

For any object you write, then, you can define what equals() means as is appropriate for the application you’re writing.

Overriding equals()

You can define what equals() means to your application’s objects by overriding the default behavior of Object.equals()— and you can do this in Eclipse. With Employee having the focus in the IDE’s source window, select Source > Override/Implement Methods.You want to implement the Object.equals() superclass method. So, find Object in the list of methods to override or implement, select the equals(Object) method, and click OK. Eclipse generates the correct code and places 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 — name, and age — are the same.

Autogenerating equals()

Eclipse can generate an equals() method for you based on the instance variables (attributes) that you define for a class. Because Employee is a subclass of Person, you first generate equals() for Person. In the Eclipse Project Explorer view, right-click Person and choose Generate hashCode() and equals(). In the dialog box that opens, click Select All to include all of the attributes in the hashCode() and equals() methods, and click OK. Eclipse generates an equals() method that looks like the one in Listing 6.

Listing 6. 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;
}

The equals() method generated by Eclipse looks complicated, but what it does is simple: If the object passed in is the same object as the one in Listing 6, equals() returns true. If the object passed in is null (meaning missing), it returns false.

Next, the method checks to see if the Class objects are the same (meaning that the passed-in object must be a Person object). If they are the same, 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, the equals() checks as many as it can, and if those match, the objects are considered equal. You might not want this behavior for every program, but it works for most purposes.

Exercises

Now, work through a couple of guided exercises to do even more with Person and Employee in Eclipse.

Exercise 1: 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 JUnit test case (which I’ve called yetAnotherTest()) to it:


@Test
public void yetAnotherTest() {
  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:


Sep 19, 2015 11:27:23 AM com.makotojava.intro.EmployeeTest yetAnotherTest
INFO: Q: employee1 == employee2?      A: false
Sep 19, 2015 11:27:23 AM com.makotojava.intro.EmployeeTest yetAnotherTest
INFO: Q: employee1.equals(employee2)? A: true

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

Exercise 2: Override toString()

Remember the printAudit() method from the beginning of this unit? 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 into Object itself, in a method called (no surprise) toString(). The default implementation of toString() isn’t especially useful, but every object has one. In this exercise, you override toString() to make it a little more useful.

If you suspect that Eclipse can generate a toString() method for you, you’re correct. Go back into your Project Explorer and right-click the Person class, then choose Source > Generate toString()…. In the dialog box, select all attributes and click OK. Now do the same thing for Employee. The code generated by Eclipse for Employee is shown in Listing 7.

Listing 7. A toString() method generated by Eclipse

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

The code that Eclipse generates for toString doesn’t include the superclass’s toString() ( Employee‘s superclass being Person). You can fix that situation quickly, using Eclipse, with this override:


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

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.

Class members

Every object instance has variables and methods, and for each one, the exact behavior is different, because it’s based on the state of the object instance. The variables and methods that you have on Person and Employee are instance variables and methods. To use them, you must either instantiate the class you need or have a reference to the instance.

Classes can also have class variables and methods — known as class members. You declare class variables with the static keyword. The differences between class variables and instance variables 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.
  • Class methods can access only class variables.
  • Instance methods can access class variables, but class methods can’t access instance variables.

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)
  • 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. The brackets 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 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 on it. Listing 8 shows a common use of class variables.

Listing 8. 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 8, 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 would preface it with the name of the class where it was declared:

String genderValue = Person.GENDER_MALE;

Class methods

You’ve already called the static Logger.getLogger() method several times — whenever you retrieved a Logger instance to write output to the console. Notice, though, that to do so you didn’t need an instance of Logger. Instead, you referenced the Logger class, which 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.

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

Listing 9. 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 9:

  • 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 — 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();

Previous: Writing good Java codeNext: Exceptions

J Steven Perry