DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Introducing Graph Concepts in Java With Eclipse JNoSQL, Part 3: Understanding Janus
  • Introducing Graph Concepts in Java With Eclipse JNoSQL, Part 2: Understanding Neo4j
  • How to Introduce a New API Quickly Using Micronaut
  • Introducing Graph Concepts in Java With Eclipse JNoSQL

Trending

  • SaaS in an Enterprise - An Implementation Roadmap
  • Go 1.24+ Native FIPS Support for Easier Compliance
  • Software Delivery at Scale: Centralized Jenkins Pipeline for Optimal Efficiency
  • Developers Beware: Slopsquatting and Vibe Coding Can Increase Risk of AI-Powered Attacks
  1. DZone
  2. Coding
  3. Java
  4. 26 Items for Dissecting Java Local Variable Type Inference (Var Type)

26 Items for Dissecting Java Local Variable Type Inference (Var Type)

Learn everything you need to know and more about the var type inference!

By 
Anghel Leonard user avatar
Anghel Leonard
DZone Core CORE ·
Updated Oct. 24, 19 · Presentation
Likes (30)
Comment
Save
Tweet
Share
31.8K Views

Join the DZone community and get the full member experience.

Join For Free

This article is also part of my book Java Coding Problems.

Let's get started!
Image title

The Java Local Variable Type Inference (LVTI), or shortly, the var type (the identifiervaris not a keyword, is a reserved type name), was added in Java 10 via JEP 286: Local-Variable Type Inference. As a 100 percent compile feature, it doesn't affect bytecode, runtime, or performance. Mainly, the compiler will inspect the right-hand side and infer the concrete type. It looks at the right-hand side of the declaration, and if there is an initializer, then it simply uses that type to replacevar. Addtionally, it is useful to reduce verbosity, redundancy, and boilerplate code. It is also meant to speed up the ceremonies involved while writing code. For example, it is very handy to writevar evenAndOdd =...instead of Map <Boolean, List <Integer>> evenAndOdd.... Depending on the use case, it has a trade-off in code readability that is covered in the first item below.

Further, it is a list of 26 items meant to cover thevartype use cases including its limitations.

Item 1: Strive for Meaningful Local Variables Names

Commonly, we focus on meaningful names for global variables, but we don't put the same attention in choosing the local variables names. Especially when our methods are short, have good names, and implementations, we may tend to drastically shortcut the local variables names. But when we go withvarinstead of explicit types, the concrete types are inferred by the compiler. As a consequence, it is more difficult for humans to read/understand the code. This is the place wherevarcan cut a piece of readability of the code. Most of the time, this is happening because we tend to look at the type as the primary information and to the variable name as the secondary information, while this should be vice versa.

Example 1:

Even here, some people may still sustain that local variables names are too short. Let's take a look:

// HAVING
public boolean callDocumentationTask() {

    DocumentationTool dtl = ToolProvider.getSystemDocumentationTool();
    DocumentationTask dtt = dtl.getTask(...);

    return dtt.call();
}

When using or switching tovar, avoid:

// AVOID
public boolean callDocumentationTask() {

    var dtl = ToolProvider.getSystemDocumentationTool();
    var dtt = dtl.getTask(...);

    return dtt.call();
}

Prefer:

// PREFER
public boolean callDocumentationTask() {

    var documentationTool = ToolProvider.getSystemDocumentationTool();
    var documentationTask = documentationTool.getTask(...);

  return documentationTask.call();
}

Example 2:

Avoid:

// AVOID
public List<Product> fetchProducts(long userId) {

    var u = userRepository.findById(userId);
    var p = u.getCart();

    return p;
}

Prefer:

// PREFER
public List<Product> fetchProducts(long userId) {

    var user = userRepository.findById(userId);
    var productList = user.getCart();

    return productList;
}

Example 3:

Striving to use meaningful names for local variables doesn't mean to fall into the over-naming technique.
Avoid having a single type of output stream in a short method:

