Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
88 views

Java For C# Programmers

This page contains an overview of the differences between the Java and C# programming languages, from the perspective of a C# programmer who is new to Java. The comparison is not encyclopedic but rather highlights some fundamental points that are potentially troublesome or otherwise remarkable. New features introduced in the major revisions Java SE 5–11 are noted where appropriate.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
88 views

Java For C# Programmers

This page contains an overview of the differences between the Java and C# programming languages, from the perspective of a C# programmer who is new to Java. The comparison is not encyclopedic but rather highlights some fundamental points that are potentially troublesome or otherwise remarkable. New features introduced in the major revisions Java SE 5–11 are noted where appropriate.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 25

Kynosarges

Coding Gymnasion since 1999

Java for C# Programmers

This page contains an overview of the differences between the Java and C# pro-
gramming languages, from the perspective of a C# programmer who is new to Java.
The comparison is not encyclopedic but rather highlights some fundamental points
that are potentially troublesome or otherwise remarkable. New features introduced
in the major revisions Java SE 5–11 are noted where appropriate.
Since both languages are closely tied to their respective runtimes, I’ll also
cover any relevant differences between the Java Virtual Machine (JVM) and the .NET
Framework, as well as basic library classes. I don’t cover the library frameworks for
networking, serialization, GUI, XML, and so on. The capabilities of both platforms
are broadly equivalent in these respects, but the implementations are quite
different.

C# 7 and later — After the release of C# 6, Microsoft moved the rapidly evolving
language to Github and apparently no longer bothers to write proper specifications.
Some of the new features are directly copied from Java (e.g. digit separators) but it’s
generally safe to assume that C# 7+ features won’t be available in Java. The rest of
this page only covers C# up to version 6.

Further Reading

See Compiling Java Code for a quick primer on how to compile and run Java
programs.
Oracle’s JDK 11 Documentation comprises all reference material, including
the Java Language and VM Specifications, the Java Platform SE 11 API Specification,
and the Java Tutorials. For a thorough performance analysis of Java language and li-
brary elements, see Mikhail Vorontsov’s Java Performance Tuning Guide.
The best printed introductions are Horstmann’s Core Java and Bloch’s
Effective Java, as well as Horstmann’s Core Java for the Impatient as a quick overview.
Please see Java Books for further recommendations. Andrei Rinea’s tutorial series,
Beginning Java for .NET Developers, covers selected topics in more detail.

Article Contents

1. Keywords
2. Primitives
3. Arithmetic
4. Control Flow
5. Arrays
6. Strings
7. Classes
8. Inheritance
9. Interfaces
10. Nested Classes
11. Lambda Expressions
12. Enumerations
13. Value Types
14. Packages & Modules
15. Exceptions
16. Generics
17. Collections
18. Annotations
19. Comments

1. Keywords

Java and C# use a very similar syntax and provide similar sets of keywords, both de-
rived from C/C++. Here I’ll briefly list some noteworthy differences that aren’t
mentioned in the following sections.

assert is equivalent to C# Debug.Assert calls. The assertion facility required a new


language keyword because Java offers no other way to conditionally elide method
calls. See Exceptions for details on catching assertion failures. (Java SE 1.4)
class is equivalent to C# typeof when used as the “class literal” after a type name
(example).
final is equivalent to C# const on primitive or string variables holding compile-
time constants; C# readonly on other fields; and C# sealed on classes and meth-
ods. Java reserves const but does not use it.
instanceof is equivalent to C# is for checking an object’s runtime type. There is
no equivalent to C# as , so you must always use an explicit cast after a successful
type check.
native is equivalent to C# extern for declaring external C functions.
C# string is missing. You must always use the capitalized library type, String .
synchronized is equivalent to C# MethodImplOptions.Synchronized as a method attri-
bute, and to C# lock around a code block within a method.
transient is equivalent to C# NonSerializableAttribute for exempting fields from
serialization.
var declares implicitly typed local variables as in C#. (Java SE 10, but see below)
Object... args (three periods) is equivalent to C# params , and also implicitly cre-
ates an array from all listed arguments. (Java SE 5)

Java versions prior to SE 10 lacked var but offered type inference for lambda expres-
sions (Java SE 8) and for generic type arguments, including diamond notation for
generic constructors. Do not combine var with generic type inference, or the omit-
ted type variables may be inferred as Object ! See Stuart W. Marks’ style guidelines
for more information.

Missing Keywords

Java entirely lacks the following C# keywords and functionality:


