Chapter:Inheritance

From Juneday education
Jump to: navigation, search

This page is replaced by:

Meta information about this chapter

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

Introduction

Inheritance is a mechanism to create sub types from a more general type. This is similar to the way we think in normal life.

Purpose

Getting familiar with inheritance is important since a lot of existing code uses inheritance (in some way or another) and also the fact that every class we write, or use, extends the class Object - either directly or indirectly.

Goal

The student shall:

  • understand the inheritance concept
  • be able to extend an existing class
  • write a class for later extension
  • know how to make it impossible to extend a class
  • know how to override methods in a super class
  • understand and use polymorphism (e.g. using a heterogeneous Collection or array, with elements of a super type, or writing a method accepting a reference to a supertype)
  • understand that every object ultimately extends java.lang.Object, and how to override methods from Object and see the benefits of that

Concepts

Instructions to the teacher

Common problems

Videos

All videos in this chapter:

See below for individual links to the videos.

What is inheritance

Description

Many object oriented languages have a mechanism for extending existing classes to a subtype. Public (and protected) members of a class will still exist in an extending class, but their implementation may be "overridden" (changed) by the subclass.

To extend (create a subclass, or subtype) a class, you use the extends keyword. There are many examples of this in the Java API (the class library which comes with your Java installation).

Objects of a subtype should have properties and behavior so that they can take the place of any objects of the supertype whithout hurting the (expected) behavior of your system.

An often used example of what could go wrong if this rule is not followed, is if you have a type Rectangle which allows you to change the sides independently from each other, and then create a type Square as a subclass to Rectangle. The Square class will inherit the behavior of changing sides independently from each other, which is not how we think about Squares - Squares sides are equal in length.

If we changed the implementation of the methods for changing one particular side so that it changes both sides, we will break the expected effect for how Rectangles behave!

Note that this problem consists of two parts, first a questionable decision to consider Squares as a subtype of Rectangles, second the decision to make Rectangles mutable. If no rectangles were mutable (they would have only methods to read the sides, not change them), this problem would disappear.

We'll explain inheritance in this chapter by looking at existing inheritance in classes from the Java API (the class library which comes with your Java installation), and by looking at the fact that actually all classes eventually extends the java.lang.Object class.

Videos

  1. What is inheritance?: (eng) (sv) (download presentation as PDF)

Exercises

No exercises - this is only an introduction.

Solutions

No exercises - no solutions

No solutions since we have no exercises!

Every class extends Object

Description

Java is designed so that every class extends the class Object - either directly or indirectly.

Videos

  1. Every class extends object I: (eng) (swe)
  2. Every class extends object II: (eng) (swe)
  3. Live coding I: (eng)

(download presentation as PDF)

Note: In the live video we write a class, Customer to represent a Customer in for example a web shop. We write a toString() method to get a nicer String representation of a Customer object. We also write a test class,CustomerTest to test our Customer class.

Exercises

In the exercises below we want you to (evetually) use the following directory structure:

.
`-- com
    `-- sportmanagers
        `-- sportman
            |-- main
            |   `-- Main.java
            |-- Member.java
            |-- Team.java
            `-- test
                |-- MemberTest.java
                `-- TeamTest.java

This means we strongly suggest you enter the directory where the com dir is located - but do not enter the com directory. This way the relative paths to the files corresponds directly with the package names and you will have a much nicer time compiling and running your program.

The task for the exercises below is to start writing parts for a system to manager sport clubs. We start by writing class to represent a member and the club itself. To make sure we're writing code for the same kind of system we will decide the package structure, class names and class strucure. Feel free to write the classes the way you want it but it may mean that the suggested solutions will not help you.

A member need to have a name, an email addres and an id.

A team need to have a name and a list (possibly empty) of members (players)

1 Write a class, Member with the following private instance variables:

  • name (String)
  • email (String)
  • id (int)

The class should be located in the package: com.sportmanagers.sportman.

