Java Concepts for C++ Programmers

Copyright 1996-2000

Clinton Staley

This list assumes you know C++. Java shares many concepts with C++, so C++ is an important prerequisite to understanding Java. The list is built around the differences between C++ and Java.

0. Syntax like C++

For common syntax -- loops, if-statements, assignments -- Java is nearly identical to C++.

1. Better Elemental Types

"Elemental" Arithmetic types are regularized, with byte, short, int, and long having 1, 2, 4, and 8 bytes. Characters are 2-byte Unicode quantities. There are no unsigned types.

2. References instead of Pointers

There are no pointers, per se. Instead, there are references, which are really pointers in disguise. These are very much like non-const C++ references, with an important difference -- they are assignable to one another in the body of code, not just upon declaration. Thus a reference can be made to "refer" to any object of the appropriate type, just as a C++ pointer can be made to "point" to any object of the appropriate type.

There are no pointer operators &, *, or ->. The "." operator works with a reference like the "->" operator works with a pointer in C++. The common misconception that Java's lack of pointers makes linked lists, trees, etc. impossible is completely wrong. Java's new operator is very much like C++'s, except that it returns a reference. You can set up dynamic structures just as you would in C++.

3. All Objects are Dynamically Allocated

All objects are dynamically allocated. You cannot directly declare an object variable -- you declare references, and initialize them via "new" calls. Simple assignment between references is always shallow -- both references refer to the same object. To get an object copied, call "clone" (see below).

An interesting side effect of this rule is that there are really only three categories of variables or member data in Java: integer elementals, floating point elementals, and references. All object data is unnamed and not directly declared.

4. Object Base Class

Every non-elemental type in Java is ultimately derived from Object -- the universal base class. A reference to Object is the Java equivalent of a "void *" in C++. Many of the library classes have methods that accept or return Object, which means they accept or return a reference to any object of any possible class. The container class library maintains collections of Objects, for instance.

5. Class Declaration Syntax

Class declaration syntax is similar to C++, with the following differences (plus some more conceptual ones listed later).

a. All function bodies are declared inline, in the class declaration. There is no analog to .h/.C file pairs in Java. Every source file is terminated by .java, and looks like a .h file with all code listed inline.

b. All member data - static or nonstatic, final or modifiable -- can be conveniently initialized inline by the usual assignment syntax. Initialization for statics replaces C++'s awkward initialization-upon-definition. Initialization for nonstatics is effectively an alternate way to specify construction.

c. Syntax for specifying the base class is nicer, with an extends keyword, and a base class is mandatory. If you don’t specify a base class, one is provided for you automatically -- Object.

d. Access (private/protected/public) is specified per member datum or method with a keyword in the datum or method declaration.

e. Java has no :: operator. Scope, where necessary, is provided with the "." operator preceded by the class name: Java's Object.clone is the analog of the C++ Object::clone.

f. The familiar this variable exists for nonstatic methods, but it's a reference, not a pointer.

g. There is no inline constructor syntax. Since the only member data are elementals and references, the only need for inline syntax would be for base-object construction. This is done instead via a super keyword. Also, constructors may call one another.

h. There is a syntax for providing a block of code to be executed once when the class is first loaded, usually used to initialize static member data when simple assignment initialization (point b) is insufficient.

i. All methods are virtual by default. There is no virtual keyword in Java. Methods may be made nonvirtual via the final keyword (see below).

6. Garbage Collection

There is no delete operator. Destruction of an object is done by automatic garbage-collection, when there are no more references to the object. To make an object go away, just "drop it on the floor". Remember though, that the garbage-collection service is a union shop -- collection is not guaranteed on any particular schedule J .

7. Universal Methods

The base Object class, and thus all classes, provide certain fundamental operations: clone, which is the analog of the C++ copy constructor; equals, for deep comparisons between objects; toString, which provides an automatic conversion from any object to a String and finalize, which is the analog of the C++ destructor.

You redefine these in your derived classes appropriately. The base versions are not too useful: Object.equals does a shallow comparison of references, producing the same result as an == comparison. Object.clone throws an exception (see below). Object.finalize does nothing.

 

8. Final Declarations, and Inline Methods.

The keyword "const" is replaced by "final". One may declare elemental types as final, but final references and final objects do not exist. Classes and methods may be declared final, but this applies to the ability to derive from or override them. Final classes may not be a base class in derivations, and final methods may not be overridden.