#if/#define/#pragma and conditional compilation. Workarounds include dynamic
class loading and external preprocessors or other build tools.
#region blocks have no equivalent, but Java IDEs of course allow syntactic code
folding.
async/await for asynchronous callbacks and yield break/return for enumerators.
You must write the required state machines by hand.
dynamic for dynamic typing at runtime. Java SE 7 provides the invokedynamic JVM
instruction but does not expose it in Java.
event and implicit code generation for events. You must write the entire event in-
frastructure by hand, as demonstrated in From C# To Java: Events (but see
below).
fixed/sizeof/stackalloc/unsafe and pointer operations. Workarounds include na-
tive methods and platform-specific libraries.
get/set for field-like properties. Use traditional method syntax with correspond-
ing prefixes, as standardized by the JavaBeans pattern.
operator/explicit/implicit and operator overloading, including custom indexers
and conversion operators. See special notes on string operators.
partial classes and methods. Each class and non-abstract method is fully defined
in one file.
ref/out and call-by-reference. The only way to pass method parameters by refer-
ence is to wrap them in another object, for example a one-element array.
Named and optional method parameters. Again, you must use object wrappers to
set parameters by name or to provide default parameter values.
Object initializers that set members by name, and index initializers that set ele-
ments by index. You can use the rather ugly double brace initialization idiom to
simulate them.
Any of these new features in C# 6: null-conditional ( ?. ) and nameof operator, ex-
pression-bodied functions, exception filters ( catch…when ), and string interpola-
tion ( $ ).

As an alternative to writing your own event infrastructure, consider using the “ob-
servable” objects defined in the JavaFX package javafx.beans and its sub-packages.
Such objects let you attach listeners that are notified on value changes – often the
intended use of events. (Note: JavaFX has become a separate download as of Java SE
11 and is now available here.)
Java has no equivalent to LINQ keywords. As of Java SE 8, lambda expres-
sions and stream libraries replicate the method-based incarnation of LINQ to
Objects, complete with lazy and/or parallel evaluation. Third-party libraries for
LINQ-like queries in Java include iciql, Jinq, jOOQ, and QueryDSL.

2. Primitives

Java and C# have equivalent sets of primitive types ( int etc.), with the following
exceptions:

Java only has signed numeric types, including byte . All unsigned variants are
missing. Java SE 8 added unsigned operations as library methods.
C# decimal is similar to the Java class BigDecimal which has no corresponding
primitive.
All Java primitives have equivalent library classes. However, those are not syn-
onyms as in C# but rather boxed versions. This is because Java does not support
value types in its class hierarchy.
For the same reason, Java has no nullable variants of primitives. Simply use the
equivalent library classes – they are all reference types and therefore nullable.
In addition to decimal, hexadecimal, and octal literals for numeric values, Java SE
7 added binary literals and underscore literals.

Note the distinction between primitive-equivalent classes in C# and Java. In C#, int
and System.Integer are synonyms: both represent an unboxed primitive value type.
You need an (Object) cast to explicitly wrap a reference around such values.
But in Java, only int is an unboxed primitive value whereas
java.lang.Integer represents the strongly-typed boxed version! C# programmers
must take care not to use Java primitives and the corresponding class names inter-
changeably. See autoboxing for more details. (Java SE 5)
Java automatically unboxes primitive values from their strongly-typed
wrapper objects as needed within the context of assignments, mathematical opera-
tions, or method invocations. Explicit casts to primitive types are only required
when unboxing from a more general class ( Number , Object ).

3. Arithmetic

Java lacks C# checked/unchecked to toggle overflow checking for arithmetic expres-


sions. Instead, integral overflow silently truncates bits as in C/C++, and floating-
point overflow produces negative or positive infinity. Floating-point 0/0 produces
NaN (not a number). Only integral division by zero throws an ArithmeticException .
Java SE 8 added various …Exact methods for arithmetic operations and type conver-
sions to java.lang.Math that always throw an exception on overflow.
Java provides the modifier strictfp that can be applied to classes, inter-
faces, and methods. It forces all intermediate results of floating-point arithmetic to
be truncated to IEEE 754 sizes, for reproducible results across platforms. The library
class java.lang.StrictMath also defines a set of standard functions with portable be-
havior, based on fdlibm.

4. Control Flow

Java reserves goto but does not define it. However, statement labels do exist, and in
a strange twist break and continue were enhanced to accept them. You can only jump
out of local blocks, but break operates on any block – not just loops. This makes Java
break almost the full equivalent of C# goto .
Java switch operates on (boxed or unboxed) primitives and enums, and
since Java SE 7 also on strings. You don’t need to qualify enum case values with the
enum type as in C#. Java allows fall-through from one case to the next, just like
C/C++. Use the compiler option -Xlint:fallthrough to warn against missing break
statements. There is no equivalent to C# goto targeting a case label.