// AVOID
var byteArrayOutputStream = new ByteArrayOutputStream();

Instead, use this, which should be clear enough:

// PREFER
var outputStream = new ByteArrayOutputStream();

// or
var outputStreamOfFoo = new ByteArrayOutputStream();

Lastly, did you know that Java internally uses a class named:

InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState

Well, naming a variable of this type should be challenging :)

Item 2: Use literals to Help var to Infer the Expected Primitive Type (int, long, float, double)

Without using literals for the primitive types, we may discover that the expected and inferred types may differ. This is caused by the implicit type casting used by thevartype.

For example, the following two snippets of code are behaving as expected. First, we declare a booleanand acharusing explicit types:

boolean flag = true; // this is of type boolean
char a = 'a';        // this is of type char

Now, we usevar instead of explicit primitive types:

var flag = true; // this is inferred as boolean
var a = 'a';     // this is inferred as char

Okay, so far, so good. Next, let's follow the same logic for anint, along, adouble, and afloat:

int intNumber = 20;       // this is of type int
long longNumber = 20;     // this is of type long
float floatNumber = 20;   // this is of type float, 20.0
double doubleNumber = 20; // this is of type double, 20.0

While the above snippet of code is trivial and clear, now let's usevarinstead of explicit types.

Avoid:

// AVOID
var intNumber = 20;    // this is inferred as int
var longNumber = 20;   // this is inferred as int
var floatNumber = 20;  // this is inferred as int
var doubleNumber = 20; // this is inferred as int

So, all four variables have been inferred asint. In order to fix this behavior, we need to rely on Java literals.

Prefer:

// PREFER
var intNumber = 20;     // this is inferred as int
var longNumber = 20L;   // this is inferred as long
var floatNumber = 20F;  // this is inferred as float, 20.0
var doubleNumber = 20D; // this is inferred as double, 20.0

But what is happening if we declare a number using some explicit decimals?

Avoid doing this if you think that your number is a float:

// AVOID, IF THIS IS A FLOAT
var floatNumber = 20.5; // this is inferred as double

Prefer using the corresponding literal in order to avoid this issue:

// PREFER, IF THIS IS A FLOAT
var floatNumber = 20.5F; // this is inferred as float

Item 3: In Certain Cases, Var and Implicit Type Casting May Sustain Maintainability

In certain cases, relying onvarand implicit type casting may sustain maintainability. For example, let's assume that our code sits between two methods of an API (or services, endpoints, etc). One method receives a shopping cart with different items and computes the best price by comparing different prices on the market and return the total price as a float. Another method simply debits thisfloatprice on a card.

First, let's look at the API method that computes the best price:

public float computeBestPrice(String[] items) {
   ...
   float price = ...;

   return price;
}

Second, let's check out the API method that reflects the debit card:

public boolean debitCard(float amount, ...) {
    ...
}      

Now, we put our code between these two external services methods as a client of them. Our users/customers can choose the items to buy, and we compute the best price for them and debit their cards:

// AVOID
public void purchaseCart(long customerId) {
    ...
    float price = computeBestPrice(...);
    debitCard(price, ...);
}

After a while, the company that owns the API decides to cut off the prices' decimals as a discount policy and useintinstead offloat. Of course, we update the dependencies as well. So, they modify the API code like this:

public int computeBestPrice(String[] items) {
   ...
   float realprice = ...;
   ...
   int price = (int) realprice;

   return price;
}

public boolean debitCard(int amount, ...) {
    ...
} 

The problem is that our code is using explicit float; therefore, it will not tolerate these modifications well. The code will produce compile-time errors. But if we have anticipated such situations and usedvarinstead of float, then our code will work without problems thanks to the implicit type casting:

// PREFER
public void purchaseCart(long customerId) {
    ...
    var price = computeBestPrice(...);
    debitCard(price, ...);
}

Item 4: When literals Are Not a Solution, Rely on Explicit Downcast or Better Avoid var