2 Add public constructor for setting the three instance variables

3 Add public methods for users of your class (or rather instances from that class) that return the instance variables (in separate methods of course).

4 Ok, time to test our class. We will not (why should we?) add a main method to our Member class just as there's no main method in the String class. So write a test class for Member. Let's call it MemberTest. Write a main method which creates three objects and stores them in an ArrayList. Use the package name com.sportmanagers.sportman.test for the test class.

Hint: in the chapters about Classes you created ArrayLists. Go back and check there, and possibly redo the exercise, if you don't know how to create ArrayLists

5 Compile and run test test class

6 Nothing is printed. Have the objects been created if we don't "see" them?

7 Add a printout to check the "content" of the objects. Do this by looping (e.g. use the for each-loop) through the object references in the ArrayList and call System.out.println and pass the object reference as argument to println.

Hint: We've printed the user arguments (String instances in an array) in earlier exercises.

8 Why is the output looking like it does?

The output is not very impressing. But this is enough for now. We will continue with this Member class in the next section.

So we can consider our Member class to be ready to be used. So let's continue with the Team class.

9 Write a new class, Team with the following private instance variables:

  • name (String)
  • members (ArrayList)

10 Write a constructor for Team that can be used to set the name

Note: the constructor should create an internal empty ArrayList. This is needed if adding members later on.

11 Write a constructor for Team that can be used to set the name and the list of members

12 Write two public methods that can return the name and the list of members. BTW, Does the Team class need a main method?

13 Let's start writing tests to make sure our Team class works.

  • Let's call the class TeamTest. Write a main method which:
  • Use the package name com.sportmanagers.sportman.test for the test class.
  • Creates a Team instance by invoking the second constructor by passing;
    • a name, e g "My Team", and
    • an ArrayList with three Members
  • Verify that the number of members in the team is correct.

Hint: ask the Team instance for the list of members and count the members (or is there a method in the ArrayList that already does this?)

14 Create one more team in the TeamTest class' main method. When creating this Team you should you the constructor with only one parameter. Verify the amount of members.

15 How do we add members? Add a method, public void addMember(Member m) to the Team class.

Hint: Something like this:

public void addMember(Member m) { 
  // fill in your code here 
}

16 Add three Member instances to the second Team instance you created. Verify that the amount of members is correct in that team.

17 Output the first team, using System.out.println().

18 Output the first team like above but do an explicit call to toString().

19 Since you have not added (or at least was not instructed to) a toString() method you need to hink about how that method can exist in the Team instance.

Hint: System.out.println(myTeam.toString());

20 You now have two Team objects. We are going to compare them, but first let's check one thing:

  • Have you written a method in your Team class called equals?

Note: If you have written such a method we kindly ask you to "comment it out" - that is wrap the method in comments to make the compiler ignore it

21 Write code to compare the two Team objects, using your reference variables. You compare the objects, actually check if they are "equal", by using the method equals. Can you compile the code - please note that you do not have a method called equals?

Hint: Invoke equals on one object and pass the other object as parameter. A simple and stupid example of comparing two String objects below:

        String oneString     = "Blah blah blah";
        String anotherString = "New values";

        System.out.print("\"" +
                         oneString +
                         "\" and \"" +
                         anotherString +
                         "\" are ");
        if (!oneString.equals(anotherString)) {
            System.out.print("not ");
        }
        System.out.println("equal.");

22 If you change the name of the method you invoke from equals to checkIfEquals. Note that you have not written a method in your class with that name. Will the code compile? Why?

23 Change the code so that is uses the equals method again. Is the comparison done by the equals method as inherited from Object satisfying?

24 Create two Team instances with the same name using the first constructor (with one parameter). Compare these objects the same way as you did with the other Team objects. If we go back to the real world and think about how you interpret the clubs "AS Roma" and "AS Roma" mentioned twice in an article. We consider this to be the same club. So our Team object should do the same. The teams objects both referr to the same team - since the teams have the same name - so they should be "equal". Are they? Why?