5. Arrays
As in .NET, Java arrays are specialized reference types with automatic index check-
ing. Unlike .NET, Java supports only one array dimension directly. Multi-dimen-
sional arrays are simulated by nesting one-dimensional arrays. However, when all
dimensions are specified during initialization, all required nested arrays are allo-
cated implicitly. For example, new int[3][6] allocates both the outer array and all
three nested arrays of six integers, without the need for repetitive new statements.
Any number of rightmost dimensions can be left unspecified to later manually cre-
ate a ragged array.
Much array-related functionality is provided by the helper class Arrays:
comparison, conversion, copying, filling, hash code generation, partitioning, sort-
ing & searching, and output as a human-readable string which the instance method
Array.toString notably doesn’t do. Java SE 8 added several parallel… methods that
perform multithreaded operations if possible.

6. Strings

As in C#, Java strings are immutable sequences of UTF-16 code units which each fit
into one 16-bit char primitive. For strings containing any 32-bit Unicode code
points (i.e. real-world characters) that require surrogate pairs of two UTF-16 code
units, you cannot use the ordinary char indexer methods. Instead, use various
helper methods for code point indexing which Java defines directly on the String
class.
Java SE 9 introduced a compact representation for strings containing only
ISO-8859-1 (Latin-1) characters. Such strings use 8 rather than 16 bits per charac-
ter. This is an automatic and purely internal optimization that does not affect public
APIs.

Operators — Unlike C#, Java does not special-case the == operator for strings, so
this will only test for reference equality. Use String.equals or
String.equalsIgnoreCase to test for content equality.
Java does special-case the + operator for string concatenation (JLS §15.18.1).
Annoyingly, this performs JavaScript-like automatic conversion of all types to
String . As soon as one String operand is found, any existing intermediate sum to
the left and any remaining individual operands to the right are converted to String
and concatenated as such. As in C#, piecemal string concatenation can also be inef-
ficient. Use the dedicated StringBuilder class for better performance.

7. Classes

Java lacks C# static classes. To create a class that only contains static utility meth-
ods, use the old-fashioned approach of defining a private default constructor. Java
does feature a static class modifier, but only for nested classes and with very dif-
ferent semantics.
Class Objects contains some simple but useful helper methods for objects of
any type, including hash code generation for multiple objects and various null-safe
operations.

Construction — Constructor chaining uses this(…) as in C# and super(…) for base


classes, but these calls appear as the first line in the constructor body rather than
before the opening brace.
Java lacks static constructors but offers anonymous initializer blocks in both
static and instance variants. Multiple initializer blocks are acceptable, and will be
executed in the order in which they appear before any constructor runs. Static ini-
tializer blocks are executed when the class is first loaded.

Destruction — Java supports finalizers that run before the garbage collector de-
stroys an object, but these are rather sensibly called finalize instead of C#’s mis-
leading C++ destructor syntax. Finalizer behavior is complex and problematic, so
you should generally prefer try/finally cleanup.
Java provides not only weak references like C# that may be collected at any
time, but also soft references that are only collected in response to memory
pressure.

8. Inheritance
Base classes are called superclasses and accordingly referenced with the keyword
super rather than C# base . When declaring derived classes, Java extends (for super-
classes) and implements (for interfaces) specify the same inheritance relationships
for which C# uses simple colons.
C# virtual is missing entirely because all Java methods are virtual unless
explicitly declared final . There is little performance penalty because the JVM
Hotspot optimizer, unlike the rather stupid .NET CLR optimizer, can dynamically
inline virtual methods when no override is detected at runtime.
Java SE 5 added covariant return types to support its type-erasing generics.
Covariance is achieved through compiler-generated bridge methods, so watch out
for versioning issues with subclasses.

9. Interfaces

Like C#, Java supports single class inheritance and multiple interface inheritance.
Interface names are not prefixed with I as in .NET, nor in any other way distin-
guished from class names.
Java does not support C# extension methods to externally attach imple-
mentation to an interface (or class), but it does allow implementation within an in-
terface. Java interfaces may contain constants, i.e. public static final fields. The
field values may be complex expressions which are evaluated when the interface is
first loaded.
Java SE 8 added static and default methods on interfaces, and Java SE 9 pri-
vate methods as well. Default methods are used in the absence of a normal class im-
plementation. This obviates the need for abstract default implementing classes, and
also allows extending interfaces without breaking existing clients.
Java does not support C# explicit interface implementation to hide inter-
face-mandated methods from public view. This is probably a good thing since the
access semantics for explicit interface implementations are notoriously error-prone
when multiple classes are involved.

10. Nested Classes


