CMSC 330, Fall 2005

Organization of Programming Languages

Safety in disguise: Fast Java for C++ people II

By Asad B. Sayeed

Greetings, fleshly beings.

In our last episode, we learned the rudiments of Java and the Transformers. We learned how to write a basic Java program that has a main function and produces output. We learned about Java polymorphism and inheritance. And we learned how to use basic data structures and about the forms of Transformer leader, Optimus Prime.

So now you really have enough information to start writing Java programs that do stuff. So I guess this tutorial ends here.





Just kidding. There's still a lot of stuff you should know about Java, even if you now have sufficient knowledge to do any form of computation in it. We'll explore a couple of these now.

Ah! Ah! Ah! Ah! Stayin' alive! Stayin' alive!

One of the much touted Java features is that of "safety". Java is a safer language than C. We've mentioned, vaguely, that Java has no explicit memory management. Those of you who've debugged a C++ program with mysterious segmentation faults or memory leaks can probably imagine how this makes Java safer. But what does it mean to be safe? It doesn't mean that programs are guaranteed to work in Java—that's impossible. It doesn't mean that programs have effects that are easily predicted by the programmer, even.

The most basic definition of safety in the Java context is that it has, at least ideally, no undefined behaviour. That is to say, no matter how you rig the environment, program behaviour in Java is always repeatable, good or bad. Now, of course, Java implements more safety features than this basic definition in order to help you deal with some of the more common bug and mental typos that occur when you write code. But no matter what kind of mistake you make, you are at least guaranteed repeatability. As always, in theory, naturally...

(I keep saying "in theory" for a few very good reasons. One of these is that, well, in real life you can never avoid undefined behaviour.)

Let's compile this code (I won't go through the old discussion about how to compile code except to say that if X is the name of the class, X.java is the name of the file it should be written in):

public class EvilDecepticonTrick {
  public static void main(String[] args) {
    boolean[] temptation = new boolean[3];

    temptation[0] = true;
    temptation[1] = true;
    temptation[2] = true;

    for (int i = 0; temptation[i]; i++) 
      System.out.println("I believe the Decepticon!");
  }
}

This is the first time we've explicitly declared an array. Note that while arrays are represented by special syntax in Java, they are still objects. And thus, you must use "new" to obtain a reference to an Array object of fixed size. Also note, again, the necessity of an explicitly boolean type in the condition of the for loop.

