final

  • set limitations on extensibility

final class

  • cannot be extended
  • does not mean class is immutable
  • we can’t extend it to override the method and fix the problem
  • example: String

final methods

  • cannot be overridden
  • compared to final class, we can extend and add new methods but cannot change existing methods
  • example: Thread class: isAlive() method is final and cannot be overridden

final variables

final fields

  • can be either constants or write-once fields
  • class constants name use full upper case with underscores
  • Compiler knows that if it is static field then static initializer is called only once and constructor is never called. But if it is a non-static field then we can initialize in constructor since it is called only once.
  • can be initialized:
    • static final (constants):
      • declaration
      • static initializer block
    • instance final (write-once):
      • declaration
      • instance initializer block
      • constructor

final arguments

void doSomething(final String arg ) {  // Mark argument as 'final'
  arg = "giraffe";  // Compiler error
}

Capturing Lambdas

  • Lambda expressions which use variables defined in an outer scope
  • variables can be static fields, instance fields, and local variables
  • local variables must be final or effectively final
  • https://stackoverflow.com/questions/70149939/curious-about-lambda-capture-in-java
  • Capturing lambdas store internally:
    • this reference so that it can call methods + fields if they are specified
    • local variables that are referenced

Effectively final

  • relationship with Atomic variables
  • https://www.baeldung.com/java-lambda-effectively-final-local-variables
  • We could say that a variable is effectively final if the compiler wouldn’t complain were we to declare it final
  • You cannot change the value of variable inside the lambda, because that will conflict with the final philosophy
  • Why it is imposed?
    • If we can change the value of the local variable later in the code block, then Visibility issues will happen and all the threads executing the lambda will not see the value
    • For static and instance fields, we can use volatile synchronization for example to avoid this issue
  • Interesting points:
  • Atomic Variables
    • Since we cannot update the value inside the lambda, we can use holder class such as AtomicInteger to update the integer local variable for example
class MyTask {
    private String instanceField = "hola";
 
    void exec() {
        instanceField = "newString";
        String localVariable = "lala";
        Arrays.asList(1, 2, 3)
                .forEach(num -> {
                    // instance field, hence fine!
                    System.out.println(instanceField);
 
                    // this is effectively final, hence fine!
                    System.out.println(localVariable);
                });
 
        fun(localVariable);
    }
 
    void fun(String localVariable) {
        localVariable = "I am trying to change";
    }
}

final vs virtual

  • Java does not have virtual keyword unlike for example C# or C++
  • A virtual method is the one which shows runtime polymorphism via overriding it
  • All methods in Java are virtual by default
  • Only case when method is not virtual is when it is marked final
  • Virtual method:
    • It is declared within the parent class and can be redefined by the child class.
  • Pure Virtual method:
    • It is virtual method that is not defined at all in class.
    • In Java world we call it abstract methods or interface methods (which are abstract by default)
  • The difference lies in the compile time vs runtime polymorphism through dynamic dispatch
  • C# example:
    • virtual and override are being used together
    • Draw() shows compile time polymorphism
    • Draw2() show runtime polymorphism via dynamic dispatch
 
using System;
 
class Shape
{
    // runtime polymorphism
    // will show dynamic dispatch
    public virtual void Draw()
    {
        Console.WriteLine ("Drawing a shape...");
    }
 
    // compile time polymorphism
    // will not show dynamic dispatch
    public void Draw2()
    {
        Console.WriteLine ("Drawing a shape...");
    }
}
 
class Circle : Shape
{
    public override void Draw()
    {
        Console.WriteLine ("Drawing a circle...");
    }
    
    public void Draw2()
    {
        Console.WriteLine ("Drawing a circle...");
    }
}
 
public class Test
{
    public static void Main(string[] args)
    {
        Shape shape = new Circle();
        shape.Draw(); // Circle class: Drawing a circle...
        shape.Draw2(); // Shape class: Drawing a shape...
    }
}