Use the static modifier to define nested classes that behave the same way as in C#.
Nested classes without that modifier are Java’s special inner classes. These may also
appear locally within a method, either with a dedicated class name or anonymously.

Inner Classes — Non-static nested classes are inner classes which carry an implicit
reference to the outer class instance that created them, similar to the implicit this
reference of instance methods. You can also use outerObj.new InnerClass() to asso-
ciate a specific outer class instance. The inner class can access all private fields and
methods of its outer class, optionally using the prefix OuterClass.this for disam-
biguation. The static modifier on a nested class prevents this implicit association.

Local Classes — Inner classes may appear as local classes within methods. In addi-
tion to all private members of the implicitly associated outer class instance, local
classes also have access to all local variables that are in scope within the declaring
method, so long as they are effectively final .

Anonymous Classes — Local classes may be declared as single-use instances, with


an initializer expression that specifies a superclass or interface. The compiler inter-
nally generates a class with a hidden name that extends the superclass or imple-
ments the interface. This practice is known as anonymous classes.
Until Java SE 8, anonymous classes served as Java’s equivalent to lambda
expressions, although more powerful since they may contain most ordinary class
members. Constructors are disallowed – use initializer blocks instead. (This feature
can be abused for the double brace initialization idiom.)
Java’s version of functional programming ultimately relies on anonymous
classes. Consequently, interfaces that define only a single method are called func-
tional interfaces, and anonymous classes that implement them are called function
objects.

11. Lambda Expressions

Java SE 8 added lambda expressions as an alternative to function objects, syntacti-


cally more concise and with a faster internal implementation. The syntax is identical
to C#, except with -> instead of => as the function arrow. Argument types are in-
ferred if absent.
Java predefines basic functional interfaces in java.util.function and else-
where. Unfortunately, due to Java’s type-erasing generics and its lack of value types,
the predefined types are both uglier and less comprehensive than .NET’s delegate li-
brary. Edwin Dalorzo explains the details, and also warns about possible conflicts
with checked exceptions.
Since lambda expressions are semantically equivalent to anonymous
classes, they are implicitly typed as whatever interface the caller requires, e.g.
Comparator<T>. You can use functional interface types to store lambda expressions
in variables, just as with C# delegate types. Moreover, lambda expressions can ac-
cess any local variables in outer scopes that are effectively final which somewhat
surprisingly includes for-each loop variables.

Method References — Instead of defining a lambda expression where a function


object is expected, you can also supply a method reference to any existing static or
instance method. Use a double colon ( :: ) to separate class or instance name from
method name. This syntax can also reference superclass methods as super::method
and constructors as ClassName::new . Passing typed array constructors such as
int[]::new to generic methods allows creating arrays of any desired type.
While very convenient, method references to instance methods are evalu-
ated somewhat differently from equivalent lambda expressions which can lead to
surprising behavior. See Java Method Reference Evaluation for examples.

12. Enumerations

Java SE 5 introduced type-safe enums as an alternative to loose integer constants.


Unlike C# enum types, Java enums are full-fledged reference types. Each enumerated
constant represents one named instance of the type. Users cannot create any new
instances aside from the enumerated ones, ensuring that the stated list of constants
is final. This unique implementation has two important consequences:
1. Java enum variables can be null and default to null. This means you don’t have to
define a separate “no value” constant, but you must perform null checking if you
do require a valid enum value.

2. Java enum types support arbitrary fields, methods, and constructors. This lets
you associate arbitrary data and functionality with each enum value, without re-
quiring external helper classes. (Each enum value is an anonymous subclass in-
stance in this case.)

Two specialized collections, EnumMap and EnumSet, provide high-performance


subsets of enum values with or without associated data. Use EnumSet when you would
use a C# enum with the [Flags] attribute. The internal implementation is in fact
identical, namely a bit vector.

13. Value Types

One significant defect of Java is the lack of user-defined value types. When released
in an undecided future version, Project Valhalla should offer .NET-style value types
with generics support – see the proposals State of the Values and Minimal Value
Types for more details. Right now, the only value types offered by Java are its primi-
tives which live completely outside the class hierarchy. This section briefly describes
the impact on semantics, performance, and generics.

