|
CMSC 330, Fall 2005Organization of Programming LanguagesLess than meets the eye: Fast Java for C++ peopleBy Asad B. SayeedSome of you in this semester's (Fall 2005) version of CMSC330 come from a previous stream in which C++ was taught and not Java. In previous semesters, you would have had lectures and discussion sections to introduce you to Java, but now that we're on a new course sequence, well, unfortunately you're expected to know Java. But we aren't totally unmindful of the fact that some of you haven't seen Java: we're giving you this little tutorial to set you in the right direction. And seriously, it's not that hard. In reality, C++ is a superset of Java. What you have to learn, mostly, is to do without some of the advantages and flexibility C++ gives you—and without some of the grief, too. Most importantly, what you don't have to knowSo like I said, there are many things in C++ you can now forget about. For instance,
What you have to learnSo Java is actually somewhere between C++ and Ruby in behaviours. It's got a C++-style syntax, but behind it are some Ruby-like concepts, at least in terms of object-orientedness. But it also has some of its own unique ideas. Here are some of the things you'll have to learn, as opposed to unlearn like above:
And, of course, there are many other little details in which the languages from one another. Some resourcesWe've linked to some Java resources on the main page, particularly the Java 1.5.0 core API. Once you become familiar with Java, this will become the most important piece of documentation you will result, as it catalogues the details of the builtin standard Java library. The other biggest resource is Google (or the search engine of your choice). It will find literally thousands of Java tutorials, articles, and so on, though very few oriented towards making the transition from C++ to Java like this one is. The example below, incidentally, are inspired by the children's science fantasy cartoon series, The Transformers. For a TV series, it lasted a very long time in several (increasingly computer-animated) incarnations. In my very arrogant and unquestionable opinion, the 80s version was the best, and it's the one I'm using below. And now you know a significant influence on my warped psyche. ![]() Your first Java programSo let's get down to business. Here's a sample trivial Java program:
public class TransformerWorld {
public static void main(String[] args) {
System.out.println("The Transformers: More than meets the eye.\n");
}
}
So what does this do? Well, you have to first paste it into a file
called TransformerWorld.java. (In fact, every class should have its
own file.) Then you have to run
javac TransformerWorld.javato compile it. This creates a file in the same directory called TransformerWorld.class. Finally, you have to run java TransformerWorldin order to get it to print out "The Transformers: More than meets the eye." and an extra newline character. All of this works on the Grace cluster. But what does the code itself mean? Well, first of all, it defines a class called TransformerWorld. This class is public: we won't worry about how a class can be public until/unless we start talking about Java packages. This class is also implicitly a descendent of Object. Next, within the class it defines a main function. This is just like a C++ or C main function, except that in the case of Java, you cannot write methods outside a class. The main function is public, which means what you would expect it to mean if it were C++: it is accessible by any other class. The main function is static, which also means what it does in C++: it is accessible directly on the class, rather than on instances of the class (created by the new operator). It is void; again, just like C—it returns nothing. None of that was really very interesting or difficult. What's more
interesting is the Now for the action. System is a special class you never instantiate with new. It simply serves as a placeholder for a collection of static variables containing important objects needed to interact with the operating system. One of these is System.out, an object representing the standard output. println is a method on System.out: it prints things to the console with a newline. Note, however, that System.out is a reference. It's actually stored behind the scenes as a number that points to the location of a stream object. If this were C++, you'd be accessing its constituent methods and data members with -> rather than a period character. But this is Java, and there's no other way to access members of a class except as a pointer (once again, without actual pointer arithmetic). So we just use a period in all cases. Also note that all parameters are passed by reference/pointer, never by value, unless it's a primitive type (int, char, and so on). So this is your first Java program. It prints out one line and two newlines (since the escape characters within strings are almost the same as in C++). The code is entirely written in the body of the class definition; this is the rule in Java, in contrast with C++, where you can (and usually should) write the method definitions separately from the overall class definition. Next, we will talk about inheritance! Simple inheritance and polymorphism
Energon cubes: necessary for Transformer life Unlike C++, all methods on Java classes are inherently virtual. So there's no virtual keyword. So with that, we'll write a few classes. First: Tranformer.java
public class Transformer {
// protected means what it does in C++, give or take a giant
// caveat through which you could drive a couple of trucks.
// But we won't get to that this time.
protected int energonCubes;
protected String name;
// Constants
public static int GOODGUY = 0x0;
public static int BADGUY = 0x1;
// Constructor
public Transformer(int energon, String name) {
energonCubes = energon;
this.name = name;
}
// Get and set methods.
public int getEnergonCubes() {
return energonCubes;
}
public void setEnergonCubes(int energon) {
energonCubes = energon;
}
public String getName() {
// Do note that objects are returned by reference, not copied.
// It's always safe to return an object!
return name;
}
public void setString(String name) {
this.name = name;
}
// This function performs an action.
public void showStatus() {
System.out.print("I am a Transformer from planet Cybertron. "
+ "My name is " + name + ". I have "
+ energonCubes + " energon cubes left.\n");
if (energonCubes == 0)
System.out.println("Ooops, no more energon! That's it for me!");
else energonCubes--;
}
}
![]() Optimus Prime, the Autobot leader. Under stressful conditions he turns into a freight truck. Now we'll write two child classes of this class. First, Autobot.java:
public class Autobot extends Transformer {
int alignment;
public Autobot(String name) {
super(5, name);
alignment = Transformer.GOODGUY;
}
public int getAlignment() {
return alignment;
}
public void setAlignment(int alignment) {
// Look: we have the "this" pointer here. Only now
// it's a Java reference to the current object.
this.alignment = alignment;
}
public void showStatus() {
super.showStatus();
System.out.println("In addition, I am an Autobot and a "
+ (alignment == GOODGUY ? "good guy."
: "bad guy."));
}
}
And then in Decepticon.java:
public class Decepticon extends Transformer {
public Decepticon() {
super(100, "Starscream");
}
public void showStatus() {
System.out.println("I am a Decepticon and I'm going to tell you "
+ "NOTHING!");
}
}
So we have used the extends keyword to make two classes from the Transformer class. Each constructor calls the parent constructor. And each of the two derived classes redefines showStatus. Now let's make a TransformerWorld2.java that uses it.
public class TransformerWorld2 {
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.");
Transformer t = new Transformer(5, "Oracle");
t.showStatus();
t.showStatus();
t = new Autobot("Optimus Prime");
t.showStatus();
// The following involves a downcast.
((Autobot) t).setAlignment(Transformer.BADGUY);
t.showStatus();
t = new Decepticon();
t.showStatus();
}
}
All these classes should be in the same directory. And now you can compile all of this with: javac *.java This actually figures out all the dependencies for you along a system similar to make. You can run it by typing: java TransformerWorld2You should get the following output: The Transformers: More than meets the eye. Here are some robots in disguise. I am a Transformer from planet Cybertron. My name is Oracle. I have 5 energon cubes left. I am a Transformer from planet Cybertron. My name is Oracle. I have 4 energon cubes left. 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 Optimus Prime. I have 4 energon cubes left. In addition, I am an Autobot and a bad guy. I am a Decepticon and I'm going to tell you NOTHING! Now take a look at the code for TransformerWorld2. We created one variable of type Transformer and instantiated a Transformer in that variable. We called showStatus twice. Then, using the same variable, we instantiated an object of type Autobot. And then we called showStatus once. Even though t is a Transformer, it still called showStatus. Like I said, all methods are virtual. In order to call setAlignment, however, we had to cast t down to an Autobot, since setAlignment doesn't exist on the parent Transformer class. Note that Autobot's showStatus also calls Transformer's showStatus via the super keyword (similar to Ruby). Both constructors for Autobot and Decepticon use super to call the parent constructor. Do note that we never deleted the objects we created. In fact, when we reassigned t, we let the previous object in t float off into the ether. That's exactly what we wanted to do. In C++, that would be a classic memory leak. But in Java, we have a friend helping us: the garbage collector. It rummages through apparently "lost" objects, and it destroys them. Now there are things you may need to clean up: there's a way to make the garbage collector do this, but we might save this for later. Nevertheless, Java doesn't really contain an explicit concept of a destructor. Rudimentary data structure funLet's make things a little more interesting. We'll define a TransformerWorld3 class as below (in TransformerWorld3.java):
import java.util.*;
public class TransformerWorld2 {
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 ts = new Vector();
ts.addElement(new Transformer(5, "Oracle"));
ts.addElement(new Autobot("Optimus Prime"));
ts.addElement(new Decepticon());
Enumeration e = ts.elements();
while (e.hasMoreElements()) {
((Transformer) e.nextElement()).showStatus();
}
}
}
We touched on Arrays earlier. And while there are some differences between C++ arrays and Java arrays, the basic semantics are quite similar. They're both fixed-length containers, for one thing. Java offers a simple varible-length container called Vector. You instantiate it as you would any other object, and you fill it with any arbitrary descendent of Object, such as Transformer. (And what, you ask, is that "import" line on top? Well, C++ has #include directives for header files. Java doesn't use header files, but it organizes libraries into "packages". The import line asks Java to include all the classes in the java.util package, which includes Vector—and Enumeration. You could also omit that line, but then you'd have to write java.util.Vector every time you want to refer to the class. In situations where classes have the same name but different packages, you have to do just that.) ![]() This is Starscream, Megatron's annoying henchbot. He causes a lot of exceptions. We'll learn about Java exceptions in a later episode. Now the most interesting part of this code is the Enumeration bit. Enumerations are ways of allowing us to access members of data structures without having to worry about the details of the structure. It doesn't really matter too much with Vector, but it does give us a handy way of simulating Ruby iterators like "each". So we take the Enumeration of the Vector and step through it in the while loop. Enumerations give us only Objects, so we have to downcast them to Transformers in order to call showStatus. So let's compile this: javac *.java But on Grace, it gives us an error message. Note: TransformerWorld3.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. This is because Grace is using one of the latest and greatest versions of Java, 1.5.0. This version of Java allows something similar C++ templates, but it uses them in such a way as to help programmers enforce type consistency and avoid downcasting, which is unsafe. (What if the Enumeration also contained a String? Java would throw an exception safely, but you still want to avoid this.) Nevertheless, it will compile code that hasn't been "fixed" this way, and so it's generated a .class file for us. We can run it as usual. java TransformerWorld3 And we get: The Transformers: More than meets the eye. Here are some robots in disguise. I am a Transformer from planet Cybertron. My name is Oracle. I have 5 energon cubes left. 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 Decepticon and I'm going to tell you NOTHING! Try stuff on your ownI think you should now have a reasonable idea of what Java entails: syntactically, it's just a fussier and less flexible C++, but with it's good points: less ugly memory management to worry about, for one thing. Of course, you should now take these files and spend some time modifying them to do stuff. Some suggestions:
Until next time. |