【问题标题】:Final variable from resources file资源文件中的最终变量
【发布时间】:2015-07-06 23:13:10
【问题描述】:

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

问题:

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

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'

问题是:如何从资源文件中分配我的最终变量?如果这是不可能的,解决方案的最佳实践是什么?

提前致谢。

【问题讨论】:

  • 你不能。您确定需要 PREFERENCE_NAME 进行本地化吗?
  • 是什么阻止您在需要时从资源中获取它?
  • @Blackbelt 我需要在多个活动中使用它;如果我出于什么原因需要更改 PREFERENCE_NAME,我想从资源中执行此操作。
  • 我真的看不出多次调用资源的危害。如果您需要更改值,您仍然只需要在一个地方进行,据我所知,这不是一个昂贵的电话
  • 不知道为什么-1,这个问题对我来说似乎是合理的。我已经 +1 了

标签: java android android-activity android-studio


【解决方案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 ,....);
   }
}

【讨论】:

  • 我没有实例化我的类,我必须使用 onCreate()。我认为这对我不起作用。还是谢谢。
【解决方案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);
    

【讨论】:

  • 感谢您的建议。我尽量避免创建自定义应用程序类,所以我会接受 Dreagen 的建议。还是谢谢你。
【解决方案3】:

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

【讨论】:

  • 我会选择这个解决方案。但是,我想指出其他有类似问题的人,这不是 only 解决方案。
【解决方案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;
    }
}

这将输出:

世界你好!
你好火星!

太好了,所以我们更改了最终变量。没问题吧?

好吧,以这个例子代替:

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

这将输出:

世界你好!
世界你好!

等等,什么?

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

总结一下:您可以通过反射更新 final 字段(假设不存在阻止您这样做的Security Manager),但您不应该这样做,因为它可能会产生非常非常奇怪的副作用。

如果您真的决定实施,如果您的房子有白蚁或您的猫着火,我概不负责。

【讨论】:

  • 哇,读得很好。感谢您提供详细解释的示例以及为什么不这样做。 +1。
【解决方案5】:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-15
    • 2013-08-22
    • 2023-03-15
    • 1970-01-01
    • 2018-09-15
    相关资源
    最近更新 更多