Some Java primitives types don't take advantage of literals. For example, this includes byteandshortprimitive types. By using the explicit primitive types, we can do this with no issue.

Use this instead of usingvar:

// PREFER THIS INSTEAD OF USING VAR
byte byteNumber = 45;     // this is of type byte
short shortNumber = 4533; // this is of type short

But, why to prefer in this situation explicit types instead of usingvar? Well, let's switch the above code tovar. Notice that the inferred type isintin both cases, so not the expected types.

And, avoid the following:

// AVOID
var byteNumber = 45;    // this is inferred as int
var shortNumber = 4533; // this is inferred as int

There are no literals to jump in here and help us, so we need to rely on an explicit downcast. Personally, I will avoid doing this since I don't see any advantage in it, but it works.

Use this only if you really want to usevar:

// PREFER THIS ONLY IF YOU WANT TO USE VAR
var byteNumber = (byte) 45;     // this is inferred as byte
var shortNumber = (short) 4533; // this is inferred as short

Item 5: Avoid Using var if the Called names Don't Contain Enough Type Information for Humans

Usingvarhas the advantage of providing more concise code. For example, in the case of using constructors (which is a common use case for local variables), we can simply avoid the necessity of repeating the class name and, therefore, eliminate redundancy.

Avoid the following:

// AVOID
MemoryCacheImageInputStream inputStream = new MemoryCacheImageInputStream(...);

Instead, use:

// PREFER
var inputStream = new MemoryCacheImageInputStream(...);

Or, in a construction like below, var is, again, a nice approach for simplifying the code without losing information.

Avoid:

// AVOID
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fm = compiler.getStandardFileManager(...);

Instead, use the following code:

// PREFER
var compiler = ToolProvider.getSystemJavaCompiler();
var fileManager = compiler.getStandardFileManager(...);

Well, why do we fill comfortable with thesevar-based examples? Because the needed information is there in the called names. But whenvar, in combination with the called names, leads to the loss of information, then it is better to avoid the usage ofvar.

Avoid:

// AVOID
public File fetchCartContent() {
    return new File(...);
}

// As a human, is hard to infer the "cart" type without 
// inspecting the fetchCartContent() method
var cart = fetchCartContent();

Instead, use:

// PREFER
public File fetchCartContent() {
    return new File(...);
}

File cart = fetchCartContent();

Or, think of an example based onjava.nio.channels.Selector. This class has astaticmethod namedopen()that returns a new and openSelectorbut is easy to think thatSelector.open()may return abooleanrepresenting the success or unsuccess of opening the current selector, or evenvoid. Usingvarwhile losing information can induce exactly these kinds of confusions.

Item 6: the var Type Is Ensuring Compile-Time Safety

Thevartype is compile-time safety. This means that we cannot compile an application, which tries to achieve a wrong assignment. For example, the below code will not compile:

// IT DOESN'T COMPILE
var items = 10;
items = "10 items"; // incompatible types: String cannot be converted to int

While this will successfully compile:

var items = 10;
items = 20;

And, this successfully compiles as well:

var items = "10";
items = "10 items" ;

So once the compiler has inferred the concrete/actual type ofvar, we can assign the only values of that type.

Item 7: var Cannot Be Used to Create an Instance of a Concrete Type and Assign it to a Variable of an Interface Type

In Java, we are used with the "programming to the interface" technique.

For example, we create an instance ofArrayList, as shown below (bind the code to the abstraction):

List<String> products = new ArrayList<>();

And, we avoid something like this (bind the code to the implementation):

ArrayList<String> products = new ArrayList<>();

So, it is preferable to follow the first example and instantiate theArrayListclass, but we also need to declare a variable of typeList. SinceListis an interface (a contract), we can easily switch the instantiation to other implementation ofListwithout further modifications.

Well, while "programming to the interface" is the way to go, varcannot take advantage of it. This means that when we usevar, the inferred type is the concrete implementation. For example, in the next snippet of code, the inferred type is ArrayList <String>:

var productList = new ArrayList<String>(); // inferred as ArrayList<String>

There are several arguments that sustain this behavior:

  • First,var is used for local variables, where, in most of the cases, "programming to the interface" is less "exploited" than in the case of method parameters/return types or fields types

  • The scope of local variables should be small, so in case of issues caused by switching to another implementation, it should have a small impact in detecting and fixing them

  • varperceives the code from its right as being an initializer used for inferring the actual type, and if, in the future, the initializer will be modified, then the inferred type may differ, resulting in issues in the subsequent code that relies on this variable;

Item 8: the Possibility of Unexpected Inferred Types

The var type combined with the diamond operator may lead to unexpected inferred types if the information needed for inferring the expected type is not present

Before Java 7 in Project Coin, we'd write something like this:

// explicitly specifying generic class's instantiation parameter type
List<String> products = new ArrayList<String>();

From Java 7 onwards using Project Coin, we have the diamond operator, which is capable of inferring the generic class instantiation parameter type, as follows:

// inferring generic class's instantiation parameter type 
List<String> products = new ArrayList<>();

Having this example on the table andvaron the learning list, you may ask what will be the inferred type of the below code?

First, you should avoid using:

// AVOID
var productList = new ArrayList<>(); // is inferred as ArrayList<Object>

The inferred type will be theArrayListof theObject. This is happening because the information needed for inferring the expected type (String) is not present. This causes the inferred type to be the broadest applicable type, which, in this case, isObject.

So excepting the case when this is what we want, we must provide the information needed for inferring the expected type. This can be done directly or can be derived.

Prefer (directly):

// PREFER
var productList = new ArrayList<String>(); // inferred as ArrayList<String>

Prefer (derived):

var productStack = new ArrayDeque<String>(); 
var productList = new ArrayList<>(productStack); // inferred as ArrayList<String>

Prefer (derived):

Product p1 = new Product();
Product p2 = new Product();

var listOfProduct = List.of(p1, p2); // inferred as List<Product>

// DON'T DO THIS
var listofProduct = new ArrayList<>(); // inferred as ArrayList<Object>
listofProduct.add(p1);
listofProduct.add(p2);

Item 9: Assigning an Array to var Doesn't Require Brackets, []

We all know how to declare an array in Java. We do it like this (check the left-side brackets):

int[] numbers = new int[5];

// or, less preferred
int numbers[] = new int[5];

Now, how about using var? In this case, there is no need to use brackets in the left side.

Avoid the following (this will not even compile):

// IT DOESN'T COMPILE
var[] numbers = new int[5];

// or
var numbers[] = new int[5];

Instead, use:

// PREFER
var numbers = new int[5]; // inferred as array of int

numbers[0] = 2;   // work
numbers[0] = 2.2; // doesn't work
numbers[0] = "2"; // doesn't work

Also, this usage ofvardoesn't compile. This is happening because the right-hand side doesn't have its own type:

// explicit type work as expected
int[] numbers = {1, 2, 3};

// IT DOESN'T COMPILE
var numbers = {1, 2, 3};
var numbers[] = {1, 2, 3};
var[] numbers = {1, 2, 3};

Item 10: the var type cannot be used in compound declarations

If you are a fan of compound declarations, then you have to know thatvaris not allowed in such declarations. The following code doesn't compile:

// IT DOESN'T COMPILE
// error: 'var' is not allowed in a compound declaration
var hello = "hello", bye = "bye", welcome = "welcome";

Instead, use:

// PREFER
String hello = "hello", bye = "bye", welcome = "welcome";

Or, use:

// PREFER
var hello = "hello";
var bye = "bye";
var welcome = "welcome";

Item 11: Local Variables Should Strive to Minimize Their Scope. The var Type Reinforces This Statement.

Keep a small scope for local variables — I am sure that you heard this statement beforevarexists.

Readability and quick bug fixes are arguments that sustain this statement. For example, let's define a Java stack as follows:

Avoid:

