I read a lot of blogs and see people all the time talking about bad things in the java programming language; a lot of them are about annotations and generics that were added to the language in 1.5 release. What are the things in the language or the API that you don't like or would design differently?
Primitive types (ints etc.) are not objects like in Smalltalk, forcing the programmer to use boxing classes such as Integer.
Smalltalk integers are real objects. In order to avoid overhead, they are not implemented as "boxed" primitives, like Java does, but instead as "immediate" objects: the object's value is stored within the object "pointer" (the same is true with individual characters) To do so, it uses the pointers' low bits, which are always zero for regular objects (since structures are usually word-aligned), as "tags" to differentiate immediate objects with the former. For example, if a pointer's bit 0 is set, it designates a SmallInteger object: its integer value is stored in the remaining 31 bits. Larger integers use regular objects with infinite precision (AKA bigint).
Java 5's autoboxing is the worst of all solutions, because it hides the overhead behind syntactic sugar: the programmer is unaware of the fact that objects are created behind his back although he's using primitives. Not only that, but several Integer objects could be created for the same int value. Whereas in Smalltalk, the value IS the objects. This also allows the compiler to generate optimal code, in a way that can't be done with boxed objects. Smalltalk proves that primitive types can be objects AND be implemented efficiently.
This is one of the reasons why Java cannot be considered a high level language (not that this can be a problem or drawback in any way, there is ample room for system level programming languages).
I tend to get a little irked by the deprecation of good useful objects and their replacement with ugly, hard to use objects (example: Date & Calendar).
On the java platform: AWT, and the XML APIs (it's amazing what you have to do just to parse a String into a DOM tree)
On the language: add type inference and tuples, generics without erasure, make 'volatile', 'strictfp' and 'transient' annotations
I think the Java language is overall well designed. I just think it's way too verbose.
Bundling .jar files: 1st: .jar files don't really have icons (wtf?!), then you cannot include .jars in .jars (omgwtffff). Together: write your code once, run it anywhere and write a 6-page manual for every platform on how to properly bring your UI up. Include sentences like: "we recommend setting the classpath. to do that, run msconfig in a shell"
Make a contract with the nearest suicide aids provider.
JNI could be one of the worst in Java.
It is much much more time wasting if you compare it with PInvoke in .NET.
I really don't like the manipulation of dates in Java (java.util.Date, java.sql.Timestamp, java.sql.Date, java.util.Calendar).
No destructor. I wish there was a standard (optional) way to say "I'm done with this, run some code", rather than relying on the finalize method that may never get called. I'm not talking about having to manage memory, just that when you do want to finish with something, there was a standard call to make which would do whatever clean up you wanted and then provide (maybe) some hint to the Garbage Collector that you're done with it. I'm sick of having some classes with a "close" method, and some with a "done" method, and so on and so forth.
- Checked exceptions.
- Every object Is a monitor.
- Primitives are not objects.
- Lack of properties.
- The way generics are implemented.
Ever since I have learned python, I just find that Java lacks the possibility of returning multiple values easily:
#In python:
name, age = john.summary()
//In Java:
Object[] summary = john.summary();
String name = (String) summary[0];
int age = ((Integer) summary[1]).intValue(); #primitive types require boxing
Edit: as mmyers pointed out, the following is legal as of Java 5:
int age = (Integer) summary[1];
Keeping too much C syntax that was known to be problematic. The switch statement should have been redone for Java. Operator precedence is at least a more complicated problem; while Java could do better than C there was some advantage in keeping it.
One goal for C++ was to be at least a better C, and so Stroustrup had an excuse for keeping bad C decisions. Gosling et al. didn't.
Edit: Also octal literals (thanks, Dan Dyer, for pointing that out). Those are very useful things for writing OS internals and bit-grovelling code, which are applications Java isn't usually used for.
- The default access mode should be private.
- Bytes should be unsigned.
- The Cloneable interface should include the clone() method.
A couple of years ago I appreciated Java more than I do today.
I fell deeply in love with their packaging. Today I despise it. A gazillion classes all over the place packaged in a way that would have very little sense hadn't you had experience armed with you. Check out the language ref for AS3 and see what I mean. The 2nd day I started working in AS3 I wasn't going on google looking for tutorials on how to do something, I was already a natural, knowing instantly where that class that I never knew even existed, that did exactly what I needed, was located.
Java still has a great community, but it's not as intuitive getting involved in their communities as it is with other languages. Other languages have provided way better developer portals, way better and more resources.
Basically, Java got too big to handle, got big before they'd lain out everything for its growth. It's too bogged, too confusing. They used to be the forward thinkers in so many aspects, now they are good in few and in some we just wish Java would collapse once and for all.
[Hmmm... not sure why the downvotes...]
Java should not have used the same syntax as C++ with different semantics.
A couple of examples
meaning of "protected" is different
- C++: "me and my subclass"
- Java: "me, my subclass, and my package"
meaning of "Dog d" is different
- C++: "d is an instance of Dog"
- Java: "d is a POINTER to a Dog"
This causes a great deal of confusion with C++ folks who learn Java. I still see many people shocked when they hear that "protected" includes other elements in the same package...
(If you don't think Java has pointers, see http://javadude.com/articles/passbyvalue.htm)
- Fields, parameters and local variables should be final by default and only mutable when set. e.g. var
- There is no consistent way to make an object immutable. (Only references and primitives)
- wait(long timeout, int nanos) on every object, even though it does not have nano-second precision and probably never will.
- Object.getClass() returns Class<?> i.e. getClass() doesn't know what class it is at compile time. e.g
new Integer(0).getClass()
in code returns a Class<?> not Class<Integer> however when you compile the code it returns Class<? extends Integer>, thank you @Mr. Shiny and New - array types don't override toString() as so print something like "[B@ef172a" However, Arrays.toString(byte[]) would be a useful default behaviour.
- Integer.class.isAssignableFrom(int.class) == false. int.class.isAssignableFrom(Integer.class) == false. Yet with autoboxing and reflections there is very few examples where this is the case.
- @Deprecated are never removed, even if it has been deprecated since version 1.0.x
No destructors, therefore no possibility of RAII.
Most objects are memory-only, which Java's GC handles just fine. But whenever you have a class that allocates non-memory resources (e.g. DB handles), you need to remember to clean it up in a finally
block every time you create an instance -- so you need finally
blocks everywhere containing the same cleanup code, and if you forget one place, bang, resource leak as soon as an exception is thrown in that scope.
This is one of the (few?) things C++ got right -- you write the cleanup code just once in a destructor, and the language guarantees that it will always be called when an instance of that class goes out of scope, even in the event of an exception. I realise that Java is GC-collected, but RAII and GC are not mutually exclusive -- there just needs to be a way to specify deterministic destruction at scope exit for a particular class or instance, and to provide a destructor. Really, you would not believe how much this simplifies resource management.
It's been 5 years since I touched Java, maybe this has changed?
I'm by no means an authority for what Java is doing wrong, but from this rant on comp.lang.lisp it looks like there is logic error in their exception handling. Is that a fair assertion?
Too much focus on simplicity at the expense of expressiveness. That basically sums it up.
The test-unfriendly servlet API... you need a framework to be able to mock a bloody Request!
All References are nullable
All references are nullable, which causes a lot of NullReferenceExceptions. I think something like "Option" (Scala) is better suited for those rare cases where an object reference actually should be able to be "Nothing". Of course that would have required Generics and some Pattern Matching right from Version 1.
Non-resizeable arrays. Yes I know you can allocate another array and System.arraycopy() the original... but would it have been so hard to have, e.g., array.resizeTo(x), array.growBy() and array.shrinkBy().
And no way to make an array entirely final (it's reference and all of it's elements).
Edit: Arrays don't implement the Collection or List interfaces.
Checked exceptions are a problem in Java because they may break encapsulation.
There is an interview worth reading with Anders Hejlsberg on Artima where he talks about that:
Anders Hejlsberg: Let's start with versioning, because the issues are pretty easy to see there. Let's say I create a method foo that declares it throws exceptions A, B, and C. In version two of foo, I want to add a bunch of features, and now foo might throw exception D. It is a breaking change for me to add D to the throws clause of that method, because existing caller of that method will almost certainly not handle that exception.
- Variables should have been not nullable by default.
- Fields and variables should have been final by default.
- There ought to have been a type for method references (first-class functions).
- Missing the ability to seal types (not classes, but types).
- Inability to do case-analysis on complex types (enumerations only, sorry).
The collection hierarchy in the standard library is completely broken as designed. Just as an example, the List
interface has 25 methods that you have to implement, and most of them mutate the list. Collections rely on the elements implementing equals
and hashCode
properly, which must be done by inheritance, which disfavours composition as a design strategy if you want your libraries to work with the standard library.
In JDBC the java.sql.Date class has no time component. This forces you to use Timestamp, which confuses Oracle when your dates are stored as Oracle DATE values.
Also I'm not crazy about how Java is packaged. It's annoying to have to set up class paths and jar files and don't get me started with EAR files and WAR files. There is room for improvement here.
GC
While GC is incredibly convenient, an unpredictable GC is not suitable for certain applications. One such example would be hard real time systems. In a hard real time system a single unexpected delay can result in mission failure.
Examples of such hard real time system would be: fly by wire systems on a fighter jet, navigation systems on a missile, robotic arms that perform surgery, etc.
Also, getting killed in a real time video game is also a mission failure. You don't want to GC right at the moment the player pressed the "dodge the giant fireball that will kill my character and force me to redo the 20 minute long level," button. That is a very important button. (Although it has been noted that in multi-tasking OS you cannot control the task priority which could easily be worse than a large GC operation.)
Static Polymophism
Ignoring static polymorphism. Static polymorphism could go beyond "C++ like" templates, and it is a very useful optimization. Generics as implemented in Java is still dynamic and it loses that opportunity to eliminate run-time type checking where compile time type checking would is enough. Of course is possible for increased code size to reduce performance more so than dynamic types would. As with all optimizations it should be profiled.
Everything is in a class
Although nit picky, there are times when you just want a function. Currently in Java you must put such functions in a class as a static function. A better solution would be a function in a namespace. While a class can work like a namespace, classes cannot span multiple files and libraries.
I like Java a lot, but mainly two things bug me:
- No unsigned data types - I find that most of the time, it doesn't make sense for the data I have to be negative. Unsigned is like a form of documentation, and it also happens to double the maximum value.
- No structs - mainly a performance thing.
Also I think doSomething()
looks worse than DoSomething()
, but that is merely a coding standard, and not really a problem.
I don't like how collections in Java have a toArray() method that returns an Object[]. It's very difficult to cast between a primitive array and a collection.. You end up doing this:
ArrayList<Integer> list = callSomeWeirdAPIMethod();
Object[] f*ckedArray = list.toArray();
int[] realArray = new int[list.size()];
//...copy elements from f*ckedArray to realArray
where you can't do this:
int[] realArray = (int[]) list.toArray();
or even this:
Integer[] realArray = (Integer[]) list.toArray();
- No operator overloading
- No real array literals
- Primitives are not objects
- No decent multiple inheritance (I know there are problems, but they are fixable)
- Too verbose
- No decent way to declare "variables" constant unless they are primitives (i.e. you can still call 'setBar' on a 'final Foo foo')
Not necessarily things they did wrong, but things that I would have liked:
- Closures and function pointers
- Destructors
- Possibility to return multiple values from a method
There are serious downsides which result in boilerplate patterns:
- type erasure
- checked exceptions
- inflexible catch clause
- no default arguments constructor
- JavaBean conventions
- need for redundant type information
- no observable collections
- no covariance
...and so on.
java.nio.Buffer, ByteBuffer, etc. are classes, not interfaces, and have no way of allowing you to wrap anything but arrays of primitive types (byte[]
for ByteBuffer) in a Buffer so you can pass the resulting object to a method which uses a Buffer.
No public mutable BigInteger and BigDecimal for more performant compound operations and the way requests for them gets always turned down citing the evilness of mutable objects in multi-threaded application. Please then remove StringBuilder as well!
Using question marks instead of named placeholders in PreparedStatements - who wants to count each question mark all the time to check if everything got assigned?
Inconsistent usage checked/unchecked exceptions in the runtime: Integer.parseInt(String) unchecked, String.getBytes(String) checked.
Sub package cross-dependencies in the runtime: java.lang <-> java.io?
Making up a whole new logging api (java.util.logging) to be the new standard was a mistake that annoys me. I have to create a subclass of a Formatter just to get output on only one line! What was wrong with log4j?
One thing that bites me regularly is the fallthrough behaviour of the switch statement.
Reading the replies made so far, I notice the almost complete absence of multiple inheritance. It is mentioned only once, but used to be a subject of heated discussion a number of years ago. I'm wondering why this is the case. Do people feel it is not really needed? Do people no longer care?
Lack of Class modifier Friend. One long and verbose class can be broken into two Friend-ly Classes in C++, but not in Java. A static inner class will do the same thing as long as the code never needs to be reused.
Failure by Sun to incorporate new APIs into the platform quickly. If you're new to Java, and want to do some logging, there are about 100 different ways to do it. Which should you use? Who knows. There's too many choices, and you have to find out which one fits your needs. There should just be one way to do it, or at least a recommended way to do it.
Lack of SIMD operators.
BigInteger
should be autoboxed as well as Integer
. (Even if you don't like autoboxing, if it applies to one then it should also apply to the other.)
Overloading +
for string concatenation was a bad idea. It should have been a separate operator.
The implementors of the collections framework seem to have a strange set of priorities.
Have you ever wished you had
List<Integer> Arrays.asList(new int[] {1, 2, 3}); ?
Me too, but it's not in there.
Have you ever wanted to do Arrays.sort(new float[] {1.0f, 2.0f, 3.0f})
? Me neither, but that is in there.
And on the subject of omissions from the collections framework, why not
<K, V> Map<K, V> Collections.weakKeys(Map<WeakReference<K>, V> existingMap)
instead of a separate WeakHashMap
class?
Ugly looking default GUI, I think Java would gain a lot if they provided a nice default look-and-feel.
Not that important, but something I never thought about before today:
- The
Object
class should be abstract.
Ones that have already been mentioned:
- No unsigned types - the worst thing here is that Gosling basically justifies this by saying that developers are too stupid to know how unsigned arithmetic works, so it's better to just not have them available.
- Primitives are not interchangeable with their equivalent classes. That is, an int is not an Integer and so on. The real killer is that if you have two Integer's that wrap the same value and you compare them, the comparison will return false since the Integer objects aren't the same object.
- Related to that, there's no operator overloading at all. I know people bitch about operator overloading, but they make life so much easier. They'd let Integer behave like an int, for one thing.
- Multiple inheritance. I'm willing to let this one slide since there are a lot of implementation nightmares involved, but it still lets you do things so much more cleanly. Sure it's abuseable, but if we removed everything abuseable from a programming language we'd have nothing left.
The one that hasn't been mentioned yet is the lack of basic enums. Sure, they added something they /called/ an enum in 1.5, but it's not. It's a neat construct, and it has its uses, but it's not an enum and it doesn't cleanly replace just having a bunch of constant int values.
There are other things that bug me, especially the things that make Java so slow, but those five are the things that irk me most from a strictly code development viewpoint.
Java believes its own hype.
No way to take off the training wheels. Whenever I have to use Java I feel like I am on a kiddy trike.
It is overly difficult to reuse code. Other languages encourage decoupling shared functionality from a single object, but in java the only way is inheritance which is often blocked for other reasons. How do you add a method to String?
No first class functions. Not everything is always object oriented.
Tying the class name to the file name. This makes it hard to use the same file in multiple projects.
No pointers.
No preprocessor. Unless your projects are very simple or very isolated, having a preprocessor is invaluable.
No manual memory management. It is convenient to ignore memory management sometimes, but not to be forced to it.
No way to tell if an object implements a method. The information is there, but hidden because somebody might be allergic to details or something.
No way to call a method on an arbitrary object by name. Right now you basically have to make an interface for every single function, when that should just be implied.
No consistency in the core data structures. There are so many implementations of lists and maps and vectors and arrays and none are interchangeable.
Not cross platform. The one strength of java should be that once you port the virtual machine everything else just works, but that is not always the case in practice. I have one product for two platforms and very little code works on both. There is not a single file that I could use in both versions of the same program without some modification.
Java always feels so slow. You can argue that it is just as fast as this or that, but my perception is always that if it was not java it would be much faster.
Java mixes up value and object identity equality by using the same operator ==
for both. Hence you have to use ==
for int
, but equals()
for Integer
, and so on. A good example of a language doing this right is Python (value equality: ==
and !=
, identity comparison: is
and is not
) and VB (value equality: =
and <>
, identity comparison: Is
and IsNot
).