Solutions

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


You can find complete source code to the suggested solutions below in the every-class-extends-object directory in this zip file or in the git repository.

1 Suggested solution

package com.sportmanagers.sportman;

public class Member {

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

2 Suggested solution

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

3 Suggested solution

    public int id() {
	return id;
    }

    public String name() {
	return name;
    }

    public String email() {
        return email;
    }


4 Suggested solution

package com.sportmanagers.sportman.test;

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

public class MemberTest {

    public static void main(String[] args) {

        ArrayList<Member> register = new ArrayList<>();

	register.add(new Member("Ada", "ada@lovelace.org", 1));
	register.add(new Member("Bart", "bart@simpson.net", 2));
        register.add(new Member("Bart", "bart@simpson.net", 2));

        // The above is a shorter way of writing the code below                                                                 
        //                                                                                                         
        // We create the Members and store the references to them                                                  
        // directly without using a variable name.                                                                 
        //                                                                                                         
        // It is a reference (or references) to instance(s) we store                                               
        // in an variable(s), array or ArrayList, never the actual                                                 
        // object.                                                                                                 
        //                                                                                                         

        /*                                                                                                         
        Member m1 = new Member("Ada", "ada@lovelace.org", 1);                                                      
        Member m2 =new Member("Bart", "bart@simpson.net", 2);                                                      
        Member m3 =new Member("Marge", "marge@simpson.net", 3);                                                    
        register.add(m1);                                                                                          
        register.add(m2);                                                                                          
        register.add(m3);                                                                                          
        */
        // or another example:                                                                                     
        /*                                                                                                         
        Member m1 = new Member("Ada", "ada@lovelace.org", 1);                                                      
        register.add(m1);                                                                                          
                                                                                                                   
        m1 =new Member("Bart", "bart@simpson.net", 2);                                                             
        register.add(m1);                                                                                          
                                                                                                                   
        m1 =new Member("Marge", "marge@simpson.net", 3);                                                           
        register.add(m1);                                                                                          
        */


    }


}

5

To compile:

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

To execute:

java com.sportmanagers.sportman.test.MemberTest

... and if you, as we would like to suggest, compile and execute (only if compilation succeeds) at the same time:

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

6 The objects have been created. Both the Member instances as well as the ArrayList instance. Outputing text to a stream does not do anything magic. Fact is that most of the things a program do, whether it is creating objects, calculating or what have you is done without printing anything).

7 Suggested solution

        for (Member m : register) {
            System.out.println(m);
        }

8 What happens when we invoke println and pass an object reference is that the method toString() (eventually) is called to get a String representation of each Member object. We inherit this method toString() from Object. In one implementation of Java we can find this source code in Object.java:

    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

So print an object, by passing the reference of course, we eventually end up invoking the method above. This explains the look of the printout. The printout is neither magic nor a string representation of a reference.

9 Suggested solution

package com.sportmanagers.sportman;

import java.util.ArrayList;

public class Team {

    private String    name;
    private ArrayList<Member> members;

}

10 Suggested solution

    public Team(String name) {
	this.name = name;
        this.members = new ArrayList<>();
    }

11 Suggested solution

    public Team(String name, ArrayList<Member> members) {
	this.name   = name;
	this.members = members;
    }

12

    public String name() {
        return name;
    }

    public ArrayList<Member> members() {
        return members;
    }

No, we don't need to add a main method. Just as String does not need a main method. Team is not a program - it is soon to be part of a program.

13 Suggested solution

package com.sportmanagers.sportman.test;

import java.util.ArrayList;

import com.sportmanagers.sportman.Member;
import com.sportmanagers.sportman.Team;

public class TeamTest {

