DEV Community

Discussion on: Did you ever try to write Java equals() with clean code style?

Collapse
 
johannesvollmer profile image
Johannes Vollmer • Edited

My personal strategy has two methods: One for comparing against any Object, and one more to compare against objects of the same Type.
This will also get rid of the noisy boilerplate null checks and boilerplate class comparisons and boilerplate runtime cast (when used with Cat objects at compile time).

// inside class Cat

// contains verbose boilerplate code
@Override 
public boolean equals(@Nullable Object object){
    return object == this || (
        other != null 
        && other.getClass() == this.getClass() 
        && this.equals((Cat)object) // call the other method
    );
}

// contains core equality logic
public boolean equals(@NonNull Cat cat){
    return cat == this || (
        cat.name.equals(this.name)
        && cat.age == this.age
    );
}
Collapse
 
voins profile image
Alexey Voinov

Yeah, this approach looks nice. But I see two problems here:

  1. this nice and shiny @NonNull annotation does nothing. You still can call the second method even with literal null argument and compiler won't even issue a warning. Yeah, IDE may warn you about it, but imagine, that someone on your team is still using vim or emacs. :)

  2. instanceof is not exactly what you need to use if your Cat class has any descendants.

Collapse
 
johannesvollmer profile image
Johannes Vollmer • Edited

That's right. That instanceof call is problematic. I've editet that to compare classes.

To be honest, I personally think, the best approach to handling null in Java is simply to avoid it. Nullability is, in my opinion, very rarely really needed. Java 8 has the Optional<T>, which pretty much renders null useless (unless you're in that 0.5% bottleneck).

That's why I think @NonNull should be the implicit default for Parameters or basically any part of the API, and Optional<T> should be used instead, in your team. Exclusively in implementation you sometimes cannot avoid null, but you'll have to live with that.

The compiler may not warn you when you pass null as an argument, regardless of using @NonNull. Your App will crash either way. The only difference is a slightly more precise Exception when it crashes.

I've come to a point where just seeing null as an argument is suspicious. Even without Optional<T> you could often write an overloaded method that simply omits that argument.

So that's a convention you'd have to follow, just like so many conventions in Java. It's not that much work to check for any null literals, and it saves you a hundred of noisy and verbose null-checks.

If you want to use a language that can be used without tons of conventions, don't use Java. I recently started learning Rust, which seems to solve some of these Problems very well. If you need JVM-Bytecode, try Kotlin. It handles null more elegant than Java.

Thread Thread
 
voins profile image
Alexey Voinov

Yes! Yes! Yes! I agree. Well, almost. :) It's still easy to get null without actually writing literal null. And you should probably check for nulls in your public API. But otherwise, yes, I prefer to trust my own (or my team's) code in private scope.

But all of that (including the edited version of your example), leads us back to the original problem: how to write equals() with clean code rules in mind. :) I like your answer: use Kotlin, but unfortunately, I'm working on a rather large project and I cannot just change the language.

Thank you. :)

Thread Thread
 
johannesvollmer profile image
Johannes Vollmer

Thanks, I agree. :)

Did you try to speak with your team about Kotlin? I heard it can be used virtually side by side with Java source code. You can exchange maybe just a single file with Kotlin.
Of course, the developers would have to learn Kotlin (which is said to be not that hard if you already know Java).

In hindsight, I realize that I just like the separation of logic and boilerplate in my example.

Cheers! :)

Thread Thread
 
voins profile image
Alexey Voinov

Well, actually I am the one, who's against adding new languages to the code base. It just doesn't work with the team of about a thousand engineers working on a monolithic java project with size over 10GB (source code only). :) I know how to deal with such projects and turning it into something manageable, but it seems like our management don't. :)

Thread Thread
 
johannesvollmer profile image
Johannes Vollmer

I see, that sounds difficult. :)