Chapter:Inheritance - Overriding methods in Object - Exercises

From Juneday education
Jump to: navigation, search

Exercises

We will now continue to work on the classes created in the previous section. We provide a version of Member class below to make it easy for you to check you have a similar member class:

package com.sportmanagers.sportman;

public class Member {

    private String   name;
    private String   email;
    private int      id;


    public Member(String name, String email, int id) {
        this.name  = name;
        this.email = email;
        this.id    = id;
    }

    public int id() {
        return id;
    }

    public String name() {
        return name;
    }    

}


Q1

Compile and execute the MemberTest classes, written in the previous exercise.

Expand using link to the right to see the full content.

To compile: javac com/sportmanagers/sportman/Member.java com/sportmanagers/sportman/test/MemberTest.java

To execute: java com.sportmanagers.sportman.test.MemberTest

... and, as usual, here's how you compile and if that succeeds execture:

javac com/sportmanagers/sportman/Member.java com/sportmanagers/sportman/test/MemberTest.java  && java com.sportmanagers.sportman.test.MemberTest

Q2

The output of the objects wasn't that impressing so we want to improve things a bit. Let's assume we are the authors of the Member class and we have several other developers in our project who use our Member class. We could say to all other developers something like "if you want a string representation of a Member instance you have to do one yourself". An example of such code could be:

   String memberAsAString = m.id() + " " + m.name() + " " + m.email();

So when ever someone needs a String representation of a Member object they would need to write the code above. Hmm, not good. Why should other developers need to have information about the methods of the Member class? What if we add a instance variable to Mamber and a corresponding method - then we need to change the code creating the String representation as well. Not good. We, the authors of this book, argue for that it is better for the developers of the class to write such a method since these developers are the "experts" of that class. So let's write our own toString() method in the Member class.

Expand using link to the right to see the full content.

Suggested solution:

    public String toString() {
        return this.id + "," + this.name + "," + this.email;
    }

Q3

Compile the Member and MemberTest classes and execute MemberTest. Did the output change?

Expand using link to the right to see the full content.

Se exercise 1 on how to compile. The ouput looks better in the sense that it now represents the Member. The output changed since we wrote our own versionof toString and overrode the method in Object.

Q4

Rename your toString() method to toStringie() and compile the Member and MemberTest classes and execute MemberTest. Did the output change? Try adding @Override just above the toString method (see below). WHat does the compiler say when compiling?

Example on how to add @Override

@Override
public String toString() {
  //.... your code 
}

Can you figure out what does @Override means?

Expand using link to the right to see the full content.

We're back to the original printout, when we had no toString method. This is not strange since we need to be exact when overriding.

When adding @Override just above the method we can't compile. The meaning of @Override is to ask the compiler to check if the method following the @Override really overrides a method. If the method does not override a method, the compilation will fail.

Using @Override is recommended when overriding methods.

Q5

Rename your toStringie() method to toString() again and compile the Member and MemberTest classes and execute MemberTest. Did the output change?

Expand using link to the right to see the full content.

Yes, the output changed. So if the toString method is the same as the one in Object we override it.

Q6

Imagine you wanted the original String representation of the object as well as your new. Do you need to copy the source code of the Object's toString() method? Of course not, instead you can call the the super class' (parent class) method liks this:

super.toString();

Your job now is to rewrite your toString method to return a String with the String as returned by Object's toString + your printout. An example String, as returned from your method should look something like this:

com.sportmanagers.sportman.Member@2a139a55:1,Ada,ada@lovelace.org

where com.sportmanagers.sportman.Member@2a139a55 comes from the Object's toString method and :1,Ada,ada@lovelace.org comes from your method.

Hint: calling a method in a super class is done like this (assuming the method is called someMethod()): super.someMethod()

Expand using link to the right to see the full content.

Suggested solution:

    public String toString() {
        return super.toString() + ":" + this.id + "," + this.name + "," + this.email;
    }

Note: we explicitly use this (e g this.id) here. Since the compiler has no problem knowing that we mean the instance variable id we can leave out this. and write super.toString() + ":" + id + "," + name + "," + email instead.

Q7

override toString() in Team. The method should return the name of the team, but does not (yet!) include information about the team members. Use the TeamTest to verify that your toString works.