An important note. Declaring a method "final" is the Java equivalent of an "inline" hint in C++. If the method can't be redefined, it's automatically non-virtual, and thus inlineable. Most Java compilers will inline small, final methods.

9. Arrays are Objects

Arrays are derived from Object, too, and should be viewed as built-in class types. Arrays include a length member, and they do automatic bounds-checking, throwing an exception when bounds are violated. As with other classes, you declare an array reference, and create the actual array object by allocation. If it’s an array of objects, you must allocate each element as well.

Multidimensional arrays are arrays of arrays, as in C++, but each subarray must be dynamically allocated, so Java multidimensional arrays are closer in structure to C++ dynamically allocated multidimensional arrays.

10. Strings are Objects

Like arrays, strings are objects. The classes String and StringBuffer are derived from Object and have a host of member functions (but alas no direct indexing). The String class represents constant strings, and implements an efficient memory-sharing scheme. String has a full set of string access methods, including a built-in concatenation using "+". Its relative StringBuffer represents read/write strings and includes a full set of string modification operations.

11. No Global Code or Data

There are no global-scope functions in Java. All code appears as part of a class in a method or initialization block. All data is either member data or a local variable.

12. Automatic Initialization

It's impossible to use an uninitialized variable in Java. Member data not otherwise initialized are automatically set to 0, 0.0, or null, for integer, floating, and reference types respectively (and recall that these three are the only types of declared variables). Local variables are not initialized, but the compiler is smart enough to detect possible use of uninitialized local variables and flag them -- as a hard error, not a warning.

13. Classpath and Import

Each class in a .java file compiles into a .class" file containing bytecode and all the linking information necessary for other .java files to use the compiled class -- somewhat like a bytecode COM object, but much more sophisticated. To use classes, you simply refer to them in declarations. The Java system maintains a "classpath" of directories in which .class files are searched-for, and the name of a .class file always matches exactly (case-sensitively) the name of the class it contains.

14. Packages

The ability to import classes from a wide variety of different .class files necessitates a careful, hierarchical naming scheme for classes. Classes are divided into "packages" similar to C++ namespaces, but with a hierarchical naming pattern (e.g. "java.lang" or "java.awt.event"). Since classes are often grouped in packages it's convenient to use the import keyword, which is like the C++ "using namespace".

The hierarchical structure of the package name is reflected in a directory structure. For instance, .class or .java files in the com.truelink.tse package would fall in the X/com/truelink/tse directory, where X is one of the directories in the classpath for .class files or a "source path" for .java files.

There is a world-wide naming scheme for packages based on Internet domain name of the package author.

15. Exceptions and Runtime Exceptions

Exceptions look much the same as in C++, but must be derived from Throwable, another built-in class. Also, each method follows a "catch or declare" rule, where any exceptions that might escape from the method must be explicitly declared in the method header. Exceptions are used ubiquitously in Java development; they're not viewed as a "fancy feature" as they sometimes are in C++.

There are no assertions, but runtime exceptions are a rough analog. These exceptions, derived from RuntimeException are exempt from the "catch or declare" rule, and are used for runtime faults like array bounds errors, null reference following, and division by 0. Since such faults can occur virtually anywhere, it makes no sense to require that runtime exceptions be declared in method headers. You can define your own runtime exceptions to serve as a sort of assertion. An uncaught runtime exception stops the application and shows up as a nice legible stack trace of the exception and its location on the Java text window.

16. Reflection and Checked Casts.

Java's RTTI is called "reflection", and it's much more complete than C++'s. One can print a complete list of any object or classes' member data, function headers, and base class at runtime. Upcasting and downcasting are checked. Bad casts result in runtime exceptions. You can see if a cast is safe in advance with the instanceof operator.

Java's reflection also replaces the complexities of COM/ActiveX. You can set up a class that permits runtime loading and interface querying very easily -- all Java classes do this automatically.

17. No Classic C++ Memory and Initialization Bugs

The combination of automatic initialization, garbage collection, and checked casting means that you cannot commit any of the following classic C++ errors: using an uninitialized variable, following an uninitialized pointer, incorrect downcasting, creating a storage leak, double deleting, using data after deletion, invalid downcasting. If new blows up, it means there really is an error in the library, not something you did. (One wonders what Java programmers talk about in bug-swapping sessions.)

