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.