来自资源文件

时间:2015-04-28 08:03:25

标签: java android android-activity android-studio

我的活动包含一些最终变量。我将它们的值(让我们假设它们都是字符串)提取到资源文件中。

问题:

如果我在实例化中直接分配它们(如下所示):

private final String PREFERENCE_NAME = getResources().getString(R.string.preference_name);

我收到以下错误:

Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.Resources android.content.Context.getResources()' on a null object reference

我理解这个问题;尚未调用onCreate()方法,这意味着我无法访问与上下文相关的方法(getResources())。

如果我想在活动的onCreate()方法中指定值,我会收到错误Cannot assign value to final variable 'PREFERENCE_NAME'

问题是:如何从资源文件中分配最终变量?如果无法做到这一点,那么解决方案的最佳实践是什么?

提前致谢。

5 个答案:

答案 0 :(得分:4)

简单的答案是你做不到。

声明final的变量只能在实例化对象时设置(即在构造函数中或使用初始化代码)。

要么一直使用getResources().getString(R.string.preference_name);,要么使用非最终变量。

复杂的答案是你可以,但你不应该。

当您声明变量final时,编译器和VM使用它来进行优化和假设。它可以做到这一点,因为保证变量永远不会改变。在初始化之后更改它可能会导致非常奇怪的错误,所以你绝对不应该这样做。

这是你如何做到的:

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;
    }
}

这将输出:

  

Hello World!
  火星你好!

很好,所以我们改变了最后一个变量。没问题吧?

好吧,请改为举例:

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;
    }
}

这将输出:

  

Hello World!
  Hello World!

等等,什么?

问题是编译器可以看到变量message总是"Hello World!",因此它内联"Hello World!"而不是我们对f.getMessage()的调用。如果在调试器中运行它,您将看到调试器将实例中更新的消息反映到"Hello Mars!",但由于该变量实际上从未被访问过,因此不会影响程序的结果。

总而言之:你可以通过反射来更新最终字段(假设没有Security Manager存在阻止你这样做),但你不应该这样做,因为它可能有非常非常奇怪的一面 - 的效果。

如果你的房子得到白蚁,或者如果你真的决定实施它,你的猫会着火,我不负责任。

答案 1 :(得分:3)

在这种情况下,我认为最好的做法就是多次调用资源。您仍然只需要在一个地方更改值,而对getResources()的调用并不昂贵。

答案 2 :(得分:2)

使用您的应用程序上下文:

  1. 创建一个应用程序类:

    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);
        }
    
    }
    
  2. 在清单中使用它:

    <application
        android:name=".MyApplication"
        ...
    
  3. 在应用程序的任何位置调用它:

    static final String NAME = MyApplication.getStr(R.string.app_name);
    

答案 3 :(得分:1)

您可以使用factory pattern来解决此问题。

构建器是一个聚合创建对象所需数据的类,完成后 - 只需构建类。

在这种方法中,生成对象所需的数据也可供工厂使用,他可以轻松创建对象,并在调用其构造函数时初始化final字段。

您将拥有类似

的内容
class MyFactory {
   private Resource getResources() { ... }
   public MyObject build() { 
       String perference_name = getResources().getString(R.string.preference_name);
       /...
       return new MyObject(perfence_name ,....);
   }
}

答案 4 :(得分:0)

您可以简单地将其声明为最终方法:

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