// AVOID
...
var stack = new Stack<String>();
stack.push("George");
stack.push("Tyllen");
stack.push("Martin");
stack.push("Kelly");
...

// 50 lines of code that doesn't use stack

// George, Tyllen, Martin, Kelly  
stack.forEach(...);
...

Notice that we call theforEach()method, which is inherited from the java.util.Vector. This method will traverse the stack as any vector, and this is what we want. Now, we decide to switch fromStacktoArrayDeque. When we do that, theforEach()method will be the one fromArrayDeque, which will traverse the stack as a stack (LIFO).

// AVOID
...
var stack = new ArrayDeque<String>();
stack.push("George");
stack.push("Tyllen");
stack.push("Martin");
stack.push("Kelly");
...

// 50 lines of code that doesn't use stack

// Kelly, Martin, Tyllen, George
stack.forEach(...);
...

This is not what we want, but it is hard to see that a bug was introduced since the code containing theforEach() part is not in the proximity of the code where the developer completed the modifications. In order to maximize the chances of getting this bug fixed quickly and avoid a bunch of scrolls up and down to understand what is happening, it is much better to write this code with a small scope for thestackvariable.

It is best to use the following:

// PREFER
...
var stack = new Stack<String>();
stack.push("George");
stack.push("Tyllen");
stack.push("Martin");
stack.push("Kelly");
...

// George, Tyllen, Martin, Kelly  
stack.forEach(...);
...

// 50 lines of code that doesn't use stack

Now, when the developer switches fromStacktoArrayQueue, they should notice the bug faster and fix it.

Item 12: the var Type Facilitates Different Types of Operands on the Right-Hand Side of the Ternary Operator

We can use different types of operands on the right-hand side of a ternary operator.

With explicit types, we cannot compile this:

// IT DOESN'T COMPILE
List code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
// or
Set code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);

Nevertheless, we can do this:

Collection code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
Object code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);

Or, we cannot compile this:

// IT DOESN'T COMPILE
int code = intOrString ? 12112 : "12112";
String code = intOrString ? 12112 : "12112";

But, we can do this:

Serializable code = intOrString ? 12112 : "12112";
Object code = intOrString ? 12112 : "12112";

In such cases, we prefer var:

// PREFER
// inferred as Collection<Integer>
var code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
// inferred as Serializable
var code = intOrString ? 12112 : "12112";

Don't conclude from these examples thevartype is inferred at runtime! It is NOT!

Of course,varworks when we use the same types of operands as well:

// inferred as float
var code = oneOrTwoDigits ? 1211.2f : 1211.25f;

Item 13: the var Type Can Be Used Inside for Loops

We can easily replace explicit types used insideforloops with thevartype. Here are two examples.

Replace the explicit typeintwith var:

// explicit type
for (int i = 0; i < 5; i++) {
     ...
}

// using var
for (var i = 0; i < 5; i++) { // i is inferred of type int
     ...
}

Then, replace the explicit typeOrderwithvar:

List<Order> orderList = ...;

// explicit type
for (Order order : orderList) {
    ...
}

// using var
for (var order : orderList) { // order type is inferred as Order
    ...
}

Item 14: the var Type Works Just Fine With the Java 8 Stream

It is pretty straightforward to combine Java 10varwith the Java 8 Stream.

You will need to replace the explicit typeStreamwithvar:

Example 1

// explicit type
Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);                
numbers.filter(t -> t % 2 == 0).forEach(System.out::println);

// using var
var numbers = Stream.of(1, 2, 3, 4, 5); // inferred as Stream<Integer>               
numbers.filter(t -> t % 2 == 0).forEach(System.out::println);

Example 2

// explicit types
Stream<String> paths = Files.lines(Path.of("..."));
List<File> files = paths.map(p -> new File(p)).collect(toList());

// using var
var paths = Files.lines(Path.of("...")); // inferred as Stream<String>
var files = paths.map(p -> new File(p)).collect(toList()); // inferred as List<File>

