tags:

views:

253

answers:

5

Is it possible to lazily instantiate a final field?

The following code does not compile:

public class Test{
    private final Connection conn;

    public Connection getConnection(){
        if(conn==null){
            conn = new Connection();
        }
        return conn;
    }
}

Is there an alternative?

+9  A: 

No. The point of a final field is that it's set once, during construction, and will never change thereafter. How could the compiler or the VM know anything useful about conn in your case? How would it know that only that property should be able to set it, and not some other method?

Perhaps if you explained what you want the semantics to be, we could come up with an alterative. You could potentially have a "provider" interface representing a way to fetch a value, and then a MemoizingProvider which proxies to another provider, but only once, caching the value otherwise. That wouldn't be able to have a final field for the cached value either, but at least it would only be in one place.

Jon Skeet
A: 

As Jon Skeet said, no, there isn't.

Interpreting your code sample you may want to do something like this:

public class Test{
    private final Object mutex = new Object(); // No public locking
    private Connection conn;

    public Connection getConnection(){
        if(conn==null){
            synchronized (mutex) {
                if(conn==null){
                    conn = new Connection();
                }
            }
        }
        return conn;
    }
}
dhiller
I guess the final modifier on the "conn" field is a typo, right?
Nicolas
You should mark conn as volatile. Double checked locking is broken without it, and even with volatile it's broken for java 1.4 and earlier
jassuncao
A: 

As a side note, it's possible to change a final field. At least instance fields. You just need some reflection:

import java.lang.reflect.Field;

public class LazyFinalField {

  private final String finalField = null; 

  public static void main(String[] args) throws Exception {
 LazyFinalField o = new LazyFinalField();
 System.out.println("Original Value = " + o.finalField);
 Field finalField = LazyFinalField.class.getDeclaredField("finalField");
    finalField.setAccessible(true);
    finalField.set(o, "Hello World");   
    System.out.println("New Value = " + o.finalField);  
  }
}


Original Value = null
New Value = Hello World
jassuncao
A: 

dhiller's answer is the classic double checked locking bug, do not use.

james
A: 

Here's one way you can do it using Memoisation (with Callables):

Class Memo:

public class Memo<T> {
    private T result;
    private final Callable<T> callable;

    private boolean established;

    public Memo(final Callable<T> callable) {
        this.callable = callable;
    }

    public T get() {
        if (!established) {
            try {
                result = callable.call();
                established = true;
            }
            catch (Exception e) {
                throw new RuntimeException("Failed to get value of memo", e);
            }
        }
        return result;
    }
}

Now we can create a final conn!

private final Memo<Connection> conn = new Memo<Connection>(
    new Callable<Connection>() {
    public Connection call() throws Exception {
        return new Connection();
    }
});

public Connection getConnection() {
    return conn.get();
}
dogbane