Moving from C++ to Java March 1996 Dr. Dobb's Journal by Gary Aitken Gary has been the technical lead and chief architect for a large, commercial, UNIX-based C++ toolkit for the past seven years. He now is an engineer at Integrated Computer Solutions, where he is working on Java-based technology. He can be reached at garya@ics.com. _________________________________________________________________ If you haven't been banished to a desert island, then you've heard about Java and its potential impact on both developers and users. In this article, I'll highlight some of the differences between Java and C++. My purpose is not to teach you how to program in Java, but rather to make you aware of potential problems and opportunities when moving from C++ to Java. I'll provide brief explanations of those concepts with which you may not be familiar, although I won't provide in-depth coverage of how they work or how to use them. Keep in mind that these are the major differences as I perceive them and are the result of my personal experiences with Java. Java Executes on a Virtual Machine Java source is not compiled into normal machine code. It is translated into code for a virtual machine specifically designed to support Java's features. A Java interpreter then executes the translated code. No link step is required; the interpreter dynamically links in additional classes on demand; see Figure 1. Figure 1: The Java development environment. Java is Totally Object Oriented Java is a totally object-oriented language. This means everything you do must be done via a method invocation (member function call) for a Java object. To start with, there is no such thing as a stand-alone main function. Instead, you must begin to view your whole application as an object; an object of a particular class. But what class? Most Java applications simply make a new class derived from the primitive Java Object class and implement everything they need, but you can save much time and improve consistency between applications by creating a base application class to handle features common to all applications. The strict, object-oriented nature of the Java environment means existing C and C++ code can't be used directly; the same goes for system calls. In C++, you can get to existing C procedures, including most system calls, simply by declaring the C procedure as outside of the normal C++ namespace using the extern "C" syntax. In Java, there's a similar escape hatch, but it isn't nearly as simple to use. You must define a native method, whose purpose is to interface to the C function, then provide the glue to connect to it. The Java environment provides tools to help with this task, but the whole process certainly isn't as trivial as the C++ extern escape. Interfacing to C++ classes is even more complex, involving the interface to C classes and the normal problems of invoking C++ functions and member functions from C. Fortunately, many of the more-common system-utility functions are already provided via methods in the System class, but these obviously won't include any of the useful procedures or classes you may have built up over the years. Save delving into this until you really need to. Separate Header Files Don't Exist in Java In Java, everything about a class is contained in a single file. The signature of a method appears in only one place, and the implementation of a method must appear simultaneously with its declaration. The advantage of this is that it is more difficult to mistakenly program using files that are out of synchronization with the implementation, or to get a library that is missing the implementation of some member function. Class declarations (function signatures and public variables) are available to the Java translator even from the binary output of a compilation, so no additional header files are needed, just the compiled object file. The disadvantages of this are primarily related to how we program. Many C++ programmers use header files more or less as documentation. To see what the interface to a particular member function is, you bring up the header file and find the function. You can usually look at most header files on a single page and get a good idea of how a particular class should be used. In Java, there is no such concise summary available. Since the code to implement a method must appear with the method definition, and the code for a single function frequently occupies a page or more, it is difficult to look at any Java source and get an impression of how the class should be used. You must have adequate documentation for any classes you intend to use, which should go without saying, but often documentation is sorely lacking when you are dealing with in-house-designed classes or classes that are not part of a fully supported commercial product. Two tools supplied in current Java environments which should help with this are javap, a disassembler that prints class signatures, and javadoc, which produces HTML documentation from comments embedded in source files. Packages Partition the Namespace in Java One problem large C++ projects encounter is namespace pollution--how can you ensure that some other developer working on a different aspect of a project won't create a class with the same name as a totally different class in another part of the project? Worse yet, a vendor may deliver a library with a class that uses a name that you have already used. There are various ways to minimize these problems in C++, but a project may be well underway before the problem rears its ugly head, and correcting it then is painful and costly. Java addresses this problem using a concept called "Packages," which effectively partition the name space for classes by collecting classes into named packages. Two classes with the same name, in different packages, are still unique. The key, then, is to be sure related classes are collected into their own package. Remember, however, that Java does not solve the general problem of name collision. Extending a base class and thereby causing a collision with a derived class will remain a problem. For example, if your favorite vendor has supplied a set of classes that you use as base classes and your derived class has a method named foo, you will have problems if the next version of the vendor's class contains a new method also named foo. Exceptions are First-Class Characteristics in Java In C++, exceptions and exception handling are rather esoteric; many C++ programmers may never deal with them and may have no idea what they are. Exceptions are error conditions that are not expected to occur in normal processing. Consequently, they are not returned from a method as either arguments or the return value; nonetheless, they cannot be ignored. An example would be a method to compute the square root of a number. The normal interface expects a positive real number as an argument and returns a positive real number as a result. Since a program might incorrectly pass a negative number as an argument, the method can check for this and throw an exception when it occurs. In most systems, programmers are not required to deal with exceptions, and the occurrence of an unexpected exception causes abnormal program termination. In Java, exceptions are a full-fledged part of the language. The signature of member functions includes exception information, and the language processor enforces a programming style whereby if you call a method that can throw an exception, you must check to see if any of the possible exceptions occurred and handle them. Almost every Java programmer will encounter exceptions, since some of the more-useful classes from the supplied libraries throw them. Dealing with exceptions is not difficult but is something you will need to be aware of. The documentation for a method must indicate the exceptions it throws. If you forget about them, don't worry; the compiler will remind you. Strings are Different from Character Arrays in Java Java includes a String object, which is a constant. A String is not the same as an array of characters, although it is easy to build a String object given an array of characters. You should use Strings instead of arrays of characters wherever possible, as they cannot be overwritten by different values unintentionally. Java has Limited Support for Constant Objects and Methods In C++, you may declare a formal parameter to a function or a return value as const, effectively preventing the body of the function from modifying the argument and the caller from modifying the return value. In addition, you may declare a member function as constant, indicating it cannot change any aspect of the object upon which it operates. Java supports the notion of constant, read-only values, using the final modifier. However, it does not support the notions of constraining writeable objects to be read only when passed as an argument, constraining return values to be read only, or constraining a method to not modify the object upon which it operates. This omission is less of a problem in Java than it is in C++, mostly because of the differentiation between the String class and an array of characters, but it does leave a hole for errors. In particular, there is no way of ensuring that a method that should not modify an object does not inadvertently do so. Java has no Pointers Understanding the concept of pointers is one of the most difficult aspects of C and C++ programming. Pointers are also one of the biggest sources of errors. Java has no pointers; instead objects are passed as arguments directly, rather than passing pointers to objects. In addition, you must manipulate arrays using indices. Neither of these are a big deal in most cases; however, the lack of pointers is a major deal if you are writing systems where pointers to functions or pointers to member functions are involved. These situations arise frequently in systems involving callbacks with known signatures to objects of a base type, where numerous different methods having the same signature may all be used for a particular callback and are dynamically assigned. There are ways around the problem, but they are not particularly intuitive or convenient. Java has no Parameterized Types A parameterized type provides a means of writing one piece of code to describe an implementation for several similar types of arguments. An example would be a square-root method that operates on either integers or floating-point numbers. In C++, this capability is provided by templates. Java has no equivalent of C++ templates. If you have been using templates merely as a convenience, such as to build several similar functions using different types of arguments (as in the previous example), this isn't too big a disaster. It means more cutting and pasting to write each of the similar classes or methods by hand, but does not present a serious roadblock in terms of whether or not you can write an equivalent program. However, if you have been using templates to automatically generate classes, it is a problem, and there is no easy way around it. Java is Garbage Collected In a garbage-collected language, the underlying run-time environment keeps track of which pieces of memory are in use and which are not. When a piece of memory is no longer needed, the system automatically reclaims the memory. For example, an object created inside a method, but not passed back to the caller or stored as part of a global object, can no longer be of any use after the method is exited. Call it magic if you like, but the system really does know which objects you are using, and which ones you can't possibly touch again because there are no references remaining that address them (this is one of the benefits of not having pointers in the language). As a result, you no longer need to worry about destroying objects when they are no longer in use or freeing memory returned by some function call. An incredible amount of time and debugging effort in C++ is focused on bugs caused by deleting objects which are still in use, or memory leaks caused by not deleting objects no longer in use. Java's use of garbage collection greatly reduces these errors, although it does not eliminate them--bad program logic can still leave no-longer-needed objects linked onto an active data structure, in which case they won't be garbage collected. Many classes in C++ contain destructors primarily to release auxiliary storage used by an object of the class. The fact that Java is garbage collected means you don't have to write destructors for these classes. It does not, however, mean you can forget about writing destructors for all classes. For example, an object that opens a network connection still needs to clean up gracefully by closing the connection when it is destroyed. In Java, a destructor is known as a "finalization method." Java Does not Implement Multiple Inheritance In any complex, object-oriented system, implementing a class that needs to inherit the functionality of more than one other class is a frequent problem. A Manager class, for example, might need to serve as the head of a linked list (of the employees the manager manages), but a Manager also needs to be an Employee. There are several ways of dealing with this problem. One of these is multiple inheritance--allowing a class to be derived from more than one base class. In this example, Manager could be derived from both Linked List and Employee. Java does not implement multiple inheritance. Instead, you may declare interfaces, which describe the programming interface used to achieve some functionality. A class may then implement one or more interfaces, as well as its own unique functionality. Different, unrelated classes may implement the same interface. Formal parameters to methods may be declared as either classes or interfaces. If they are interfaces, objects of different classes that implement the interface may be passed as arguments to the method. The concept of interfaces is considerably easier to master than multiple inheritance, but has its limitations. In particular, you must write code to reimplement the desired functionality in each class implementing an interface. Java Supports Multithreading Multithreading lets you write a program that potentially does two or more operations at the same time. For example, you could finish reading a large file while still allowing the user to edit the part already read in. To do this, you break your program into different threads of execution. To work correctly, the program needs to be careful about how the different threads manipulate any data or make decisions based on data common to more than one thread. Java was designed to support multithreaded applications from the start. The classes and interfaces provided make breaking an application into different threads simple. Language primitives handle automatic synchronization and locking of critical data structures and methods. Java Comes with a Diverse Set of Predefined Classes The default Java environment currently consists of several different Java packages implementing a diverse set of fundamental classes. These give you a real jump start in terms of your ability to quickly write a meaningful application. They include the following: java.awt. Most applications developed today make heavy reliance on GUIs. Java provides an abstract window toolkit (awt), which allows you to deal with GUI objects in a generic manner without regard to the system on which your program is running. The big advantage is that your program will automatically run on all supported Java platforms. As of this writing, that includes Windows 95/NT and Sun UNIX platforms, but by the time you read this it will most likely include others, such as the Mac and most other UNIX flavors. The current awt is a least-common-denominator GUI toolkit, a problem not inherent in the Java design, but rather, a result of the rapid explosion of the technology, and time-to-market and resource constraints on the Java development team. Expect the awt to evolve into a more full-functioned set of classes. java.applet. An applet, in the context of Java, is a graphic piece of a larger program, focused primarily on providing some form of browser-related content. Applet itself is a subclass of an awt component and provides extended capabilities to support rendering dynamic images, such as animations and audio. java.io. The java.io package supplies classes to support reading and writing streams, files, and pipes. java.lang. These classes support the basic Java objects and native types: Class, Object, Boolean, Float, Double, Integer, String, and so on, plus those dealing with the extended capabilities of the language and connection with the rest of the system environment. java.net. The java.net package supplies classes to support network programming. These include dealing with sockets, Internet addresses, network datagrams, uniform resource locators (URLs), and content handlers for data from a URL. java.util. These are general-purpose utility classes for data structures, such as dictionaries, hashtables, dates, stacks, bit sets, and strings. The package does not have the breadth of similar, commercial C++ libraries, but does provide convenient and time-saving implementations for some commonly needed classes. Summary Much of the popular press is claiming Java is much easier to learn than C++, heralding it as a breakthrough in that regard. Certain aspects of Java are easier to learn than C++, but the difficulties most people have in learning to program in both C++ and Java have little to do with the language itself. Instead, they have to do with fundamental, object-oriented concepts. If you understand those, picking up the Java syntax will be a breeze. If you don't, you will probably find Java just about as confusing as C++. DDJ _________________________________________________________________ Copyright © 1996, Dr. Dobb's Journa