You can follow a null reference or index an array out of bounds, but this generates an immediate runtime exception and is easy to trace.

18. Interfaces instead of Multiple Inheritance

Java has no multiple inheritance per se. One may declare "interfaces", which are classes with only abstract method declarations and final (const) data. In particular, interfaces have no standard member data or code. Any class may be derived from one other normal class, and may also be derived from an arbitrarily large number of interfaces. The derived class is said to "implement" the interfaces. Interfaces allow much of the advantages of MI, without the nasty difficulty of multiple base objects.

19. Inner classes

What would be a "nested class" in C++ is an inner class in Java, declared similarly. However, C++ nested classes do not enjoy any special access to their enclosing class, and an object of a nested class does not have any special connection to an object of the enclosing class. By contrast, an object of a Java inner class must be created by an instance of the enclosing class, and it has an implied connection to that enclosing-class object for its lifetime (maintained by a hidden reference internal to the nested-class object). The inner-class object also enjoys private access to the methods and members of its associated enclosing-class object. Inner-class objects thus follow a classic Proxy design pattern, and can be used in some cases where interfaces aren't a good enough substitute for multiple inheritance.

A special syntax even lets you define an inner class on the fly, with no name, and allocate just one object of that inner class. This feature is used almost exclusively to set up inner-class objects that handle events intended for their related enclosing-class object.

If you want classic C++ nested-class behavior, you can get that by adding the keyword static in front of the inner class declaration.

20. No Templates or Operator Overloading

There are no templates in Java. Java's standard container classes use inheritance-genericity -- they contain Objects, so they can contain any class type. If a container of elemental types is needed, there is a set of "elemental wrapper classes", one for each elemental type: Integer, Long, Double, etc.

Templates are the principal reason for operator overloading in C++, since one must often overload operators to allow a type to be used in a template instantiation. Since Java has no templates, it also has no operator overloading.

21. No Preprocessor, Defines, or Enums

The title says it. If you need a constant or a set of constants for an enumeration, you use static final member data. "Ifdefs" can be done with simple if-statements with constant flags. Almost all Java compilers automatically drop the bytecode for an if-branch that is unreachable.

22. No Default Parameters

There are no default parameters. Default parameters are instead implemented via overloaded methods, which sometimes results in pretty long lists of overloaded methods. Many Java developers think this is a mistake.

23. Standard Style

A lot of the debate over programming style doesn't apply in Java. There is a nearly universal naming convention for variables and methods, and an equally universal indentation and {}-placement standard. Everyone uses these; they're virtually a part of the language definition.

 

24. Java Traps

Yes, there are some, even though they're not as much fun as in C++. Here's a list of a few classics:

a. Finalize is not a destructor

C++ programmers new to Java tend to latch onto finalize as the right place to put object cleanup code. Remember though, that finalize is called at an indefinite time in the future -- whenever the garbage collector feels like it. When you need cleanup that happens immediately, you should declare and call a cleanup method, often called "dispose" by convention. Conversely, if a class has a dispose method, you must understand when it should be called, and be sure to call it. (java.awt.Graphics is a classic example).

b. Reference assignments and parameter passes are shallow copies

If you want a copy, you gotta call clone. Otherwise, even a passed parameter will refer back to the same original object. As you can imagine, this can result in bugs where an object is modified in one part of a program, and has an unexpected effect in a completely different area that never got its own copy of the object, but is still referring back to the original.

c. Implement clone correctly.

A special version of the shallow copy trap concerns implementations of "clone". Object.clone implements what a C++ programmer would call a "bitwise copy" duplication. It's common to call Object.clone from Derived.clone to get most of the legwork done. Don't forget, however, that Derived.clone must still do deep-copies where needed on any objects referred to by references from the object being cloned. Otherwise the original and the clone will point to the same objects. This is very similar to the design issues surrounding copy constructors in C++.

d. Beware of becoming Javafied

Before you know it, you'll be writing C++ code and thinking "I don't need to initialize that member datum, it'll automatically be 0", or "I'll just drop this object and the garbage collector will get it.". I call this loss of your C++ jungle instincts "being Javafied". Beware of it if you go back and forth between the languages.

e. Garbage collectors are nice, but slow

In speed-critical inner loops, avoid using new, since each new call implies increased use of the garbage collector, especially if it's happening on each iteration of a big loop. Instead, try to reuse objects in speed-critical sections.