26 recommendations for using the var type in Java

Published on January 31, 2019

26 recommendations for using the var type in Java

https://dzone.com/articles/var-work-in-progress
  • Transfer

The Java Local Variable Type Inference (LVTI) or briefly the type var (the var identifier is not a keyword, but a reserved type name) was added to Java 10 using JEP 286: Local-Variable Type Inference . Being a 100% compiler function, it does not affect bytecode, runtime, or performance. Basically, the compiler checks the right side of the assignment operator and, based on it, determines the specific type of the variable, and then replaces it with var .


In addition, it is useful for reducing the verbosity of the template code, as well as allows you to speed up the programming process itself. For example, it is very convenient to write var evenAndOdd =...instead Map<Boolean, List<Integer>> evenAndOdd =....


The appearance of var does not mean that it is always convenient to use it everywhere, sometimes it will be more practical to do with standard tools.


In this article we will look at 26 situations, with examples of when var can be used and when it is not worth doing.


Point 1: try to give meaningful names to local variables


Usually we focus on giving the correct names to the fields of the classes, but we do not pay the same attention to the names of local variables. When our methods are perfectly implemented, contain little code and have good names, then very often we do not pay attention to local variables, or even abbreviate their names.


When we use var instead of writing explicit types, the compiler automatically identifies them and substitutes var instead . But on the other hand, as a result, people find it harder to read and understand the code, since using var can complicate its readability and understanding. In most cases, this happens because we tend to look at the type of a variable as the primary information, and its name as the secondary information. Although it should be just the opposite.


Example 1:


Probably many would agree that in the example below the names of local variables are too short:


// HAVING
public boolean callDocumentationTask() {
    DocumentationTool dtl = ToolProvider.getSystemDocumentationTool();
    DocumentationTask dtt = dtl.getTask(...);
    return dtt.call();
}

When using short names in conjunction with var , the code becomes even less clear:


// AVOID
public boolean callDocumentationTask() {
    var dtl = ToolProvider.getSystemDocumentationTool();
    var dtt = dtl.getTask(...);
    return dtt.call();
}

More preferred option:


// PREFER
public boolean callDocumentationTask() {
    var documentationTool = ToolProvider.getSystemDocumentationTool();
    var documentationTask = documentationTool.getTask(...);
  return documentationTask.call();
}

Example 2:


Avoid naming variables like this:


// AVOID
public List<Product> fetchProducts(long userId) {
    var u = userRepository.findById(userId);
    var p = u.getCart();
    return p;
}

Use more meaningful names:


// PREFER
public List<Product> fetchProducts(long userId) {
    var user = userRepository.findById(userId);
    var productList = user.getCart();
    return productList;
}

Example 3:


In an effort to give clearer names to local variables, do not go to extremes:


// AVOID
var byteArrayOutputStream = new ByteArrayOutputStream();

Instead, you can use a shorter, but no less clear option:


// PREFER
var outputStream = new ByteArrayOutputStream();
// or
var outputStreamOfFoo = new ByteArrayOutputStream();

Did you know that Java has an internal class named:
InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState


Well, naming variables with this type can be tricky :)


Point 2: use literals to help var determine the exact primitive type (int, long, float, double)


Without the use of literals for primitive types, we may find that the expected and expected types may differ. This is caused by the implicit type conversion, which is used by var- variables.


For example, the following two code fragments behave as expected. Here we explicitly declare types boolean and char :


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

Now use var , instead of explicit type declaration:


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

So far so good. And now let's do the same for the types int , long , float and double :


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

Although the code snippet above is simple and straightforward, now let's use var , instead of explicitly specifying 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

All four variables will be displayed as int . To fix this behavior, we need to use Java literals:


// 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 happens if we declare a number with a decimal part?


Avoid this if you expect to get a float variable :


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

To avoid surprise, use the appropriate literal:


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

Point 3: in some cases, var and implicit type conversions can simplify code maintenance.


For example, let's assume that our code is between two methods. One method gets a shopping cart with different products and calculates the best price. To do this, it compares various prices in the market and returns the total price as a float . Another method simply deducts this price from the card.


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


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

Secondly, let's take a look at the method that works with the card:


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

Now we put our code between these two external service methods as a client. Our users can choose products to buy, and we expect the best price for them, and then write off money from the card:


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

After some time, the company that owns the API decides to abandon the real price representation in favor of the decimal (instead of float , int is now used ). So they modified the API code as follows:


public int computeBestPrice(String[] items) {
   ...
   float realprice = ...;
   ...
   int price = (int) realprice;
   return price;
}
public boolean debitCard(int amount, ...) {
    ...
}

The point is that our code uses the explicit declaration of a float variable as the price. In its current form, we will receive an error at compile time. But if we had foreseen such a situation and used var instead of float , then our code would continue to work without problems, thanks to the implicit type conversion:


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

Point 4: when literals are not the right solution, use explicit type casts or discard var


