CMSC 330, Fall 2005

Organization of Programming Languages

Truk not munky, input not output: Fast Java for C++ people III

By Asad B. Sayeed

Alright, now. Let's make some things clear. It's only a Transformer if it takes the form of a common automobile (the Autobots) or a fighter jet or transportation accessory (the Decepticons). It's not a transformer if it's a monkey, or a spider, or whatever the new-fangled things they made after the original Generation 1 transformers.

Of course, they don't listen to me. I guess there comes a time in the life of an implausible race of alien robots that it choose to evolve, become better, become...biological. And that's just what they did. The toy company and its indentured animators created the "Maximals" and the "Predacons." Naturally, the Predacons were nasty carnivorous dinosaurs. The Maximals were wholesome...other animals, heirs surely to the autobots. And led by the intrepid robot gorilla, Optimus Primal. What a travesty.


Does this look like the Transformers to you? Not me!

In the same way, you have sufficient knowledge of Java as a model of programming. Your knowledge of C++ already allows you to write function bodies, but you now know how to structure your code into classes and methods. You know about Java inheritance and polymorphism, exceptions and interfaces. You know about the legacy data structures and the collections class. And you know how to emit output.

But just like the Transformers evolved, so must you. Not only must you provide output, you must also obtain input. Just like the Transformers had to become biological on a prehistoric Earth, you have to accept input on primitive non-mind-reading machines.

Input? What input?

So far you have seen one particular special variable called System.out. This is a static variable on the System class. This variable contains an object of type OutputStream. And this object represents, as you would expect, the standard output.

So you might suspect that there should be a similar way to access the standard input. And you would be correct. That would be System.in. It is, unsurprisingly, an InputStream. However, neither System.in nor System.out are just OutputStreams and InputStreams. Those two classes are "abstract" classes, a subject I discuss below, but from your knowledge of C++ you should already understand intuitively. System.in and System.out are actually instances of some descendent classes, and you can find out what they are using Object.getClass() and Class.getName()—an exercise for the reader.

Because Java does not have operator overloading, and because Java is far more obsessed with safety than C++, you do not get the convenience that the C++ STL iostream library gives you. No cin, no cout, and no stream operators. You have to write a lot more code in Java in order to do the same input. But it's a lot easier to know what went wrong in Java.

So let's use System.in.

import java.io.*;
 
public class TransformerName {
    public static void main(String[] args) {
        String[] names = new String[4];
        names[0] = "Optimus Prime";
        names[1] = "Megatron";
        names[2] = "Starscream";
        names[3] = "Jazz";
 
        System.out.println("Enter your name!");
 
        try {
            BufferedReader in =
                new BufferedReader(new InputStreamReader(System.in));
 
            String yourName = in.readLine();
            int code = Math.abs(yourName.hashCode() % 4);
 
            System.out.println("Your Transformer name is " + names[code]);
        } catch (IOException ioe) {
        }
    }
}

So this is one way in which we obtain input from the standard input. And it is quite involved. Java is very paranoid about things coming from the outside world, so it forces you to invoke many layers of protection. First and foremost, many I/O functions, such as receiving input, must be protected by an try/catch block expecting an IOException. If you omit this, the compiler will complain, as we discussed the previous episode.

Secondly, InputStream itself is not very good at providing input. It will give you input character by character. Actually, using it is quite similar to I/O programming in C. You have to convert it to a BufferedReader in order to obtain input line-by-line. In order to do that, you first have to create an InputStreamReader as a kind of adapter. To see why you need to do this, you need look no farther than the Java API documentation (hint hint).

If you were to run this code, it would work as you would expect it to: it would let you enter a string on the console, and it would find the string's hash value and select a member of the names array from that. The hash value is provided by Object.hashCode(), which is redefined on String. Note that we need to use the static method abs() on the Math class to get the absolute value, since the % operator preserves the sign—and hashCode() can return a negative int!

Let's make a slight modification to this code and have it read in a number.

import java.io.*;
 
public class TransformerName {
    public static void main(String[] args) {
        String[] names = new String[4];
        names[0] = "Optimus Prime";
        names[1] = "Megatron";
        names[2] = "Starscream";
        names[3] = "Jazz";
 
        try {
            BufferedReader in =
                new BufferedReader(new InputStreamReader(System.in));
 
            System.out.println("How many names do you want to convert?");
            String snum = in.readLine();
            int num = (new Integer(snum)).intValue();
 
            for (int i = 0; i < num; i++) {
                System.out.println("Enter a name");
                String yourName = in.readLine();
                int code = yourName.hashCode() % 4;
 
                System.out.println("Your Transformer name is " + names[code]);
            }
        } catch (IOException ioe) {
        }
    }
}