(You may wonder whether a Decepticon trick can properly be called evil, when obviously tricks are merely the fulfillment of a Decepticon's being—they aren't called Decepticons for nothing. This is a profound and pressing deontological question, and I refer you to the university's philosophy department for an answer.)


This is the Decepticon logo. Pretty sinister, eh?

Now compile and run it:

sporty:~/javapages: java EvilDecepticonTrick
I believe the Decepticon!
I believe the Decepticon!
I believe the Decepticon!
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
        at EvilDecepticonTrick.main(EvilDecepticonTrick.java:9)

This is what you get, of course, for trusting a Decepticon. Exceptions. For those of you who haven't studied exceptions, they are conditions that do not refer to profound system errors, but nonetheless refer to behaviour that would be otherwise unsafe or undefined. Exceptions are a way of defining undefined behaviour—conditions hindering expected execution.

(Here again I'd like to emphasize the "in theory" nature of all of this. In fact, Java itself acknowledges this by defining a counterpart to exceptions: errors. Errors in Java behave in a extremely similar fashion to exceptions. But errors are thrown when there is a real danger of undefined behaviour. For example, if the system were to run out of memory, that's an obvious place where no software environment can ever prevent undefined behaviour. Java throws an error in that conditon.)

Here, the name of the exception tells you precisely what the problem is, in case you couldn't already tell: you used an ArrayIndexOutOfBounds. The array index was 3. Since Java arrays, like C++ arrays, start at 0, the only valid indices for our array were 0, 1, and 2. So as soon as we requested 3 (because the terminating condition of the for loop was never reached), Java took, well, exception.

Note that would not necessarily be the case in C++ at all. If we had an array declared as a pointer, for instance, we could technically go well beyond the allocated bounds. Of course, we would likely have garbage beyond those bounds, and then we would have undefined behaviour without any exceptions—each execution would encounter new garbage (and maybe new civilizations, and boldly go where no code has gone before). Some of the time, it may leave the bounds of memory allowed to the program, and you might get a segmentation fault. Java never allows any of these things to happen. It is an Autobot ally.

(No, I did not spell "behaviour" the wrong way. I spelled it the honourable way, merci. Eh?)


I'm a patriotic Canadian and committed to spelling
some words with an extra "u".
Consider yourself orthographically educated now.

Face off!

Let us set aside, temporarily, from the matter of Java exceptions, and instead turn to the more pressing matter of Transformer history. The Transformers are essentially refugees on Earth from an ancient robot war on the planet Cybertron. And like many refugee communities, they quickly become plagued by factionalism. On the one hand, there are the hardworking, honest Autobots, who take the form of cool automotives in order to get decent jobs and fit in. Average Joes, if mechanical. The Decepticons, however, become fanatical revanchists and irredentists, determined to abuse the planet Earth in their ancient and futile war.

Each faction has a leader. The Autobots have Optimus Prime (which is almost Latin for "best first"), and the Decepticons have Megatron (which is almost Greek for "big"...tron). Leaders, naturally, have a special property: the ability to give orders. We want to be able to encode this property in our Java representation of the Transformers. To make things worse, later versions of the Transformers have different leaders, like Optimus Primal.


Optimus Prime vs. Megatron. Megatron is above.
Megatron actually has a serious goal.
In later incarnations of the Transformers, we
apparently see that he might have a point.

So how do we go about doing this? Well, we can declare a new class, LeaderTransformer as a child of Transformer for all leaders. However, this has the effect of losing the distinction between Decepticons and Autobots among the leadership, and Optimus Prime is very distinctly an Autobot, being a freight truck. Alright, then, we simply "push" the distinction down to the subclasses; we declare classes LeaderAutobot and LeaderDecepticon.

All right, let's write these classes.

import java.util.*;

public class LeaderAutobot extends Autobot {
  private Map led;

  public LeaderAutobot(String name) {
    super(name);

    led = new HashMap();
    addFollower(this);
  }	


  public void addFollower(Autobot a) {
    led.put(a.getName(), a);
  }

  public Autobot getFollower(String name) {
    return (Autobot) led.get(name);
  }

  public Autobot removeFollower(String name) {
    return (Autobot) led.remove(name);
  }

  public void lead() {
    Iterator i = led.values().iterator();
    
    System.out.println("I am " + getName() + " and I am leading the "
                       + "Autobots.  See me lead:");

    while (i.hasNext())
      ((Autobot) i.next()).showStatus();

    System.out.println("I have finished leading my followers.");
  }
}

Now, of course, you can also write something very similar for the Decepticons. Just replace every reference to "Autobot" with "Decepticon" and you instantly have LeaderDecepticon. For the most part. (Actually, you'll have to edit the Decepticon constructor slightly. We leave this as an exercise for the reader. A trivial exercise.)

Do take note of the family of data structures introduced in this code. In the previous episode, we showed you Vectors and Enumerations. It turns out that Vectors and Enumerations are "legacy" classes from earlier version of Java. They have some unfortunate inflexibilities, such as obligatory synchronization (synchronization is a subject which we will deal with in this course). "Modern" Java has a new group of builtin data structures: the Collection classes. In this case, we used a Map to hold the data, and we instantiated the Map with a HashMap. This is equivalent to using the legacy Hashtable class. Then we went through every element in the Map using an Iterator, which is a slightly less verbose version of the legacy Enumerations. In order to get the Iterator, we had to obtain a Collection of values in the hash table. (Note also that I decided that a leader should lead itself.)

Also note that retrieving elements from either the hash table or the iterator requires a typecast. That's because Maps only know how to return Object. And to return an object as an Autobot, you need a cast. C++ usually requires something similar.

This code will also give the compiler warning we saw in the last episode, which you should again ignore, because it has to do with parametric polymorphism, and you will learn this in class.

Let's rewrite the last version of the TransformerWorld class from the previous episode. We called it TransformerWorld3, but we'll just call it TransformerWorld from now on so that we don't keep generating versions.

import java.util.*;
 
public class TransformerWorld {
    public static void main(String[] args) {
        System.out.println("The Transformers: More than meets the "
                           + "eye.\n");
        
        System.out.println("Here are some robots in disguise.");
 
        Vector leaders = new Vector();

	LeaderAutobot optimus = new LeaderAutobot("Optimus Prime");
	LeaderDecepticon megatron = new LeaderDecepticon("Megatron");

	optimus.addFollower(new Autobot("Sideswipe"));
	optimus.addFollower(new Autobot("Mirage"));

	megatron.addFollower(new Decepticon("Starscream"));
	megatron.addFollower(new Decepticon("Skywarp"));	

	leaders.addElement(optimus);
	leaders.addElement(megatron);
 
        Enumeration e = leaders.elements();
        while (e.hasMoreElements()) {
	    Transformer t = (Transformer) e.nextElement();
	    if (t instanceof LeaderAutobot)
                ((LeaderAutobot) t).lead();
            else if (t instanceof LeaderDecepticon)
                ((LeaderDecepticon) t).lead();
        }
    }
}

Do note this interesting little while loop. We want to be able to store the list of Transformer leaders in a structure. Why would we want to do this? Because as the TV series progresses, new types of Transformers appear as well as new leaders. We want them all to be able to lead their followers, and we don't want to have to write a separate loop for each.

Saving face

Since I'm about to launch into a bit of a sermon, let's run the code:

The Transformers: More than meets the eye.
 
Here are some robots in disguise.
I am Optimus Prime and I am leading the Autobots.  See me lead:
I am a Transformer from planet Cybertron. My name is Mirage.  I have 5
energon cubes left.
In addition, I am an Autobot and a good guy.
I am a Transformer from planet Cybertron. My name is Optimus Prime.  I
have 5 energon cubes left.
In addition, I am an Autobot and a good guy.
I am a Transformer from planet Cybertron. My name is Sideswipe.  I
have 5 energon cubes left.
In addition, I am an Autobot and a good guy.
I have finished leading my followers.
I am Megatron and I am leading the Decepticons.  See me lead:
I am a Decepticon and I'm going to tell you NOTHING!
I am a Decepticon and I'm going to tell you NOTHING!
I am a Decepticon and I'm going to tell you NOTHING!
I have finished leading my followers.

Not bad, eh?

But behold the code itself! Examine carefully, once again, that while loop. If we want to call the "lead" methods on the leaders, well, we have to check their types with the "instanceof" keyword and cast them to the right type. (Yes, you can check object types in Java. You can even get fully qualified Java type names using the Object methods. Consult the API docs to which we linked on the resources page.) Even though the method is called "lead" in both! Not very good. It defeats the purpose of using a Vector to uniformly store all the different leader types: it's not extensible at all. If we were to separate that loop into its own function so that other programmers can call it, these programmers would be stuck with our two leader types, or they would have to rewrite our function. Again, not good.

Now how would we solve this little problem? Well, we want to be able to cast both leader types to the same type. The only type they have in common is the Transformer type, however, and it's really bad design to put leadership functionality on the Transformer type: it's opposed to the whole point of OO design. Not every Transformer gets to be a leader! We could create a special Leader class that's a child of Transformer, and then make LeaderAutobot and LeaderDecepticon children of that. That's nearly as bad. Why? Because they cease to have the characteristics Autobots and Decepticons. And remember from the LeaderAutobot constructor that a leader leads itself: the code must be able to cast it to an Autobot!

C++ has a solution to this little conundrum. It begins with "m". And it ends with "ultiple inheritance." And it's a dirty word in Java. But C++ can handle this case easily. Both LeaderAutobot and LeaderDecepticon use virtually the same code...so why not make a single class out of them? "Makes sense to me," you say.

Well, you could. But then you suddenly have to keep in mind all sorts of guidelines for keeping your code consistent. (That was the first hypertext link of the tutorial, by the way. I usually prefer to make you look things up yourself, see.) The Java designers are allergic to this sort of thing, not only because of the quasi-ambiguous behaviour (possibly worse than undefined!), but because it relies on the programmer to keep the code safe. But the Java designers have recognized our conundrum, and they've come up with a compromise.

public interface Leader {
  public void addFollower(Transformer a);
  public Transformer getFollower(String name);
  public Transformer removeFollower(String name);
  public void lead();
}

So what are these mysterious masked men, these "interfaces"? They are, essentially, classes. They're compiled like classes, and they are stored in their own files, just like any other Java class. But they're totally abstract classes with no code at all. Essentially, they function as a contract. A contract that the programmer will satisfy a particular API, and thus any class that implements that API can be cast to that interface, explicitly or implicitly. This forces the programmer to repeat the code as in LeaderAutobot and LeaderDecepticon, which true multiple inheritance wouldn't make you do. But it gives you the rest of the advantages of multiple inheritance without the hassles of keeping things consistent.

So how do you use these interfaces? Simple. Let's edit the first line of LeaderAutobot.

public class LeaderAutobot extends Autobot implements Leader {

Now let's compile Leader and LeaderAutobot as usual...but wait. When we compile LeaderAutobot we have an error!

LeaderAutobot.java:3: LeaderAutobot is not abstract and does not
override abstract method addFollower(Transformer) in Leader
public class LeaderAutobot extends Autobot implements Leader {

That's because the compiler noticed that our contract in Leader was for functions defined over Transformers, but LeaderAutobot is defined over Autobots only. Actually, it only cares about addFollower. removeFollower and getFollower return Autobots, but that return value is consistent with the contract for Transformers: all Autobots are Transformers, so these methods will never return something inconsistent. However, Leader.addFollower requires that all Transformers should be able to apply. LeaderAutobot.addFollower will only accept an Autobot, so this does not fulfill the contract.

No matter, we shall simply change LeaderAutobot.addFollower. (All of these changes, by the way, are parallel for LeaderDecepticon.)

public void addFollower(Transformer a) {
    led.put(a.getName(), a);
}

If you compile it, it will succeed now. And we can now rewrite the while loop in TransformerWorld.

Enumeration e = leaders.elements();
while (e.hasMoreElements())
    ((Leader) e.nextElement()).lead();

If you compile and run this, it has the same output as before, but it is now much more extensible. For instance, there are, in fact, human characters in the Transformers series. We can now create human leaders outside of the Transformers class hierarchy, and this while loop will still work.

Except! Except!

Well, now. Things are sure looking good, eh?

But, alas, it is too good to be true. Because I can write code like this:

megatron.addFollower(new Autobot("Jazz"));

Uh oh. Autobots can't follow Megatron. Megatron is a LeaderDecepticon. Well, this isn't strictly true, of course. Autobots can be traitors, or can fall under unwholesome influences. But we want to know when this happens. See, this is unexpected behaviour, but we want it to remain defined. It's a prime candidate for an exception.

And look, what are Java exceptions? They're just objects. Whenever possible, Java makes something into an object. And frequently you can play with these object types. In fact, in many cases, you should customize these object types, because they are good design. This condition with Transformers is a prime candidate for an exception. So,

public class IncorrectTransformerException extends Exception {
  public IncorrectTransformerException(Transformer t, 
                                       String correct) {
    super(t.getName() + " is of type " + t.getClass().getName()
          + " but you really need " + correct);
  }
}

This extends the basic Exception class, as it says. And it calls the Exception constructor and gives it a more informative message. So now we can fix LeaderDecepticon.addFollower:

public void addFollower(Transformer a) {
    if (!(a instanceof Decepticon))
        throw new IncorrectTransformerException(a, "Decepticon");
                                                                                
    led.put(a.getName(), a);
}

The "throw" keyword does what it says it should do. It throws an exception whenever the condition is reached: that the transfomer is not a Decepticon. This is a much more legitimate use of "instanceof". We actually want only Decepticons, ever.

So lets compile this. Alas,

LeaderDecepticon.java:17: unreported exception
IncorrectTransformerException; must be caught or declared to be thrown
            throw new IncorrectTransformerException(a, "Decepticon");

What's Java telling us here? Well, when we throw an exception, it wants us to declare that exception. In fact, whenever an exception could occur, it has to either be caught or declared.

Look up. Wayyyy up.

But at the beginning of this episode, we saw an ArrayIndexOutOfBoundsException. And we didn't have to catch or declare that anywhere for it to be accepted by the compiler. That's because the ArrayIndexOutOfBoundsException is a RuntimeException. These are the only exceptions that can be thrown in "stealth." We chose not to use these, because we really do want this checked by the programmer of other classes.


The Friendly Giant was an even earlier
component of my childhood. He always
asked us to "Look up, way up"
at the beginning of every episode.
He has a harp-playing chicken, a
RuntimeException for sure.

The modifications to LeaderDecepticon are simple.

public void addFollower(Transformer a) throws IncorrectTransformerException {

There. That was easy. The "throws" keyword declares a non-runtime exception. Now let's compile again.

LeaderDecepticon.java:15: addFollower(Transformer) in LeaderDecepticon
cannot implement addFollower(Transformer) in Leader; overridden method
does not throw IncorrectTransformerException
    public void addFollower(Transformer a) throws

Argh. Java still doesn't like it. That's because throwing this exception is not part of the Leader contract. Anything that uses a case to Leader to call this method will not know that it's supposed to handle this exception. So we have to edit the Leader interface to handle this too. (And make the requisite changes to LeaderAutobot.)

public interface Leader {
    public void addFollower(Transformer a)
        throws IncorrectTransformerException;
    public Transformer getFollower(String name);
    public Transformer removeFollower(String name);
    public void lead();
}

Now compile again! But, yet again,

LeaderDecepticon.java:11: unreported exception
IncorrectTransformerException; must be caught or declared to be thrown
        addFollower(this);

It just keeps on propagating. That's because we call addFollower in the constructor. So we edit again! We put in the throws keyword in the constructor.

public LeaderAutobot(String name) throws IncorrectTransformerException {

(Just for fun, I showed you the changes to the LeaderAutobot too.) Now this will compile correctly. So let's compile the TransformerWorld.

I won't reproduce the errors here, but everytime we call addFollower, the compiler complains that we didn't catch or declare the exception. So let's handle it. Under TransformerWorld, we're going to write a function that generically adds followers to leaders.

public static void addFollowerToLeader(Transformer t, Leader l) {
    try {
        l.addFollower(t);
    } catch (IncorrectTransformerException ite) {
        System.out.println(ite);
        System.out.println("Rejecting transformer");
    }
}

Now every time we called addFollower in main, we instead call this function. What is it doing? Well, it's catching the exception, printing it out, and printing out an additional message. And it doesn't add the Transformer, because addFollower in both Leader classes throws the exception before it does anything. Do also note that we don't need to distinguish between LeaderAutobot and LeaderDecepticon here either: the Leader interface is sufficient.

Also note that we can simply print the exception. That's because Exception has a toString function which returns a string. Any object that has a toString method can thus be printed.

TransformerWorld should look something like this now:

import java.util.*;

public class TransformerWorld {
    public static void addFollowerToLeader(Transformer t, Leader l) {
        try {
            l.addFollower(t);
        } catch (IncorrectTransformerException ite) {
            System.out.println(ite);
            System.out.println("Rejecting transformer");
        }
    }
 
    public static void main(String[] args) {
        System.out.println("The Transformers: More than meets the "
                           + "eye.\n");

        System.out.println("Here are some robots in disguise.");

        Vector leaders = new Vector();
 
        LeaderAutobot optimus = null;
        LeaderDecepticon megatron = null;
 
        try {
            optimus = new LeaderAutobot("Optimus Prime");
            megatron = new LeaderDecepticon("Megatron");
        } catch (IncorrectTransformerException ite) {
        }

        addFollowerToLeader(new Autobot("Sideswipe"), optimus);
        addFollowerToLeader(new Autobot("Mirage"), optimus);

        addFollowerToLeader(new Decepticon("Starscream"), megatron);
        addFollowerToLeader(new Decepticon("Skywarp"), megatron);

        leaders.addElement(optimus);
        leaders.addElement(megatron);

        Enumeration e = leaders.elements();
        while (e.hasMoreElements())
            ((Leader) e.nextElement()).lead();
    }
}

We had to put the constructors in a try-catch block as well, because they call addFollower which can throw our exception. However, we do nothing with it, not even exit. Why? Because we're guaranteed that the Leader classes are of the correct type to add themselves to their follower lists.

This code compiles. And it runs with the same output as it did before. However, I encourage you to introduce the following line in the main method:

addFollowerToLeader(new Decepticon("Thundercracker"), optimus);

Now when you run it, it should produce the same output, but with an additional

IncorrectTransformerException: Thundercracker is of type Decepticon
but you really need Autobot
Rejecting transformer

And now you know the basics of interfaces and exceptions in Java.

Uhh, wait a moment

There's one more thing I wanted to show you before this episode ends. Let's add a little bit to the addFollowerToLeader static method.

public static void addFollowerToLeader(Transformer t, Leader l) {
    try {
        l.addFollower(t);
        System.out.println("Successfully added transformer " + t.getName());
    } catch (IncorrectTransformerException ite) {
        System.out.println(ite);
        System.out.println("Rejecting transformer " + t.getName());
    } catch (Exception e) {
        System.out.println("Wow, we got an exception");
    } finally {
        System.out.println("Attempt to add follower " + t.getName()
                           + " complete");
    }
}

What have we done?! Oh, the humanity!

Actually, all we've done is add a "finally" block to the exception handler and add another exception to catch, this time for the generic Exception. "finally" blocks are for code that should run regardless of whether or not the exception occurred. Why do we need this? Because an exception handler may want to "re-throw" the exception or throw a new exception. If we didn't have the "finally" block, any cleanup that must be done before the function terminates will not be done. "finally" blocks run with or without an exception.

The additional catch block has an interesting property. All catch blocks are tested for compatibility with the thrown exception. Since the second catch block is the base Exception, it should always run whenever the IncorrectTransformerException is also caught, right? Wrong. As soon as an exception is caught, no other catch blocks are ever tested. That means that the order matters. If we had placed the Exception before the IncorrectTransformerException, we'd never ever reach the IncorrectTransformerException even if it were to be thrown. However, all other exceptions that might occur will be caught by the second catch block. You should generally aim to catch exceptions by the most specific exception type that you expect, but especially in debugging it can be OK simply to catch them with Exception as long as you are planning to permanently prevent the exception from ever happening and get rid of the try-catch block.

Let's compile and run the TransformerWorld with this new code.

The Transformers: More than meets the eye.
 
Here are some robots in disguise.
Successfully added transformer Sideswipe
Attempt to add follower Sideswipe complete
Successfully added transformer Mirage
Attempt to add follower Mirage complete
Successfully added transformer Starscream
Attempt to add follower Starscream complete
Successfully added transformer Skywarp
Attempt to add follower Skywarp complete
IncorrectTransformerException: Thundercracker is of type Decepticon
but you really need Autobot
Rejecting transformer Thundercracker
Attempt to add follower Thundercracker complete
I am Optimus Prime and I am leading the Autobots.  See me lead:
I am a Transformer from planet Cybertron. My name is Mirage.  I have 5
energon cubes left.
In addition, I am an Autobot and a good guy.
I am a Transformer from planet Cybertron. My name is Optimus Prime.  I
have 5 energon cubes left.
In addition, I am an Autobot and a good guy.
I am a Transformer from planet Cybertron. My name is Sideswipe.  I
have 5 energon cubes left.
In addition, I am an Autobot and a good guy.
I have finished leading my followers.
I am Megatron and I am leading the Decepticons.  See me lead:
I am a Decepticon and I'm going to tell you NOTHING!
I am a Decepticon and I'm going to tell you NOTHING!
I am a Decepticon and I'm going to tell you NOTHING!
I have finished leading my followers.

Nice, eh? All easily predictable, which is one of Java's biggest improvements over C++.

The fun activities you've always wanted

  1. You've always wanted to write your own implementation of a hash table in Java. Yes. In fact, you've been aching to write an implementation where the hash keys must be in the form of integers represented as Vectors of individual digits arranged from largest to smallest.
  2. All your life, you've been dreaming of writing underlying Array-based and binary search tree-based implementations of this hash table.
  3. And it's been your greatest aspiration to write it so that other users can supply their own implementation at will.

What an ambitious life you lead.


I'm insufficiently ambitious to own
this gold Jazz Autobot figure.
Naturally, the Transformer was a plot
to brainwash kids into buying toys.
Judging by my parents' basement,
I'd say it worked on me.

Valid HTML 4.01!