Expand using link to the right to see the full content.

Suggested solution:

In the Team class we add:

    public String toString() {
        return this.name;
    }


In the TeamTest class we add:

	// Print teams
	System.out.println("myTeam:           " + myTeam);
        System.out.println("myOtherTeam:      " + myOtherTeam);
	System.out.println("teamWithName:     " + teamWithName);
	System.out.println("teamWithSameName: " + teamWithSameName);

Q8

Can you think of a way to create an even nicer printout of a Team? How about printing the members as well? Let's do that. Your job is to do this.

Expand using link to the right to see the full content.

Suggested solution:

public String toString() {
  // Below, the + operator will trigger a call
  // to the toString() in members (which happens to
  // be an ArrayList, which has a perfectly useful
  // toString() implementation).

  return name + " members: " + members;

}

Explanation:

We want to return the Team's name followed by the list of members of this Team.

What we return is:

name + " members: " + members

But, wait a minute! name is a String, and " members: " is also a String, but members is a List! What Java does, in runtime, is that it finds the + operator between the strings and the List. In order to make the whole expression a String, it calls the toString() method on the only operand which isn't already a String, i.e. on the members List. As it happens, the toString() implementation in all List classes creates a String described like this:

The string representation consists of a list of the collection's elements in the order they are returned by its iterator, enclosed in square brackets ("[]"). Adjacent elements are separated by the characters ", " (comma and space). Elements are converted to strings as by String.valueOf(Object).

The implementation is actually found in a super class to the lists, called AbstractCollection.

Q9

Looks kind of nice. But the Member's string representation would be better without the text from Object's toString() method. Change the toString() method in Member to make a String representation of an object look like:

1,Ada,ada@lovelace.org

Compile necessary files and execute the TeamTest class again.

Expand using link to the right to see the full content.

Suggested solution:

The toString() method in the Member class should (or could) look something like this:

    public String toString() {
        return this.id + "," + this.name + "," + this.email;
    }

Q10

Looks better. But now we have the same separator of the variables as we have between the Members so the output is not perfect. Change the separator between the variables in the Member class to be "|" instead.

Expand using link to the right to see the full content.

Suggested solution:

The toString() method in the Member class should (or could) look something like this:

    public String toString() {
        return this.id + "|" + this.name + "|" + this.email;
    }

Q11

You had to change the separator at two places. This should ring a bell. Let's put the separator in a variable, which should be made:

  • static
  • final
  • private

Expand using link to the right to see the full content.

Suggested solution:

In the Member class you need to add:

    private final static String SEPARATOR = "|";


The toString() method in the Member class should (or could) look something like this:

    public String toString() {
        return this.id + SEPARATOR + this.name + SEPARATOR + this.email;
    }


Q12

Ok, time to see if the equals method can compare two Member instances. Create a new class, MemberEqualsTest, in the same package as the other test classes. In the main method of this class you should create two Member objects with the same name, email and id. Compare the objects with each other. Since the object referr to the same Member (name, email and id are the same so it must be the same Member) one would expect equal to return true. Does it?

Hint: The class Object has a method called equals.

Expand using link to the right to see the full content.

Suggested solution:

package com.sportmanagers.sportman.test;

import java.util.ArrayList;
import com.sportmanagers.sportman.Member;

public class MemberEqualsTest {

    public static void main(String[] args) {

        Member m1 = new Member("Ada", "ada@lovelace.org", 1);
        Member m2 = new Member("Ada", "ada@lovelace.org", 1);

        System.out.print("Comparing two member object with the same content. They are");
        if ( ! m1.equals(m2)) {
            System.out.print(" not");
        }
        System.out.println(" equal");

    }

}

When executing the program we get:

Comparing two member object with the same content. They are not equal

Q13

Add a method equals to the member class. It should look similar to the below:

  public boolean equals (Object obj)
   {
     // your code goes here
   }

Write code, in the section marked your code goes here, that:

  • checks if obj is null. If so return false
  • checks if obj is an instance of Member, if not return false
  • checks if all instance variables have the same value


Adding this method to your Member class will make the class override the equals method as inherited from Object. Recompile and execute the MemberEqualsTest class again.

