Disclaimer: long post!
Updated on 2018-11-09
While some of my friends and colleagues seem to think that I don’t like Java and that I am some sort of Microsoft/.NET zealot, that is actually not true! I do like Java, and I worked with it for several years.
There are several articles out there on the differences and similarities between C# and Java, but none actually satisfied me. In this new series of posts, I will try to explain these similarities and differences, as extensively and accurately as I can. I don’t want to start a religious war, but I won’t refrain from emitting judgments on what I think is best in each. I will try to cover as much as I can, but I won’t drill into APIs, instead will focus on each language’s intrinsic characteristics.
This first post will focus on high-level constructs, what I call structure: namespaces and types. I will be covering Java 11 and C# 7.3, the latest (as of November 2018) versions of these languages.
Update: See the second post here.
Both languages (Java and C#) are case-sensitive, strictly object oriented, offering classes, enumerations and interfaces, single inheritance model and all of the types live in namespaces/packages. Also, all support attributes/annotations, methods and fields, including static ones. The base class for all types, in both cases, is called Object. Both have the same basic operators, comparable base types and exception handling mechanisms. Both start executing by a static method called main/Main.
A Java class compiles to an identically-named class file; these files exist on their own, but are usually stored together inside a jar, war or ear, which are basically ZIP files with a manifest file attached to, for better manageability. These files, of course, can contain other resources, such as images or text files.
C# classes always exist in binary assemblies, which can have two basic forms (extensions):
Assemblies can also include metadata and embedded resources, of any type. C#/.NET actually defines another compilation unit, the module, but typically a module matches an assembly, although it is possible to have it other way, for more advanced scenarios.
Both in Java and in C# we have the notion of namespaces or packages, which can be nested. In C#, the namespace declaration must wrap all of its types, and therefore we can have several namespace declarations in a source file, even nested:
In Java, the package declaration goes on the top of a source file, meaning, it can only contain a single package:
There is one important difference between Java and C#: in Java, the namespace must be identical to the physical folder structure, that is, a class belonging to the a.b package must be physically located in an a\b folder; Java won’t compile it otherwise. The generated .class file must be located in the same folder, such as a\b\MyClass.class.
Java and C# can simplify accessing classes in other namespaces/packages by importing these, like we see here for Java, where we can import all types (*), or a type at a time:
Java "imports" (knows about) automatically the types in the java.lang package, C# does not automatically import any namespace (except all namespaces above the current one), and it doesn’t allow importing a single type:
But it also lets us define type aliases per source file using the same syntax:
In C# you can use the global:: keyword to lookup a type from the root, that is, not from on the current namespace:
Java and C# offer a very close syntax, in some cases, if we discount the different language conventions, it’s really hard to tell one from the other, but there are some important differences.
Java offers the following top level elements, besides packages:
And C# the same plus some more:
The basic types in the two languages (C#/Java) are:
As you can see, C# offers unsigned and signed versions of all integer types and also a high-precision Decimal type. It also as a dynamic type, used for late-binding (runtime) operations without strong compile-time checks.
C# offers three kinds of arrays:
Java also has single dimension and jagged arrays, but no multi dimension ones.
C# and Java lets us use the var keyword for declaring a variable and automatically initializing it. This is a shorthand for the initialization’s type:
In C# as in Java, we can specify suffixes for clarifying the desired type of a literal:
Both lowercase or uppercase letters are allowed as the suffix.
C# also allows us to use _ as a digit separator, to make the number easier to read:
Classes in Java and C# are allocated in the heap. A class can inherit from a single base class, if not specified, it will inherit from Object. It can implement any number of interfaces.
C# has a unified type system, meaning, primitive types (integers, floating points, booleans, etc) coexist in the same type hierarchy as composite classes. This is different in Java, where, for example, the int and Integer types are not related, even if it is possible to convert between the two. All primitive types in C# are structures, not classes, which means they are allocated in the stack instead of the heap. In Java this also occurs for primitive types, but there is no notion of explicit structures, and we can’t build our own types to be stored in the stack. A structure in C# cannot inherit from any class explicitly, but it can implement any number of interfaces, and also cannot declare a destructor/finalizer:
Structures and enumerations in C# are called value types and classes and interfaces are called reference types. Because of C#’s unified type system, a structure always implicitly inherits from System.ValueType.
In C#, an interface can only have:
It can be generic or non-generic. Both classes and structures can implement interfaces. An interface can always be assigned null, it is a reference type. Also, a generic interface in C# can be made covariant or contravariant.
In Java, things are a bit different, since they can have also have statics and (the horror!), method implementations:
They can also be generic or otherwise, and can be implemented by enumerations. In Java, an interface's members can also have visibility levels defined, that is, they are not always public.
If a Java interface only has one method, or, at least, one non-default method (more on this later), it can be marked as a functional interface, in which case, it can be used in lambda functions, the method is implicitly called (see an example below in the Delegates section).
Generics are quite different, internally, in Java and C#. Both languages support generic classes and interfaces, but in C# they are a first-class construct, with reflection support, but in Java they cease to exist once a generic class is compiled. That is, in Java, a List<String>, at runtime, becomes just List, the generic parameter String is erased, this is in order to ensure backward compatibility with prior Java versions that didn’t have generics. This doesn’t happen in C#, and we can at runtime reflect on a generic class and its parameters.
Both languages support any number of generic parameters and constraints on them. In C# these constraints are:
Java accepts the following constraints:
In Java, we can specify a generic of an unknown type:
Java also has some terrible limitations:
Because C# supports any kinds of generic parameters, if we want to initialize explicitly some variable of a generic parameter type, we need to use the default keyword:
Finally, the base class of some class inheriting from a generic type is not that generic type, but its base class, which can seem awkward. This happens in C# and in Java.
A delegate in C# is a method signature, composed of:
Delegates are the building blocks of events. A delegate can either point to a static, an instance or an anonymous method (lambda), provided the signature is the same:
A delegate can be generic:
Delegates inherit automatically from System.Delegate, because of this, they have built-in support for dynamic and asynchronous invocation.
Java has a similar construct, functional interfaces. These are interfaces with a single non-default method, which can be used in lambda functions:
Enumerations in Java can have members (constructors, fields and methods) and even implement interfaces, something that is not possible in C#:
In C#, no methods or interfaces, but we can have an enumeration be implemented based on a primitive integer type, including unsigned (the default is signed int):
Implicitly, all C# enumerations inherit from System.Enum.
In both cases, C# and Java, we can specify integral values for each enumeration member, and, if we don’t, members will get sequential values starting with one.
In Java, however, it is not possible to directly convert an enumerated value to a primitive type, as we can do in C#.
A type in Java has two possible visibilities:
And C# types have similar ones, but called:
In C#, the syntax for extending a class and for implementing an interface is exactly the same:
Whereas in Java there are the extends and the implements keywords, respectively, for classes and interfaces:
Both can inherit from a single class and implement as many interfaces as desired. Also, an interface can itself inherit from several interfaces.
In C# it is possible to implement interfaces in one of two ways:
Let’s see how they look in C#, in this example, interface IMyInterface1 is explicitly and IMyInterface2 implicitly implemented:
Explicitly-implemented members are always private and cannot be virtual or abstract. If we want to call a method or access a property of an explicitly implemented interface, we need to cast the instance first:
Java only has implicit interface implementations:
In Java as in C#, we can have multiple levels of nested/inner classes, structures and interfaces, but in Java they can be static or instance:
Instance inner classes can only be instantiated when we have an instance of its containing class (do notice the awful syntax):
In C#, any inner class can be instantiated, with or without an instance of the containing class, provided its visibility level is respected:
For C# the following visibility levels exist for types:
Whereas for Java:
In Java as in C# we have abstract classes, and the syntax is exactly the same:
C# structures cannot be abstract.
Both frameworks allow a class to be marked as sealed/final, meaning, it cannot be inherited from:
C# structures are always implicitly sealed.
In C# we can have static classes, which are roughly equivalent to being at the same time abstract and sealed. Static classes only allow static members (properties, methods, fields and events):
Java does not have the concept of static classes.
Because it is allocated in the stack, a variable of a structure or enumeration type always has a value, so, it cannot be null, but we can use a handy syntax to turn it into a nullable type, which can itself be made null:
In Java, primitive values can never be null, we need to resort to their corresponding wrapper classes:
Classes and interfaces in C# (reference types) are always nullable, meaning, can always be assigned null.
C# allows marking a class as partial, meaning, its contents may spread through several different source files; the actual compile-time class will be built from all of these files. This is very useful when we have automatically generated files that we don’t want to change, but rather complement:
Java has anonymous classes: we can create anonymous classes that implement some interface or extend some class, by implementing all of its abstract methods:
Anonymous classes in C# do not contain explicitly defined methods, only read-only properties; two anonymous classes are considered of the same type if their members are declared in the same order and with the same types:
In order to support anonymous classes, C# introduced the var keyword, which allows us to have a variable infer its type automatically. An anonymous type is created when a variable is created without a static type.
In .NET we have the following type members:
Java only has:
Static constructors or class initializers are basically the same in C# and Java, but have a slightly different syntax, here is the Java one:
And the C# syntax:
Java offers another weird thing: constructor blocks. You can have any number of them, and their code will be included automatically into all of the class’ constructors:
In C# a destructor, or finalizer, is just a shorthand syntax to the Finalize method. This method is called by the Garbage Collector when an instance is about to be freed. Java has an identical method, called finalize, which serves a similar purpose. Strictly speaking, none of these methods is actually a destructor, but they are sometimes called that.
In C#, we can use the C++ syntax instead of overriding Finalize:
Unlike C#, Java allows referencing static members through an instance variable, for example:
Properties are a useful C# construct, which allows a cleaner syntax to changing fields:
We can have auto-implemented properties (such as in this example) or properties with an explicit backing field:
The Java equivalent can only be achieved with methods:
In C# we can also define indexed properties for classes, interfaces and structures, like in this example using an integer index:
We are not limited to integer indexes, any type can be used as the key to an indexed property.
Finally, properties can have different visibility levels for the getter and setter methods, and can even have just one of them (usually just a setter does not make much sense):
Events are C#’s implementation of the Publisher/Subscriber and Observer Patterns: it allows to register methods that will be called when the event is raised, and offers a simple syntax for registering, unregistering and clearing event handlers. An event handler is just an instance of a delegate, the delegate is the event’s signature:
Like with properties, it is also possible in C# to implement the event add and remove methods explicitly, so as to add our own behavior:
All fields declared in a class are initialized to their type’s default value (0 for integers and floating point number, false for booleans, null for classes). C#’s auto-implemented properties are also implicitly initialized to their type’s default value. This behavior is the same in both languages, of course, Java does not have properties.
C# has four visibility levels for members:
And Java, we only have:
In Java, all methods are virtual by default (there is no virtual keyword), unless marked as final.
In C#, a method, property or event needs to be explicitly marked as virtual so that it can be overridden, and all overrides must state so:
If a derived class member with the same name as one in the base class exists, but it is not an override of it, we need to mark it as new:
In C# as in Java, it is possible to mark a member (method) as sealed/final, meaning, it is not available for overriding in a derived class. In C# the same applies to events and properties, which, of course, don’t exist in Java.
And Java syntax:
In both languages, abstract members (methods) can exist in abstract classes, but they are not required: we can have abstract classes without any abstract members. In C#, besides methods, we can also have abstract properties and events.
Methods can also be generic, regardless of living in generic classes or not. The same constraints apply, but generic methods also have automatic type inference:
Both Java and C# have read-only fields, but C# uses the readonly keyword:
And Java uses final:
C# also offers another kind of read-only field, constants. A constant is always static and can only be of one of the primitive types, or an enumerated value:
The difference between readonly and const is that the C# compiler inlines all constants, that is, it actually replaces any references to it by their concrete values. The Java compiler does something similar for static final fields. Read-only fields can be initialized inline, together with the field declaration, or in constructors (static or instance).
That’s it for now. Stay tuned for the next post, where I will talk about other language differences. Let me hear from you!