    public static void main(String[] args) {

        // Create an ArrayList with three members (to pass to the constructor)                                                                                                                                                                               
        ArrayList<Member> threeMembers = new ArrayList<>();
        threeMembers.add(new Member("Ada", "ada@lovelace.org", 1));
        threeMembers.add(new Member("Bart", "bart@simpson.net", 2));
        threeMembers.add(new Member("Marge", "marge@simpson.net", 3));

        // Crete a team, pass a name and the list of three members                                                                                                                                                                                           
        Team myTeam = new Team("One Team", threeMembers);

        // Verify size (three members - so the size should be three)                                                                                                                                                                                         
        System.out.print("Checking size of myTeam:       ");
        assert (myTeam.members().size()==3);
        System.out.println("ok");
    }
}


14 Suggested solution

        // Crete another team, pass a name ... no list                                                                                                                                                                                                       
        Team myOtherTeam = new Team("One More Time");

        // Verify size (no members - so the size should be 0)                                                                                                                                                                                                
        System.out.print("Checking size of myOtherTeam:  ");
        assert (myOtherTeam.members().size()==0);
        System.out.println("ok");

15

    public void addMember(Member m) {
        members.add(m);
    }

16

        // Add the members to the second team                                                                                                
        myOtherTeam.addMember(new Member("Lisa", "lisa@simpson.net", 4));
        myOtherTeam.addMember(new Member("Maggie", "maggie@simpson.net", 5));
        myOtherTeam.addMember(new Member("Ned", "ned@flanders.org", 6));

        // Verify size (no members - so the size should be 0)                                                                                
        System.out.print("Checking size of myOtherTeam:  ");
        assert (myOtherTeam.members().size()==3);
        System.out.println("ok");

17 Suggested solution:

        System.out.println("myTeam: " + myTeam);

18 Suggested solution:

        System.out.println("myTeam: " + myTeam.toString());

19 It is inherited from Object. Each and every object inherits from Object - either directly or indirectly - so each and every object has a toString() method.

20 No, you have not written any method yourself in your class. Yes, as some of you may remember your class is inheriting Object and therefore inherits that method - but the question was if you had written on such method yourself so the answer is no.

21 Suggested solution:

        System.out.print("Comparing my teams.. they are");
        if ( ! myTeam.equals(myOtherTeam)) {
            System.out.print(" not");
        }
        System.out.println(" the same");


22 It will not compile since there's no such method. Exmple printout from the compiler below:

com/sportmanagers/sportman/test/TeamTest.java:49: error: cannot find symbol
        if ( ! myTeam.checkIfEquals(myOtherTeam)) {
                     ^
  symbol:   method checkIfEquals(Team)
  location: variable myTeam of type Team
1 error


24 Suggested solution:

        Team teamWithName     = new Team("AS Roma");
        Team teamWithSameName = new Team("AS Roma");
        System.out.print("Comparing two teams objects with the same name.. they\
 are");
        if ( ! teamWithName.equals(teamWithSameName)) {
            System.out.print(" not");
        }
        System.out.println(" the same");


We get this result since we're inheriting a very basic equals from Object. We need to write our own equals method, which by pure conincidence is something we're going to do in the next section.


Overriding methods in Object

Description

If you extend a class you might be able to change the behaviour, that is write your own version of a method. The mechanism to do this is called override.

Videos

  1. Overriding methods in Object: (eng) (sv) (download presentation as PDF)

Bonus videos with live programming

We've recorded a few videos showing you how we can refine a small class by adding a toString() method (overriding the method inherited from Object) and also a simplistic implementation overriding hashCode(), also inherited from Object.

  • Live coding II: (eng) We add methods to retrieve name and email from a Customer object. We show you how to use a simple annotation.
  • Live coding III: (eng) In live video II In live video III we show how to override equals().
  • Live coding IV: (eng) In live video IV we show how to override hashCode().


Note: The examples in the live videos are mainly a way for us to show you how we can override methods.

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

}


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

2 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.

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

4 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?

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


6 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()

7 override toString() in Team. The method should print each Member as well as info about team (i e name). Use the TeamTest to verify that your toString works.

