Programming languages have advanced a lot in recent years. There's a countless number of flavours and concepts, and it's increasing every day. I've seen and actively worked with quite a lot of languages, but I find myself coming back to Java after a while. Even though I personally believe that Java is one of the cleanest out there with extremely strict and clear semantics, from time to time it feels a bit outdated and I wish Java had a couple of features from other languages. In this article, I want to present a run-down of "language features done right". The list is of course stronlgy opinionated, but maybe it contains a concept you haven't been aware of so far.
This is the first post in a series of posts. I decided to split them up for easier readability.
Features from Xtend
Xtend is a really interesting language. It considers itself to be an extension of Java, and it actually compiles to Java source (!) code. Xtend brings a lot of new ideas to the table. Here's a selection of my personal highlights.
Syntactical Sugar for Getters & Setters
We all know getters and setters in Java. We all know why we need them, and we also all know that they can sometimes be a pain, especially on the caller side. Why can't I just write obj.name
instead of obj.getName()
and let the compiler figure out the rest? Well, that's exactly what you can do with Xtend. If it encounters a member access (e.g. obj.name
) it first checks if there is a matching public member, and if there isn't, it looks for a get
property()
method and calls it. It gets even better if your property is a number, because then you can use obj.value++
and others, which will be compiled into suitable getter/setter constructions. Plain, simple, effective. It's entirely a compiler feature, no need to change existing bytecode or runtimes anywhere. Why Java isn't doing this today is a mystery to me.
Extension Methods
The Java Runtime library is really quite good. However, traditionally this library has been known to be rather... conservative in what it offers. "If in doubt, leave it out" seems to have been the guiding principle. The consequence is that we have an entire army of "productivity libraries" out there which offer the infamous XYUtils
classes (e.g. StringUtils
, IOUtils
...). The most well-known ones are the Apache Commons packages as well as Google Guava. These helpers are great as far as functionality is concerned, but the syntax is a bit counter-intuitive. For example, StringUtils.leftPad(myString, 10)
is so much less intuitive than myString.leftPad(10)
, and also in the first case the programmer needs to know about the existence of StringUtils
, whereas in the second case, the code completion of the IDE will just tell them. Again, this is an entirely syntactical matter. Compilers to the rescue! Xtend allows you to import a static method as an extension
(here's the documentation ):
import static extension java.util.Collections.*; // note the 'extension' keyword
// ... later in your code ...
myList.max(); // compiles to Collections.max(myList)
So far, Xtend is the only language that I have ever seen which got this feature nailed down right. That's how you deal with utils. It might seem like a ridiculously small change, but once you get used to this, you can't do without it. For javac
, this would really be a low-hanging fruit. I would even take it one step further and drop the extension
keyword. Any static method with a matching signature in my current scope should be eligible to be used as an extension method, with methods defined on the object taking precedence in case of name clashes.
Talking to classes without .class
One of the greatest (syntactical) pain points when writing reflective code is the fact that you are constantly writing .class
, for example in MyClass.class.getName()
. Well, duh. Of course want to talk about the class object when I write MyClass
. So why do I have to even write .class
here? Because the parser requires one look-ahead less? Come on. Xtend helps out here as well. Just writing MyClass.getName()
is enough and it compiles to MyClass.class.getName()
. Of course, MyClass.getName()
could also refer to a public static String getName()
method defined in the class. In Xtend, this static method would take precedence (you can qualify the access in Xtend to make it explicit). But let's be honest here: I've yet to define my first public static Field[] getDeclaredFields()
method in any Java class. Another small change, another step towards developer happyness.
The almighty dispatch
There's an issue with method dispatching in Java (and plenty of other languages, including C#) that goes unnoticed 99% of the time, but when it happens, it's really going to cause headaches for you. Imagine you have several overloads of a method like this:
public void print(Person p) { ... }
public void print(Student s) { ... }
public void print(Object o) { ... }
(Let's assume that Student
extends Person
). Quick quiz: which overload is being called by the following code:
Object me = new Student();
print(me);
The answer is print(Object o)
. Because Java selects the method overload to call based on the static type (i.e. the type of the variable, in this case Object
), rather than the dynamic one (the type of the actual value stored in the variable, in this case Student
). This is mostly for efficiency and alright in most cases, it's called "single dispatch". But what if you want the other case? You would have to do this:
public void print(Object o) {
if(o instanceof Student) {
print((Student)o);
}else if(o instanceof Person) {
print((Person)o);
}else{
// print the object like before
}
}
This code realizes what is called a "double dispatch", and writing this code is a pain. It's repetitive and overall something that I feel the compiler should do. Also, you need to take care to check the most specific type first. In Xtend, all you have to do to achieve the behaviour above is using the dispatch
keyword:
public dispatch void print(Person p) { ... }
public dispatch void print(Student s) { ... }
public dispatch void print(Object o) { ... }
... which will compile to exactly the instanceof
-cascade in print(Object)
as outlined above. Thank you compiler for saving me some typing!
Features from Ceylon
Ceylon is a JVM alien, i.e. a language which compiles to JVM byte code. It has a lot of features (some of them debatable), but it also has some gems in its language toolset.
Flow Typing
Flow typing is a feature which is used by many languages nowadays (hello, TypeScript!), but it was in Ceylon where I first witnessed this feature in its full form, and I was blown away. It's easy to explain what it does. We all have written code like this in Java:
for(Object element : myCollection){
if(element instanceof Dog) {
((Dog)element).bark()
} else {
// ... do something else
}
}
Did it ever strike you why you have to do the down-cast to Dog
in the example above after checking that element
is indeed of type Dog
? It's repetitive, it often means introducing new variables (for which you have to pick a proper name...), and overall it's just plain annoying to write. In Ceylon, the above cast is not necessary. At all. Once you verified that the object stored in a variable is of a certain class via a type-check, the variable will be treated as that type by the compiler while you are inside the conditional block. We don't even need new snytax for this feature. We just get rid of noise. Seeing this in practice for the first time was like magic to me - I never knew I wanted this, but now I do, and I frown about every down-cast I have to do after an instanceof
check.
Nullability and strict-nullness via ?
You know the first thing I do in 99% of my method implementations in Java? It goes like this...
public void save(User u){
if(u == null){
throw new IllegalArgumentException("User must not be NULL!");
}
// do ACTUAL work
}
This is widely considered to be good practice, and it is. Tracing a null
value backwards through the code base in the debugger is no fun at all. However, in the method above, I specify that I want a User
instance. Strictly speaking, null
is no User
(which you can tell, because null instanceof User
actually returns false
in Java). And yet, calling save(null)
is legal in Java. Ceylon provides an elegant way to fix this. By default, you cannot pass null
as argument, except if the argument specifically allows a null
value. This is done by appending a ?
to the parameter:
public void save(User? u){ ... } // accepts nulls
public void save(User u){ ... } // compiler rejects (potential) null values
The boon here is that in the second case, I as the programmer do not need to perform the manual null
-ness check at all. The compiler does it at compile time. So this method is not only safer, it is also faster at runtime and catches errors at the earliest possible point in time (the compile phase). Win-win.
Compiler-checked reflection
This is a unique feature that I haven't really seen anywhere else other than in Ceylon. Here's the original blog post where I discovered this gem:
value d = `value Movie.year`;
print( d.name); //prints "year"
In this case, Movie
is a class. We reflectively access the field Movie.year
. The Java equivalent would be Movie.getClass().getField("year")
. So how is Ceylon better? Well, if Movie
actually has no field named year
, the compiler will complain. This is a game-changer for reflective coding. How many times did your hibernate templates not work because a field name was mis-spelled? Or lost during a refactoring? There you go.
This concludes the first part of this series. I hope you enjoyed it, and maybe learned about a few new language features. Xtend and Ceylon have many other things to offer, and I strongly suggest checking them out, at least for the experience if not for production.
(The second part is now online.)
Top comments (10)
Thinking from the POV of starting out in an existing codebase that uses these features, I’m not sure I agree with all your points.
Extension methods: I absolutely agree that it’s an inconvenience to always have to add libraries like Apache Commons to projects, and having to divert to those for a large part of the use cases. What seems to me like the disadvantage of extension methods though, is that it would be less explicitly clear what is actually happening and more difficult to port to other codebases or even parts of the same codebase.
If now, I remember a call to StringUtils.leftPad, I can check which classes named StringUtils are on the classPath, or if that fails google which classes called StringUtils offer leftpad functionality and go from there. In the extension methods scenario, I wouldn’t really explicitly know where the leftPad method came from (if I only remembered that line), and especially junior developers might think it’s just part of the standard library, not realizing extension methods were being used. Maybe this can be worked around with good IDE support, but I’m not sure.
Strict null-ness: How does this work with implicit nulls? For example: I have an object called Person, which I retrieve from database (for example via Hibernate). Person has firstName, lastName, and age. For one person I might retrieve, the age could be null. What if I then pass in person.age to some method which accepts an Integer argument? I would assume that Ceylon can’t guard against this case at compile-time?
Additionally, there’s also the difficulty of backwards compatability. Given this syntax, this seems to be the one feature on your list that breaks that.
Compiler-checked reflection: That is a very nice feature!
All in all, nice overview, hadn’t heard of Ceylon or Xtend yet.
Thanks for the response! I agree that some of the features I described in this blog post (in particular strict null-ness) are only intended for new projects. Then again, this is a long-term wish list, we all know that Java isn't exactly the fastest-moving language out there when it comes to the introduction of new features.
Extension methods: I can only talk about the Xtend-case here, I've yet to see this feature in another language working in this fashion. In Xtend, you still have to import the utils classes you want to use; that alone limits the room for misunderstandings. In the Xtend IDE, the call to an extension method is highlighted slightly differently than a regular method call, so it's always clear what you are calling. If in doubt, a quick "jump to declaration" will give a definitive answer to what is being called here. Here's another example why extension methods make life so much easier:
Strict null-ness: The way this works in Ceylon under the hood is by using so-called Union Types.
null
is not assignable to any class in Ceylon, other thanNull
(i.e. it has it's own class). When you define a parameter asUser?
, it is a shorthand forUser | Null
, i.e. "this paramter has to be aUser
ornull
". A method call on a variable of a union type is only valid if all type members of the union support the method. Any method call, e.g.getUsername()
, is invalid on a parameter variable of typeUser?
, becauseNull
defines no methods. So how do you transformUser?
intoUser
? Flow typing has you covered:Similar concepts were introduced e.g. in TypeScript. And yes, this check can break existing code bases. It's an opt-in compiler feature and migrating an existing code base is not always easy. However, I've seen an interesting behaviour in programmers. Let's say method
a(obj)
callsb(obj)
, andb(obj)
in turn callsc(obj)
.c
cannot deal withnull
parameters. Therefore,b
will get a compiler error if it tries to pass it's nullable parameter intoc
. As a consequence, programmers tend to forbid the parameter ofb
to be nullable. Thena
has an error, and so on. The treatment ofnull
s is shifting automatically and intuitively to where it belongs: towards the I/O handling at the system boundary.Why can't I just write obj.name instead of obj.getName()
You can, simply make name public. In many cases they are POJOs and encapsulation is not needed. It's more a problem of the frameworks that require them to be beans (have a getter and a setter). Hibernate for instance.
the infamous XYUtils classes
I agree that the syntax is not optimal, but the goal of this classes is to allow NULL therefore you can't use instance methods (many of them already exist in the Java API by the way). They must be static and take the string, collection ... as a parameter.
The almighty dispatch
Perhaps instead of overloading print you should use polymorphism and move the print method to the corresponding classes (defining a Printable interface to make it better) Seems more natural.
First of all, Java is fine as-is. We are talking mostly about "quality of life" enhancements here. Of course you can work without them, countless successful Java projects show that.
Public properties: if I find a junior developer doing that at our company, I'll have a serious word with them. I'm absolutely not willing, under no circumstances, to give up on accessor methods. That point isn't debatable for me. However, I see nothing wrong with some syntactic sugar on the caller side.
Extension methods: I think there's a misunderstanding here. Using a static util method in an extension-like fashion does not change its semantics, at all. If
myStringUtilFunction(s)
acceptsnull
as its parameter, you can writenull.myStringUtilFunction()
and it will run just fine. It may look awkward (and nobody would do that), but the result is the same as when calling it the old-fashioned way.Dispatch: I agree, in some cases you can use polymorphism to avoid the issue alltogether. And I think that if you can, by all means do it. But there are cases where code like in the example I outlined is unavoidable. Sometimes you need to separate the algorithm from the objects it runs on. You can work around it by implementing a full-blown variant of the visitor pattern (which really is nothing else than a double dispatch in fancy disguise), but that's a lot of code and a lot of chances for bugs to creep in. I think it would be nice (and in particular it wouldn't hurt) to have this option in Java. If you have no use for it - that's perfectly fine, you could safely ignore it.
Public properties
Even when Sun defined core J2EE patterns (a couple of decades ago) recommended using public properties for transfer objects (aka DTOs) check this oracle.com/technetwork/java/transf... Maybe your junior dev read that !
I don't say it should be used blindly, but in this case IMHO is acceptable. In other cases (Hibernate for instance) is a requirement to implement lazy initialization.
It seems to me some of the features you have requsted are part of Kotlin (which I am learning at the moment). So, e.g. concerning your property request, see:
kotlinlang.org/docs/reference/prop...
Have you heard of it? Kotlin is fully compatible with Java, btw.
Of course, it's almost impossible today to be a Java developer and not having heard about Kotlin. I'm sure that most features in the article appear in several languages - I just listed them for the languages where I saw them for the first time. I never actually tried Kotlin so far, maybe I'll get the chance one day.
I liked reading this, thanks for posting! I whole-heartedly support features that help reduce the clutter of null checks.
But I've worked with languages that have the "syntactic sugar" for getters and setters, and I found the experience unpleasant. Maybe it's years (and years...) of ingrained habit, but the difference between a variable assignment and a function call is important, especially when reading through code and debugging.
But here's the real hitch as to why it's problematic to have the compiler "figure out" what you mean when you type,
foo.name
:Output:
That's because Java dispatches to member fields based on the reference type, (
Fizz
in this example), but dispatches to methods based on the object type (Buzz
).Having the compiler "figure out" what you mean requires accounting for this, which requires that developers memorize yet more arcane rules.
I'm all for simplicity and readability, but I think in this case it's an illusion. Typing & reading
getFoo()
aren't the hard part of coding. Understanding what the code does is the hard part. And I think adding this syntactic sugar make the hard part marginally harder in order to make the easy part easier. I just don't think that's a good trade-off.I have similar thoughts on letting the compiler resolve
Foo.getName()
asFoo.class.getName()
. (Though I've been doing a lot of work with Jackson XML parsing/generation lately, and having to passFoo.class
as a type token is... unattractive.)Thanks for posting this. I like mulling new language features, and I'm now curious to check out Ceylon. Thanks for the pointer!
Thanks for reading! I'm glad you enjoyed it. The next part will be online this week or next week (I hope, pretty busy over here).
Regarding your comment on the getter/setter syntax. To be honest, the problem that I see in your example code is not so much the fact that the programmer has to be aware that they are calling a method. I think the problem lies within the fact that there is something other than a method that can be adressed with
object.something
. I've seen, written and reviewed my fair share of Java code in particular and so far, nobody could ever provide me with a reasonable example why public member variables (with the sole exception ofpublic static final
constants) are necessary. They break encapsulation. Once exposed in public API, a field can never be replaced with a getter, whereas a method body can be modified freely as long as the contract isn't violated. Public members always means exposing more internal logic than necessary. To be honest, I consider public non-constant fields deprecated and even harmful. If you only ever expose methods, thenobject.something
can only have one meaning: it's a getter (or setter). No arcane magic here to keep in mind. Perhaps somebody can prove me wrong here, but I've yet to see a real use case for public non-constant fields, other than "I'm too lazy to write a getter/setter pair for that."Ceylon has cool concepts, the syntax is quite hard to handle if you ask me. As far as I know, language development for Ceylon has pretty much died, unfortunately. Still, the features and core ideas are great.
The second part of the series is now online:
dev.to/martinhaeusler/modernizing-...