Semantics — Value types have two important semantic properties: they cannot be
null (i.e. have “no value”), and their entire contents are copied on each assignment,
making all copies independent of each other in terms of future mutations. The first
property is currently impossible to achieve for user-defined types in Java, and can
only be approximated by frequent null checking.
Surprisingly, the second property doesn’t matter because value types should
be immutable anyway, as Microsoft discovered the hard way. Value types in .NET are
mutable by default, and that caused no end of obscure bugs due to implicit copying
operations. Now the standard recommendation is to make all value types im-
mutable, and that’s also true for value-like Java classes such as BigDecimal . But once
an object is immutable the theoretical effects of mutation are irrelevant.
Performance — Value types store their contents directly on the stack or embedded
within other objects, without a reference or other metadata. This means they require
far less memory, assuming the contents aren’t much bigger than the metadata.
Moreover, the garbage collector’s workload is reduced, and no dereferencing step is
needed to access the contents.
Oracle’s Server VM is quite adept at optimizing small objects that C# would
implement as value types, so there’s no big difference in computational perfor-
mance. However, the extra metadata inevitably bloats large collections of small ob-
jects. You need complex wrapper classes to work around this problem, see e.g.
Compact Off-Heap Structures/Tuples In Java.

Generics — As outlined in Project Valhalla: Goals, the fact that primitives are not
classes means they cannot appear as generic type arguments. You must use equiva-
lent class wrappers instead (e.g. Integer for int ), resulting in expensive boxing op-
erations. The only way to avoid this is hard-coding variants of generic classes that
are specialized for primitive type arguments. The Java library is littered with such
specializations. There is currently no better solution for this, until Project Valhalla
delivers value types that integrate primitives into the class hierarchy.

14. Packages & Modules

Java packages are largely equivalent to C# namespaces, with some important differ-
ences. As of Java SE 9, modules provide additional features for dependency checking
and access control.

Storage Format

The Java class loader expects a directory structure that replicates the declared pack-
age structure. Fortunately, the Java compiler can automatically create that structure
( -d . ). Moreover, each source file can only contain one public class and must have
the name of that class, including exact capitalization.
These restrictions have an unexpected benefit: the Java compiler has an in-
tegrated “mini-make” facility. Since the locations and names of all object files are
exactly prescribed, the compiler can check automatically which files need updating
and recompiles only those.
For distribution, the entire directory structure of compiled Java class files
along with metadata and any required resources is usually placed in a Java archive
(JAR). Technically, this is simply an ordinary ZIP file. The extension .jar is the same
for both executables and libraries; the former are marked internally as having a
main class.
Unlike .NET assemblies, JAR files are completely optional and have no se-
mantic significance. All access control is achieved through package and (to a greater
degree) module declarations. In this regard, Java packages and modules combine the
functionality of .NET namespaces and assemblies.

Packages

Java packages are the basic way to organize classes. They are not quite expressive
enough for large projects, such as the JDK itself, which led to the development of a
new module system for Java SE 9. Non-modular packages continue to be supported,
however, and should be sufficient for smaller applications.

Declaration — A Java package statement is equivalent to a C# namespace block, but


implicitly applies to the entire source file. This means you cannot mix packages in a
single source file, but it does remove one pointless level of indentation compared to
the C# format.
Java import is equivalent to C# using for namespace imports, but always
references individual classes. Use .* to import all classes in a package. The form im-
port static is equivalent to using static (C# 6) and allows using static class mem-
bers without qualification (Java SE 5). There is no class name aliasing, however.

Storage — The directory holding a package’s source code may contain an optional
file package-info.java that is only used for documentation. In a non-modular appli-
cation, directory trees for the same package can occur multiple times in different
sub-projects. The contents of all visible occurrences are merged.
Visibility — The default visibility for classes, methods, and fields is package-inter-
nal. This is roughly equivalent to C# internal but refers to declared packages (C#
namespaces), not to physical deployment units (JAR files or .NET assemblies).
Outside code can therefore gain access to all default-visible objects in a deployment
unit, simply by declaring itself part of the same package. You must explicitly seal
your JAR files if you wish to prevent this, or else use modules (Java SE 9).
There is no dedicated keyword for the default visibility level, so it’s implied
if neither public , private , nor protected is present. C# programmers must take spe-
cial care to mark private fields as private to avoid this default! Moreover, Java pro-
tected is equivalent to C# internal protected , i.e. visible to derived classes and to all
classes in the same package. You cannot restrict visibility to subclasses only.
Finally, Java packages have no concept of “friend” access
( InternalsVisibleTo attribute) that provides elevated visibility to specific other
packages or classes. Package members that should be visible to any other package
must be public or protected .

Modules

Java SE 9 introduced modules that combine any number of packages with explicit
dependency and visibility declarations. Technically all code runs in modules now,
but for backward compatibility any non-modular code is treated like a module that
depends on all present modules and exports all its packages.
Oracle does not currently provide concise documentation for modules. You
can dig through Mark Reinhold’s link-studded announcement, consult chapter 7 of
the Java Language Specification, or buy Cay Horstmann’s Core Java 9 for the
Impatient. The following overview is non-exhaustive.

