The ultimate superclass, Part 3

My previous post in this three-part series on the java.lang.Object class and its methods covered finalize(), getClass(), and hashCode(). In this post, you explore toString(), the wait/notification methods, and Object's methods in an interface and Java 8 context.

String Representation

Q: What does the toString() method accomplish?

A: The toString() method returns a string representation of the object on which this method is called. The returned string is useful for debugging purposes.

Q: What does the string representation look like when toString() isn't overridden?

A: When toString() isn't overridden, the string representation is returned in the format classname@hashcode, where hashcode is presented in hexadecimal. For example, given a hypothetical Employee class, toString() might return Employee@1c7b0f4d.

Q: Can you provide me with an example on how to override toString()?

A: Check out Listing 1.

public class Employee
{
   private String name;
   private int age;

   public Employee(String name, int age)
   {
      this.name = name;
      this.age = age;
   }

   @Override
   public String toString()
   {
      return name + ": " + age;
   }
}

Listing 1: Returning a non-default string representation

Listing 1 presents an Employee class with private name and age fields, which are initialized by the constructor. It also overrides toString() to return a String object composed of these fields' values and a colon separator.

Q: How do I obtain the string representation?

A: Given an object reference, call toString() on this reference. For example, assuming that emp contains an Employee reference, call emp.toString() to return this object's string representation.

Q: What is the difference between System.out.println(o.toString()); and System.out.println(o);?

A: Each of System.out.println(o.toString()); and System.out.println(o); obtains and outputs object o's string representation. System.out.println(o.toString()); explicitly calls toString(), whereas System.out.println(o); implicitly calls toString().

Waiting and Notification

Q: What do the wait(), notify(), and notifyAll() methods accomplish?

A: The wait(), notify(), and notifyAll() methods allow threads to coordinate their actions. For example, one thread might produce an item that another thread consumes. The producing thread should not produce an item before the previously produced item is consumed. Instead, it should wait until it's notified that the item was consumed. Similarly, the consuming thread should not attempt to consume a nonexistent item. Instead, it should wait until it's notified that the item is produced. These methods support this coordination. Essentially, one thread waits for a condition to occur (e.g., an item to be produced) and another thread notifies the waiting thread after creating the condition (e.g., by producing an item).

Q: What is the difference between the various wait() methods?

A: The no-argument wait() method causes the calling thread to wait until another thread calls the notify() method or the notifyAll() method on the same object (i.e., the object on which wait() was called). In contrast, the wait(long timeout) and wait(long timeout, int nanos) methods wait for notification or for the specified timeout to occur, whichever happens first.

Q: What is the difference between the notify() and notifyAll() methods?

A: The notify() method wakes up one of the waiting threads (the woken thread is arbitrarily chosen), whereas notifyAll() wakes up all waiting threads.

Q: What happens after a thread has been woken up?

A: After a thread has been woken up, it's unable to proceed until the current thread relinquishes the lock on the object (on which notify() or notifyAll() was called). The awakened thread competes in the usual manner with any other threads that might be actively competing to synchronize on this object. With notifyAll(), either none of the awoken threads or only one of these threads will be able to proceed.

Q: Why is it necessary to call the waiting and notification methods in a synchronized context?

A: It's necessary to call the waiting and notification methods in a synchronized context to avoid a race condition. Consider that a waiting thread confirms that a condition doesn't exist (usually by checking a variable) before calling wait(), and another thread sets this condition (by setting the same variable) before calling notify(). Given this scenario, the race condition occurs as follows:

  1. The first threads tests the condition and verifies that it needs to wait.
  2. The second thread sets the condition.
  3. The second thread calls notify(). Because the first thread isn't waiting at this point, this call accomplishes nothing.
  4. The first thread calls wait(). It doesn't wake up because it didn't receive the notification.

Q: What happens when these methods are called outside of a synchronized context?

A: When these methods are called outside of a synchronized context, they throw java.lang.IllegalMonitorStateException.

Q: What happens when wait() is called inside a synchronized context?

A: When wait() is called inside a synchronized context, it releases the lock that was obtained by the calling thread upon entering the synchronized context prior to this method being called. This lock is reacquired before wait() returns. There is no race condition between releasing and reacquiring the lock because wait() is tightly integrated with the lock mechanism. The lock won't be released until wait() is able to receive notifications.

Q: Why should I call wait() in a while loop context as opposed to an if decision context?

A: You should call wait() in a while loop context as opposed to an if decision context because of the possibility for spurious wakeups. Check out stackoverflow's Do spurious wakeups actually happen? topic to learn more about this phenomenon.

Q: Can you provide me with an example of the waiting and notification methods?

A: Check out Listing 2.

