Lab 6: Inheritance

We've just learned about inheritance, in which one class can extend another, adding (or slightly changing) functionality. We're going to play with this today using a custom string class. We are going to make other subclasses that extend or modify functionality.

The class diagram to the right shows how the various classes relate to each other. This image is a subset of a diagram from UML (Unified Modeling Language). You will see these frequently. You can here more about them here if you are interested.

Note

Pay attention to exact file names, class names, and method names.

Use the names the names that are requested in this lab.

Misnamed files, classes, or methods will not be graded.

Step 1: Our Base Class

The parent class is often called the 'Base Class' or 'superclass'. Our base class is named ICString. It creates a String-like object. Copy the code below and save it to a file named ICString.java in a directory named Lab06.

public class ICString {

    private char[] charArray;

    public ICString(String s) {
        charArray = s.toCharArray();
    }

    public int length() {
        return charArray.length;
    }

    public String toString() {
        return new String(charArray);
    }

    public static void main(String[] args) {
        ICString s = new ICString("Test");
        System.out.println("ICString: " + s);
        System.out.println("length:   " + s.length());
    }
}

Compile and run the code from the command-line.

Here are some things to notice:

Step 2: Our first subclass

The Java String class is sometimes difficult to work with because it is immutable (once initialized, it cannot be changed.) We are going to create our own string class that can be changed.

Create a file named MString.java ("Mutable String") with the following Requirements:

At this point MString has the same functionality as ICString. It just has a different name.

Compile and test MString until you get it working properly.

Step 3: Changing characters in the string

The Requirement for this step is to add a new function to MString:

public boolean setCharAt(int i, char c)

Add some test code to the MString main() method to verify that the char is being overwritten properly, and that the code does not crash if you request to overwrite an illegal index.

Step 4: Our second subclass

We now have a mutable string class. This is pretty useful. We are now going to inherit again to take advantage of this new functionality.

Imagine that we wanted a string that was always upper-cased. We want to be able to create the string with both upper and lower-case letters, but the string's characters will always be stored and displayed as capitals. We are going to do this by inheriting from MString and using that class's built-in mutability to keep our letters upper-cased.

Create a file named UString.java ("Uppercase String") with the following Requirements:

Compile and test the above code. You should, for example, be able to create a UString object initialized to 'Mixed Case' and see 'MIXED CASE' when you print it out.

Step 5: Hardening our second subclass

There is a problem with the above description and requirements for UString: It is still possible for UString to be able to hold a lowercase character. Can you see this logic hole? We are going to plug it.

Add the following test case to UString's main method:

    UString test1 = new UString("test one");
    System.out.println(test1);
    test1.setCharAt(0, 't');
    System.out.println(test1);

This test should print out "tEST ONE", which has an illicit lowercase character

Our challenge is to fix setCharAt() without modifying MString, and without completely reimplementing the functionality in UString. One thing about class inheritance - you should never reimplement functionality if you do not absolutely have to. Doing so adds extra headaches for maintenance.

Here are the Requirements for this step:

Re-run the test above. It should now print "TEST ONE".

Step 6: Our third subclass

We are going to create a sibling class for UString named LString ("Lowercase String")

Here are the Requirements for this step:

Step 7: More functionality for MString

The Requirement for this step is to add a new function to MString:

public void reverse()

Once you have verified that the reverse() method works in MString, try running it in LString:

LString test2 = new LString("Backwards");
test2.reverse()
System.out.println(test2);

This should output "sdrawkcab"

*** This is the whole point of inheritance - we just added a new piece of functionality to MString, and we got the same functionality for free in LString and UString.***

Step 8: So where do static variables fit in?

By now, I am sure you have found yourself wondering how inheritance affects a static variable. Let's do an experiment to find out.

Add the following variable definition to your MString class:

public static String staticTest = "set from MString";

Run this from UString.main():

    System.out.println("\nOriginal static string value ...");
    System.out.println(MString.staticTest);
	
    System.out.println("\nChanging static string ...");
    UString.staticTest = "set from UString";
    System.out.println(MString.staticTest);
    System.out.println(UString.staticTest);
    MString m = new MString("x");
    System.out.println(m.staticTest);

    System.out.println("\nChanging static string again...");
    m.staticTest = "set from m";
    System.out.println(MString.staticTest);
    System.out.println(UString.staticTest);
    System.out.println(m.staticTest);

Make sure you understand why the above code outputs the way it does before your move on. Add some more experiments until you figure it out.

Step 9: Ruggedization testing

You do not need to create a Lab6.java file for this exercise. Your instructors will generate their own for testing. The test program will import each of the above classes and check that they create the expected output.

*** Your classes and methods must match the above Requirements exactly for this to work ***

The actual cases we use for testing are kept secret, but the test code will look something like the following. Go ahead and use it for your own sanity-check:

public class Lab06 {
    public static void main(String[] args) {
        // Test 0 - ICString
        ICString i = new ICString("test");
        System.out.println(i);
        System.out.println(i.length());
        System.out.println();

        // Test 1 - MString
        MString m = new MString("Mutable String");
        System.out.println(m);
        boolean b;
        b = m.setCharAt(0, 'X');
        System.out.println(m + "   " + b);
        b = m.setCharAt(-1, 'z');
        System.out.println(m + "   " + b);
        b = m.setCharAt(13, 'Y');
        System.out.println(m + "   " + b);
        b = m.setCharAt(14, 'z');
        System.out.println(m + "   " + b);
        m.reverse();
        System.out.println(m);
        System.out.println();

        // Test 2 - UString
        UString u = new UString("Upper-Case String");
        System.out.println(u);
        b = u.setCharAt(1, 'x');
        System.out.println(u + "   " + b);
        b = u.setCharAt(-1, 'A');
        System.out.println(u + "   " + b);
        u.reverse();
        System.out.println(u);
        System.out.println();

        // Test 3 - LString
        LString l = new LString("Lower-Case String");
        System.out.println(l);
        b = l.setCharAt(1, 'X');
        System.out.println(l + "   " + b);
        b = l.setCharAt(-1, 'A');
        System.out.println(l + "   " + b);
        l.reverse();
        System.out.println(l);
        System.out.println();
    }
}

What to submit

Submit the following files:

All of the above must be in a directory named 'Lab06'

Delete any *.class or other unneeded files prior to submitting

When you run the submit script, the directory and its contents get zipped and sent to your instructor. Ask for help early if you are having problems running the submit script.

Make sure that you run the correct submit script for your instructor: