How to make your own custom object Listener

The example below is a parallel to the way ActionListeners and WindowListeners work in the Java Swing/AWT libraries. Understanding this example will help you understand them better.

The Setup

Let's say that we want to simulate a Classroom with a Teacher who has 20 Students. The basic fundamentals of our simulation is that Students will have the ability to raise and lower their hands. Additionally, the Students can be acknowledged (for raising their hands), and will then put their hand down.

The basic code for this simulation might look something like:

Classroom.java
public class Classroom{
  public static void main(String[] args){
    Teacher t = new Teacher();
    t.simulate();
  }
}
Teacher.java
import java.util.*;

public class Teacher{
  private ArrayList<Student> students;

  public Teacher(){
    students = new ArrayList<Student>();
    for (int i = 0; i < 20; i++)
      students.add(new Student());
  }

  public void simulate(){
    for(Student s : students)
      s.simulate();
  }
}
Student.java
import java.util.*;

public class Student{
  private static int nextID = 0;
  private int id;
  private boolean handRaised;

  public Student(){
    id = nextID++;
    handRaised = false;
  }

  private void raiseHand(){
    System.out.println(this + ": I'm raising my hand.");
    handRaised = true;
  }

  private void lowerHand(){
    System.out.println(this + ": I'm lowering my hand.");
    handRaised = false;
  }

  public void acknowledged(){
    if (handRaised){
      System.out.println(this + ": I have been acknowledged, "
        + "and will I put my hand down.");
      lowerHand();
    }
  }

  public String toString(){
    return "Student " + id;
  }

  public void simulate(){
    //1 in 3 chance of raising hand
    if (new Random().nextInt(3) == 0)
      raiseHand();
  }
}

Getting objects to Listen

So the fundamental problem that we have to address is:

Or conversely (and more accurately):

So if the Students are going to tell the Teacher when they raise their hands, then the Teacher has to listen for the Students to do so.

Step 1: Design your Listener interface

  1. Name your interface. Following suit with all of the existing Listeners we've in Java thus far, we name the interface for the object that we want other objects to listen to. So in this example, we would name our Listener interface: StudentListener
  2. Pick your interface methods. What you're looking to do here is decide what your Students are going to say to the Teacher (or anyone else who might also want to listen.) These become your method names. So in this example, we might want to tell those who want to listen that a student has raised or lowered their hand.

And we end up with an interface that looks something like:

StudentListener.java
public interface StudentListener{
  //the methods that go here are the things that the
  //Student wants to communcate to anyone listening to it.
  public void raisedHand(Student s);
  public void loweredHand(Student s);
}

Note that the prototype of the methods take a Student object as a parameter. This is important because it will allow a Student object to call these methods and pass itself as the parameter using this.

Step 2: Setting up your Listening classes
(i.e. Implementing the Listener).

This step is fairly straight forward, and we have done it several times already in the course. Simply put implements NameofListener in the class definition, and then define the methods of the interface. You have do decide what the object that is listening to do in those methods when they are called.

