Posted: Sat Oct 08, 2005 1:49 pm Post subject: Why Java sucks
Why does Java suck?
A few notes before I dive in:
Some of these points I care about more than others. They are not necessarily in order of perceived importance.
None of these flaws, by themselves are "deal breakers". I enjoy using languages which make many of these same mistakes. However, when you put all of these together they become overwhelming, in my opinion. Fixing even a few of these flaws would go a long way toward making Java more pleasant to work with.
I believe this document serves an important purpose. There are many programmers who may not have realized that there were alternatives to the way Java does things. If these complaints serve only to point that out, then it has accomplished a great deal.
ArrayList<Integer> lst = new ArrayList<Integer>();
lst.add(42);
lst.add(27);
lst.add(12);
But why can't Java have some kind of literal syntax for lists or arrays? Plenty of other languages do just that and it is most definitely not considered wasteful syntactic sugar.
code:
lst = [42, 27, 12]
Why can't functions return multiple values?
Yeah yeah... in Java they're called "methods".
Why can't they return multiple values?
Because C and C++ can't isn't a good reason, but it's the one that we're given. Instead we have to add extra classes to the library, or return a list.
That second option seems nice, but it means we have to create a list (using the tedious syntax listed above), return it, assign that return value to an array or list variable, then manually grab each element in that list and assign it to some other variable.
What's wrong with being able to just return more than one value?
Python:
def foo():
return(1, 2, 3)
a, b, c = foo()
Where are the tuples?
This one's related to the previous question.
Simple support for tuples would ease programming tremendously. Where is this concept that so many languages have gotten right? It's missing entirely.
For-each and modification?
For-each loops make it possible to avoid off-by-one errors when iterating over collections. But this only works when we're reading the value of the elements in the collection.
If we want to modify those elements, we cannot use the for-each loop.
There's no needless naming. Really, does anyone care about "ActionListener" or "actionPerformed"? No, we only care about the fact that we have a method taking one argument that performs some action.
Yes, the anonymous inner class can provide flexibility, particularly when you want persistent state. However, the vast majority of the time it is complete overkill, and a bad cludge for an important missing feature.
Where are the argument labels?
Let's say I have a method or constructor which takes several arguments. Which argument does what? Okay, I'll guess. Oops, I got it wrong and the program didn't compile.
Hmmm... a little editing and voila! It compiles. Now let me run it. Oh... weird... I got some odd logic error. Oh, after half an hour of searching for the bug, it was because I had the right types for the arguments, but i mixed up two of them. Let me go consult the API reference.
Why do we need to do this?
Surely there's a better way. Many languages already know this.
code:
let win = window ~title:"hello" ~width:400 ~height:300 ()
Do I need to remember if width or height came first in the argument order? Nope.
Why do methods which take no parameters need parentheses?
In C and C++, a function's name can be used as a pointer to that function. On its own, it's the address in memory of that function. It makes sense, therefore to have parentheses after the function as an indicator that it's been called.
Java has no such concept. Nor does it have a delegate concept like D or C#. There is no way to use the name of a method outside of the definition of that method which isn't a method call.
And yet, we still have to tack on parentheses, even when there's nothing for them to group. It adds noise to a program and forces countless extra keystrokes.
Perhaps the rational is that, with method overloading, it's impossible to know if a method called without parentheses is a deliberate call without arguments, or a mistake. But in this case, why not assume the programmer knows what he or she is doing?
While we're at it, why not remove the empty parentheses from method definition? Because it would cause confusion in the syntax?
Surely there's no other place where we'd use curly brackets directly after a name.
The "return" keyword is ultimately about control flow. It takes us from any given point in a method and skips right to the end, optionally yielding some value.
There we go. We get to the end of the method, and there's a string. Hey... my method returns a string. Isn't that an incredible coincidence? Maybe my method should return this string.
Less syntactic noise is good.
But if I have a more complex method, "return" is clearly necessary, right?
String fullName, toString
{ if(first.equals("")) {
last
} else {
first + " " + last
} } }
There is no conflict, since a subsequent type declaration would start a new set of parameters.
Why must cases fall through?
The fact that cases fall through in Java is only to maintain syntactic and semantic compatibility with C and C++, which are themselves limited by hardware considerations. Of course, the Java environment doesn't have those limitations, so maintaining compatibility with that restriction is just ridiculous.
Cases falling through is created to address a lack of another feature. Let's look at a contrived example. We want to return true if a number is 2, 4, 6 or 8, and false otherwise.
But since this switch statement doesn't fall through, there's no need for a control flow break to prevent fall-through. So, if this switch is alone in a function, then there's nowhere else the control flow can go, and we can avoid the returns.
Additionally, we can use the braces as punctuation.
This shouldn't compile, right? I mean, "f" is clearly a constant Foo object.
Yet, it does, and it runs perfectly well.
You see, the "final" keyword merely means that the variable can't point to another value. This works fine for simple immutable data, like integers, but for an object with mutable state, it doesn't prevent changes to the object at all.
When we designate data as immutable, we give the compiler extra information. In this case we're telling it to stop us if we try to violate that restriction and make changes to a piece of data. With large APIs, this can be done quite unintentionally.
But Java offers us no ability to check for such problems when the program is compiled.
Of course, to make this work, we'd also have to be able to mark methods as being valid to call on constant objects. The other option is to assume that any method which returns "void" has side-effects and thus cannot be called on a constant object.
Non-void methods in a void context?
That might sound odd, but you've all dealt with this. It's possible to have something like:
Java:
f.getBar();
Which simply returns the "bar" field of the object "f". There's nothing stopping you from having that as-is in a Java program. It serves no purpose, yet the Java compiler does not warn about this or prohibit it.
Java needs a high-priority cast.
The sheer ugliness of this makes it a point against Java.
Java:
((Foo)bar).baz()
You'd think we could write:
Java:
(Foo)bar.baz()
... but that's low-priority, so it translates to:
Java:
(Foo)(bar.baz())
Instead, here's the perfectly reasonable:
Java:
(bar as Foo).baz()
As I've said many times, less syntactic noise is a good thing, and this kind of code is common enough to warrant attention.
Why can't Java do deeper code analysis?
Why is switch limited to integers?
It's be a piece of cake for "switch" to allow comparison of pretty much any types, using either == for primitive types, or the "equals" method for object types. This could greatly simplify a lot of code, and it'd be relatively easy. It wouldn't even change how switch works currently, but instead just add functionality.
Why hasn't this been done? Oh, right, to not make C++ programmers feel out of place.
Why can't I write stand-alone functions?
The "static" context is just silly. It confuses the heck out of many people and accomplishes nothing in terms of code re-use that couldn't be achieved with stand-alone functions and a proper module system, mix-ins or multiple implementation inheritance.
It makes sense for languages where classes are themselves objects, and are treated as such. In such a language, "static" methods and field are simply non-static fields and methods of the class object.
Certainly:
Java:
void main(String[] args) {
Foo f = new Foo();
System.out.println(f);
}
class Foo
{ publicString toString() { return"Hello";
} }
Makes more sense than:
Java:
class Foo
{ publicstaticvoid main(String[] args) {
Foo f = new Foo();
System.out.println(f);
}
Why not implement this powerful and simple mechanism for code reuse?
One of the primary uses of static methods is to provide a class full of utility methods for use on various types of objects which all implement some interface. Just look at the Collections class for an example.
Quote:
This class consists exclusively of static methods that operate on or return collections. It contains polymorphic algorithms that operate on collections, "wrappers", which return a new collection backed by a specified collection, and a few other odds and ends.
By and large, these methods take a single collection of some sort (Lists, Sets, etc.) as their first argument. I know I've seen this pattern before...
Oh yes, in Python, where the "self" object is passed as the first argument to every member function in a class. These member functions act as methods.
Except, of course, "sort" isn't a method of the List class, so that won't work.
Let's look at the way it works in another language. In this case, Ruby.
I want to be able to sort anything that implements a particular means of iterating over its contents (let's call this an "interface"), but I don't want to have to write all of the code for each of these classes, especially since I know it's going to be the same for each and every one of them. It simply depends on that iteration scheme, and nothing else about the state of the object.
So I have a class Array. Array objects have an "each" method which handles the iteration. I then "mix-in" the module Enumerable, and among other things, I get the implementation of a "sort" method.
What about not wanting to add those methods to an entire class? Just want them added for a single object? That's easy enough. The result is singleton.
Even C#, not the world's most innovative language, is implementing mix-ins, including singleton support, though Microsoft prefers to call them "extension classes".
Why has Java ignored such a source of power? Why does the language rob its users of such power?
There can be no good reason for such a profound lack.
What happened to compile-time code analysis?
Clearly, at compile-time we can look at the following and realize that only one String object has to be created. Since Strings are immutable, there's no harm in this.
Yet, via the "==" operator, which tests for referential equality, we can see that two different String objects have been created containing the exact same contents.
Java:
class Test
{ staticString foo = "hello world";
staticString bar = " ";
staticString baz = "hello" + bar + "world";
Thus, essentially what's happening is that the compiler is waiting until run-time to perform the string concatenation that could easily have been done at compile-time.
Why can't we do some type-inferencing?
C#, a language which shares much with Java in terms of design, is gaining a limited form of type-inferencing. Why doesn't Java employ such capabilities to make programming easier?
The compiler can easily figure out at compile-time that "keyboard" is a BufferedReader instance. Why should I have to tell it explicitly?
Sponsor Sponsor
rizzix
Posted: Sat Oct 08, 2005 5:39 pm Post subject: (No subject)
i hope you're feeling better now...
for the most part i'd agree with most of the stuff... but with a few exceptions:
#1) the syntax you proposed as an alternative approach in some cases was really ugly (it just dosent feel right, but yea i know it can be improved)
#2) Java's switch statement does integers __and__ Enums
#3) lists are unnecessary.. you can just as well use the Arrays.asList() function. It serves the purpose.
#4) instead of implementing mix-ins.. i believe it would be best to implement AOP directly into the language.
#5) i don't like the way c++ implements immutability (i,e a special keyword)... infact i believe it is best that annotations are used to represent that as well as the other things such as the access restriction of methods classes etc (i.e public, protected, so on..), after all all that falls under the category of Meta-data anyways.
#6) none of those arguments above justify for the amount of bashing that java has received from you,, here in compsci ...
wtd
Posted: Sat Oct 08, 2005 5:50 pm Post subject: (No subject)
rizzix wrote:
#3) lists are unnecessary.. you can just as well use the Arrays.asList() function. It serves the purpose.
Well, I can deal with backing it as arrays, lists, arraylists... whatever, but some literal form would be nice.
wtd
Posted: Sat Oct 08, 2005 5:59 pm Post subject: (No subject)
rizzix wrote:
#4) instead of implementing mix-ins.. i believe it would be best to implement AOP directly into the language.
Aspect-oriented programming is a somewhat ambiguous term.
Perhaps some type inferencing (deeper than what I described in my original post) is what you're looking for?
Kind of Ruby-esque duck-typing, but enforced at compile-time.
You may wish to look at O'Caml for an example of this combined with objects.
code:
$ ocaml
Objective Caml version 3.08.2
# let foo bar = print_endline bar#baz;;
val foo : < baz : string; .. > -> unit = <fun>
# class wooble = object end;;
class wooble : object end
# foo (new wooble);;
This expression has type wooble but is here used with type
< baz : string; .. >
Only the second object type has a method baz
# class ninja = object method baz = "hello" end;;
class ninja : object method baz : string end
# foo (new ninja);;
hello
- : unit = ()
#
wtd
Posted: Sat Oct 08, 2005 7:52 pm Post subject: (No subject)
rizzix wrote:
#5) i don't like the way c++ implements immutability (i,e a special keyword)
Ironically the same keyword that Java has reserved but unused.
And at least C++ has immutability.
wtd
Posted: Sat Oct 08, 2005 9:29 pm Post subject: (No subject)
rizzix, I'd like to thank you for the reasonable response. I was hoping leaving out particularly hot issues like performance and operator overloading would encourage that kind of response.
wtd
Posted: Sat Oct 08, 2005 9:58 pm Post subject: (No subject)
Let me add another thing to wish Java had.
No, it's not C++ preprocessor directives, but it does pretty much the same thing at a much higher level.
D's "static if" allows for compile-time decisions.
rizzix
Posted: Sun Oct 09, 2005 12:48 am Post subject: (No subject)
actually by AOP i was refering to AspectJ. i just think it should be an integrated part of java.
Sponsor Sponsor
wtd
Posted: Sun Oct 09, 2005 1:11 am Post subject: (No subject)
I can see the DbC stuff being useful, but I would make it an integral part of the language. To additionally encourage the use of such a technique, I'd make it as easy as possible.
I think that perhaps forcing programmers to write out an "if" statement to evaluate a condition and throw an exception is making them work too hard. Consider Eiffel as an example of what I'm thinking of.
code:
foo(x : INTEGER) is
require
arg_too_small : x >= 42
do
std_output.put_integer(x)
std_output.put_new_line
end
Certainly debugging aspects (no pun intended) of it are important as well. I've always liked D's "debug" keyword which just makes it so easy.
code:
debug
{
writefln("We're debugging!");
}
debug (4) // we can have numbers to represent different debugging levels
writefln("We can have the debug line apply to just one statement");
debug (hello):
// we can have string labels for controlling debug blocks,
// and the debug section can extend to the end of the current block
wtd
Posted: Sun Oct 09, 2005 1:16 am Post subject: (No subject)
For wrapping methods with pre and post actions... the pattern matching capabilities of AspectJ are interesting. Without them it'd certainly be tedious to have the same pre or post action for several different methods.
wtd
Posted: Sun Oct 09, 2005 1:18 am Post subject: (No subject)
I see little widespread future support for something like AspectJ, though, unless they dramatically improve the documentation. It's decent enough, but they go way overboard on the buzzwords. PHBs may love it, but programmers won't.
wtd
Posted: Sun Oct 09, 2005 1:58 am Post subject: (No subject)
Posted: Sun Oct 09, 2005 2:12 am Post subject: (No subject)
Also fun.
code:
>>> def require(*types):
... def decorate(f):
... def x(*args, **kwds):
... for n, (type_name, arg) in enumerate(zip(types, args)):
... if not isinstance(arg, type_name):
... raise TypeError("Type mismatch on arg %d. Should be %s but was %s." % (n + 1, str(type_name), str(type(arg))))
... return f(*args, **kwds)
... x.__name__ = f.__name__
... return x
... return decorate
...
>>> @require(int, int)
... def foo(a, b):
... return a + b
...
>>> foo("hello", "foo")
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 6, in x
TypeError: Type mismatch on arg 1. Should be <type 'int'> but was <type 'str'>.
>>> foo(1, 2)
3
>>>
Naveg
Posted: Sun Oct 09, 2005 12:27 pm Post subject: (No subject)
wtd wrote:
leaving out particularly hot issues like performance and operator overloading
I don't completely understand this, but I think it talks about how poor java performance is an abused myth in some respects. Take a look.\
Posted: Sun Oct 09, 2005 12:31 pm Post subject: (No subject)
Yes, it does.
That's why I chose not to complain about Java's performance. I think there's enough to complain about with respect to the language, without touching the JVM.