47

I know this is fairly simple topic, but I really want to wrap my head around it.

This is what I'm trying to do, but it doesn't like the final modifier. Is there another way to achieve the effect I'm looking for? Which is basically that I want to make sure the id can not change durning the Activities entire life.

private final long mId;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mId = getIntent().getLongExtra(ID_KEY, -1);
}

I should point out that this is Android code. Thanks for all the help. I'm not worried about getters or setters or anyone changing my code. The reason I asked was to future proof my code for the next developer to take over. I found this post that also helps shed some light. Android - Activity Constructor vs onCreate

Community
  • 1
  • 1
Frank Sposaro
  • 8,511
  • 4
  • 43
  • 64
  • Here's an example of something analagous (but local variable, not a class field) just I stumbled across - it's in the JDK - some code in java.util.PriorityQueue that seems to be initialising a final variable in two steps. In method "public E poll()", a local final variable is declared in line 593 (but not initialised), then initialised in the next line. final int n; final E x = (E) es[(n = --size)]; – Razzle Jun 14 '20 at 10:46

7 Answers7

62

You can set a final variable only in a constructor or in an initializer. Regular methods cannot change the value of variables declared final.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Do you know what is the logic behind this? It would make perfect sense IMO to allow it to be set at exactly one place in the class. ... But as I think more about it, if you don't set the value right away, the compiler cannot guarantee that it's been set. – GregT Mar 01 '18 at 12:12
  • @GregT That's right, if the variable is not set by the time you are done with the constructor, it wouldn't be set at all, even though there may be only one place in the code that sets it. The only way to guarantee that the variable is set only once by the time the constructor is done is to allow it to be set only in the constructor or an initializer (which is merged with all constructors anyway, so it's as good as being a constructor). – Sergey Kalinichenko Mar 01 '18 at 13:22
22

You can't. But you can guarantee no external object changes it if it's private and you don't have a setter for it.


Alternatively, you can wrap the long value in another class - LazyImmutableLong. But this is a more verbose approach, and you probably don't need it (note: the class below is not thread-safe)

class LazyImmutableLong {

    private Long value;

    public void setValue(long value) {
         if (this.value != null) {
             return; // the value has already been set
         }
         this.value = value;
    }
    public long getValue() {return value;}
}

And in your activity

private LazyImmutableLong id = new LazyImmutableLong();

public void onCreate(..) {
    id.setValue(..);
}
Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140
  • 2
    Make it a private instance variable. And just as Bozho said, don't expose a setter method for other classes to call. – Ryan Gray Jul 20 '12 at 16:38
  • Thanks. But this seems over kill for the purpose I'm using it for – Frank Sposaro Jul 20 '12 at 16:46
  • And by overkill I mean that I would rather not waste resources allocating, garbage, and function calling – Frank Sposaro Jul 20 '12 at 16:49
  • yes, as I said - you probably don't need it :) but I put it for the completeness of the answer – Bozho Jul 20 '12 at 17:06
  • In my code I want to avoid not just external object changes, but also that the value isn't changed inside by accident. That's why I want it to be `final` in addition to `private`. – GregT Mar 01 '18 at 12:10
5

The following Worm (Write-Once-Read-Many) class could help in this kind of scenario.

We could create a nested Wrapper class, that stores the final variable you need. To initialize this variable, you just should call a constructor of the wrapper object. When you call the method getData(), you will get a reference of the final variable in case it is initialized, otherwise, you will get null.

The methods getData() and setData(T data) are required to be thread-safe. To provide it, we use a volatile modifier for the wrapper object. Reading a volatile variable is synchronized and writing to a volatile variable is synchronized, too. Even though some efforts were made to make this code thread-safe I didn't test it in this respect. Depending on the level of thread safety you may consider to make setter and getter synchronized.

public class Worm<T> {
    private volatile Wrapper<T> wrapper;

    public Worm() {}
    public Worm(T data) throws IllegalAccessError
    {
        setData(data);
    }

    public T getData()
    {
        if (wrapper == null)
            return null;
        return wrapper.data;
    }

    public void setData(T data) throws IllegalAccessError
    {
        if (wrapper != null)
            throw new IllegalAccessError();
        else
            wrapper = this.new Wrapper<>(data);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Worm<T> other = (Worm<T>) obj;
        return Objects.equals(this.getData(), other.getData());
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(this.getData());
    }

    final private class Wrapper<T> {
        final private T data;

        Wrapper(T data) {
            this.data = data;
        }
    }
}
0
private final long mId;

final reference cann't be modified at runtime as per java spec. So, once you declared it as final, mId can't point to something else throughout its lifetime (Unless you use reflection (or) wrap the value in object and modify it through other reference).

kosa
  • 65,990
  • 13
  • 130
  • 167
0

You can set later a global final Variable only in your constructor. Example:

public class ClassA {
   private final long mID;

   public ClassA(final long mID) {
      this.mID = mID;
   }
}

In this case in each constructor you have to initialize the final variable.

christian.vogel
  • 2,117
  • 18
  • 22
  • 2
    the point is, he can't use the constructor. He has to use a framework initialization method (onCreate) – Bozho Jul 20 '12 at 16:42
0

you have to initialize the constructor as soon as you create it, or you could initialize it at the max, in the constructor. Not later than that..

Vinay W
  • 9,912
  • 8
  • 41
  • 47
-1

NO it can not be done

if you can declare final at one place and initialize it later, Then What is the mean of final.

If you want to have a constant ID, why don't you use Shared Preferences , store it in SP and retrieve whenever want.

AAnkit
  • 27,299
  • 12
  • 60
  • 71