DEV Community

Martin Häusler
Martin Häusler

Posted on • Updated on

Modernizing Java - A language feature wish list (Part 1)

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 getproperty() 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)
Enter fullscreen mode Exit fullscreen mode

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) { ... }
Enter fullscreen mode Exit fullscreen mode

(Let's assume that Student extends Person). Quick quiz: which overload is being called by the following code:

Object me = new Student();
print(me);
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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) { ... }
Enter fullscreen mode Exit fullscreen mode

... 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
   }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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.)

Discussion (10)

Collapse
wimtibackx profile image
Wim Tibackx

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.

Collapse
martinhaeusler profile image
Martin Häusler Author • Edited on

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:

// Java 8 (ok, but super verbose)
List<String> lowercased = myStrings.stream().map(String::toLowerCase).collect(Collectors.toList());

// Java 8 with static utils
List<String> lowercased = CollectionUtils.map(myStrings, String::toLowerCase)

// with Xtend-style extension methods:
List<String> lowercased = myStrings.map(String::toLowerCase);

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 than Null (i.e. it has it's own class). When you define a parameter as User?, it is a shorthand for User | Null, i.e. "this paramter has to be a User or null". 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 type User?, because Null defines no methods. So how do you transform User? into User? Flow typing has you covered:

   User? user = userService.loadUserById(1234);
   // user.getUsername() is illegal here because of union type with NULL
   if(user != null){
      // flow typing narrows union type down from (User | Null) to just User...
      user .getUsername(); // ... so this is ok here
   }

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) calls b(obj), and b(obj) in turn calls c(obj). c cannot deal with null parameters. Therefore, b will get a compiler error if it tries to pass it's nullable parameter into c. As a consequence, programmers tend to forbid the parameter of b to be nullable. Then a has an error, and so on. The treatment of nulls is shifting automatically and intuitively to where it belongs: towards the I/O handling at the system boundary.

Collapse
lluismf profile image
Lluís Josep Martínez • Edited on

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.

Collapse
martinhaeusler profile image
Martin Häusler Author

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)accepts null as its parameter, you can write null.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.

Collapse
lluismf profile image
Lluís Josep Martínez • Edited on

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.

Collapse
bertilmuth profile image
Bertil Muth

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.

Collapse
martinhaeusler profile image
Martin Häusler Author

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.

Collapse
craser profile image
Chris Raser • Edited on

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:

public class Fizz
{
    public String name = "fizz";

    public String getName() {
        return name;
    }
}

public class Buzz extends Fizz
{
    public String name = "buzz";

    public String getName() {
        return name;
    }
}

public class Main
{
    static public void main(String[] args) {
        Fizz fizz = new Buzz();
        System.out.println("fizz.name     : " + fizz.name);
        System.out.println("fizz.getName(): " + fizz.getName());
    }
}

Output:

fizz.name     : fizz
fizz.getName(): buzz

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() as Foo.class.getName(). (Though I've been doing a lot of work with Jackson XML parsing/generation lately, and having to pass Foo.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!

Collapse
martinhaeusler profile image
Martin Häusler Author

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 of public 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, then object.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.

Collapse
martinhaeusler profile image
Martin Häusler Author

The second part of the series is now online:
dev.to/martinhaeusler/modernizing-...