8 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.

Hint #01: You've seen Arrays.toString in our videos and presentations (pdf). If you want to use that method, which is an easy way to print an array, you need to transform the ArrayList member to an array. This is easily done using the toArray() method in ArrayList. Given that the ArrayList is called members that code to transform that ArrayList will look like this: members.toArray()

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

10 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.

11 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

12 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.

13 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).

14 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.

15 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.

Solutions

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


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.


1

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

2 Suggested solution:

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

3 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.

4 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.


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

6 Suggested solution:

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

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

8 Suggested solution:

    public String toString() {
	String result = this.name;

        // Solution using Arrays.toString                                                                                                                                       
        // * members.toArray() transforms our ArrayList to an Array                                                                                                             
        result += " members: " + Arrays.toString(members.toArray());

	// Solution where we loop and add the String representations                                                                                                            
        // of our Member instances one by one                                                                                                                                   
        /*                                                                                                                                                                      
        result += " members: [";                                                                                                                                                
        for (Member m : members) {                                                                                                                                              
            result += m;                                                                                                                                                        
            // There are better (faster) ways to append/concat many                                                                                                             
            // strings to a string ... Look at StringBuilder :)                                                                                                                 
        }                                                                                                                                                                       
        result += " ]";                                                                                                                                                         
        */
        return result;
    }


9 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;
    }

10 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;
    }

11 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;
    }

12 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;
    }


13 Suggested solution:

package com.sportmanagers.sportman.test;


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

14 Suggested solution:

    public boolean equals (Object obj) {

        // this - the object associated with the invcation                                                                                                                      
        // obj - the object that this should be compared with                                                                                                                   

        // Is "this" the same as obj?                                                                                                                                           
        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

15 No extra information needed.

16 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 :)


It is possible to extend your own classes

Description

It is possible to create your own class hierarchy, using inheritance (and extends). This chapter will introduce you to how to do that but at the same time show that some of the suggested gains from inheritance may not actually be a gain at all.

Videos

  1. Inheriting your own classes: (eng) (sv) (download presentation as PDF)

Exercises

Let's write ourself a File browser. Since we don't yet know how to create graphical user interfaces we'll settle for a text interface. We need a class representing the files on the disk - the files we want to manage. If we call this class File we have a name collision with the File class that already exists in Java. Since we have packages this will work fine - but it may be confusing for the reader se we need another name for the class representing files. Let's use: FBFile, as in File Browser File.

We provide a class, FileLister, which you can use to create a List (e g ArrayList) files and directories. Download the file and save it according to its package declaration.

package com.superpower.filebrowser.utils;

import java.util.List;
import java.util.ArrayList;
import java.io.File;

import com.superpower.filebrowser.*;


public class FileList {

    public static List<FBFile> list() {
        return list(".");
    }

    public static List<FBFile> list(String dir) {
        return list(new File(dir));
    }
    
    public static List<FBFile> list(File dir) {

        if ( (dir==null) || dir.isFile() ) {
            return null;
        }
        
        List<FBFile> list = new ArrayList<FBFile>();

        // Note that we use File below. File is a class in Java.
        File[] files = dir.listFiles();
        for (File file : files) {
            if (file.isFile()) {
                list.add(new FBFile(file.getPath()));
            } else if (file.isDirectory()) {
                System.err.println("Ignoring directory: " + file.getPath());
            }
        }
        return list;
    }

}

Note: this code uses the old File class instead the newer Path classes. We will update this some day.


1. Let's write the class FBFile, with methods and parameters as follows:

  • an instance variable name representing the actual file's name.
  • a method, name() returning the name of the file.
  • a method, thumbnail() returning a thumbnail image, which in our case will be a String "[file]".
  • a constructor whith which we can set the name variable. No other constructor is needed. Do not create a contrsuctor with an empty argument list.