Declaration & Storage — Each module corresponds to one single directory with the
(arbitrary) module name, containing the file module-info.java and subdirectory
trees for all contained packages. The packages are declared as usual. All module dec-
larations reside in module-info.java , using special Java keywords valid only there.
Dependency — requires declares any modules required by the current module.
Optionally, transitive makes a required module an implicit requirement of anyone
using the current module. Non-required modules are unavailable to the current
module, even if they are present on the module path.

Visibility — exports declares any packages exported for use, and opens declares any
packages open to external reflection. Optionally, exports/opens can restrict visibility
to a given list of named modules. Any packages not made visible are inaccessible to
other modules. Thus, public members of non-exported packages are equivalent to
C# internal members.
While modules may have the same name as packages, all module names and
all visible package names within an application must be unique. It is therefore not
possible to augment a package declared in another module, fixing a strange loop-
hole of Java packages.

15. Exceptions

Java is somewhat infamous for its checked exceptions, i.e. exception types that must
be specified in a throws clause if a method throws but does not catch them. The value
of checked exceptions has long been debated, on the grounds of programmer psy-
chology (compiler errors are silenced by swallowing exceptions, which is worse than
not handling them) and component interaction.
For example, meaningless throws clauses may proliferate through worst-
case scenarios, or else the exceptions are handled at inappropriate locations to stop
that proliferation. Anders Hejlsberg famously rejected checked exceptions when de-
signing C#. Some programmers simply avoid them altogether by wrapping checked
exceptions in unchecked ones, although Oracle is not fond of that practice.
Conceptually, however, checked exceptions are quite simple: all exceptions
are checked unless derived from Error (serious internal errors) or RuntimeException
(typically programming errors). The usual suspects are I/O errors that are expected
during normal operation and must be handled accordingly.
Otherwise, Java exception handling is very similar to C#. Java SE 7 added
multiple exception types in one catch block, and a try version that replicates C# us-
ing . The try-with-resources statement relies on the (Auto)Closeable interface, just
like C# using relies on IDisposable . Java SE 9 allows try-with-resources to use effec-
tively final variables, too.

Assertion Errors — Java’s base class for all runtime errors is not Exception as in
.NET but rather Throwable from which both Exception and Error derive.
Unfortunately Java’s AssertionError , thrown by assert failures, is an Error rather
than an Exception . So if you wish to handle assertion errors along with exceptions,
for example on a background thread, you must catch Throwable rather than
Exception . See Catching Java Assertion Errors for details and links.

Jumps and finally — As in C#, exceptions thrown in finally clauses discard previ-
ously thrown exceptions in the associated try blocks. Unlike C#, which forbids
jumping out of finally , simply returning from a Java finally clause also discards all
previous exceptions! The reason for this surprising behavior is that all jump state-
ments ( break , continue , return ) classify as “abrupt completion” in the same sense
as throw . The finally clause’s abrupt completion discards the try block’s previous
abrupt completion.
An even weirder consequence, although less likely to occur in practice, is
that jumping out of finally overrides ordinary return statements in the associated
try block. See Jumping out of Java finally for examples and further information.
Enable the compiler option -Xlint:finally to check for this pitfall.

16. Generics

Java SE 5 introduced generics two years before Microsoft added them to .NET 2.
While both versions look similar in source code, the underlying implementations are
quite different. To ensure maximum backward compatibility, Sun opted for type era-
sure that eliminates type variables at runtime and replaces all generic types with
non-generic equivalents. This does allow seamless interoperation with legacy code,
including precompiled byte code, but at a huge cost to new development.
C# generics are simple, efficient, and nearly foolproof. Java generics resem-
ble C++ templates in their tendency to generate incomprehensible compiler errors,
yet don’t even support unboxed primitives as type arguments! If you want an effi-
cient resizable integer collection in Java, you cannot use any implementation of
List<T> etc. because that would force wasteful boxing on all elements.
Instead, you must define your own non-generic collection that hard-codes
int as the element type, just as in the bad old days of plain C or .NET 1. (Of course,
you could also use one of several third-party libraries.) Primitives in generics are
planned as part of Project Valhalla – see Value Types above and Ivan St. Ivanov’s ar-
ticle series Primitives in Generics (part 2, part 3).
Rather than attempting to explain the complex differences between Java
and C# generics, I point you to the sources cited above, and to Angelika Langer’s ex-
tremely comprehensive Java Generics FAQ. In the rest of this section I’ll cover just a
few noteworthy points.

Construction — Java lacks the C# new constraint, but nonetheless allows instantia-
tion of generic type arguments with the class literal trick. Supply the class literal
Class<T> c for the desired type argument T to a method, then within the method use
c.newInstance() to create a new instance of type T .
As of Java 8, you can also use method references to constructors which were
introduced along with lambda expressions and are described in that section.