Item 15: the var Type Can Be Used to Declare Local Variables Meant to Break Up Nested/Large Chains of Expressions

The var type can be used to declare local variables, which is meant to break up nested/large chains of expressions

Large/nested expression look impressive, and usually, they are perceived as clever pieces of code. Sometimes, we wrote them like this on purpose; another time, we start from a small expression and keep growing its logic until we end up with a large one. In order to increase readability of the code, it is advisable to break a large/nested expression using local variables, but sometimes, adding these local variables seems to be an exhausting work that we want to avoid. This is how we obtain the expressions shown below.

Avoid:

List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);

// AVOID
int result = intList.stream()
    .collect(Collectors.partitioningBy(i -> i % 2 == 0))
    .values()
    .stream()
    .max(Comparator.comparing(List::size))
    .orElse(Collections.emptyList())
    .stream()
    .mapToInt(Integer::intValue)
    .sum();

Prefer:

List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);

// PREFER
Map<Boolean, List<Integer>> evenAndOdd = intList.stream()
    .collect(Collectors.partitioningBy(i -> i % 2 == 0));

Optional<List<Integer>> evenOrOdd = evenAndOdd.values()
    .stream()
    .max(Comparator.comparing(List::size));

int sumEvenOrOdd = evenOrOdd.orElse(Collections.emptyList())
    .stream()
    .mapToInt(Integer::intValue)
    .sum();

I think that the second snippet of code is more readable and clear, but there is nothing wrong with sustaining the first approach, as well. It is absolutely normal for our minds to adapt to understanding such large expressions and prefer them against local variables. Nevertheless, the triviality of using the var type is a temptation for adopting local variables style because it saves the time spent to fetch the explicit types.

Prefer:

var intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);

// PREFER
var evenAndOdd = intList.stream()
    .collect(Collectors.partitioningBy(i -> i % 2 == 0));

var evenOrOdd = evenAndOdd.values()
    .stream()
    .max(Comparator.comparing(List::size));

var sumEvenOrOdd = evenOrOdd.orElse(Collections.emptyList())
    .stream()
    .mapToInt(Integer::intValue)
    .sum();

Item 16: the var Type Cannot Be Used As a Method Return Type or Method Arguments Type

Trying to write something in the following two snippets of code will not compile.

Usingvaras a method return type:

// IT DOESN'T COMPILE
public var countItems(Order order, long timestamp) {
    ...        
}

Usingvaras a method arguments type:

// IT DOESN'T COMPILE
public int countItems(var order, var timestamp) {
    ...  
}

Item 17: the Local Variables of the Type var Can Be Passed as a Method Arguments or Store Method Return

Thevar type can be used for local variables that store or intercept the result of calling a method or will be passed to a method as arguments. The below snippet of code will compile and is working fine.

public int countItems(Order order, long timestamp) {
    ...
}

public boolean checkOrder() {

    var order = ...;     // an Order instance
    var timestamp = ...; // a long representing a timestamp

    var itemsNr = countItems(order, timestamp); // inferred as int type
    ...
}

It works with generics, as well. The below snippet of code is working fine.

public <A, B> B contains(A container, B tocontain) {
    ...
}

var order = ...;   // Order instance
var product = ...; // Product instance

var resultProduct = contains(order, product); // inferred as Product type

Item 18: the var Type can be used with anonymous classes

Using thevartype with anonymous classes is pretty straightforward and easy to intuit.

Avoid:

public interface Weighter {

    int getWeight(Product product);
}

// AVOID
Weighter weighter = new Weighter() {
    @Override
    public int getWeight(Product product) {
        ...
    }
};

Product product = ...; // a Product instance
int weight = weighter.getWeight(product);

Prefer:

public interface Weighter {

    int getWeight(Product product);
}

// PREFER
var weighter = new Weighter() {
    @Override
    public int getWeight(Product product) {
        ...
    }
};

var product = ...; // a Product instance
var weight = weighter.getWeight(product);

Item 19: Variables of Type var Can Be Effectively Final

