I have been working with Java for about 12 years, starting with Java 2. I have been using other languages, Python, JavaScript, C, Swift,…
There are a few things that are frustrating when writing Java that are not present in other languages.
Here is my Top 5
void
is special
void
is only ever used to indicate that a method does not return anything. It also means that a method defined as public <T> T func();
must return something, and can never return a void
.
There is even a Void
type when you want a generic function to not really have a return value. It is just a placeholder, though, and your function still needs to return something. Usually null
.
Primitives
int
, long
, char
, bool
, byte
, float
, double
, short
all have equivalent objects, and are auto-boxed and -unboxed. Still, it is a bit weird, and can lead to unexpected behavior:
Integer a = null;
int b = a;
causes a NullPointerException
because you can’t assign null
to an int
.
Other surprise:
Integer.valueOf(500) != Integer.valueOf(500);
// But
Integer.valueOf(5) == Integer.valueOf(5);
Because of how small integers are cached and the same Integer
objects are returned. Of course this is implementation specific.
Operators are special too!
Operators in Java work by combining one (for unary operators) or two (for binary operators) operands of compatible types into a result of the same type as one of the operand. Only primitives (and String for +
) are usuable with operators, or their Object equivalent, through auto-unboxing.
This means you cannot use an operator with generic objects. Your operands have to be of the proper type, and they have to be so at compile time.
And also that means that you cannot refer to an operator, because it is not a function.
Functions as first-class objects
To access a function and learn things about it, like its return type and its arguments, you need to use reflexion, which is tedious.
With Java 8, there are function references, lambdas, and functional interfaces. It is much better, but still not perfect.
While Functional Interfaces provide a way to reference a function, they do not provide direct access to the function itself. You still need a functional interface that matches your function’s signature to create a variable that holds it.
List
is broken
List
is an interface for ordered collections. You can iterate on it, or access the elements based on their position.
But a List
does not tell you if it is mutable or not. In other words, all the add
and remove
operations are marked (optional operation).
For example:
List<String> strings = Arrays.asList("one", "two", "three");
strings.add("four");
Throws an UnsupportedOperationException
. Whenever you receive a List
as a parameter, you do not know if you are capable of mutating it. What is worse, there are several implementations of mutable lists. ArrayList
and LinkedList
being the more famous. But there is no way to indicate in your method signature that you need a mutable list, short of choosing one of those.
List
is also very generic in the sense that it does not give any indication of how is performing the underlying implementation. Every operation in a List
has a complexity that ranges from O(1)
to usually O(n)
depending on what type of list it is. See Big-O notation for each type of list. Not knowing which list is used prevents from knowing the complexity of an algorithm.
This is why I always tend to try to accept the most generic interface I know I can work with (i.e., not List
if I need to add
to it), but return the most specific type I can commit to. My methods never return List
, unless I really need to. (Typically returning the result from Arrays.asList
). In which case, the returned List
should not be considered mutable.
How other languages do it
As I mentionned, I have use other languages, because all languages have unique features that make them interesting, and it is by comparing languages that I get to see what is specific, what is good, and what I like less about a language.
Python
void
There is no such thing as a void
type, nor as a return type. There is None
, but it can be affected to any variable, and is simply a null
pointer. It is implicitely returned by functions that have otherwise not returned anything.
Primitives
There is no such thing. There are literals, which allow you to express certain types (numbers, lists, dicts, strings, regex…), which is convenient, but all types are objects. Try (1).__doc__
.
Operators
Operators are shorthand for functions. This is really neat. Because there are no primitives, all things are objects, and all objects have methods. And because Python ducktypes and does not need interfaces, all you need to do is implement the function matching your operator. For example the +=
operator (in-place addition) is implemented by the __iadd__
function.
Also, because operators are functions and are also defined in the operator
module, you can refer to them, and use them in higher-order functions: functools.reduce(operator.iadd, [1,2,3])
Functions
Are a type in python. The have properties, and even functions.
List
list
in python is an array-backed list. Other types of elements you may use are subscriptable
(can be accessed by key or index) and iterable
(can be iterated sequentially).
JavaScript
void
Return statement are not mandatory, and return types are not a thing, so nothing more to say here.
Primitives
Some types are represented by primitives similarly to Java. Fortunately, the dynamic typing prevents us from having to worry about it.
Operators
All types can be used with all operators. But watch your step. For fun, try things like
[] + []
[] + {}
{} + []
{} + {}
Functions
Every function in JavaScript is a Function object.
in Mozilla functions reference
Array
The basic list type in JavaScript is Array. To use other types, define it yourself, or use a library. There is no indication to the developer, however what type of list (or, come to that, of what type of variable at all) they are receiving in a function, but it is generally admitted that unless indicated otherwise, Array
is used.