Static Fields — Static fields are shared among all type instantiations of a generic
class. This is a consequence of type erasure which collapses all different instantia-
tions into a single runtime class. C# does the opposite and allocates new storage for
all static fields of each generic type instantiation.
Java disallows static fields and methods from using any generic type vari-
ables. Type erasure would produce a single field or method using Object (or some
more specific non-generic type) on the shared runtime class. Due to type erasure,
only instance fields and methods can be type-safe for distinct type arguments from
different type instantiations.

Type Bounds — Optional bounds for generic type variables (JLS §4.4) are similar to
C# but with a different syntax. Bounds consist of one principal type (class or inter-
face) and zero ore more interface bounds, appended with & . For example, <T extends
C & I> is equivalent to C# <T> where T: C, I . This ensures that the actual type of T is
some subtype of C that also implements interface I which C itself does not imple-
ment. Interestingly, Java also allows interface bounds in cast expressions (JLS
§15.16).

Void — Just as you cannot specify primitives as generic type arguments, you also
cannot specify the keyword void . Use the Void class for any type parameter of a
generic interface that your implementing class does not use.

Wildcards — Any generic type parameter that is never referred to may be simply
specified as ? , a so-called wildcard. Wildcards can also be bounded with extends or
super . This allows co- and contravariance like C# in/out but is not limited to inter-
faces. To reference a wildcarded type argument, capture it with a separate method
declaring a named type parameter.
There’s one neat trick related to wildcards. If a container holds some generic
element type with a wildcard, e.g. the collection returned by
TableView<S>.getColumns, then you can put instances with different concrete types
for the wildcard in the same container. This is not possible in C# where different
concrete type arguments produce incompatible classes.

17. Collections

The Java Collections Framework (tutorial) is much better designed than its .NET
equivalent. Collection variables are usually typed from a rich hierarchy of interfaces.
These are nearly as powerful as their implementing classes, so the latter are only
used privately for instantiation. Hence, most collection algorithms work on any
framework-conforming collection with appropriate semantics. This includes a vari-
ety of composable wrapper methods, such as dynamic subranges and read-only
views.
Some combinations of interface methods and concrete implementations
may perform poorly, e.g. indexing a linked list. Java prefers exposing a possibly slow
operation to not exposing the operation at all, which is generally the case with
.NET’s more restrictive collection interfaces.
Iterators — Java allows mutating a collection while iterating over its elements, but
only through the current iterator. Java also features a specialized ListIterator that
can return its element index. .NET allows neither mutation nor index retrieval when
using a collection iterator.
Java SE 5 added a for-each loop equivalent to the C# foreach statement, but
without a dedicated keyword. This loop does not expose the mutation and index re-
trieval facilities of Java collection iterators. As in C#, for-each loops over arrays are
special-cased to avoid creating iterator objects.

Streams — Java SE 8 added streams & pipelines that chain method calls for cumula-
tive operations, like the method-based version of C# LINQ. Streams can be created
from regular collections, but also from generator functions or external files.
Pipelines fetch new elements only as needed, and can process them either sequen-
tially or in parallel. Lambda expressions are used to customize pipeline operations.
Finally, a terminal operation converts the result into another regular Java object or
collection.

18. Annotations

Java SE 5 introduced annotations which are roughly equivalent to .NET attributes.


Annotations tag program elements with arbitrary metadata, for later extraction by
library methods or programming tools. Aside from syntactic differences, here are
some noteworthy points for C# developers:

Annotations cannot change the semantics of the annotated program element. In


particular, they cannot completely suppress method calls, like
[Conditional("DEBUG")] on .NET assertions.
@FunctionalInterface verifies that an interface contains only a single method,
and so can be implemented by lambda expressions or method references.
@Override replaces C# override which is inexplicably missing from the Java
language.
@SuppressWarnings and the specific form @SafeVarargs are equivalent to C#
#pragma warning . They are often needed in conjunction with Java’s type-erasing
generics.

Java SE 8 allows annotating type usage in addition to type declarations. You need
external tools to benefit from such annotations, though.

19. Comments

Like C#, Java defines a standard format for code comments on classes and methods
that can be extracted as formatted HTML pages. Unlike C#, the Javadoc processor
that ships with the JDK directly performs output formatting, so you don’t need an
external formatter such as NDoc or Sandcastle.
The capabilities are similar, although Javadoc lacks a compiler-checked way
to reference parameters within comment text. The syntax is quite different and
much more concise, as Javadoc mostly relies on implicit formatting and compact @
tags. HTML tags are used only where no appropriate @ tag exists.
If you need to convert large amounts of C# XML comments to Javadoc for-
mat you should check out my Comment Converter that does most of the mechanical
translation for you.

