The danger of overusing primitive types
From early on, I was taught that primitive types in Java are the most performant and I need to prefer them when I can.
While this is true, since primitive types are much more lightweight than regular objects, overusing them creates a different kind of problem. Pretty much everything can be represented by a combination of strings and numbers. For instance, whether you need to represent an id for a user or an entity owned by the user (e.g. a blog post), you can use a long
.
Although this might seem innocent, it's extremely error-prone. Having a method that removes a user and another that removes an entity owned by the user will both accept a long
in our example.
fun deleteUser(long userId) {
[...]
}
fun deleteBlogPost(long postId) {
[...]
}
If someone is confused and passes the wrong id type, the best-case scenario is that a failure will be thrown at runtime and the worst-case scenario is that the wrong thing will be deleted (assuming both namespaces have an object with the same id). When error-prone situations such as these are identified, it's better to be proactive and set up practices that will avoid them.
There's an easy solution for these types of errors: create domain-specific types by wrapping the primitive types. In our previous example, you can have a UserID
and BlogPostId
classes that just contain a long
.
fun deleteUser(UserId userId) {
[...]
}
fun deleteBlogPost(BlogPostId postId) {
[...]
}
This way a method for deleting a user and a method deleting a post will have different parameter types. In case of confusion where the wrong kind of id is passed, it will be caught on compile time.
Of course, using this approach has a performance hit. The wrapping and unwrapping of objects will cost a few more milliseconds. If your application is not performance-sensitive I think it's worth the cost. If not, take a look at Kotlin inline classes and the upcoming Java inline classes feature that are designed to solve exactly this issue.
Happy coding!