Specific to this example:

  1. Modify the class definition:
    public class Teacher implements StudentListener{
  2. Define the raisedHand(Student s) method:
          public void raisedHand(Student s){
            //put what you want the Teacher to do
            //when a Student raises his/her hand
            //The Student s in the parameter is a reference
            //to the Student object that raised its hand.
          }
  3. Define the loweredHand(Student s) method:
          public void loweredHand(Student s){
            //put what you want the Teacher to do
            //when a Student lowers his/her hand
            //The Student s in the parameter is a reference
            //to the Student object that raised its hand.
          }

And you might end up with something that looks like this when complete:

Teacher.java
import java.util.*;

public class Teacher implements StudentListener{
  private ArrayList<Student> students;

  public Teacher(){
    students = new ArrayList<Student>();
    for (int i = 0; i <20; i++)
      students.add(new Student());
    //have this Teacher listen to each Student
    for (Student s : students){
      s.addStudentListener(this);
    }
  }

  public void simulate(){
    for(Student s : students)
      s.simulate();
  }

  //what this Teacher does when it sees a
  //Student raise his/her hand
  public void raisedHand(Student s){
    System.out.println(
      "Teacher: I saw " + s +
      " raise his/her hand up, " +
      "and I acknowedged."
    );
    s.acknowledged();
  }

  //what this Teacher does when it see a
  //Student lower his/her hand
  public void loweredHand(Student s){
    System.out.println(
      "Teacher: I saw " + s +
      " lower his/her hand down."
    );
  }
}

Step 3: Setting up your "talking" class.

Now we go back to the class that is being listened to (aka the Student in this example), and we have to do a couple of things:

  1. First, we (the Student) need to know who/what other object is listening to us (the Student). So, we (the Student) need a field to store this information. This will enable us (the Student) to "tell" that other object that something has happened.
  2. Second, we (the Student) need a way (aka a method) to set the reference for the field mentioned above.
  3. Third, we (the Student) need to decide when to tell the other listening object that something happened (using the reference for the field from above). In other words, somewhere in your code, you need to call the methods that are in the Listener interface.

Specific to this example:

  1. Add a field so that the Student knows who/what is listening to it. For example:
    //a field to know who's listening to this student
    private StudentListener sl;
  2. Add a method to set the StudenListener sl For example:
    //a method to add the StudentListerner for this
    public void addStudentListener(StudentListener sl){
      this.sl = sl;
    }
  3. Decide when to call each of the StudentListener interface methods. Specifically:
    1. Decide when to call sl.raisedHand(this). For example:
      private void raiseHand(){
        System.out.println(
          this + ": I'm raising my hand."
        );
        handRaised = true;
        //tell whomever is watching/listening that
        //this Student raised his/her hand
        sl.raisedHand(this);
      }
    2. Decide when to call sl.loweredHand(this). For example:
      private void lowerHand(){
        System.out.println(
          this + ": I'm lowering my hand."
        );
        handRaised = false;
        //tell whomever is watching/listening that
        //this Student lowered his/her hand
        sl.loweredHand(this);
      }

And you might end up with something that looks like this when complete:

Student.java
import java.util.*;

public class Student{
  //nextID enables easy assignment of IDs to students
  private static int nextID = 0;

  private int id;
  private boolean handRaised;

  //a field to know who's listening to this student
  private StudentListener sl;

  public Student(){
    id = nextID++;
    handRaised = false;
    sl = null;
  }

  //a method to add the StudentListerner for this
  public void addStudentListener(StudentListener sl){
    this.sl = sl;
  }

  private void raiseHand(){
    System.out.println(
      this + ": I'm raising my hand."
    );
    handRaised = true;
    //tell whomever is watching/listening that
    //this Student raised his/her hand
    sl.raisedHand(this);
  }

  private void lowerHand(){
    System.out.println(
      this + ": I'm lowering my hand."
    );
    handRaised = false;
    //tell whomever is watching/listening that
    //this Student lowered his/her hand
    sl.loweredHand(this);
  }

  public void acknowledged(){
    if (handRaised){
      System.out.println(this + ": I have been acknowledged, "
        + "and will I put my hand down.");
      lowerHand();
    }
  }

  public String toString(){
    return "Student " + id;
  }

  public void simulate(){
    //1 in 3 chance of raising hand
    if (new Random().nextInt(3) == 0)
      raiseHand();
  }
}

Step 4: Finishing it off.

Now the thing that remains is to go back to your implementing class and make one final change:
It needs to tell the subject objects that it's listening to them! In other words, it (the implementing class) needs to call the method to set itself (this) as the Listener for that object.

So in our example, you might go back into Teacher.java and modify the constructor in the following manner:

Teacher(){
  students = new ArrayList<Student>();
  for (int i = 0; i <20; i++)
    students.add(new Student());
  //have this Teacher listen to each Student
  for (Student s : students){
    s.addStudentListener(this);
  }
}

One Final Thing...

At this point you will have achieved success in getting your custom Listener to work. However, you are currently limited to allowing only ONE other object to listen to your object at any given time.

Remember this change:

//a field to know who's listening to this student
private StudentListener sl;

When in reality, there could be an untold number of things (of the same or class or different classes) that might want to listen to the same object.
In our example here, what if there were two Teachers watching the same group of Students; or what if the Teacher had an assistant?

This is no different than having multiple other GUI objects that are all listening to the same JButton for a click or to the same JTextField for new text to be entered.

So, what you need to do now, is go back into your "talking" class and modify the code to accept a limitless number of Listeners in the following manner:

  1. Modify the field declaration to be a container of Listeners.
    (An ArrayList makes this easy.)
  2. Make sure that your new field is properly created in memory.
    (Doing this in or via the constructor is the best option.)
  3. Modify your add method to work with the new container of Listeners.
  4. For each call to the implemented methods: Make the appropriate changes to loop through the container of Listeners.

Specific to this example:

Change these:

  1. The field:
    //a field to know who's listening to this student
      private StudentListener sl;
  2. The constructor:
    public Student(){
      id = nextID++;
      handRaised = false;
      sl = null;
    }
  3. The add Listener method:
    //a method to add the StudentListerner for this
    public void addStudentListener(StudentListener sl){
      this.sl = sl;
    }
  4. When calling each of the StudentListener interface methods. Specifically:
    1. Inside the raiseHand() method:
      private void raiseHand(){
        System.out.println(
          this + ": I'm raising my hand."
        );
        handRaised = true;
        //tell whomever is watching/listening that
        //this Student raised his/her hand
        sl.raisedHand(this);
      }
      
      
    2. Inside the lowerHand()method:
      private void lowerHand(){
        System.out.println(
          this + ": I'm lowering my hand."
        );
        handRaised = false;
        //tell whomever is watching/listening that
        //this Student lowered his/her hand
        sl.loweredHand(this);
      }
      
      

To these:

  1. The field:
    //a field to know who's listening to this student
    private ArrayList<StudentListener> sls;  //changed to ArrayList
  2. The constructor:
    public Student(){
      id = nextID++;
      handRaised = false;
      sls = new ArrayList<StudentListener>();  //created the ArrayList
    }
  3. The add Listener method:
    //a method to add the StudentListerner for this
    public void addStudentListener(StudentListener sl){
      sls.add(sl);  //adding to the ArrayList
    }
  4. When calling each of the StudentListener interface methods. Specifically:
    1. Inside the raiseHand() method:
      private void raiseHand(){
        System.out.println(
          this + ": I'm raising my hand."
        );
        handRaised = true;
        //tell whomever is watching/listening that
        //this Student raised his/her hand
        for (StudentListener sl : sls)  //loop through the ArrayList
          sl.raisedHand(this);  //tell each Listener
      }
    2. Inside the lowerHand()method:
      private void lowerHand(){
        System.out.println(
          this + ": I'm lowering my hand."
        );
        handRaised = false;
        //tell whomever is watching/listening that
        //this Student lowered his/her hand
        for (StudentListener sl : sls)  //loop through the ArrayList
          sl.loweredHand(this);  //tell each Listener
      }

Fin

When it's all said and done, you'll have created and implemented your own fully funtional custom Listener. You can even add additional Listeners to your Students. For example, the final version below has added a TeachersAssistant class that is reponsible for counting the total number of students that raised their hand.

Classroom.java
public class Classroom{

  public static void main(String[] args){
    Teacher t = new Teacher();
    t.simulate();
  }
}
StudentListener.java
public interface StudentListener{
  //the methods that go here are the things that the
  //Student wants to communcate to anyone listening to it.
  public void raisedHand(Student s);
  public void loweredHand(Student s);
}
Teacher.java
import java.util.*;

public class Teacher implements StudentListener{
  private ArrayList<Student> students;
  private TeachersAssistant ta;

  public Teacher(){
    students = new ArrayList<Student>();
    ta = new TeachersAssistant();
    for (int i = 0; i <20; i++)
      students.add(new Student());
    //have this Teacher and his/her TeachersAssistant
    //listen to the Students
    for (Student s : students){
      s.addStudentListener(this);
      s.addStudentListener(ta);
    }
  }

  public void simulate(){
    for(Student s : students)
      s.simulate();
    System.out.println(ta);
  }

  //what this Teacher does when it sees a
  //Student raise his/her hand
  public void raisedHand(Student s){
    System.out.println(
      "Teacher: I saw " + s +
      " raise his/her hand, " +
      "and I acknowedged."
    );
    s.acknowledged();
  }

  //what this Teacher does when it see a
  //Student lower his/her hand
  public void loweredHand(Student s){
    System.out.println(
      "Teacher: I saw " + s +
      " lower his/her hand."
    );
  }
}
TeachersAssistant.java
public class TeachersAssistant implements StudentListener{
  private int handCount = 0;

  //what this TeachersAssistant does when it
  //sees a Student raise his/her hand
  public void raisedHand(Student s){
    System.out.println(
      "TA: I saw " + s +
      " raise his/her hand up, " +
      "and I incremented the count to " +
      ++handCount + "."
    );
  }

  //what this TeachersAssistant does when it see a
  //sees a Student lower his/her hand
  public void loweredHand(Student s){
    //nothing
  }

  public String toString(){
    return "TA: I counted " +
      handCount + " Students who raised their hand.";
  }
}
Student.java
import java.util.*;

public class Student{
  //nextID enables easy assignment of IDs to students
  private static int nextID = 0;

  private int id;
  private boolean handRaised;

  //a field to know who's listening to this student
  private ArrayList<StudentListener> sls;  //changed to ArrayList

  public Student(){
    id = nextID++;
    handRaised = false;
    sls = new ArrayList<StudentListener>();  //created the ArrayList
  }

  //a method to add a StudentListerner for this
  public void addStudentListener(StudentListener sl){
    sls.add(sl);  //adding to the ArrayList
  }

  private void raiseHand(){
    System.out.println(
      this + ": I'm raising my hand."
    );
    handRaised = true;
    //tell whomever is watching/listening that
    //this Student raised his/her hand
    for (StudentListener sl : sls)  //loop through the ArrayList
      sl.raisedHand(this);  //tell each Listener
  }

  private void lowerHand(){
    System.out.println(
      this + ": I'm lowering my hand."
    );
    handRaised = false;
    //tell whomever is watching/listening that
    //this Student lowered his/her hand
    for (StudentListener sl : sls)  //loop through the ArrayList
      sl.loweredHand(this);  //tell each Listener
  }

  public void acknowledged(){
    if (handRaised){
      System.out.println(this + ": I have been acknowledged, "
        + "and will I put my hand down.");
      lowerHand();
    }
  }

  public String toString(){
    return "Student " + id;
  }

  public void simulate(){
    //1 in 3 chance of raising hand
    if (new Random().nextInt(3) == 0)
      raiseHand();
  }

}

You can run the above program with:

java Classroom
And you might see an output like:
bhawkins@faculty:~$ java Classroom
Student 0: I'm raising my hand.
Teacher: I saw Student 0 raise his/her hand, and I acknowedged.
Student 0: I have been acknowledged, and will I put my hand down.
Student 0: I'm lowering my hand.
Teacher: I saw Student 0 lower his/her hand.
TA: I saw Student 0 raise his/her hand up, and I incremented the count to 1.
Student 10: I'm raising my hand.
Teacher: I saw Student 10 raise his/her hand, and I acknowedged.
Student 10: I have been acknowledged, and will I put my hand down.
Student 10: I'm lowering my hand.
Teacher: I saw Student 10 lower his/her hand.
TA: I saw Student 10 raise his/her hand up, and I incremented the count to 2.
Student 13: I'm raising my hand.
Teacher: I saw Student 13 raise his/her hand, and I acknowedged.
Student 13: I have been acknowledged, and will I put my hand down.
Student 13: I'm lowering my hand.
Teacher: I saw Student 13 lower his/her hand.
TA: I saw Student 13 raise his/her hand up, and I incremented the count to 3.
Student 16: I'm raising my hand.
Teacher: I saw Student 16 raise his/her hand, and I acknowedged.
Student 16: I have been acknowledged, and will I put my hand down.
Student 16: I'm lowering my hand.
Teacher: I saw Student 16 lower his/her hand.
TA: I saw Student 16 raise his/her hand up, and I incremented the count to 4.
TA: I counted 4 Students who raised their hand.