Local Variable Type Inference

Local Variable Type Inference in Java 11 simplifies the syntax used to declare local variables. With the introduction of the var keyword, you can now declare a variable without specifying its explicit type, which the compiler infers from the context.

What is Local Variable Type Inference?

  • Keywordvar“.
  • Allows you to declare variables without a specified type.
  • The type for these variables is inferred by the compiler based on the initializer on the right side of the declaration.
int a = 5;
var b = 3;
var sum = a + b;

The Use of var

The var keyword enables you to write more concise and readable Java code. This feature is especially useful in reducing the verbosity of your Java code, making it easier to read and maintain.

Before Java 11:

BufferedReader reader = new BufferedReader(new FileReader("data.txt"));
String line = reader.readLine();
List<LocalDate> dates = Arrays.asList(LocalDate.of(2022, 1, 1), LocalDate.of(2022, 12, 31));
for (LocalDate date : dates) {
    System.out.println(date);
}
AnIncrediblyLongInterfaceName foobar = new AnIncrediblyLongImplementationName();

After Java 11:

var reader = new BufferedReader(new FileReader("data.txt"));
var line = reader.readLine();
var dates = Arrays.asList(LocalDate.of(2022, 1, 1), LocalDate.of(2022, 12, 31));
for (var date : dates) {
    System.out.println(date);
}
var lorem = new AnIncrediblyLongImplementationName();

Rules of Using var

  1. Immediate Initialisation: var can only be used when the variable is declared and initialised in the same statement; the compiler must be able to infer the type from the initial value.
        var a = 5; // ✅
        var b; // ❌
        b = 5;
  1. Local Variables Only: var is only permitted for local variables inside methods or blocks. It cannot be used for member variables, method parameters, or return types.
public class Person {
    private String firstName;
    private var lastName; // ❌

    public String getFullName(){
        var fullName = firstName + " " + lastName; // ✅
        return fullName;
    }
    
    public var getFirstName(){ // ❌
        return firstName;
    }
    
    public void setFirstName(var firstName){ // ❌
        this.firstName = firstName;
    }
}
  1. Non-Nullable: var cannot be used with null initialization without a type hint, as the compiler cannot infer the type.
        var personA = null; // ❌
  1. No Arrays Just by Initializers: When using var with arrays, the type and dimensions should be clear from the context. You cannot use var for anonymous arrays without explicit type.
        var names = new String[]{"Adam", "Bill", "Chris"}; // ✅
        var fruits = {"Apple", "Banana", "Cherry"}; // ❌

👩‍💻 Hands-on Demo: Local Variable Type Inference

🔎 Click here to expand

1. Multiple Choice Quiz

Questions:

Which of the following statements about the var keyword in Java 11 is true?

A) var can be used to declare both local variables and class fields.
B) var requires an initializer to infer the variable type.
C) var allows for declaring multiple variables of different types in a single statement.
D) var can be used to declare a method’s return type.

2. Fill-in-the-Blanks

  • Fill in the blanks to correctly declare variables using var (or using an explicit type if var wouldn’t work) in the given Java code snippet:
____ numbers = List.of(1, 2, 3, 4, 5);
____ stream = numbers.stream();
for (____ num : numbers) {
    System.out.println(num);
}

3. Debugging Challenge: Code Correction

The following Java code snippet using var has multiple issues. Identify and correct them to make the code compile and run successfully.

var x;
x = 10;
var list = null;
list = new ArrayList<String>();
var map = new HashMap<>();
map.put("key", "value");

4. Using var In Our Own Code

  1. Create a new VarDemo-class in which you declare the following variables using var:
    • An whole number initialised to 10.
    • A decimal number with a value of 3.1415.
    • A character set to 'j'.
    • A Boolean set to true.
    • A LocalDateTime set to the current time.
  2. In a separate NameGenerator-class, write a method named “generateNames()” that returns a collection of names as a Collection<String>.
    • A randomly generated number decides whether the names are returned as a mutable ArrayList, HashSet or PriorityQueue.
  3. Back in the main-method of VarDemo, use var to store the collection returned by your method.
  4. Print the type of collection it is (e.g. “The collection in the var is a PriorityQueue”)
  5. Add the name “Java” to the collection in a following statement.
  6. Use an enhanced for-loop (also using var for the loop’s local variable) to print each name in the list.
  7. Use Java’s IO and NIO libraries to create a “data” directory if it doesn’t exist, and then write the names in the collection to a text file named “vars.txt“, using var wherever possible.

Solutions

🕵️‍♂️ Click here to reveal the solutions

1. Multiple Choice Quiz

  • B) var requires an initializer to infer the variable type.

2. Fill-in-the-Blanks

var numbers = List.of(1, 2, 3, 4, 5);
var stream = numbers.stream();
for (var num : numbers) {
    System.out.println(num);
}

3. Debugging Challenge: Code Correction

Original:
var x;
x = 10;
var list = null;
list = new ArrayList<String>();
var map = new HashMap<>();
map.put("key", "value");
Corrected:
var x = 10;
var list = new ArrayList<String>(); 
var map = new HashMap<String, String>();
map.put("key", "value");
  • var x: var needs initialization at the point of declaration.
  • var list: Cannot use var with null without a type hint.
  • var map: Specify the type on the right-hand side for clarity and type inference.

4. Using var In Our Own Code

