【问题标题】:java: "final" System.out, System.in and System.err?java:“最终” System.out、System.in 和 System.err?
【发布时间】:2011-05-10 14:16:48
【问题描述】:

System.out 被声明为public static final PrintStream out

但您可以致电System.setOut() 重新分配。

嗯?如果是final,这怎么可能?

(同一点适用于System.inSystem.err

更重要的是,如果您可以更改公共静态最终字段,那么就final 为您提供的保证(如果有)而言,这意味着什么? (我从来没有意识到也不期望 System.in/out/err 表现为final 变量)

【问题讨论】:

  • Final 字段并没有享受 JVM 本身带来的很多好处,尽管它们经过验证者的严格检查。也有一些方法可以修改最终字段,但不能通过标准的 java 代码(因为它是验证程序的主题)。它是通过 Unsafe 完成的,并通过 Field.set 暴露在 java 中(需要可访问的 true),它编译为提到的 Unsafe 内容。 JNI 也可以做到这一点,因此 JVM 并不热衷于尝试优化... {也许我应该将评论构建为答案,但是 meh}

标签: java final


【解决方案1】:

JLS 17.5.4 Write Protected Fields:

通常,最终静态字段可能不会被修改。然而,System.inSystem.outSystem.err 是最终的静态字段,由于遗留原因,必须允许通过方法 System.setInSystem.setOutSystem.setErr 进行更改。我们将这些字段称为写保护,以区别于普通的 final 字段。

编译器需要将这些字段与其他 final 字段区别对待。例如,普通 final 字段的读取对同步“免疫”:锁定或易失性读取中涉及的屏障不必影响从 final 字段读取的值。由于写保护字段的值可能会发生变化,同步事件应该对它们产生影响。因此,语义规定这些字段被视为普通字段,用户代码无法更改,除非该用户代码在 System 类中。

顺便说一句,实际上你可以通过反射改变 final 字段,方法是在它们上调用 setAccessible(true)(或使用 Unsafe 方法)。这种技术在反序列化过程中,被 Hibernate 和其他框架等使用,但它们有一个限制:在修改之前已经看到 final 字段值的代码不能保证在修改后看到新值。所讨论的字段的特殊之处在于它们不受此限制,因为它们被编译器以特殊方式处理。

【讨论】:

  • 愿 FSM 保佑遗留代码以可爱的方式破坏未来的设计!
  • >>该技术在反序列化过程中使用
  • 了解setAccessible(true)只适用于非static字段,这使得它适合帮助反序列化或克隆代码的任务,但没有办法改变static final字段。这就是为什么引用的文本以“通常,最终的静态字段可能不会被修改”开头,指的是这些字段的final static性质和三个例外。实例字段的情况在另一个地方讨论。
  • 我想知道他们为什么不简单地去掉final修饰符;似乎比所有这些“写保护”的东西都简单。我很确定这不是重大变化。
  • @Holger 有一种方法可以更改静态最终字段(直到 Java 8)。公共类 OnePrinter { 私有静态最终整数 ONE = 1;公共静态无效 printOne(){ System.out.println(ONE); } } 然后你可以 Field z = OnePrinter.class.getDeclaredField("ONE"); z.setAccessible(true);字段 f = Field.class.getDeclaredField("修饰符"); int 修饰符 = z.getModifiers(); f.setAccessible(true); f.set(z,modifiers & ~Modifier.FINAL); z.set(null, 2); OnePrinter.printOne();
【解决方案2】:

Java 使用本机方法来实现setIn()setOut()setErr()

在我的 JDK1.6.0_20 上,setOut() 看起来像这样:

public static void setOut(PrintStream out) {
    checkIO();
    setOut0(out);
}

...

private static native void setOut0(PrintStream out);

您仍然不能“通常”重新分配 final 变量,即使在这种情况下,您也不能直接重新分配字段(即您仍然无法编译“System.out = myOut”)。本机方法允许一些您在常规 Java 中根本无法做到的事情,这解释了为什么本机方法存在一些限制,例如要求对小程序进行签名才能使用本机库。

【讨论】:

  • 好的,所以它是纯 Java 语义的后门......你能回答我添加的问题的一部分,即如果你可以重新分配流,final 在这里实际上有什么意义吗?
  • 它可能是最终的,因此无法执行 System.out = new SomeOtherImp() 之类的操作。但是您仍然可以使用本机方法使用设置器,如上所示。
  • 我猜在这种情况下,对原生 setIn0 和 setOut0 方法的调用将真正修改最终变量的值,原生方法可能可以做到这一点......这就像在游戏中使用作弊码:S
  • @Danilo,是的,它确实修改了:)
  • @Jason,无法直接设置它需要在调用 setIn/setErr 时进行安全检查。一切公平公正。 java确实修改最终字段的示例:java.util.Random(字段种子)
【解决方案3】:

为了扩展 Adam 所说的,这里是 impl:

public static void setOut(PrintStream out) {
    checkIO();
    setOut0(out);
}

setOut0 定义为:

private static native void setOut0(PrintStream out);

【讨论】:

    【解决方案4】:

    取决于实施。最后一个可能永远不会改变,但它可能是实际输出流的代理/适配器/装饰器,例如 setOut 可以设置 out 成员实际写入的成员。但在实践中,它是本地设置的。

    【讨论】:

      【解决方案5】:

      System类中声明为final的out是类级变量。 下面方法中的 out 是一个局部变量。 我们无法将实际上是最终级别的类级别传递到此方法中

      public static void setOut(PrintStream out) {
        checkIO();
        setOut0(out);
          }
      

      上述方法的用法如下:

      System.setOut(new PrintStream(new FileOutputStream("somefile.txt")));
      

      现在数据将被转移到文件中。 希望这个解释有意义。

      因此,本机方法或反射在改变 final 关键字的目的方面没有任何作用。

      【讨论】:

      • setOut0 正在修改类变量,是 final 的。
      【解决方案6】:

      至于如何,我们可以去java/lang/System.c看源码:

      /*
       * The following three functions implement setter methods for
       * java.lang.System.{in, out, err}. They are natively implemented
       * because they violate the semantics of the language (i.e. set final
       * variable).
       */
      JNIEXPORT void JNICALL
      Java_java_lang_System_setOut0(JNIEnv *env, jclass cla, jobject stream)
      {
          jfieldID fid =
              (*env)->GetStaticFieldID(env,cla,"out","Ljava/io/PrintStream;");
          if (fid == 0)
              return;
          (*env)->SetStaticObjectField(env,cla,fid,stream);
      }
      
      ...
      

      换句话说,JNI 可以“作弊”。 ; )

      【讨论】:

        【解决方案7】:

        我认为setout0正在修改本地级别变量out,它不能修改类级别变量out

        【讨论】:

          猜你喜欢
          • 2011-06-26
          • 2023-03-12
          • 1970-01-01
          • 1970-01-01
          • 2013-02-15
          • 1970-01-01
          • 2023-01-20
          • 1970-01-01
          相关资源
          最近更新 更多