Some primitive types in Java do not have special literals, for example, byte and short types . In this case, using explicit type notation, we can create variables without any problems.


Use this instead of var :


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

But why in this situation, give preference to the explicit designation of types instead of just using var ? Well, let's write this code using var . Note that in both cases, the compiler will assume that you need variables of type int .


Avoid this error:


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

There are no literals that would come to our aid, therefore we are forced to use explicit descending type conversions. Personally, I will avoid such situations, because I do not see any advantages here.


Use this entry only if you really want to use var :


// 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

Step 5: Avoid using var if variable names do not contain sufficient type information to understand the code.


The advantage of using var is to write more concise code. For example, in the case of constructors, we can avoid having to repeat the class name and, therefore, eliminate the redundancy of the code.


Avoid the following:


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

Instead, use:


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

For the construction below, var will also be a good way to simplify the code without losing any information.


Avoid:


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

Use the following code:


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

So why is it more comfortable for us to work with var in the examples presented? Because all the necessary information is contained in the names of variables. But if var , in conjunction with the variable name, leads to a decrease in the clarity of the code, it is better to refuse to use it.


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();

Use:


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

Consider, for example, using a class java.nio.channels.Selector. This class has a static method open()that returns a new selector and opens it. But here you can easily think that a method Selector.open()can return a boolean type , depending on the success of opening an existing selector, or even return void . Using var here will lead to information loss and confusion in the code.


Point 6: the var type guarantees security at compile time


This means that we cannot compile an application that tries to do a mis-assignment. For example, the code below will not compile:


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

But this one will compile:


var items = 10;
items = 20;

And this code is successfully compiled:


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

Once the compiler has determined the value of the var variable , we cannot assign anything else except this type.


Item 7: var cannot be used to create an instance of a particular type and assign it to an interface type variable


In Java, we use the "programming using interfaces" approach. For example, we create an instance of the ArrayList class, associating it with an abstraction (interface):


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

And we avoid things like binding an object to a variable of the same type:


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

This is the most common and desirable practice, since we can easily replace the interface implementation with any other. For this, it is only necessary to declare an interface type variable.


We will not be able to follow this concept using var variables, since a specific type is always displayed for them. For example, in the following code snippet, the compiler will define the type of a variable as ArrayList<String>:


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

There are several var arguments that explain this behavior:


  • var is used for local variables, where, in most cases, programming using interfaces is used less than in cases with method parameters, return values ​​or fields


  • The scope of local variables should be small, so solving the problems caused by switching to another implementation should not be a big deal.


  • var interprets the code to the right as an initializer used to determine the actual type. If, at some point, the initializer is changed, then the type being defined may also change, causing problems in code that relies on this variable.



Point 8: the probability of outputting an unexpected type


Using var in conjunction with a diamond operator (<>) in the absence of information to identify the type, can lead to unexpected results.


Before Java 7, explicit types were used for collections:


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

Starting with Java 7, the diamond operator was introduced . In this case, the compiler will display the required type:


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

What type will be displayed in the code below?


You should avoid similar constructions:


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

The type will be defined as ArrayList<Object>. This is because the information needed to correctly determine the type is not presented. This leads to the fact that the nearest type will be selected, which can be compatible with the context of what is happening. In this case - Object.


Thus, var can only be used if we provide the necessary information to determine the expected type. The type can be specified directly or passed as an argument.


Directly specify the type:


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

Pass arguments of the required type:


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

Product p1 = new Product();
Product p2 = new Product();
var listOfProduct = List.of(p1, p2); // inferred as List<Product>
// DON'T DO THIS
var listofProduct = List.of(); // inferred as List<Object>
listofProduct.add(p1);
listofProduct.add(p2);

Point 9: assigning an array to a var-variable does not require brackets []


We all know how to declare arrays in Java:


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

How about using var when working with arrays? In this case, there is no need to use brackets on the left side.


Avoid the following (it will not even compile):


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

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

The code below does not compile with var either. This is because the compiler cannot determine the type on the right side:


// 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};

Paragraph 10: var cannot be used when declaring several variables in one line.


If you like to declare variables of the same type at once, then you need to know that var is not suitable for this. The following code will not 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 that:


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

Point 11: local variables should aim to minimize their scope. The var type reinforces this statement.


Keep a small scope for local variables — I'm sure you heard this statement before the appearance of var .


Readability and quick bug fixes are arguments for this approach. For example, let's define a stack like this:


Avoid this:


// 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(...);
...

Note that we call a method forEach()that is inherited from java.util.Vector. This method will go through the stack like any other vector and this is what we need. But now we decided to use instead Stack- ArrayDeque. When we do this, the method forEach()will get an implementation from ArrayDeque, which will pass through the stack as a standard 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. It is too difficult to trace the error here, since the code containing the part forEach()is not located next to the code to which changes were made. To increase the speed of searching and correcting errors, it is much better to write code that uses a variable stackas close as possible to declaring this variable.


It’s best to do this:


// 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 from Stackto ArrayQueue, he can quickly notice the error and correct it.