and the class use the package com.superpower.filebrowser. The package name reflects that we're a company called Superpower and our product is class File Browser.

Note: We could choose several other different ways to solve this but we will settle for this solution since we're currently practicing inheritance

2. Write a class called TextMain with a main method. We suggest you use the method list in the class, FileList listed above that returns a List (ArrayList inherits from List) of files. You can get a list of files from that class like this: List<FBFile> files = FileList.list();The class should use the package name com.superpower.filebrowser.main

We provide a zip file Media:Inheritance-files.zip where you can find some example/sample files. Download and unpack the zip file.

Print the list to stdout using System.out.println, to see the files. Enter this directory and execute the program. When entering the directory Java will no longer find your class if your using the same command line as before. We need to tell java to look for our Java classes in the parent directory:

java -cp ../ com.superpower.filebrowser.main.TextMain

Note:you should enter the files directory before you start your program, hence the -cp flag passed to Java.

3. Change the output, in the main method of the TextMain class, so that it prints the thumbnail of the file followed by the name of the file.

The output should look something like this:

[file] ./medium.txt
[file] ./small.txt

4. It's a bit disappointing not adding directories to the list of files and instead see a warning printout (see below). Good thing we have already written some code for you to solve this. Do the following to make things work a bit better. In the class FileList we want you to remove the call to System.err.println and instead add code that adds the directory as an FBFile (see below).

Example of warning printouts:

Ignoring directory: ./com
Ignoring directory: ./files

Code to add directories:

            } else if (file.isDirectory()) {
                list.add(new FBFile(file.getPath()));                                                  
            }


Note: you hopefully think that it is not very clever to create and add a FBFile in both branches of the if-statement. We agree, but want you to keep it this way since we will change one of the statements in the coming exercise.

Compile and execute again.

5. It is rather confusing for the user to see a directory listed as a file, even if a directory is a file in Java. Let's sub class FBFile. Create a new class FBDir extending FBFile. The class should override the method thumbnail and return "[dir ]" instead.

Compile and execute again. Nothing happened. Why?

6. Create a class called FBJavaFile that will represent Java files. Change to logic of FileList to create FBJavaFile instances if it finds a Java file.

Hint: to check if a string ends with a string you can use the method endsWith in String. If you write name.endsWith("am") we will get true returned if name ends with "am" and false otherwise.

Concluding remarks: We can see that it is rather easy to create subclasses to FBFile and use them accordingly. It would be fairly easy to extend this software to be able to handle all sorts of files. This will, however, mean that we'll have tons of classes (one per file type) with basically the same content. Are we gaining a lot by using inheritance? Would it be easier to let FBFile have a property (e g an instance variable) representing the file type?

Note: in the root of the it-is-possible-to-extend-your-own-classesdirectory you'll find a file called Makefile. Feel free to use it with the make command. Make (together with a Makefile) is a tool to control the building/compilation of software.

Solutions

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


You can find complete source code to the suggested solutions below in the it-is-possible-to-extend-your-own-classes directory in this zip file or in the git repository.


1 Suggested solution:

package com.superpower.filebrowser;

public class FBFile {

    private String name;

    public FBFile(String name) {
        this.name = name;
    }

    public String name() {
        return name;
    }

    public String thumbnail() {
        return "[file]";
    }

}


2 Suggested solution:

package com.superpower.filebrowser.main;

import java.util.List;

import com.superpower.filebrowser.utils.FileList;
import com.superpower.filebrowser.FBFile;

public class TextMain {

    public static void main(String[] args) {

        List<FBFile> files = FileList.list();

        for (FBFile f : files) {
            System.out.println("f: " + f);
	}

    }

}

3 Suggested solution:

Change the main method to something like this:

    public static void main(String[] args) {

        List<FBFile> files = FileList.list();

        for (FBFile f : files) {
            System.out.println(f.thumbnail() + " " + f.name());
        }

    }

