Wednesday, December 3, 2014

Diamonds are not an Object's Best Friend

Once again, I turn to a post I made on reddit for inspiration.

Some people wonder why inheritance branch but never join.  Why not tie two different families of objects together and extend both of 'em?

I can see the point of that, one could conceivably build objects that join several disparate branches of inheritance into large and versatile classes.  Conceivably.  One could also get oneself into a world of trouble, though, and this is most easily explained via the so-called diamond inheritance problem.

Please note that you cannot do this and expect your code to compile, it's just a conceptual demonstration.
Diamond.java
public class Diamond extends Class1, Class2 {
}

Class1.java
public class Class1 {
    public void doSomething() {
         System.out.println("Class1.doSomething()");
    }
}

Class2.java
public class Class2 {
    public void doSomething() {
         System.out.println("Class2.doSomething()");
    }
}
Now you have a main that uses the above classes:
public static void main(String[] args) {
    Diamond diamond = new Diamond();
    diamond.doSomething();
}
So, when you run main, what happens? This is the essence of the diamond problem. You wind up having two lines of inheritance converging on one and run into a quite serious issue.

This basically just scratches the surface of the problem, of course.  We can also think about what happens when you try to invoke something in super.  The question would have to become 'which super'?  There are many potential negative ramifications from the interactions that could be created by allowing this kind of inheritance, and the creators of Java decided that the best way to avoid them was by preventing the issue altogether.
One can achieve most of the positive effects of multiple inheritance via Interfaces and a few helpful design patterns such as Adapter, Composite and Decorator.

Inheritance Part 1 - That Class is Super!

I would like to discuss the concept of inheritance, and no, I don't mean the vase that Aunt Gladys left you in her will.

When we talk about inheritance in object oriented programming languages, we are talking about how one object can be based on another object.  In Java, we use the keyword extends to do this.

However, in order to make this as simple as possible, let's ignore even that keyword for a moment.  EVERY single Java class automatically extends the class Object without any effort at all on your part.

Let's look at an example Person data object, which I will continue to use as an example for future posts.

public class Person {
    private String firstName;
    private String lastName;

    public Person(String newFirstName, String newLastName) {
        firstName = newFirstName;
        lastName = newLastName;
    }
    ...
}

As I mentioned above, Person automatically extends Object, so in reality the class definition is really more like this:
    public class Person extends Object {

But we just don't bother writing it.  We call Object the superclass of Person and Person is called a subclass of Object.

As far as what this brings to the table, let's use what is perhaps simultaneously one of the most common features and one of the most common issues people have with objects when they are starting out.  Printing them out.

If I create a new Person object and want to see what it looks like, I might be tempted to do this:

    Person joe = new Person("Joe", "Blow");
    System.out.println(joe);

If I do this, I will be sorely disappointed when I run my program, because while I might expect to see this:

    Joe Blow

I will instead see something like this:

    Person@4f1d0d

At this point, I begin to tremble and sweat.  In a panic I turn to stackoverflow.com or /r/javahelp and complain that my object is broken and my printouts are garbled.  The regulars there probably make fun of me for not actually posting my code correctly or something.  My dog bites me and my girlfriend refuses to go out with me.  OK, I may have exaggerated a bit.

But what's happening here is that when you call 'System.out.println(joe)' you are (again, invisibly) actually in effect calling 'System.out.println(joe.toString())'.  After all, you need to turn your object into something that can be displayed on the console.  

You might at this point be saying to yourself "But wait, I don't have a 'toString' in Person" and in a sense you're right.  You certainly didn't create such a thing.  But you did in fact inherit a toString method from Object.  Object is pretty ignorant about your code, though, and has no idea how you intend to format your printouts, so it just tells you the type of object and where in your JVMs memory that object is stored.  In general, if a superclass contains a method, a subclass can call it as though it were a part of the subclass itself.

This is all inheritance really is, in the end.  If we try to call a method or refer to a variable in an object, and it isn't mentioned in that object, the compiler tries to see if it's available via inheritance.  If so, you avoid compile errors and things 'work'.  They may not work the way you want, but at least something interesting happens.

Let's fix the problem now.  I could take the cheap and easy way out like this:

    System.out.println(joe.firstName + " " + joe.lastName);

And that would work.  But honestly, all we really need to do is create our own 'toString' method in Person that does things the way we want.  This will save us the effort of having to write the above code everywhere that wants to just display someone's name.  You'll really appreciate that when you decide that you'd rather display that guy as "Blow, Joe" instead and have to change code in 23 different places.

Add the following block of code to Person:

public String toString() {
    return firstName + " " + lastName;
}

And just like that, the original program will print the name out the way you wanted it to in the first place.

There, you've done it.  You've made use of an inherited method, and then you created an override for it.  An override is nothing more or less than specifying that instead of using the method found somewhere up the line of inheritance, you want to use this specific method instead.

When it comes down to it, overriding toString is just the tip of the iceberg, and I will of course be revisiting this later on.  There are some extremely interesting things that you can do with inheritance, and it forms the basis for many important language structures.  For now though, just remember that one class can extend another one, and that when it does so it has the option of replacing methods in its superclass with its own more specific version.  There are a few other rules you'll need to follow, but most of them are not too horrible to deal with.