The biggest potential stumbling block is speed: interpreted Java runs in the range of 20 times slower than C. Nothing prevents the Java language from being compiled and there are just-in-time compilers appearing at this writing that offer significant speed-ups. It is not inconceivable that full native compilers will appear for the more popular platforms, but without those there are classes of problems that will be insoluble with Java because of the speed issue.
Everything must be in a class.
There are no global functions or global data. If you want the
equivalent of globals, make static methods and static
data within a class. There are no structs or enumerations or unions,
only classes.
All method definitions are defined
in the body of the class. Thus, in C++ it would look like all the
functions are inlined, but they’re not (inlines are noted later).
Class definitions are roughly the same form in Java as in C++, but there’s no closing semicolon. There are no class declarations of the form class foo, only class definitions.
class aType { void aMethod( ) { /* method body */ } }
There’s no scope resolution
operator :: in Java. Java uses the dot for everything, but
can get away with it since you can define elements only within a
class. Even the method definitions must always occur within a class,
so there is no need for scope resolution there either. One place
where you’ll notice the difference is in the calling of static
methods: you say ClassName.methodName( );. In addition,
package names are established using the dot, and to perform a
kind of C++ #include you use the import keyword. For
example: import java.awt.*;. (#include does not
directly map to import, but it has a similar feel to it).
Conditional expressions can be
only boolean, not integral.
The result of an expression like X
+ Y must be used; you can’t just say "X + Y" for the
side effect.
The char type uses the
international 16-bit Unicode character set, so it can automatically
represent most national characters.
Static quoted strings are
automatically converted into String objects. There is no
independent static character array string like there is in C and
C++.
Java adds the triple right shift
>>> to act as a "logical" right shift by
inserting zeroes at the top end; the >> inserts the
sign bit as it shifts (an "arithmetic" shift).
Although they look similar, arrays
have a very different structure and behavior in Java than they do in
C++. There’s a read-only length member that tells you how
big the array is, and run-time checking throws an exception if you
go out of bounds. All arrays are created on the heap, and you can
assign one array to another (the array handle is simply copied). The
array identifier is a first-class object, with all of the methods
commonly available to all other objects.
All objects of non-primitive types
can be created only via new. There’s no equivalent to
creating non-primitive objects "on the stack" as in C++.
All primitive types can be created only on the stack, without new.
There are wrapper classes for all primitive classes so that you can
create equivalent heap-based objects via new. (Arrays of
primitives are a special case: they can be allocated via aggregate
initialization as in C++, or by using new.)
No forward declarations are
necessary in Java. If you want to use a class or a method before it
is defined, you simply use it – the compiler ensures that the
appropriate definition exists. Thus you don’t have any of the
forward referencing issues that you do in C++.
Java has no preprocessor. If you want to use classes in another library, you say import and the name of the library. There are no preprocessor-like macros.
Object handles defined as class
members are automatically initialized to null. Initialization
of primitive class data members is guaranteed in Java; if you don’t
explicitly initialize them they get a default value (a zero or
equivalent). You can initialize them explicitly, either when you
define them in the class or in the constructor. The syntax makes
more sense than that for C++, and is consistent for static
and non-static members alike. You don’t need to externally
define storage for static members like you do in C++.
There are no Java pointers in the sense of C and C++. When
you create an object with new, you get back a reference
(which I’ve been calling a handle in this book). For
example:
String s = new
String("howdy");
However, unlike C++ references that must be initialized when
created and cannot be rebound to a different location, Java
references don’t have to be bound at the point of creation. They
can also be rebound at will, which eliminates part of the need for
pointers. The other reason for pointers in C and C++ is to be able
to point at any place in memory whatsoever (which makes them unsafe,
which is why Java doesn’t support them). Pointers are often seen
as an efficient way to move through an array of primitive variables;
Java arrays allow you to do that in a safer fashion. The ultimate
solution for pointer problems is native methods (discussed in
Appendix A). Passing pointers to methods isn’t a problem since
there are no global functions, only classes, and you can pass
references to objects.
The Java language promoters initially said
"No pointers!", but when many programmers questioned how
you can work without pointers, the promoters began saying
"Restricted pointers." You can make up your mind whether
it’s "really" a pointer or not. In any event, there’s
no pointer arithmetic.
There are no destructors in Java.
There is no "scope" of a variable per se, to indicate when
the object’s lifetime is ended – the lifetime of an object is
determined instead by the garbage collector. There is a finalize( )
method that’s a member of each class, something like a C++
destructor, but finalize( ) is called by the garbage
collector and is supposed to be responsible only for releasing
"resources" (such as open files, sockets, ports, URLs,
etc). If you need something done at a specific point, you must
create a special method and call it, not rely upon finalize( ).
Put another way, all objects in C++ will be (or rather, should be)
destroyed, but not all objects in Java are garbage collected.
Because Java doesn’t support destructors, you must be careful to
create a cleanup method if it’s necessary and to explicitly call
all the cleanup methods for the base class and member objects in
your class.
There’s no goto in Java.
The one unconditional jump mechanism is the break label
or continue label, which is used to jump out of the
middle of multiply-nested loops.
Java uses a singly-rooted
hierarchy, so all objects are ultimately inherited from the root
class Object. In C++, you can start a new inheritance tree
anywhere, so you end up with a forest of trees. In Java you get a
single ultimate hierarchy. This can seem restrictive, but it gives a
great deal of power since you know that every object is guaranteed
to have at least the Object interface. C++ appears to be the
only OO language that does not impose a singly rooted hierarchy.
Java has no templates or other
implementation of parameterized types. There is a set of
collections: Vector, Stack, and Hashtable that
hold Object references, and through which you can satisfy
your collection needs, but these collections are not designed for
efficiency like the C++ Standard Template Library (STL). The new
collections in Java 1.2 are more complete, but still don’t have
the same kind of efficiency as template implementations would
allow.
Garbage collection means memory
leaks are much harder to cause in Java, but not impossible. The
garbage collector is a huge improvement over C++, and makes a lot of
programming problems simply vanish. It might make Java unsuitable
for solving a small subset of problems that cannot tolerate a
garbage collector, but the advantage of a garbage collector seems to
greatly outweigh this potential drawback.
Java has built-in multithreading
support. There’s a Thread class that you inherit to create
a new thread (you override the run( ) method). Mutual
exclusion occurs at the level of objects using the synchronized
keyword as a type qualifier for methods. Only one thread may use a
synchronized method of a particular object at any one time.
Put another way, when a synchronized method is entered, it
first "locks" the object against any other synchronized
method using that object and "unlocks" the object only
upon exiting the method. There are no explicit locks; they happen
automatically. You’re still responsible for implementing more
sophisticated synchronization between threads by creating your own
"monitor" class. Recursive synchronized methods
work correctly. Time slicing is not guaranteed between equal
priority threads.
Instead of controlling blocks of
declarations like C++ does, the access specifiers (public,
private, and protected) are placed on each definition
for each member of a class. Without an explicit access specifier, an
element defaults to "friendly," which means that it is
accessible to other elements in the same package (equivalent to them
all being C++ friends) but inaccessible outside the package.
The class, and each method within the class, has an access specifier
to determine whether it’s visible outside the file. Sometimes the
private keyword is used less in Java because "friendly"
access is often more useful than excluding access from other classes
in the same package. (However, with multithreading the proper use of
private is essential.) The Java protected keyword
means "accessible to inheritors and to others in this
package." There is no equivalent to the C++ protected
keyword that means "accessible to inheritors only"
(private protected used to do this, but the use of that
keyword pair was removed).
No inline methods. The Java
compiler might decide on its own to inline a method, but you don’t
have much control over this. You can suggest inlining in Java by
using the final keyword for a method. However, inline
functions are only suggestions to the C++ compiler as well.
Inheritance in Java has the same effect as in C++, but the syntax is different. Java uses the extends keyword to indicate inheritance from a base class and the super keyword to specify methods to be called in the base class that have the same name as the method you’re in. (However, the super keyword in Java allows you to access methods only in the parent class, one level up in the hierarchy.) Base-class scoping in C++ allows you to access methods that are deeper in the hierarchy). The base-class constructor is also called using the super keyword. As mentioned before, all classes are ultimately automatically inherited from Object. There’s no explicit constructor initializer list like in C++, but the compiler forces you to perform all base-class initialization at the beginning of the constructor body and it won’t let you perform these later in the body. Member initialization is guaranteed through a combination of automatic initialization and exceptions for uninitialized object handles.
public class Foo extends Bar { public Foo(String msg) { super(msg); // Calls base constructor } public baz(int i) { // Override super.baz(i); // Calls base method } }
Inheritance in Java doesn’t
change the protection level of the members in the base class. You
cannot specify public, private, or protected
inheritance in Java, as you can in C++. Also, overridden methods in
a derived class cannot reduce the access of the method in the base
class. For example, if a method is public in the base class
and you override it, your overridden method must also be public
(the compiler checks for this).
Java provides the interface keyword, which creates the
equivalent of an abstract base class filled with abstract methods
and with no data members. This makes a clear distinction between
something designed to be just an interface and an extension of
existing functionality via the extends keyword. It’s worth
noting that the abstract keyword produces a similar effect in
that you can’t create an object of that class. An abstract
class may contain abstract methods (although it isn’t
required to contain any), but it is also able to contain
implementations, so it is restricted to single inheritance. Together
with interfaces, this scheme prevents the need for some mechanism
like virtual base classes in C++.
To create a version of the
interface that can be instantiated, use the implements
keyword, whose syntax looks like inheritance:
public interface Face { public void smile(); } public class Baz extends Bar implements Face { public void smile( ) { System.out.println("a warm smile"); } }
There’s no virtual keyword in Java because all non-static methods always use dynamic binding. In Java, the programmer doesn’t have to decide whether to use dynamic binding. The reason virtual exists in C++ is so you can leave it off for a slight increase in efficiency when you’re tuning for performance (or, put another way, "If you don’t use it, you don’t pay for it"), which often results in confusion and unpleasant surprises. The final keyword provides some latitude for efficiency tuning – it tells the compiler that this method cannot be overridden, and thus that it may be statically bound (and made inline, thus using the equivalent of a C++ non-virtual call). These optimizations are up to the compiler.
Java doesn’t provide multiple
inheritance (MI), at least not in the same sense that C++ does. Like
protected, MI seems like a good idea but you know you need it
only when you are face to face with a certain design problem. Since
Java uses a singly-rooted hierarchy, you’ll probably run into
fewer situations in which MI is necessary. The interface
keyword takes care of combining multiple interfaces.
Exception handling in Java is different because there are no destructors. A finally clause can be added to force execution of statements that perform necessary cleanup. All exceptions in Java are inherited from the base class Throwable, so you’re guaranteed a common interface.
public void f(Obj b) throws IOException { myresource mr = b.createResource(); try { mr.UseResource(); } catch (MyException e) { // handle my exception } catch (Throwable e) { // handle all other exceptions } finally { mr.dispose(); // special cleanup } }
Java has method overloading, but
no operator overloading. The String class does use the +
and += operators to concatenate strings and String
expressions use automatic type conversion, but that’s a
special built-in case.
The const
issues in C++ are avoided in Java by convention. You pass only
handles to objects and local copies are never made for you
automatically. If you want the equivalent of C++’s pass-by-value,
you call clone( ) to produce a local copy of the
argument (although the clone( ) mechanism is somewhat
poorly designed – see Chapter 12). There’s no copy-constructor
that’s automatically called.
To create a compile-time
constant value, you say, for example:static final int
SIZE = 255;
static final int BSIZE = 8 * SIZE;
Because of security issues,
programming an "application" is quite different from
programming an "applet." A significant issue is that an
applet won’t let you write to disk, because that would allow a
program downloaded from an unknown machine to trash your disk. This
changes somewhat with Java 1.1 digital signing, which allows you to
unequivocally know everyone that wrote all the programs that
have special access to your system (one of which might have trashed
your disk; you still have to figure out which one and what to do
about it.). Java 1.2 also promises more power for applets.
Since Java can be too restrictive
in some cases, you could be prevented from doing important tasks
such as directly accessing hardware. Java solves this with native
methods that allow you to call a function written in another
language (currently only C and C++ are supported). Thus, you can
always solve a platform-specific problem (in a relatively
non-portable fashion, but then that code is isolated). Applets
cannot call native methods, only applications.
Java has built-in support for
comment documentation, so the source code file can also contain its
own documentation, which is stripped out and reformatted into HTML
via a separate program. This is a boon for documentation maintenance
and use.
Java contains standard libraries for solving specific tasks. C++ relies on non-standard third-party libraries. These tasks include (or will soon include):
Networking
Database Connection (via JDBC)
Multithreading
Distributed Objects (via RMI and CORBA)
Compression
Commerce
The availability and standard nature of these libraries allow for more rapid application development.
If the access to a Java handle
fails, an exception is thrown. This test doesn’t have to occur
right before the use of a handle; the Java specification just says
that the exception must somehow be thrown. Many C++ runtime systems
can also throw exceptions for bad pointers.
Generally, Java is more robust, via:
Object handles initialized to null (a keyword)
Handles are always checked and exceptions are thrown for failures
All array accesses are checked for bounds violations
Automatic garbage collection prevents memory leaks
Clean, relatively fool-proof exception handling
Simple language support for multithreading
Bytecode verification of network applets
Modified from the list at http://www.javacoffeebreak.com/articles/thinkinginjava/comparingc++andjava.html