4 Suggested solution:

            } else if (file.isDirectory()) {
                //             } else if (file.isDirectory()) {
                list.add(new FBFile(file.getPath()));
            }

5 Suggested solution:

package com.superpower.filebrowser;

public class FBDir extends FBFile {

    public FBDir(String name) {
        super(name);
    }

    public String thumbnail() {
        return "[dir ]";
    }
    
}

Nothing happened since we've only created a class which no one uses.

6 Suggested solution:

package com.superpower.filebrowser;

public class FBJavaFile extends FBFile {

    public FBJavaFile(String name) {
        super(name);
    }

    public String thumbnail() {
        return "[java]";
    }
    
}

Complete FileList (suggested) class:

package com.superpower.filebrowser.utils;

import java.util.List;
import java.util.ArrayList;
import java.io.File;

import com.superpower.filebrowser.*;


public class FileList {

    public static List<FBFile> list() {
        return list(".");
    }

    public static List<FBFile> list(String dir) {
        return list(new File(dir));
    }
    
    public static List<FBFile> list(File dir) {

        if ( (dir==null) || dir.isFile() ) {
            return null;
        }
        
        // By returning a List we can chose any the subclasses (in
        // this case actually sub interfaces - more on that in the
        // coming chapter) and change it afterwards, since the users
        // of this method only uses the methods as "provided" by List
        List<FBFile> list = new ArrayList<FBFile>();
        
        File[] files = dir.listFiles();
        for (File file : files) {
            if (file.isFile()) {
                if (file.getPath().endsWith(".java")) {
                    list.add(new FBJavaFile(file.getPath()));
                } else {
                    list.add(new FBFile(file.getPath()));
                }
            } else if (file.isDirectory()) {
                // System.err.println("Ignoring directory: " + file.getPath());
                //
                //list.add(new FBFile(file.getPath()));
                list.add(new FBDir(file.getPath()));
            }
        }
        return list;
    }

}

Examples from Swing

Description

Swing, which is a graphical framework in Java, uses inheritance extensively and is therefor a good example on inheritance. We will look briefly at some of the Swing components and at the hierarchy.

Videos

  1. Examples from Swing I: (eng) (sv) (download presentation as PDF)
  2. Examples from Swing II: (eng) (sv) (download presentation as PDF)

Exercises

No exercises in this section but feel free to look at the code we used in the presentation/video. You can download either as a zip file or look in our git repository. The source can be found in the directory swing-examples.

Solutions

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

No solutions since we have no exercises!

Some rules and syntax

Description

This section shows you the rules (most of them) when using extends.

Videos

  1. Rules and syntax I: (eng) (sv) (download presentation)
  2. Rules and syntax II: (eng) (sv) (download presentation)

Exercises

No exercises in this section.

Solutions

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

No solutions since we have no exercises!

Problems with inheritance

  • Inherit because of laziness
    • Inheriting the wrong class, is your class really and foremost an extension of the superclass?
    • Example: Inherit JFrame to reduce code
    • Warning sign: "If I inherit this class I get some stuff for free"
    • Use the "Is a" test, is your subclass really a <Superclass> or could it just as well have an instance of the superclass as a component?
  • Do we really reduce the amount of code
  • Employee, Manager, Developer, Project Manager, etc. What about if we have two roles? Does it reduce the amount of code iw we use inheritance? WHat is we gain....apart from satisfying someone's idea of a good design.
  • Inheritance breaks information hiding - particularly using protected
  • Inheritance can change the semantics of the super class
    • Square as subclass to Rectangle

Description

Using inheritance, when used inproperly, causes lot os problems. This chapter shows some of the problems with inheritance.

Videos

  1. Problems with inheritance: (eng) (sv) (download presentation)

Exercises

  1. No exercises in this section.

Solutions

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

No solutions since we have no exercises!

Extra exercise

We've added an extra exercise on inheritance here.

Links

External links

Books this chapter is a part of

Programming with Java book

Book TOC | previous chapter | next chapter