So this time we read in an integer as a string, and convert it to a value of int type. We use a special class called Integer to convert the string. Note that we never store the Integer object: we forget about it once we've called Integer.intValue(). This is OK because the garbage collector will eventually clean it all up. Other types can be converted in a similar way.

A sample session with this code:

How many names do you want to convert?
3
Enter a name
Asad
Your Transformer name is Megatron
Enter a name
B
Your Transformer name is Starscream
Enter a name
Sayeed
Your Transformer name is Jazz

There are far too many ways to use the Java stream and reader/writer classes to count. They all tend to use some variant of the adapter design pattern, allowing you to hook up streams to one another to customize the kind of output you can generate. You can do much the same thing with FileInputStream and FileOutputStream to get I/O from files, for instance.

Truk not munky: keeping the purity of the Transformers

When the toy companies attempted to revive the Transformers line of toys, they started the Beast Wars line, as described above. However, many steadfast and true fans of the Transformers rejected this absurd travesty. The motto of their struggle was "truk not munky." We want to keep the purity of the Transformers as well. And Java indeed has some capability to limit the creation of instances and subclasses, allowing developers to prevent unintended uses of the APIs that they create. For instance, there are situations in which you would want to prevent a class from being instantiated, forcing other programmers to use its subclasses; security applications might be a place where this is relevant, where each subclass of some communications base class represents, say, an encryption scheme, and allowing the base class to be instantiated might permit security violations.


The Soviet Union vigorously opposed "formalistic
experiments among its composers.
Just like I oppose them for Transformers.
Authenticity!

Java implements this capability with the abstract keyword. We use it as follows:
public abstract class Transformer {
   ...
}
And this prevents us from writing statements like
Transformer t = new Transformer();
The compiler would complain if it ever encountered such a thing. But, fortunately, we can still write statements like
Transformer t = new Autobot();
Because it would defeat part of the purpose of inheritance if Transformer were not a valid type. Furthermore,
public abstract class Transformer {
   ...

   public abstract boolean transform();
}

is now valid code, even though the method transform has no body. This is very similar, now, to how you create abstract classes in C. The difference is the "abstract" keyword. Of course, you have to implement the function in a subclass in order for the subclass and its descendents to be instantiable in Java.

Interfaces are, of course, the ultimate abstract classes: no code at all. But you can't use abstract classes to simulate multiple inheritance in the way you can with interfaces.

Why is this a good idea in the Transformer case? It's good because it reflects the obvious known facts about the Transformers: they all belong to one faction or another—there are no generic Transformers.

Stopping unwelcome evolution

So we've prevented the toy companies from creating stray "loners" from the Transformer base class. But we need to prevent them from creating subclasses that implement Maximal and Predacon functionality.

Java gives us a way of preventing classes from being subclassed: the "final" keyword. So we are saved! We just declare some classes to be final—that is, not eligible for subclassing.

Alas, we are not really saved. Because what would we declare final? Transformer? But we need Transformer to be the parent of all the other classes. Autobot? Well, we could make Autobot final—at least that would prevent a putative Maximal class from leeching off of it. But then our scheme to create LeaderAutobots would fail. Unfortunately, the best we can do is to make LeaderAutobot (and LeaderDecepticon) final.

public final LeaderAutobot extends Autobot implements Leader {
   ...
}

So if making things final doesn't really help us in making an entire inheritance subtree impervious to subclassing, why do we do it? Well, we do it if we don't want someone overriding a specific set of methods with their own methods. Particularly if the class is intended to control, say, some Java-external module (this can be done with the Java Native Interface, which you can find on your own), you may have situations were a particular set of methods should not be overriden so that no undefined behaviour occurs. So "final" is a last-minute safety stopgap.

Back to Cybertron

For the time being, this concludes our series on Fast Java for C++ people. You already know how to program in C++; this series was intended to "translate" the basics of Java programming (which is in many ways quite similar) to the C++ user who has been through our previous course stream. We wanted to impart just enough so that you could do the assignments that we give you for this course. But there are many aspects of Java that you will have to learn on your own.


Ravaged Cybertron. Whence Transformers came and
whither they must return. Glory be.

Before we part (once again, for the time being), I'd like to leave you with a few exercises based on the material in this episode.

  1. Use the classes in the java.io package to alter the TransformerWorld from the previous episode to take input from a file. The input contains a list of names/types of Transformers, structured as you wish.
  2. Use the classes in the java.io package, particularly the ObjectOutputStream class, to write the Transformer objects to a file directly from TransformerWorld. Write a another class with a main function, TransformerDump, which reads in the file via ObjectInputStream and prints out the Transformers listed therein.
  3. Rewrite the Transformer hierarchy in order to prevent the subclassing of Autobot and Decepticon. This means abandoning the Leader interface, by the way.

Valid HTML 4.01!