Paragraph 12: the var type simplifies the use of various types in ternary operators.


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


If you explicitly specify types, the following code will not compile:


// 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);

The code below also does not compile:


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

But you can use more general types:


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

In all such cases it is better to 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";

It does not follow from these examples that the var type determines the types of objects at run time. This is not true!


And, of course, the var type will work correctly with the same types of both operands:


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

Paragraph 13: the var type can be used inside loops.


We can easily replace the explicit type declaration in for loops with var .


Changing an explicit int type to a 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
     ...
}

Changing an explicit type Orderto var :


List<Order> orderList = ...;
// explicit type
for (Order order : orderList) {
    ...
}
// using var
for (var order : orderList) { // order type is inferred as Order
    ...
}

Point 14: var works fine with streams in Java 8


It is very simple to use var from Java 10 with streams (stream) that appeared in Java 8.


You can simply replace the explicit Stream declaration with a var :


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>

Paragraph 15: var can be used when declaring local variables intended for splitting large chains of expressions into parts


Expressions with great nesting look impressive and usually seem like some clever and important parts of the code. In the case when it is necessary to facilitate the readability of the code, it is recommended to split a large expression using local variables. But sometimes writing a lot of local variables seems like a very exhausting job that I would like to avoid.


An example of a large expression:


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();

Better break the code into its component parts:


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();

The second version of the code looks more readable and simpler, but the first version also has a right to exist. It is absolutely normal for our mind to adapt to the understanding of such large expressions and prefer them to local variables. However, using the var type can help with breaking up large structures by reducing the effort to declare local variables:


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();

Clause 16: var cannot be used as a return type or as a method argument type


The two code snippets shown below will not compile.


Using var as the return type:


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

Using var as the type of the method argument:


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

Paragraph 17: local variables of the var type can be passed as method parameters or can accept the value returned by the method.


The following code snippets will compile and will work properly:


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

Generics will still work 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: var variables can be used with anonymous classes


Instead of explicitly specifying types:


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

Use var :


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

Clause 19: Var variables can be used as effectively final variables.


Recall that:


... starting with Java SE 8, the local class can access local variables and parameters of the enclosing block that are final or effectively final. A variable or parameter whose value never changes after its initialization is effectively final .

Well, var variables can be effectively final. This can be seen in the following 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

Use:


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: var variables can be final variables


Initially, the var value of a variable can be changed (except when it is declared as effectively final). But we can declare a variable as final .


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
    }
}

Paragraph 21: lambda expressions and method references need explicit types.


The var type cannot be used if it is not possible to define the final types. Thus, initialization via lambda expressions and method references, using var , 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 Java 11, it is allowed to use var- variables in the context of lambda expressions. The following code example will work in Java 11:


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

Point 22: it is forbidden to initialize var with null


It is forbidden to declare var- variables without initialization.


This code will not compile (attempt to assign null ):


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

And this one also will not compile (there is no initializer):


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

And this code will compile and will work properly:


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

Point 23: var type cannot be used in class fields


You can use var for local variables, but not as class fields.


This restriction will lead to compilation errors:


// 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
    ...
}

Use this method:


// PREFER
public class Product {
    private int price; 
    private String name;
    ...
}

Point 24: var cannot be used in a catch block


However, this is allowed in try-with-resources


Catch block


When the code throws an exception, we have to catch it using a specific type.


The following code will cause a compilation error:


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

In this case, you need to use an explicit exception type:


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

Try-with-resources


However, var works fine in the try-with-resources block .


For example, this code:


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

You can replace with var code :


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

Item 25: var type can be used with generics


For example, we have the following code:


public <T extends Number> T add(T t) {
     T temp = t;
     ...
     return temp;   
}

In this case, using var works as expected, so we can simply replace T with var :


public <T extends Number> T add(T t) {
     var temp = t;
     ...
     return temp;   
}

Let's take a look at another example where we can successfully use var :


codepublic <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
     ...     
}

Here you can safely replace List <T> with var :


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

Paragraph 26: be careful with the var type when using Wildcards (?), Covariants and countervariants


Using? Wildcards


You can safely use var in this way:


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

But do not replace Foo <?> In the var just because you have an error in your code, and using the var they miraculously disappear.


Let's look at the following code sample, it is not exciting, but I think you will understand the basic idea. Based on the first line, you can make an assumption that you tried to determine the ArrayList from the lines, and finally got a 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 the following:


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

If we mistakenly assign the wrong type and get errors at compile time, this will be exactly what we expect:


// 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 when using var :


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

Now we can assign any class to variables, and our code will be compiled. But this is not what we wanted - our variables have no limitations:


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

Conclusion


In this article, we looked at the “ var ” type, which appeared in Java 10. We also looked at many examples that demonstrate the advantages and disadvantages of using the dynamic derivation of the type of variables. Finally, we learned that type checking with var is done at compile time, which allows you to catch a lot of errors.


Use var and have Java with you!