As a quick reminder:

... starting in Java SE 8, a local class can access local variables and parameters of the enclosing block that are final or effectively final. A variable or parameter whose value is never changed after it is initialized is effectively final.

So, variables of typevarcan be effectively final. We can easily see this in the next example.

Avoid:

public interface Weighter {

    int getWeight(Product product);
}

// AVOID
int ratio = 5; // this is effectively final
Weighter weighter = new Weighter() {
    @Override
    public int getWeight(Product product) {
        return ratio * ...;
    }
};

ratio = 3; // this reassignment will cause error

Prefer:

public interface Weighter {

    int getWeight(Product product);
}

// PREFER
var ratio = 5; // this is effectively final
var weighter = new Weighter() {
    @Override
    public int getWeight(Product product) {
        return ratio * ...;
    }
};

ratio = 3; // this reassignment will cause error

Item 20: Variables of Type var Can Be final

By default, a local variable of typevarcan be reassigned (except when it is effectively final). But, we can declare it asfinal,as shown in the following example.

Avoid:

// AVOID
// IT DOESN'T COMPILE
public void discount(int price) {

    final int limit = 2000;
    final int discount = 5;

    if (price > limit) {
        discount++; // this reassignment will cause error, which is ok
    }

}

Prefer:

// PREFER
// IT DOESN'T COMPILE
public void discount(int price) {

    final var limit = 2000;
    final var discount = 5;

    if (price > limit) {
        discount++; // this reassignment will cause error, which is ok
    }

}

Item 21: Lambda Expressions and Method References Need Explicit Target-Types

The var type cannot be used since the concrete type cannot be inferred. So, lambdas and method reference initializers are not allowed. This statement is part ofvarlimitations.

The following code will not compile:

// IT DOESN'T COMPILE

// lambda expression needs an explicit target-type
var f = x -> x + 1;

// method reference needs an explicit target-type
var exception = IllegalArgumentException::new;

Instead, use:

// PREFER
Function<Integer, Integer> f = x -> x + 1;
Supplier<IllegalArgumentException> exception = IllegalArgumentException::new;

But, in the lambdas context, Java 11 allows us to usevarin lambdas parameters. For example, the code below is working in Java 11 (more details in JEP 323 (Local-Variable Syntax for Lambda Parameters)):

// Java 11
(var x, var y) -> x + y
// or 
(@Nonnull var x, @Nonnull var y) -> x + y

Item 22: Assigning nulls initializers to the var Type Is Not Allowed

Additionally, a missing initializer is not allowed as well. These are another limitation of the vartype.

The following code will not compile (try to assign null):

// IT DOESN'T COMPILE
var message = null; // result in an error of type: variable initializer is 'null'

This will not compile as well (missing initializer):

// IT DOESN'T COMPILE
var message; // result in: cannot use 'var' on variable without initializer
...
message = "hello";

Prefer:

// PREFER
String message = null;

// or
String message;
...
message = "hello";

Item 23: the var Type Is Not Allowed on Fields

Thevartype can be used for local variables, but it cannot be used for fields.

This limitation will lead to compile-time errors here:

// IT DOESN'T COMPILE
public class Product {

    private var price; // error: 'var' is not allowed here
    private var name;  // error: 'var' is not allowed here
    ...
}

Instead, use:

// PREFER
public class Product {

    private int price; 
    private String name;
    ...
}

Item 24: the var Type Is Not Allowed in catch Blocks

However, it is allowed in try-with-resources

Catch Block

When a piece of code throws an exception, we have to catch it via explicit type sincevaris not allowed. This limitation will cause compile-time errors for the following code:

// IT DOESN'T COMPILE
try {
    TimeUnit.NANOSECONDS.sleep(5000);
} catch (var ex) {
    ...
}

Instead, use:

// PREFER
try {
    TimeUnit.NANOSECONDS.sleep(5000);
} catch (InterruptedException ex) {
    ...
}

Try-With-Resources

