Types & Inference

Welcome to the series "Java Coding"; brought to you by "Coding Mantis"!

We live in a world that needs to have everything labeled in order to define what anything is and how it behaves, and concretely sometimes it works out but sometimes it doesn't.

This post is about the cases where we don't need to explicitly define what it is in order to know it.

But before we dive any deeper let's first see what types are and how they work.

What is a type?

Every fragment of data has a type. That type defines its structure and the ways we can interact with said piece of data.

There are two categories of types: primitives (e.g int, double, boolean, etc.) and references (e.g String, Double, Long, etc.), and you can read more about them here.

So, if we wanted to declare a variable and assign a value to it in Java we would do it like this:

class SomeOtherClass {
  String value;

  void setValue(String value) {
    this.value = value;
  }
}

class SomeClass {

  void foo() {
    SomeOtherClass someVariable = new SomeOtherClass();
    someVariable.setValue("someValue");

    System.out.println(someVariable.toString());
  }
}

Pretty self-explanatory, we declared a variable "someVariable" of type "SomeOtherClass" and then accessed the "setValue" setter and the "toString" method. While this approach gets the job done, it can end up being quite verbose in the long run, and also it would make code maintenance a bit of a hassle.

What is "inference"?

The programming languages we use nowadays, well most of them, offer a nifty little feature called "type inference".

Ever heard of the phrase "you are what you eat"? We can definitely use this metaphorically here.

What is meant by the above metaphor is that a variable does not need to specify the type of data that it holds since the language can automatically infer the type for us based on the assignment that is done to that variable. Once a value is assigned, the variable will have access to all the properties defined and exposed by the assigned type.

The following is possible in Java since Java 11 (Java 10 if we count non-LTS versions):

class SomeOtherClass {
  String value;

  void setValue(String value) {
    this.value = value;
  }
}

class SomeClass {

  void foo() {
    var someVariable = new SomeOtherClass();
    someVariable.setValue("someValue");

    System.out.println(someVariable.toString());
  }
}

As we can see in the above example, the magic keyword for type inference in Java is "var". It allows for less verbose code, less cognitive complexity when not abused, and increases maintainability.

Imagine that in the future we'd like to substitute "SomeOtherClass" with a sibling class named "AnotherClass". Since sibling classes share the same characteristics and can substitute one another, all we'd have to change in the above example is half a line of code.

To examine it a bit deeper, say we substitute the constructor with a factory method that was previously returning an instance of "SomeOtherClass" and now needs to return an instance of "AnotherClass".

Assume that this factory method is pretty famous in our code base and gets invoked a lot; with type inference in place we only need to change its return type and though the logic will be affected our code will not.

Verdict

Even though the examples above are extremely simple in order to demonstrate how type inference can be achieved in Java, we can easily imagine the benefit to our day-to-day production code.

We can safely say that unless we're getting paid per line or line length of code, the less code we write the better and type inference helps us achieve exactly that. It's a win-win situation since the author has to type accumulatively much less code and the reader needs to gaze their eyes upon less in order to understand what a block of code does.

What do you think about the subject?