Skip to content Skip to sidebar Skip to footer

Final Variable From Resources File

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 inst

Solution 1:

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:

publicclassFinalMessage {

    publicstaticvoidmain(String[] args)throws NoSuchFieldException, IllegalAccessException {
        FinalMessagef=newFinalMessage("Hello World!");
        System.out.println(f.getMessage());
        f.changeFinalMessage("Hello Mars!");
        System.out.println(f.getMessage());
    }

    privatefinal String message;

    publicFinalMessage(String message) {
        this.message = message;
    }

    voidchangeFinalMessage(String newMessage)throws IllegalAccessException, NoSuchFieldException {
        finalFieldfield= FinalMessage.class.getDeclaredField("message");
        field.setAccessible(true);

        FieldmodifiersField= 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:

publicclassFinalMessage {

    publicstaticvoidmain(String[] args)throws NoSuchFieldException, IllegalAccessException {
        FinalMessagef=newFinalMessage();
        System.out.println(f.getMessage());
        f.changeFinalMessage("Hello Mars!");
        System.out.println(f.getMessage());
    }

    privatefinalStringmessage="Hello World!";

    voidchangeFinalMessage(String newMessage)throws IllegalAccessException, NoSuchFieldException {
        finalFieldfield= FinalMessage.class.getDeclaredField("message");
        field.setAccessible(true);

        FieldmodifiersField= 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.

Solution 2:

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.

Solution 3:

Use your application context:

  1. Create an application class:

    publicclassMyApplicationextendsApplication {
    
        privatestaticContext mContext;
    
        @OverridepublicvoidonCreate() {
            super.onCreate();
            mContext = getApplicationContext();
        }
    
        publicstaticStringgetStr(int resId) {
            return mContext.getString(resId);
        }
    
    }
    
  2. Use it in your manifest:

    <application
        android:name=".MyApplication"
        ...
    
  3. Call it anywhere in your application:

    staticfinalString NAME = MyApplication.getStr(R.string.app_name);
    

Solution 4:

You can use a factory pattern to solve this issue.

The builder is a class that aggregates the data needed to create your object, and when you are done - you just build your class.

In this approach, the data needed to generate the object is also available to the factory, and he can easily create the object, and initialize the final field when invoking its constructor.

You will have something like

classMyFactory {
   private Resource getResources() { ... }
   public MyObject build() { 
       String perference_name = getResources().getString(R.string.preference_name);
       /...
       returnnew MyObject(perfence_name ,....);
   }
}

Solution 5:

You can simply declare it as a final method:

private final String PREFERENCE_NAME() {
    return getResources().getString(R.string.preference_name);}

Post a Comment for "Final Variable From Resources File"