I have an activity with some final variables. I extracted their values (let's assume they're all Strings) into a resources file.
The problem:
If I directly assign them on the instantiation (as following):
private final String PREFERENCE_NAME = getResources().getString(R.string.preference_name);
I get the following error:
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.Resources android.content.Context.getResources()' on a null object reference
I understand the problem; the onCreate() method has not been called yet which means I cannot access context-related methods (getResources()
).
If I want to assign the value in the onCreate() method of the activity, I get the error Cannot assign value to final variable 'PREFERENCE_NAME'
The question is: How can I get my final variables to be assigned from the resources file? And if this is not possible, what is the best practice for a solution?
Thanks in advance.
The simple answer is that you can't.
Variables declared final
can only be set when the object is instantiated (i.e. in the constructor or with initialiser code).
Either use getResources().getString(R.string.preference_name);
all the time or use a non-final variable.
The complex answer is that you can but that you shouldn't.
When you declare a variable final
the compiler and VM uses this to make optimisations and assumptions. It can do this because the variable is guaranteed to never change. Changing it after it has been initialised can cause really weird bugs so you absolutely should not do that.
Here's how you do it:
public class FinalMessage {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
FinalMessage f = new FinalMessage("Hello World!");
System.out.println(f.getMessage());
f.changeFinalMessage("Hello Mars!");
System.out.println(f.getMessage());
}
private final String message;
public FinalMessage(String message) {
this.message = message;
}
void changeFinalMessage(String newMessage) throws IllegalAccessException, NoSuchFieldException {
final Field field = FinalMessage.class.getDeclaredField("message");
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(this, newMessage);
}
String getMessage() {
return message;
}
}
This will output:
Hello World!
Hello Mars!
Great, so we changed a final variable. No problem right?
Well, take this example instead:
public class FinalMessage {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
FinalMessage f = new FinalMessage();
System.out.println(f.getMessage());
f.changeFinalMessage("Hello Mars!");
System.out.println(f.getMessage());
}
private final String message = "Hello World!";
void changeFinalMessage(String newMessage) throws IllegalAccessException, NoSuchFieldException {
final Field field = FinalMessage.class.getDeclaredField("message");
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(this, newMessage);
}
String getMessage() {
return message;
}
}
This will output:
Hello World!
Hello World!
Wait, what?
The problem is that the compiler can see that the variable message
is always going to be "Hello World!"
so it inlines "Hello World!"
instead of our call to f.getMessage()
. If you run this in a debugger you will see the debugger reflect the updated message in the instance to "Hello Mars!"
but since the variable is actually never accessed it won't affect the outcome of the program.
So to summarize: You can update final fields via reflection (granted that there is no Security Manager present that prevents you from doing it), but you should not do it since it can have very, very weird side-effects.
I am not responsible if your house gets termites or your cat catches on fire if you actually decide to implement this.
In this case I think the best thing to do it just make multiple calls to the resources. You still only have to change the value in one place and the call to getResources()
isn't an expensive one.
Use your application context:
Create an application class:
public class MyApplication extends Application {
private static Context mContext;
@Override
public void onCreate() {
super.onCreate();
mContext = getApplicationContext();
}
public static String getStr(int resId) {
return mContext.getString(resId);
}
}
Use it in your manifest:
<application
android:name=".MyApplication"
...
Call it anywhere in your application:
static final String NAME = MyApplication.getStr(R.string.app_name);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With