Summaries — By default, the first sentence of a Javadoc comment is automatically


treated as its summary. Java SE 10 introduced the tag {@summary … } to explicitly de-
fine the summary, equivalent to the <summary> element of C# XML comments.

Christoph Nahr / Development / 2013-08-21 / Updated 2018-11-02 / Latest weblog entry


19 Comments 
1
Login

G Join the discussion…

LOG IN WITH OR SIGN UP WITH DISQUS ?

Name

5 Share Best Newest Oldest

Nico Jansen − ⚑
⏲ 9 years ago
Thanks for this awesome article and references to further readings. It is helping me a lot
with learning Java as a C# developer. In general thought, Java feels like a step back if you
know C#, but a minor one at that (nothing compared to PHP).
5 0 • Reply • Share ›

Christoph Nahr Mod > Nico Jansen − ⚑


⏲ 9 years ago
Glad you found it useful! Java is indeed somewhat backward but it's slowly
getting better. Soon we'll have JDK 8 with lambdas and LINQ-style stream
filtering. Value types seem to be firmly planned for a future release as well,
though I don't know when.
0 0 • Reply • Share ›

Nico Jansen > Christoph Nahr − ⚑


⏲ 9 years ago
Yes, i've seen some presentations about the stream filters and
lambda's yesterday. Seems very interesting. I'm not so sure about
'default' methods as opposed to extensions methods... I guess we'll
find out.. I'm not sure how much I'm going to miss my valuetypes. I
think the performance drawback is not that great in the systems I'm
working on.
0 0 • Reply • Share ›

Etienne Giust > Nico Jansen − ⚑


EG ⏲ 9 years ago
More info on the upcoming lambdas here :
http://docs.oracle.com/java...
0 0 • Reply • Share ›
Etienne Giust − ⚑
EG ⏲ 9 years ago
This is indeed the most compelling read I found for an experienced C# programmer
switching to Java. Congrats!
3 0 • Reply • Share ›

Kenneth Fiduk − ⚑
KF ⏲ 9 years ago
Great article, thanks!
2 0 • Reply • Share ›

xpaulbettsx
⏲ 9 years ago
− ⚑

Thanks for the comprehensive write-up, super useful


3 0 • Reply • Share ›

Imad Marmoud − ⚑
⏲ 8 years ago
Good article, thank you !
1 0 • Reply • Share ›

anon9435939 − ⚑
A ⏲ 8 years ago
Thanks for the helpful concise information. I once studied the basics of Java about 10
years ago and hated it, and then I moved to C#.
If it were not for Android, I would not have tried to learn Java again. I just wish Google
had used its own new language instead of Java saving itself from all the lawsuits and
security problems caused by Java.
4 0 • Reply • Share ›

docesam > anon9435939 − ⚑


⏲ 7 years ago
same here
0 0 • Reply • Share ›

David Holt − ⚑
⏲ 8 years ago
Thanks for a great article. I found it very helpful as I begin my journey into Java from C#.
3 0 • Reply • Share ›

steben ⚑
steben − ⚑
S ⏲ 5 years ago
Great read. Good to see an unbiased comparison between both languages.
2 0 • Reply • Share ›

Christoph Nahr Mod > steben − ⚑


⏲ 5 years ago
Thanks, glad you enjoyed it!
0 0 • Reply • Share ›

Daniel − ⚑
D ⏲ 5 years ago
Great article, it really helped me in the transition of learning java after a long time i've
been programming in c#
1 0 • Reply • Share ›

Daniel
⏲ 5 years ago
− ⚑

Great article; thanks so much for sharing your deep knowledge.


0 0 • Reply • Share ›

Shimmy − ⚑
⏲ 4 years ago
C# is today a far better language than Java, especially now that it's open source and
compiles on any platform, including web. Looking at this comparison I could clearly see
how far Java is from C# in almost any given aspect.
Thanks for sharing!
2 0 • Reply • Share ›

Neither DoI
⏲ 4 years ago
− ⚑

Thanks a lot! Superuseful!

P.s.
I think that there should be a place for IDEs comparison and an explanation of Maven.
1 0 • Reply • Share ›

muhdAlavu − ⚑
⏲ 3 years ago
most usable article for someone from c# background.
0 0 • Reply • Share ›

D thFl ⚑
DarthFloopy − ⚑
D ⏲ 3 years ago
As in C#, piecemal string concatenation can also be inefficient. Use the dedicated
StringBuilder class for better performance

You might also like