Java

Differences between Java and C++

Language complexity - Ease of use - Code readability - Need for rules - Code optimizability - Reverse-engineering - Safety - Diagnostics - Sandbox security model - Metadata - Design considerations - Portability - Conclusions - Notes - References

This article intends to provide technical insights into differences between the Java and C++ programming languages. There's no claim of completeness. Knowledge of either Java or C++ is assumed. The status of this article is ongoing.

This article addresses mainly the languages, less the runtime environments (virtual machines, hosted, embedded), and not the library frameworks. Only typically Java is compiled to byte code and run on virtual machine; only typically C++ is used without garbage-collection.

Language complexity

The C++ programming language is far more complex than the Java language. Due to this, C++ coding is more error prone than Java code. This has impacts on maintainability1 and feasibility of large projects (within limited resources2).
+ less memory models
Java allocates all its objects on the heap (all managed by the garbage collector); C++ additionally offers stack based instantiation (including temporary objects), and static instantiation. Stack objects are automatically deleted (when they go out of scope), so a programmer has to assure no references are kept further (e.g. in a container, registry, etc.). Instantiation order with static instantiation is difficult to predict, and therefore error-prone, less portable, and restricted.
+ no temporary objects
[Based on non-trivial rules] C++ language will chose either a cast or temporary object (including object construction) to [implicitly] coerce one type into another, sometimes leading to unwanted effects or unwanted overhead.
+ no copy constructor and assignment operator needed
Due to Java's memory model, no copy constructor and assignment operator are needed, and therefore can't become a source of bugs.
+ no explicit cast code supported
C++ increases the number of routes for type coercion by allowing to defined explicit cast code.
+ no ambiguity between int, boolean, and null pointer
Due to false and null both being distinct from 0 there's no ambiguity if used e.g. as parameters in overloaded methods3.
+ no default parameters
As not supported, there can't be an ambiguity between methods with default parameters and other overloaded methods.
+ no unsigned integers
As unsigned integers don't really buy anything (in C/C++ neither, decent casts can be used), they're omitted.
~ const not supported
Object immutability is in Java at most considered via annotation4. The more intuitive final modifier allows to implement first-level constness on variables and fields (again being more intuitive than C++'s reference fields).
~ expression evaluation order
Evaluation order within expression is stricter defined in Java, leading to more deterministic behavior, and avoiding a specific source of problems. C++ compiler however may allow options to warn about undefined semantics outside of sequence points.
~ no macro pitfalls
Macros offer call-by-name semantics and therefore allow unwanted side effects, e.g. with multiple evaluation (max(i++,j++)), or precedence issues (with missing parentheses).5

Ease of use

+ no splitting between declaration and definition
As Java doesn't split the interface and its implementation (in C++ into header files and implementation files), there's less overhead with the duplication of declarations6.
+ critical section handling built-in
Support for critical section handling, typically needed with multithreaded code, is closely built into the Java language; methods synchronize on their [this] object simply by specifying the synchronized modifier; regions of code use a synchronized block; both are exception-safe.7
+ garbage collection
Java's garbage collection largely avoids the need for manual reference house-keeping8. (finalizers and java.lang.ref.SoftReferences allow some useful interaction with the garbage collector (however their use increases code complexity).) Unreferenced/unreachable objects are not occurring in Java. Garbage-collected environments allow (immutable) copy-on-write data structures, which are useful for lock-free (fast) implementations of concurrent algorithms.
+ no destructor code needed
Destructor code is not needed in Java, mainly due to its garbage collection memory model. Garbage collection prevents the types of memory leaks found in C++.
+ keeping backward compatibility
If a class [during code changes] grows in data size, the using C++ code has to be recompiled, since it's the instantiator's task to allocate memory (and thus needs knowledge about size), which is not the case in Java; it's easier to keep compatibility in Java.
+ no cross-compilation needed
Since the compiled bytecode is hardware-platform independent, there's no need for cross-compilation or having access to different platforms.

Code readability

+ additional keywords
Java incorporates more keywords where appropriate, to enhance readability: abstract, extends, implements, interface, super, null.
+ additional types
Java explicitly distinguishes between integers and boolean, as well as between bytes and characters: boolean, byte, and defines the literals false, true.

Need for rules

Being more complex and less restricted, C++ typically needs policies and coding rules, whereas Java does so in a much minor extend.

Code optimizability

Due to being optionally less dynamic, C++ is more optimizable (assuming the now usual modus of compiling each Java class on its own (to follow the model of loading each class on its own), i.e. omitting global analysis and global optimization). Some of C++'s optimizability arises from omitted method calls and dispatching, some optimizations are possible by knowing more code context; optimizability affects performance and space requirements.

C++ allows more control over compile-time optimization, whereas Java depends on the specific runtime optimization used, e.g. with just-in-time optimizing virtual machines. Just-in-time compilers may be able to consider the details of CPU instruction sets (at runtime) and therefore translate to more efficient native code. In C++ such optimizations are often done by providing many platform-specific dynamic libraries (the decision which to load/use done at runtime); however code layout has to be planned to isolate such parts and compile with a set of different platform-specifying options.

Dynamic compilers might be better suited to consider runtime statistics, to rearrange code accordingly (similar to [lower level] branch prediction).

- no inlining or macros
Inlining code allows to consider it in a specific context, to optimize specifically, over more code (without generally breaking [object-oriented] encapsulation).
- no template instantiation
Template instantiation generates type specific code that is more optimizable than its generic counterpart (that merely does type checks only).
- no non-virtualization
Allows to omit dispatch calls and to inline specific code (and hence to optimize over more code).
- no on-stack instantiation
Allows to omit memory management. Just-in-time compilers may consider on-stack instantiation though.
- no composite object
Unlike with Java, C++ allows to directly compose objects, which avoids indirection (via references), and makes exact types known (possibly further avoiding method dispatch indirection).

Reverse-engineering

Optimization generally and inlining specially make it harder to reverse-engineer binaries; so implementation secrets are stronger protected in C++ than in Java.

Safety

These [language] safety issues affect [program] reliability (and the cost to make software work). C++ programs crash where Java issues useful diagnostic information (stack traces of causing and detecting code). Many of the aspects make problem analysis harder in C++, due to deferred problem detection. Undetected error conditions, especially with [user provided] data input, cause security problems, and vulnerabilities.
+ no uninitialized pointers and data
Java implicitly initializes data fields [if not done explicitly [by the programmer], to useful defaults (null, false, 0, ...)], and forces explicit initialization on local variables. Though C++ compilers may take care on this too9, either by initializing10, or issuing warnings.
+ no dangling/stale pointers
Java's garbage collection prevents dangling references.
+ array bounds checks
Boundary violation with arrays are checked at runtime, leading to an IndexOutOfBoundsException in error case. Two issues make this detail very important: [stack] data overwrites [with 'garbage'] in C++ get noticed [much] later, making problem analysis [more] difficult; and [stack] buffer overwrites are a major source of security problems.
+ null pointer handling
In Java, dereferencing a null pointer generates a NullPointerException, which may be reasonably caught. As with [all] Java exceptions, a stack trace is included, to help identifying culprit code locations, and enhancing error diagnostics. C++ code often just crashes dereferencing a null pointer11.
+ type cast checks
Java only allows casts between object types if permitted by class hierarchy relationship, i.e. with objects only the equivalent of C++'s dynamic_cast is supported.
+ unchecked error conditions avoided through exceptions
Since in Java error conditions are [more consequently] propagated through exceptions, they cannot be easily ignored12. Ignoring error conditions at their place of appearance can cause [more] harm to the program's internal state and, as problems are reacted on only later, makes [deferred] problem analysis more difficult.
+ explicit boolean type
In Java boolean is an explicit type, different from int. Also the constants true and false are provided. This feature disables integer assignments in conditionals (though [optionally] also checked with C++ compilers13).
+ link vs. runtime compatibility
Class [linking vs. runtime] incompatibilities are detected at their origin, and can even be caught (and handled) at runtime. C++ issues 'unpredictable' behavior (e.g. in the case of a shorter or reorganized method dispatch table).
+ memory model strictly defined
Java's memory model is defined to cover all multithreading and optimization issues.

Diagnostics

+ stack trace with exceptions
Java exceptions include a stack trace, to help identifying originating buggy code locations; the given [call stack] context often provides hints for problem solution/avoidance.
+ linked exception chain
If a lower level exception triggers an upper level exception (since it's caught and transformed to an application-level exception), the originating exception is linked, thus providing information needed for analysis of all software layers.
+ thread dumps
Java runtime environments provide thread dumps with stack traces of all threads, to allow analysis of such things as deadlock conditions or starvations; these thread dumps include information about monitors held or waited for.
+ VM runtime information
Java runtime environments further may provide information about memory use, garbage collection, locks. C++ runtime environments are free to offer similar, however likely not provided in many environments.

Sandbox security model

Besides the language safety aspects, Java [typically] offers a sandbox model of runtime program execution: certain code may be restricted from using certain parts of the APIs. The model however adds complexity (if used explicitly by the [application] programmer). C++'s model makes the prevention of [arbitrary/unallowed] memory accesses infeasible; C++'s security typically is implemented by means of crossing process/system boundaries ([Solaris] door calls, or system call interfaces).
+ fine-granular permissions
Permissions for operations can be specified in a sufficiently fine-granular way, and are enforced by the runtime environment. E.g. [web] applets may not be allowed to read/write [local] files. Code often is not allowed to change the classloader (to not compromise the rest of the security).
+ privileged namespaces
Java offers the treatment of 'trusted' [privileged] package namespaces. These are enforced by the classloader.
+ restricted polymorphism
'Trusted' types may be confined (from deriving/overloading) by final modifier on classes and methods (preventing further polymorphism), to make references on the type predictable/safe in use (e.g. java.lang.String).
+ byte code verifier
The class loaders typically verify byte code for violations that may affect security or would lead to VM crashes.

Metadata

Java implements metadata, to support observing reflection (and introspection).
+ dynamic code invocation
Dynamic invocation allows to dynamically bind code into frameworks, e.g. application containers. This allows for deployment of code with less overhead than e.g. with RMI or CORBA.
+ inspectable objects
Inspectable data objects yield simple data interfaces. Samples are Java beans. Default serialization uses this feature too.
+ array length
The length of an array object is accessible through length, making 'end-element' policies needless14, and allowing for more safety (bounds violations can be checked).
+ annotations
With annotations, Java supports structured meta data, e.g. meta information that helps ensure policies. C++ may do some (but not all) of the policies via pragmas.

Design considerations

Java incorporates some design decisions that make the language simpler or more comfortable to use.
~ single inheritance
Strict single inheritance is enforced in Java, which makes the design clearer. Instead of multiple inheritance the more controlled interface construct (pure abstract class) is supported.
~ single-rooted classes
All classes are single-rooted by the class Object. This enables such things as the use of mixed-type containers.
+ useful root class methods
Methods, that are needed for the [mixed-type or not] containers, (i.e. that are of importance to building data structures), are predefined [for each class]: equals, hashCode. Likewise, string representation is [usually] done via a method toString.
+ no global data
Global data (outside class blocks) is not supported. This makes the interfaces less complex. However, static class data in (possibly uninstantiable) classes is supported.
~ no operator overloading
Overloaded operators, which may be hard or unintuitive to read, are not supported in Java.
~ no goto
goto is not supported. However, multilevel break and continue is supported.
~ multilevel break and continue
The control flow in nested loops is enhanced with multilevel break and continue.
- no reference parameters
Java lacks reference parameters, establishing the need for holder objects, returning object arrays, or introducing helper objects, or keeping objects mutable.
+ nested classes and inner classes
Java allows to further partition namespace and enhance encapsulation by offering private static [nested] classes, and eases accesses of outer context by non-static [inner] classes.
+ in-source documentation
In-source code documentation comments are provided. Documentation keywords are supported as well: @author, @version, @since, @param, @see, etc. It's however easy to use extractable documentation comments in C++ too.

Portability

Java provides portability to other [computer] platforms, and to other programming languages.
+ sizes of the integer types defined
The sizes of the integer types byte, short, int and long are defined to be 1, 2, 4 and 8 bytes. The choice of integer type doesn't need platform considerations.
+ byte order defined
The well defined network byte order is used to write primitive data types to file or network15.
+ no alignment and padding
As [native] memory can't be directly written to file or network, no portability problems with different alignments or paddings arise.
+ default serialization
Java provides [however inefficient16] default serialization for [composed] data types (objects, classes), also considering version compatibility issues.
+ container classes
Serializable container classes make it easy to ship complex data across platforms.
+ unicode provided
Unicode is used as the character type and as base for strings. It supports a large set of scripts.

Conclusions

Within a software engineering point of view, Java seems to be the more robust programming language among Java/C++. The language catches some of the ugly design and implementation features that C++ offers. The memory management based on garbage collection makes code more robust and might allow faster development. Concerning highly optimized code, C++ may be the choice.

Notes

1 Neglecting (at the time unknown) pitfalls, which are more prevalent in C++ than Java, will make code buggier during maintenance phase.
2 Resources are always limited, due to them being used on the urgent projects only... Some shops don't have required resources available at all...
3 However there's still ambiguity between pointer types, see e.g. Java's suboptimal interpretation of String.valueOf(null).
4 Object constness/immutability rather being a policy than a direct language construct; also, C++ is not strict in considering constness physical (vs. conceptual).
5 C++ of course offers inlining instead of the problematic macros (such as max).
6 Due to Java's dispatch model and memory model, private implementation details even generate less dependencies than in C++17.
7 The [recent] additions of java.util.concurrent and java.util.concurrent.locks however allow more flexibility (e.g. non-blocking atomic operations, fairness), and are syntactically used the same way C++ uses synchronization.
8 It's wise to de-reference internal data that is no longer needed (e.g. unneeded elements in java.util.Vector's object array holding the references18), to avoid [transient] memory leaks.
9 Based on compiling settings.
10 Having data unconditionally initialized will lead to more deterministic behavior, which is interesting for problem analysis.
11 A core dump at least allows for post-mortem analysis19.
12 As C++ can [still] use C paradigms [too], errors are often signalled in return values instead of exceptions.
13 However, the [silly] C++ expression if(flag==1) (instead of if(flag)) likely still isn't caught...
14 Avoiding end-elements (e.g. C string's NUL or C pointer array's NULL) too allows for efficient sub-array/sub-string implementations (e.g. Java String using [private final] int offset, count).
15 In fact, the native byte order is not [and does not need to be] visible to the Java programmer.
16 Time and space inefficient.
17 In C++ holder [facade] objects are needed to shield the implementation details.
18 Vector's allocated capacity is often larger than its [externally visible] size.
19 If enabled.

References

[1] The Java Language Specification, James Gosling, Bill Joy, Guy Steele, Gilad Bracha, Addison-Wesley, 3rd Ed. 2005, ISBN 0-321-24678-0

[2] The C++ Programming Language, Bjarne Stroustrup, Addison-Wesley, 3rd Ed. 1997, ISBN 0-201-88954-4

Keywords: Java vs. C++, Java versus C++, differences between Java and C++, differences between C++ and Java


lr / Thu Feb 18 2010 (Thu Mar 27 1997) / back to the Java page ; the Java f.a.q.