On the other hand, thevartype is a very nice fit for try-with-resource. For example:

// explicit type
try (PrintWriter writer = new PrintWriter(new File("welcome.txt"))) {
    writer.println("Welcome message");
}

This can be re-written usingvar:

// using var
try (var writer = new PrintWriter(new File("welcome.txt"))) {
    writer.println("Welcome message");
}

Item 25: the var Type Can Be Used With Generic Types, T

Let's suppose that we have the following code:

public <T extends Number> T add(T t) {

     T temp = t;
     ...
     return temp;   
}     

In this case, usingvaris working as expected, so we can replaceTwithvar, as follows:

public <T extends Number> T add(T t) {

     var temp = t;
     ...
     return temp;   
}  

Let's see another example wherevarcan be successfully used. for example:

public <T extends Number> T add(T t) {

     List<T> numbers = new ArrayList<>();

     numbers.add((T) Integer.valueOf(3));
     numbers.add((T) Double.valueOf(3.9));
     numbers.add(t);
     numbers.add("5"); // error: incompatible types: String cannot be converted to T
     ...     
} 

It is ok to replaceList <T>withvar, as follows:

public <T extends Number> T add(T t) {

     var numbers = new ArrayList<T>();
     // DON'T DO THIS, DON'T FORGET THE, T
     var numbers = new ArrayList<>();

     numbers.add((T) Integer.valueOf(3));
     numbers.add((T) Double.valueOf(3.9));
     numbers.add(t);
     numbers.add("5"); // error: incompatible types: String cannot be converted to T
     ...     
} 

Item 26: Pay Extra-Attention When Using Wildcards (?), Covariants, and Contravariants With the var Type

Using the ? Wildcard

It is safe to do this:

// explicit type
Class<?> clazz = Integer.class;

// use var
var clazz = Integer.class;

But, don't replace Foo<?>withvarjust because you have errors in code andvarmakes them disappear by magic! Take the next example, not very inspired, but I hope it points out the main idea. Think about what you actually tried to achieve when you wrote the first snippet of code and act accordingly. Perhaps, you tried to define anArrayListofStringand ended up with Collection <?>.

// explicit type
Collection<?> stuff = new ArrayList<>();
stuff.add("hello"); // compile time error
stuff.add("world"); // compile time error

// use var, this will remove the error, but I don't think that this is
// what you had in mind when you wrote the above code
var stuff = new ArrayList<>();
strings.add("hello"); // no error
strings.add("world"); // no error

Using Covariants (Foo <? extends T>) and Contravariants (Foo <? super T>):

We know that we can do this:

// explicit type
Class<? extends Number> intNumber = Integer.class;
Class<? super FilterReader> fileReader = Reader.class;

And if we mistakenly assign a wrong type and recieve compile-time errors, this is exactly what we want:

// IT DOESN'T COMPILE
// error: Class<Reader> cannot be converted to Class<? extends Number>
Class<? extends Number> intNumber = Reader.class;

// error: Class<Integer> cannot be converted to Class<? super FilterReader>
Class<? super FilterReader> fileReader = Integer.class;

But if we use var:

// using var
var intNumber = Integer.class;
var fileReader = Reader.class;

Then, we can assign any class to these variables, so our bounds/constraints vanish. This is not what we intended to do:

// this will compile just fine
var intNumber = Reader.class;
var fileReader = Integer.class;

Conclusion

Thevartype is cool, and it will be improved. Keep an eye on JEP 323 (Local-Variable Syntax for Lambda Parameters) and JEP 301 (Enhanced Enums) for more. I hope you enjoyed!

This article is also part of my book Java Coding Problems.

Java (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • Introducing Graph Concepts in Java With Eclipse JNoSQL, Part 3: Understanding Janus
  • Introducing Graph Concepts in Java With Eclipse JNoSQL, Part 2: Understanding Neo4j
  • How to Introduce a New API Quickly Using Micronaut
  • Introducing Graph Concepts in Java With Eclipse JNoSQL

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!