Note: In the case of a Member all instance variables are used to check for equality. This is not true for all classes' equals method (if they have and need one).

Expand using link to the right to see the full content.

Suggested solution:

    public boolean equals (Object obj) {

        if (obj == null ) {
            return false;
        }

        // this - the object associated with the invocation
        // i.e. the object referred to by the reference used
        // in the call to equals()
        //
        // obj - the object that this should be compared with

        // Is "this" the same as obj? Then they must be equal!
        if (this == obj) {
            return true;
        }

        // Is obj not a Member, return false
        if (!(obj instanceof Member)) {
            return false;
        }

        // .... you need to add code to check if the instances
        // this and obj are equal. We do this by checking the instance variables
        // To get access to the instance variables in obj, which we now know is a
        // Member (see the instanceof check above), we need to type cast obj to a Member.
        Member m = (Member)obj;
        return this.name.equals(m.name) && this.email.equals(m.email) && this.id == m.id  ;
    }

When executing the program (MemberEqualstest) we get:

Comparing two member object with the same content. They are equal

Q14

Ok, so equals seems to work. Great. But this is not enough, we need to write a method called hashCode which overrides the same method in Object. We need to create a checksum based on the variables (either all or some) in the objecct. This checksum should always be the same if the object had not been altered.

For more information about equals and hashCode, read Why override Object's hashCode if equals is overridden?. We will give you instructions on how to implement the checksum and thereby make your Member class correctly overriding equals (and hashCode). But first we will ask you to output the hashcodes for your Member object by adding the following code to your MemberEqualsTest class' main method.

        System.out.println("Hashcode for the objects:");
        System.out.println(" * m1: " + m1.hashCode());
        System.out.println(" * m2: " + m2.hashCode());

Compile and execute.

Expand using link to the right to see the full content.

You should see two different hash code values. That's expected. You inherited the hashCode() method from Object, and that implementation is guaranteed to give a different value for every instance created. But that's not good, if two objects indeed are "equal" each other according to the equals method, then they should also have the same hash code value as per the contract.

Q15

Ok, so two objects that are equal have different hashcodes. Not good. So, no it is time to write our own hashCode method in the Member class. Here's a rough sketch on ho you should write it:

  • add a static (and final) variable called HASH_PRIME in the Member class. Assign it a value, e g 17. This value should not be changed from now on.
  • add a public method, hashCode, like the skeleton below:
   public int hashCode ()
   {
      // your code goes here
   }
  • the method should add the following and return the result:
    • HASH_PRIME
    • the hashcode of name (i e name.hashCode())
    • the hashcode of email (i e email.hashCode())
    • the value of id

Compile and execute MemberEqualsTest again. The checksums should now be the same for the two equal object.

We will not ask you to write the methods hashCode or equal in the Team class. To do this we need to discuss how we should think about equality of a Team and we believe that this is a bit outside of the scope of this course. In short we would have had to discuss what it means to have two Team objects with the same name but with different Member objects.

Concluding remarks: We don't expect you to fully understand the details of hashCode and we will not examine it. We have used toString(), equals() and hashCode() to learn about how we override methods. We could easily have skipped hashCode() but since you really should override hashCode if you're overring equals we showed you how to do it .... we wanted you to know this.

Expand using link to the right to see the full content.

Suggested solution:

We added the following code to the Member class:

  private static final int HASH_PRIME = 17;
  public int hashCode () {
     return HASH_PRIME + name.hashCode() + email.hashCode() + id;
  }

For a little more sofisticated hash calculation, you can use this too (as suggested By Joshua Bloch in Effective Java):

  public int hashCode () {
    int result = HASH_PRIME;
    result = 31 * result + name.hashCode();
    result = 31 * result + email.hashCode();
    result = 31 * result + id;
    return result;
  }

When compiling and executing we get the following output:

Comparing two member object with the same content. They are equal
Hashcode for the objects:
 * m1: 999086130
 * m2: 999086130

Note that the hashCode/checksums are the same for the two equal objects. Nice isn't it :)

Links

Further reading

Source code (solutions etc)

You can find complete source code to the suggested solutions below in the overriding_methods_in_object directory in this zip file or in the git repository.

Where to go next

Next page is: Inheritance_-_Extending_your_own_classes

« PreviousBook TOCNext »