If we could have the best of both worlds between C# and Java, what would that look like?
Most C# developers will be quick to tell you that with more frequent updates, C# has everything Java has and more. C# had Generics and Lambdas long before we got them in Java, but there are still major components in Java that we don’t see in C#.
We researched the top features that Java developers miss in C# and chose 5 of them to list here. Some of them offer clear benefits to Java users, while others are surrounded by controversy.
Let’s take a look.
1. Checked Exceptions
Java divides exceptions into Checked and Unchecked. Checked exceptions are conditions that are checked at compile time. Certain methods that are likely to throw an exception must be handled using a try/catch block or otherwise must specify the exception with a throws statement. Because these are issues that commonly occur, Java requires us to provide the logic to handle them at compile time. Without one of these required statements the code will fail to compile.
This is simply meant to encourage developers to write more robust software.
On the other hand, not only does C# not have checked exceptions, the architects purposefully didn’t include the feature. The debate over whether having checked exceptions is a benefit or a drawback has gone on for close to 2 decades now and is still nowhere near settled.
For Microsoft Chief Architect Anders Hejlsberg, there are two issues with Java’s implementation of checked exceptions, versioning and scalability. The trouble with creating a new version of a method, he says, is that you may be adding new features that also introduce a new exception. If you do introduce a new exception, adding it to the throws clause may break the code entirely because the caller of the method most likely doesn’t handle that exception. In terms of scalability, when systems become very large, common practice is to simply write throws Exception everywhere or to write in empty try catch blocks that we’ll “come back and deal with later.” We showed this when we looked at over 600,000 Java projects on Github and Sourceforge and found that 20% of catch blocks were empty and most of these exceptions were ignored in the end.
2. Non-Static Inner Classes
Both Java and C# have nested classes, but what Java does differently is dividing nested classes into two main categories. In each language, you find static nested classes, which is a static member of the outer class and doesn’t have access to instance variables or methods from the outer class. These nested classes can be called without first initiating the outer class.
In Java, though, there is another type of nested class called inner classes, which are non-static. These classes include member, local and anonymous inner classes as shown in the chart below.
Member inner classes are simply non-static classes within a larger class but outside a method. To create an instance of a member inner class, you must first instantiate the outer class to which it belongs. These classes can then be used to access private or public instance variables or methods from the outer class. Method local inner classes are similar but are contained within a method and can only be instantiated within the scope of the method itself.
Lastly, anonymous classes are, to put it simply, inner classes that are created without being given a name. They work similarly to local inner classes, but are declared and instantiated at the same time, making them good to use in place of a local inner class that you want to use only once. In Java, anonymous classes are essentially used to extend the parent class. C# doesn’t have a direct equivalent, but a similar effect can be achieved with the use of events and delegates.
One benefit of (non-static) inner classes is that they can be made private, unlike classes in general, so that they can only be accessed by an object from the parent class. Other advantages of using inner classes in Java include accessing all members of the outer classes (including private members), writing more maintainable code and optimizing how the code is written.
3. Final Keyword
Polymorphism is one of the defining properties of Object-Oriented Languages, and it wouldn’t be possible without virtual methods. A virtual method is one whose function can be overridden by any class that inherits it. In Java, every method is assumed to be virtual by default and can be made non-virtual using the final keyword. Conversely, in C#, all methods are non-virtual by default and so a directly equivalent keyword would have no use.
In Java, the final keyword can be applied to a variable, method or class. In each instance, the keyword has similar consequences. A final variable will act as a constant and its value will be fixed. A final method can’t be overridden, and a final class can’t be extended.
To prevent a class from being inherited from in C#, you can use the sealed keyword. In the case of non-class fields, there are two different keywords that can be used to prevent modification. Readonly is to be used for runtime constants, while const is used for compile time constants. Basically, when using the const keyword the value of the constant must be explicitly stated and is evaluated at compile time, whereas the value of a readonly field is assigned by the constructor but is not evaluated until runtime.
4. Covariant Method Return Types
Although this difference is subtle and use cases are fairly uncommon, the existence of covariant return types in Java can save you from needing to create new methods.
Basically, in C#, a method in a subclass that overrides a method in the base class has to match the name, argument type and return type of the method in the base class. In this case, the overriding method is invariant with respect to return type, and if you want to narrow the return type you have to create a new method.
The following code snippet in Java narrows the return type of the Clothes method when overriding it in the Pants subclass, so Pants.newClothes returns Jeans type which is a subtype of the Clothes method. Generally, this is a more ideal way to work with inheritance hierarchies.
5. Enums as Specialized Classes
There was a time when developers bemoaned the lack of enums in Java, and now it’s one of the things that is more commonly thought to have been done better in Java. Enums in C# closely resemble those in C++ and are essentially seen as glorified integral types. A good example of an enum in C# is one which includes the days of the week as members:
By default, the value of the first list item is 0 and the value of each successive item increases by 1 (i.e. Sat=0, Sun=1, Mon=2, etc.)
Although it didn’t happen until several years later, Java did something very different when they introduced enums as specialized classes.
Enums in Java are said to be like specialized classes because they are, in fact, “full-fledged classes”. Unlike their C# counterparts, they can take arbitrary methods and fields as inputs making them much more powerful. Take this example, for instance, which includes methods and some basic logic in Java:
6. Tooling Ecosystem
Beyond the language differences between Java and C#, each has its own runtime environment (Java runs on the JVM and C# uses Microsoft’s CLR). With different runtime environments, each language has a different set of production monitoring tools available to use.
One of those tools is OverOps, that shows developers the complete source code and variable state across the entire call stack for every error and exception in production. It’s available for JVM languages like Java, and .NET compatibility is coming in the next few months. For more information and to join the waiting list for our .NET Beta click here.
If you want to read more about the differences and similarities between the two languages, check out our other post on this topic: C# vs. Java: 5 Irreplaceable C# Features We’d Kill to Have in Java.
In writing this piece, we were reminded again that individual language features in Java and C# are not the defining properties of their respective ecosystems. The two languages are in a perpetual game of leapfrog in respect to adding features, one does something first and the other is usually soon to follow.
As we said in a previous post, we don’t want to get stuck in the middle of the never-ending argument over which language is better. Each has its own advantages and disadvantages, and other external factors to take into consideration. This is just us pointing out some Java features that are lacking in C#. Did we miss a feature that you want to see added to C#? Let us know in the comments below!