public class NameGenerator {
    public static Collection<String> generateNames(){
        var names = new String[]{"Adam", "Billy", "Cheryl"};
        switch (new Random().nextInt(3)){
            case 0:
                return new ArrayList<>(List.of(names));
            case 1:
                return new HashSet<>(List.of(names));
            case 2:
                return new PriorityQueue<>(List.of(names));
            default:
                throw new IllegalStateException("Unexpected number generated.");
        }
    }
}
public class VarDemo {
    public static void main(String[] args) {
        // 1.
        var integer = 10;
        var doubleValue = 3.1415;
        var character = 'j';
        var booleanValue = true;
        var now = LocalDateTime.now();
        // 2. & 3.
        var names = NameGenerator.generateNames();
        // 4.
        System.out.printf("The collection in the var is a %s.\n", names.getClass().getSimpleName());
        // 5.
        names.add("Java");
        // 6.
        for (var name : names) {
            System.out.println(name);
        }
        // 7.
        var path = Path.of("data", "vars.txt");
        try {
            if(Files.notExists(path)){
                Files.createDirectories(path.getParent());
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        try (
                var writer = new FileWriter(path.toFile());
                var bufferedWriter = new BufferedWriter(writer);
        ){
            for (var name : names) {
                bufferedWriter.write(name);
                bufferedWriter.newLine();
            }
        } catch (IOException e){
            e.printStackTrace();
        }
    }
}

var for Lambda Parameters

Java 11 also allows you to use var for lambda parameters, making it consistent with local variables.

Before:

        List<String> fruits = List.of("Apple", "Banana", "Cherry");
        fruits.forEach((String f) -> System.out.println(f));

        List<Integer> primeNums = List.of(2, 3, 5, 7, 11);
        int product = primeNums.stream().reduce(1, (Integer a, Integer b) -> a * b);
javaCopy codeComparator<String> comparator = (String first, String second) -> first.length() - second.length();

After:

        List<String> fruits = List.of("Apple", "Banana", "Cherry");
        fruits.forEach((var f) -> System.out.println(f));

        List<Integer> primeNums = List.of(2, 3, 5, 7, 11);
        int product = primeNums.stream().reduce(1, (var a, var b) -> a * b);

Rules of Using var for Lambda Parameters

  1. Consistent Use: If you use var for one parameter in a lambda, you must use it for all parameters in that lambda.
  2. No Mix-and-Matching: If you decide to use var, you can’t then randomly use explicit types too.
  3. Parentheses Required: When using var in lambda parameters, you must include parentheses around the parameters.

What’s the point of using var for Lambda Parameters?

Lambda syntax already allows us to write ultra-concise code, even letting us omit data types for the parameters, which begs the question: why bother with var then?

        BiFunction<String, String, Integer> longF = (String s1, String s2) -> Integer.parseInt(s1) + Integer.parseInt(s2);
        BiFunction<String, String, Integer> shortF = (var s1, var s2) -> Integer.parseInt(s1) + Integer.parseInt(s2); // Why ???
        BiFunction<String, String, Integer> shorterF = (s1, s2) -> Integer.parseInt(s1) + Integer.parseInt(s2);
  • The primary motivation for using var in lambda parameters is to enable the use of annotations.
  • Without explicit type declaration, it’s impossible to apply annotations to lambda parameters.
  • By using var, you can annotate each parameter, which is crucial for functionalities like non-null validation, debugging, or other custom behaviours, while still keeping it short.
        Function<LocalDateTime, String> longF = (@NonNull LocalDateTime dt) -> dt.getDayOfWeek().name(); // ✅
        Function<LocalDateTime, String> shortF = (@NonNull var dt) -> dt.getDayOfWeek().name(); // ✅ and shorter
        Function<LocalDateTime, String> shorterF = @NonNull dt -> dt.getDayOfWeek().name(); // ❌

👩‍💻 Hands-on Demo: Local Variable Type Inference for Lambdas

🔎 Click here to expand

1. Multiple Choice Quiz

2. Fill-in-the-Blanks

  • Fill in the blanks to correctly apply var in the lambda expression to sum up all elements of a list.
List<Integer> numbers = List.of(5, 10, 15);
int sum = numbers.stream().reduce(0, (____, ____) -> ___ + ___);

3. Debugging Challenge: Code Correction

Correct the following code snippet which incorrectly uses var with lambda parameters.

List<String> words = List.of("hello", "world", "java", "stream");
String combined = words.stream().reduce("", (var wordA, String wordB) -> wordA + wordB);
Function<Integer, String> convert = @Debug var num -> String.valueOf(num);

Solutions

🕵️‍♂️ Click here to reveal the solutions

1. Multiple Choice Quiz

2. Fill-in-the-Blanks

List<Integer> numbers = List.of(5, 10, 15);
int sum = numbers.stream().reduce(0, (var a, var b) -> a + b);

3. Debugging Challenge: Code Correction

Original:
List<String> words = List.of("hello", "world", "java", "stream");
String combined = words.stream().reduce("", (var wordA, String wordB) -> wordA + wordB);
Function<Integer, String> convert = @Debug var num -> String.valueOf(num);
Corrected:
List<String> words = List.of("hello", "world", "java", "stream");
String combined = words.stream().reduce("", (var wordA, var wordB) -> wordA + wordB);
Function<Integer, String> convert = (@Debug var num) -> String.valueOf(num);

Summary

  • Simplified Syntax with var: Java 11 introduces var, allowing automatic type inference for local variables, making code less verbose and enhancing readability by removing explicit type declarations where the context makes them obvious.
    var foo = new AnIncrediblyLongNameForAClass();
  • Initialization Rules: Variables declared with var must be initialized upon declaration to enable type inference. var is restricted to local variable declarations within methods or code blocks.
  • No var for Class Fields or Return Types: Usage of var is limited to local variables inside methods; it cannot be used for class fields, method parameters, or return types.
  • Consistency in Lambda Expressions: When using var in lambda parameters, it requires all parameters to be either declared with var or none at all, and parentheses are mandatory.
  • Enabling Annotations: var in lambda parameters allows for the application of annotations, offering enhanced control and additional functionality like non-null checks, which is not possible with implicitly typed lambda expressions.