【问题标题】:Add final modifier at runtime in Java在 Java 运行时添加 final 修饰符
【发布时间】:2015-03-15 11:42:17
【问题描述】:

听起来可能有点奇怪,但是可以在运行时添加final 修饰符吗?

我有一个标记为public static int/short 的变量。在某些时候,我想防止更改它的值,并且我想将其可访问性保持为标准静态值 (ClassName.field)。

public class Main {

    private static int a = 0;

    public static void addFinalMod(Field f) {

        Field modifiersField = null;
        try {
            modifiersField = Field.class.getDeclaredField("modifiers");
        }
        catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        modifiersField.setAccessible(true);
        try {
            modifiersField.setInt(f, f.getModifiers() & Modifier.FINAL);
        }
        catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        System.out.println(a);
        try {
            Field f = Main.class.getDeclaredField("a");
            addFinalMod(f);
        }
        catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        a = 10; //I was expecting error/exception here
        System.out.println(a);
    }

输出:

0
10

【问题讨论】:

  • 其实这是一个非常有趣的问题。目前,对我来说,final 规则似乎只在编译时检查(这包括初始化要求和 JMM 保证)。
  • 我认为您的意思是 f.getModifiers() | Modifier.FINAL 而不是 f.getModifiers() & Modifier.FINAL 让其他修饰符保留。此外,如果您使用f.set(null, 5);,您将获得预期的异常。

标签: java final modifier


【解决方案1】:

在某些时候我想阻止改变它的值

在应用程序逻辑中执行此操作。使变量只能通过方法访问。保留一个标记来跟踪变量的任何更改。在您应用更改或达到某个点后,请举起标志,并抛出异常以进一步尝试更改变量。

final 是一种语言关键字/功能,用于编写源代码并防止重新分配源代码中的变量。在运行时,它(几乎)什么都不做。

【讨论】:

  • 这也是我会做的,虽然我希望看到一个真正的解决方案,但我怀疑这是否可能。
  • 我知道,这个解决方案会起作用并且很容易实现。但我宁愿不使用任何方法。
  • @user2749903 那你就不走运了。不能通过直接变量访问来完成。您基本上想要的是一个看起来像变量的函数,Java 没有(其他语言有,但 Java 没有)。
  • @SylvainLeroux 线程安全,JLS 17.5。基本上,只要您不从构造函数中泄漏this 引用,就可以保证任何final 字段都可以在(至少)处于构造时状态的任何线程中看到。例如,ArrayList 通常不是线程安全的,但如果你有一个 final ArrayList<Foo> 实例化并填写你的构造函数,并且你在构造函数之后永远不会修改它,那么任何线程可以看到您的对象保证看到 ArrayList 完全构造(包括看到它拥有完全构造的任何对象等)。
  • 整洁/特别的是,即使对象本身(具有final 字段)以非线程安全的方式发布也是如此。 String 是一个典型的例子(我相信从历史上看是一个激励的例子)。它有一个final char[],但在Java 1.5 之前(当这些新的final 语义被添加时),如果一个线程创建了一个String 并将它非安全地发布到另一个线程(例如,通过将其设置在非易失性static 字段中)。使用这些“新”语义,如果您可以访问 String,那么您将看到您应该看到的 char[]
【解决方案2】:

使用不可变对象:

public class Thing {
    private int value;
    public Thing(int value) { this.value = value; }
    public int getValue() { return this.value; }
}

然后在需要修改的时候替换它:

Thing x = Thing(1);
// do some stuff
x = Thing(2);
// more stuff

如果您在想,“但是他们仍然可以通过替换对象来修改值!”嗯,当然。防止这种情况的唯一方法是锁定一些全局状态(如果它不是常量,这实际上是不可能的),但你没有使用全局状态,对吧?全局状态是一件坏事TM

全局状态有一个非常简单的问题:它使您的代码的可重用性大大降低并且更容易出错。如果您在运行时修改该全局状态,则尤其容易出错。 “哦,废话,事情的顺序不对。”你不知道那是多么容易。试图跟踪事件的顺序是多线程如此困难的原因。就可重用性而言,这意味着您不能拥有该事物的两个独立实例,这反过来意味着您实际上不能拥有任何依赖于它的事物的任何两个实例(至少当事实证明您需要改变时)毕竟这东西)。

即使这不是全局的,这也是一个奇怪的状态,每个必须修改您的代码(包括您自己)的人在进行更改时都必须注意和思考。这听起来会让改变事情变得更容易或更难吗? (提示:后者。)不要对自己或同事这样做。让它更简单。从Zen of Python 中吸取教训:“简单胜于复杂。”

所以底线:如果您需要一个不可变对象,请使用不可变对象。如果您需要一个不是常量的不可变全局,请找出一种更好的方式来组织您的代码。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-05-12
    • 2015-04-29
    • 1970-01-01
    • 2019-07-11
    • 1970-01-01
    • 1970-01-01
    • 2013-10-19
    相关资源
    最近更新 更多