B: Comparing C++ and Java

Bruce Eckel’s Thinking in Java Contents | Prev | Next

As
a C++ programmer, you already have the basic idea of object-oriented
programming, and the syntax of Java no doubt looks familiar to you. This makes
sense since Java was derived from C++.

  1. 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.
  2. Java
    has both kinds of comments like C++ does.
  3. 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.
  4. 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).
  5. 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 */ }

    }

  6. 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).
  7. Java,
    like C++, has primitive types for efficient access. In Java, these are
    boolean,
    char,
    byte,
    short,
    int,
    long,
    float,
    and
    double.
    All the primitive types have specified sizes that are machine independent for
    portability. (This must have some impact on performance, varying with the
    machine.) Type-checking and type requirements are much tighter in Java. For
    example:

    1.
    Conditional expressions can be only

    boolean
    ,
    not integral.

    2.
    The result of an expression like X + Y must be used; you can’t just say
    “X + Y” for the side effect.

  8. The
    char
    type uses the international 16-bit Unicode character set, so it can
    automatically represent most national characters.
  9. Static
    quoted strings are automatically converted into
    String
    objects. There is no independent static character array string like there is in
    C and C++.
  10. 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).
  11. 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.
  12. 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.)
  13. 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++.
  14. 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.
  15. Java
    uses packages in place of namespaces. The name issue is taken care of by
    putting everything into a class and by using a facility called
    “packages” that performs the equivalent namespace breakup for class
    names. Packages also collect library components under a single library name.
    You simply
    import
    a package and the compiler takes care of the rest.
  16. 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++.
  17. 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.

  18. Java
    has constructors that are similar to constructors in C++. You get a default
    constructor if you don’t define one, and if you define a non-default
    constructor, there’s no automatic default constructor defined for you,
    just like in C++. There are no copy constructors, since all arguments are
    passed by reference.
  19. 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.
  20. Java
    has method overloading that works virtually identically to C++ function
    overloading.
  21. Java
    does not support default arguments.
  22. 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.
  23. 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.
  24. 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.
  25. Garbage
    collection means memory leaks are much harder to cause in Java, but not
    impossible. (If you make native method calls that allocate storage, these are
    typically not tracked by the garbage collector.) However, many memory leaks and
    resouce leaks can be tracked to a badly written
    finalize( )
    or
    to not releasing a resource at the end of the block where it is allocated (a
    place where a destructor would certainly come in handy). 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.
  26. 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.
  27. 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).
  28. Nested
    classes. In C++, nesting a class is an aid to name hiding and code organization
    (but C++ namespaces eliminate the need for name hiding). Java packaging
    provides the equivalence of namespaces, so that isn’t an issue. Java 1.1

    has
    inner
    classes

    that look just like nested classes. However, an object of an inner class
    secretly keeps a handle to the object of the outer class that was involved in
    the creation of the inner class object. This means that the inner class object
    may access members of the outer class object without qualification, as if those
    members belonged directly to the inner class object. This provides a much more
    elegant solution to the problem of callbacks, solved with pointers to members
    in C++.
  29. Because
    of inner classes described in the previous point, there are no pointers to
    members in Java.
  30. 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.
  31. 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


    }

    }

  32. 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).
  33. 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");


    }

    }

  34. 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.
  35. 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.
  36. Run-time
    type identification functionality is quite similar to that in C++. To get
    information about handle
    X,
    you can say, for example:

    X.getClass().getName();

    To
    perform a type-safe downcast you say:

    derived
    d = (derived)base;

    just
    like an old-style C cast. The compiler automatically invokes the dynamic
    casting mechanism without requiring extra syntax. Although this doesn’t
    have the benefit of easy location of casts as in C++ “new casts,”
    Java checks usage and throws exceptions so it won’t allow bad casts like
    C++ does.

  37. 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


    }

    }

  38. Exception
    specifications in Java are vastly superior to those in C++. Instead of the C++
    approach of calling a function at run-time when the wrong exception is thrown,
    Java exception specifications are checked and enforced at compile-time. In
    addition, overridden methods must conform to the exception specification of the
    base-class version of that method: they can throw the specified exceptions or
    exceptions derived from those. This provides much more robust
    exception-handling code.
  39. 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.
  40. 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;

  41. 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
  42. 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.
  43. 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.
  44. 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.

  45. Java
    1.1

    includes the Java Beans standard, which is a way to create components that can
    be used in visual programming environments. This promotes visual components
    that can be used under all vendor’s development environments. Since you
    aren’t tied to a particular vendor’s design for visual components,
    this should result in greater selection and availability of components. In
    addition, the design for Java Beans is simpler for programmers to understand;
    vendor-specific component frameworks tend to involve a steeper learning curve.
  46. 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.
  47. 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

More by Author

Must Read