public class WaitNotifyDemo
{
   public static void main(String[] args)
   {
      class Shared
      {
         private String msg;

         synchronized void send(String msg)
         {
            while (this.msg != null)
               try
               {
                  wait();
               }
               catch (InterruptedException ie)
               {
               }
            this.msg = msg;
            notify();
         }

         synchronized String receive()
         {
            while (msg == null)
               try
               {
                  wait();
               }
               catch (InterruptedException ie)
               {
               }
            String temp = msg;
            msg = null;
            notify();
            return temp;
         }
      }

      final Shared shared = new Shared();

      Runnable rsender;
      rsender = new Runnable()
                {
                   @Override
                   public void run()
                   {
                      for (int i = 0; i < 10; i++)
                      {
                         shared.send("A"+i);
                         try
                         {
                            Thread.sleep((int)(Math.random()*200));
                         }
                         catch (InterruptedException ie)
                         {
                         }
                      }
                      shared.send("done");
                   }
                };
      Thread sender = new Thread(rsender);

      Runnable rreceiver;
      rreceiver = new Runnable()
                  {
                     @Override
                     public void run()
                     {
                        String msg;
                        while (!(msg = shared.receive()).equals("done"))
                        {
                           System.out.println(msg);
                           try
                           {
                              Thread.sleep((int)(Math.random()*200));
                           }
                           catch (InterruptedException ie)
                           {
                           }
                        }
                     }
                  };
      Thread receiver = new Thread(rreceiver);
 
      sender.start();
      receiver.start();     
   }
}

Listing 2: Sending and receiving messages

Listing 2 presents a WaitNotifyDemo class whose main() method creates and starts a pair of threads that send and receive messages.

main() first declares a local class named Shared that handles the sending and receiving tasks. Shared declares a private String field named msg that stores the message being sent, and also declares synchronized send() and receive() methods that perform the send and receive operations.

The sending thread calls send(). Because the previous message passed to send() may not have been received, this method first tests the "message waiting" condition by evaluating this.msg != null. While this expression returns true, a message is waiting to be sent and the sending thread must wait, by calling wait(). Once the message has been received, the receiving thread will assign null to msg and notify the waiting sending thread, which saves the new message and calls notify(), to awaken a waiting receiving thread (if it's waiting).

The receiving thread calls receive(). Because there may not be a message waiting to be received, this method first tests the "message not waiting" condition by evaluating msg == null. While this expression returns true, a message isn't waiting to be received and the receiving thread must wait, by calling wait(). Once a message is available for sending, the sending thread will assign it to msg and notify the waiting receiving thread, which assigns null to msg and calls notify(), to awaken a waiting sending thread (if it's waiting).

After declaring Shared, main() instantiates this class. It then creates a pair of runnables and Thread objects for the sending and receiving threads. The runnables take care of sending and receiving the messages. Lastly, these threads are started by calling each Thread object's start() method.

Compile the source code (javac WaitNotifyDemo.java) and run the application (java WaitNotifyDemo). You should observe the following output:

A0
A1
A2
A3
A4
A5
A6
A7
A8
A9

Q: I would like to dig deeper into how the wait/notify mechanism works. Can you recommend a good resource that will provide me with this insight?

A: Check out Chapter 20: Thread Synchronization of Bill Venners book, Inside the Java Virtual Machine, at the artima developer website.

Object, Interfaces, and Java 8

Q: In Part 1 of this three-part series on Object and its methods, you mentioned that interfaces don't extend Object. However, I've noticed that some interfaces declare Object methods. For example, the java.util.Comparator interface declares boolean equals(Object obj). What is this all about?

A: Section 9.6.3.4 of the Java Language Specification states that an interface does have public abstract members that correspond to the public members of Object. Furthermore, if an interface chooses to declare them explicitly (i.e. to declare members that are override-equivalent to public methods of Object), then the interface is deemed to override them, and use of @Override is allowed.

Why would you want to declare a public non-final Object method (possibly with the @Override annotation) in an interface? For an answer, consider the Comparator interface with its boolean equals(Object obj) declaration. This method is declared in this interface to place the following extra condition on its contract:

Additionally, this method can return true only if the specified object is also a comparator and it imposes the same ordering as this comparator.

Because this condition is optional, a class that implements Comparator can decide whether or not it wants to override equals() to ensure that it also returns true when the argument object is a comparator that imposes the same ordering as this comparator. Although the class doesn't have to override equals(), doing so may improve performance, as mentioned in the documentation:

Note that it is always safe not to override Object.equals(Object). However, overriding this method may, in some cases, improve performance by allowing programs to determine that two distinct comparators impose the same order.

Q: Which equals() method is overridden -- Object's equals() method or the interface's equals() method?

A: The previous documentation excerpt indicates that it is Object's equals() method that is being overridden.

Q: Java 8 supports default methods in interfaces. Can I implement equals() or another public method from the Object class as a default method in an interface?

A: You cannot implement any of Object's public non-final methods as default methods in an interface. The rationale for this restriction is explained by Brian Goetz in his Allow default methods to override Object's methods topic on the Project Lambda mailing list.

Q: I'd like to learn more about Object's methods in an interface context. Can you recommend a good resource?

A: Check out the Do Interfaces really inherit the Object class in Java? blog post.

What's Next?

Next time, I focus on customizing the cell rendering of Swing's javax.swing.JList component and JavaFX's javafx.scene.control.ListView control.

download
Download the source code for this tutorial. Created for JavaWorld by Jeff Friesen.
Join the discussion
Be the first to comment on this article. Our Commenting Policies