C Programming: Java comparison with C
The Java programming language and C share many common traits. A comparison of the two languages follows here. For a more in-depth look at Java, see the Java Programming WikiBook.
Java
[edit | edit source]Java was initially created to support network computing on embedded systems. Java was designed to be extremely portable, secure, multi-threaded and distributed, none of which were design goals for C . Java has a syntax familiar to C programmers, but direct compatibility with C was not maintained. Java was also specifically designed to be simpler than C , but continues to evolve above that simplification.
During the decade between 1999 and 2009, especially in the part of the programming industry dedicated to enterprise solutions, “Coffee-based” languages, which rely on "virtual machines" that are familiar in Smalltalk, grew in prominence. This was a trade between performance and productivity, something that made perfect sense at the time where computing power and the need of simplified and more streamlined language that permitted not only easy adoption but a lower learning curve. There is enough similitude between the languages that the proficient C programmers can easily adapt to Java, that is still today in ways less complex and even in comparison more consistent in the adopted paradigms than C .
This shift in interest has however decreased, mostly due to the evolution of the languages. C and Java evolution has merged much of the gap about the problems and limitations of both languages, the software requirements today have also shifted and fragmented more. Now we have specific requirements for mobile, data-center and desktop computing this makes the programming language selection an even more central issue.
C Java Compatibility backwards compatible, including C backwards compatibility with previous versions Focus execution efficiency developer productivity Freedom trusts the programmer imposes some constraints to the programmer Memory Management arbitrary memory access possible memory access only through objects Code concise expression explicit operation Type Safety type casting is restricted greatly only compatible types can be cast Programming Paradigm procedural or object-oriented object-oriented Operators operator overloading meaning of operators immutable Preprocessor yes no Main Advantage powerful capabilities of language feature-rich, easy to use standard library
Differences between C and Java are:
- C parsing is somewhat more complicated than with Java; for example,
Foo<1>(3);
is a sequence of comparisons if Foo is a variable, but it creates an object if Foo is the name of a class template. - C allows
namespace
level constants, variables, and functions. All such Java declarations must be inside a class or interface. const
in C indicates data to be 'read-only,' and is applied to types.final
in Java indicates that the variable is not to be reassigned. For basic types such asconst int
vsfinal int
these are identical, but for complex classes, they are different.- C didn't support constructor delegation until the C 11 standard, and only very recent compilers support this.
- C generates machine code that runs on the hardware, Java generates bytecode that runs on a virtual machine so with C you have greater power at the cost of portability.
- C , int main() is a function by itself, without a class.
- C access specification (public, private) is done with labels and in groups.
- C access to class members default to private, in Java it is package access.
- C classes declarations end in a semicolon.
- C lacks language level support for garbage collection while Java has built-in garbage collection to handle memory deallocation.
- C supports
goto
statements; Java does not, but its labeled break and labeled continue statements provide some structuredgoto
-like functionality. In fact, Java enforces structured control flow, with the goal of code being easier to understand. - C provides some low-level features that Java lacks. In C , pointers can be used to manipulate specific memory locations, a task necessary for writing low-level operating system components. Similarly, many C compilers support inline assembler. In Java, assembly code can still be accessed as libraries, through the Java Native Interface. However, there is significant overhead for each call.
- C allows a range of implicit conversions between native types, and also allows the programmer to define implicit conversions involving compound types. However, Java only permits widening conversions between native types to be implicit; any other conversions require explicit cast syntax. C 11 disallows narrowing conversions from initializer lists.
- A consequence of this is that although loop conditions (
if
,while
and the exit condition infor
) in Java and C both expect a boolean expression, code such asif(a = 5)
will cause a compile error in Java because there is no implicit narrowing conversion from int to boolean. This is handy if the code were a typo forif(a == 5)
, but the need for an explicit cast can add verbosity when statements such asif (x)
are translated from Java to C .
- A consequence of this is that although loop conditions (
- For passing parameters to functions, C supports both true pass-by-reference and pass-by-value. As in C, the programmer can simulate by-reference parameters with by-value parameters and indirection. In Java, all parameters are passed by value, but object (non-primitive) parameters are reference values, meaning indirection is built-in.
- Generally, Java built-in types are of a specified size and range; whereas C types have a variety of possible sizes, ranges and representations, which may even change between different versions of the same compiler, or be configurable via compiler switches.
- In particular, Java characters are 16-bit Unicode characters, and strings are composed of a sequence of such characters. C offers both narrow and wide characters, but the actual size of each is platform dependent, as is the character set used. Strings can be formed from either type.
- The rounding and precision of floating point values and operations in C is platform dependent. Java provides a strict floating-point model that guarantees consistent results across platforms, though normally a more lenient mode of operation is used to allow optimal floating-point performance.
- In C , pointers can be manipulated directly as memory address values. Java does not have pointers—it only has object references and array references, neither of which allow direct access to memory addresses. In C one can construct pointers to pointers, while Java references only access objects.
- In C pointers can point to functions or member functions (function pointers or functors). The equivalent mechanism in Java uses object or interface references. C 11 has library support for function objects.
- C features programmer-defined operator overloading. The only overloaded operators in Java are the "
=
" operators, which concatenate strings as well as performing addition. - Java features standard API support for reflection and dynamic loading of arbitrary new code.
- Java has generics. C has templates.
- Both Java and C distinguish between native types (these are also known as "fundamental" or "built-in" types) and user-defined types (these are also known as "compound" types). In Java, native types have value semantics only, and compound types have reference semantics only. In C all types have value semantics, but a reference can be created to any object, which will allow the object to be manipulated via reference semantics.
- C supports multiple inheritance of arbitrary classes. Java supports multiple inheritance of types, but only single inheritance of implementation. In Java, a class can derive from only one class, but a class can implement multiple interfaces.
- Java explicitly distinguishes between interfaces and classes. In C multiple inheritance and pure virtual functions makes it possible to define classes that function just as Java interfaces do.
- Java has both language and standard library support for multi-threading. The
synchronized
keyword in Java provides simple and secure mutex locks to support multi-threaded applications. C 11 provides similar capabilities. While mutex lock mechanisms are available through libraries in previous versions of C , the lack of language semantics makes writing thread safe code more difficult and error prone.
Memory management
[edit | edit source]- Java requires automatic garbage collection. Memory management in C is usually done by hand, or through smart pointers. The C standard permits garbage collection, but does not require it; garbage collection is rarely used in practice. When permitted to relocate objects, modern garbage collectors can improve overall application space and time efficiency over using explicit deallocation.
- C can allocate arbitrary blocks of memory. Java only allocates memory through object instantiation. (Note that in Java, the programmer can simulate allocation of arbitrary memory blocks by creating an array of bytes. Still, Java arrays are objects.)
- Java and C use different idioms for resource management. Java relies mainly on garbage collection, while C relies mainly on the RAII (Resource Acquisition Is Initialization) idiom. This is reflected in several differences between the two languages:
- In C it is common to allocate objects of compound types as local stack-bound variables that are destructed when they go out of scope. In Java compound types are always allocated on the heap and collected by the garbage collector (except in virtual machines that use escape analysis to convert heap allocations to stack allocations).
- C has destructors, while Java has finalizers. Both are invoked prior to an object's deallocation, but they differ significantly. A C object's destructor must be implicitly (in the case of stack-bound variables) or explicitly invoked to deallocate the object. The destructor executes synchronously at the point in the program at which the object is deallocated. Synchronous, coordinated uninitialization and deallocation in C thus satisfy the RAII idiom. In Java, object deallocation is implicitly handled by the garbage collector. A Java object's finalizer is invoked asynchronously some time after it has been accessed for the last time and before it is actually deallocated, which may never happen. Very few objects require finalizers; a finalizer is only required by objects that must guarantee some clean up of the object state prior to deallocation—typically releasing resources external to the JVM. In Java safe synchronous deallocation of resources is performed using the try/finally construct.
- In C it is possible to have a dangling pointer – a reference to an object that has been destructed; attempting to use a dangling pointer typically results in program failure. In Java, the garbage collector won't destruct a referenced object.
- In C it is possible to have an object that is allocated, but unreachable. An unreachable object is one that has no reachable references to it. An unreachable object cannot be destructed (deallocated), and results in a memory leak. By contrast, in Java an object will not be deallocated by the garbage collector until it becomes unreachable (by the user program). (Note: weak references are supported, that work with the Java garbage collector to allow for different strengths of reachability.) Garbage collection in Java prevents many memory leaks, but leaks are still possible under some circumstances.
Libraries
[edit | edit source]- C standard library provides a limited set of basic and relatively general purpose components. Java has a considerably larger standard library. This additional functionality is available for C by (often free) third party libraries, but third party libraries do not provide the same ubiquitous cross-platform functionality as standard libraries.
- C is mostly backward compatible with C, and C libraries (such as the APIs of most operating systems) are directly accessible from C . In Java, the richer functionality of its standard library provides cross-platform access to many features typically only available in platform-specific libraries. Direct access from Java to native operating system and hardware functions requires the use of the Java Native Interface.
Runtime
[edit | edit source]- C is normally compiled directly to machine code that is then executed directly by the operating system. Java is normally compiled to byte-code that the Java virtual machine (JVM) then either interprets or JIT compiles to machine code and then executes.
- Due to the lack of constraints in the use of some C language features (e.g. unchecked array access, raw pointers), programming errors can lead to low-level buffer overflows, page faults, and segmentation faults. The Standard Template Library, however, provides higher-level abstractions (like vector, list and map) to help avoid such errors. In Java, such errors either simply cannot occur or are detected by the JVM and reported to the application in the form of an exception.
- In Java, bounds checking is implicitly performed for all array access operations. In C , array access operations on native arrays are not bounds-checked, and bounds checking for random-access element access on standard library collections like std::vector and std::deque is optional.
Miscellaneous
[edit | edit source]- Java and C use different techniques for splitting up code in multiple source files. Java uses a package system that dictates the file name and path for all program definitions. In Java, the compiler imports the executable class files. C uses a header file source code inclusion system for sharing declarations between source files.
- Templates and macros in C , including those in the standard library, can result in duplication of similar code after compilation. Second, dynamic linking with standard libraries eliminates binding the libraries at compile time.
- C compilation features a textual preprocessing phase, while Java does not. Java supports many optimizations that mitigate the need for a preprocessor, but some users add a preprocessing phase to their build process for better support of conditional compilation.
- In Java, arrays are container objects that you can inspect the length of at any time. In both languages, arrays have a fixed size. Further, C programmers often refer to an array only by a pointer to its first element, from which they cannot retrieve the array size. However, C and Java both provide container classes (std::vector and java.util.ArrayList respectively) that are re-sizable and store their size. C 11's std::array provides fixed-size arrays with a similar efficiency to classic arrays, functions to return the size, and optional bounds-checking.
- Java's division and modulus operators are well defined to truncate to zero. C does not specify whether or not these operators truncate to zero or "truncate to -infinity". -3/2 will always be -1 in Java, but a C compiler may
return
either -1 or -2, depending on the platform. C99 defines division in the same fashion as Java. Both languages guarantee that(a/b)*b (a%b) == a
for all a and b (b != 0). The C version will sometimes be faster, as it is allowed to pick whichever truncation mode is native to the processor. - The sizes of integer types is defined in Java (int is 32-bit, long is 64-bit), while in C the size of integers and pointers is compiler-dependent. Thus, carefully-written C code can take advantage of the 64-bit processor's capabilities while still functioning properly on 32-bit processors. However, C programs written without concern for a processor's word size may fail to function properly with some compilers. In contrast, Java's fixed integer sizes mean that programmers need not concern themselves with varying integer sizes, and programs will run exactly the same. This may incur a performance penalty since Java code cannot run using an arbitrary processor's word size. C 11 offers types such as uint32_t with guaranteed sizes, but compilers are not forced to provide them on hardware which has no native support for the size.
Performance
[edit | edit source]Computing performance is a measure of resource consumption when a system of hardware and software performs a piece of computing work such as an algorithm or a transaction. Higher performance is defined to be 'using fewer resources'. Resources of interest include memory, bandwidth, persistent storage and CPU cycles. Because of the high availability of all but the latter on modern desktop and server systems, performance is colloquially taken to mean the least CPU cycles; which often converts directly into the least wall clock time. Comparing the performance of two software languages requires a fixed hardware platform and (often relative) measurements of two or more software subsystems. This section compares the relative computing performance of C and Java on common operating systems such as Windows and Linux.
Early versions of Java were significantly outperformed by statically compiled languages such as C . This is because the program statements of these two closely related languages may compile to a small number of machine instructions with C , while compiling into a larger number of byte codes involving several machine instructions each when interpreted by a Java JVM. For example:
Java/C statement | C generated code | Java generated byte code |
---|---|---|
vector[i] ; | mov edx,[ebp 4h] mov eax,[ebp 1Ch] |
aload_1 iload_2 |
While this may still be the case for embedded systems because of the requirement for a small footprint, advances in just in time (JIT) compiler technology for long-running server and desktop Java processes has closed the performance gap and in some cases given the performance advantage to Java. In effect, Java byte code is compiled into machine instructions at run time, in a similar manner to C static compilation, resulting in similar instruction sequences.
C is still faster in most operations than Java at the moment, even at low-level and numeric computation. For in-depth information you could check Performance of Java versus C . It's a bit pro-Java but very detailed.
Comparing Imports vs Includes
[edit | edit source]There can be some confusion among C and C programmers about how imports work, and conversely among Java programmers, for example, about the proper use of include files. In a comparison between Symbol-table imports in modern Programming languages with the use of #includes, like in C and C . Although, both of these techniques are solutions to the same problem, namely compiling across multiple source files, they are vastly different techniques. Since nearly all modern Compilers consist of essentially the same stages of compilation, the biggest difference can be explained by the fact that includes occur in the Lexical-analysis stage of compilation, whereas imports are not done until the semantic analysis stage.
Advantages of imports
- Imports do not duplicate any lexical analysis effort, which generally results in faster compilation for larger projects.
- Imports do not require splitting code into separate files for declaration/implementation.
- Imports better facilitate distribution of Object code, rather than Source code.
- Imports can allow circular dependencies between source files.
- Imports implicitly carry a mechanism for resolving symbol collisions when more than one symbol table defines the same symbol.
Disadvantages of imports
- When an importable module is altered, since there is no separation of definition and implementation, all dependent modules must be recompiled, which can entail significant compilation times in large projects.
- Imports require a standard mechanism for defining a symbol table in object code. Whether this limitation is truly a weakness is debatable, as a standard symbol table is useful for a number of other reasons.
- Imports require a method for discovering symbol tables at compile time (such as the classpath in Java). When, however, there exists a standard method for doing this, this is not necessarily any more complicated than specifying the locations of include files.
- When circular dependencies are allowed, semantic analysis of several interdependent source files may need to be interleaved.
- Unless the language includes support for Partial types, languages with imports instead of includes require all source code for a class to be in a single source file.
Advantages of includes
- With includes, there is no interdependence between source files at the semantic analysis stage. This means that at this stage, each source file can be compiled as an independent unit.
- Separating definition and implementation into header and source files reduce dependencies and allows recompilation of only the affected source file, and no other files, when implementation details are altered.
- Include files, used in combination with other Preprocessor features, allow for nearly arbitrary lexical processing.
- Although the practice is not widespread, includes can provide rudimentary support for several modern language features (such as Mixins and aspects) if the language itself does not support them.
- Includes are not part of the syntax of the underlying language, but rather part of a preprocessor syntax. There are disadvantages to this (another language to learn), but there are also advantages. The preprocessor syntax, and in some cases include files themselves, may be shared among several different languages.
Disadvantages of includes
- Includes and the requisite preprocessor can require more passes in the lexical-analysis stage of compilation.
- Repeated compilation of header files included multiple times in a large project can be notoriously slow. This can be mitigated, however, through the use of Pre-compiled headers.
- Proper use of header files, particularly declarations of global variables, can be tricky for beginners.
- Because includes generally require the location of the included file to be specified in the source code, environment variables are often needed to provide part of the include file path. Even worse, this functionality is not supported in a standard way across all compilers.