java : "최종"System.out, System.in 및 System.err?


80

System.out로 선언됩니다 public static final PrintStream out.

하지만 전화 할 수 있습니다 System.setOut() 를 재 할당 .

어? 만약 그렇다면 이것이 어떻게 가능합니까?final 합니까?

(같은 점이 System.in 및에System.err )

그리고 더 중요한 것은 공개 정적 최종 필드를 변경할 수 있다면 보장 (있는 경우)이 final제공 하는 한 이것이 무엇을 의미 합니까? (나는 System.in/out/err이 final변수로 작동한다는 것을 깨닫지 못했고 예상하지 못했습니다 )


3
최종 필드는 검증자가 엄격하게 확인하지만 JVM 자체에서 많은 이점을 누리지 못합니다. 최종 필드를 수정하는 방법도 있지만 표준 Java 코드를 통하지 않습니다 (검증 자의 주제이기 때문에). Unsafe를 통해 수행되고 언급 된 Unsafe 항목으로 컴파일되는 Field.set (액세스 가능한 true 필요)을 통해 Java에 노출됩니다. 또한 JNI가 그것을 할 수 있습니다, 따라서 JVM 그래서 {아마도 내가 구조에게 대답하지만, MEH로 의견을해야했습니다} ... 최적화를 시도에 예리한되지 않는다
bestsss

답변:


57

JLS 17.5.4 쓰기 금지 필드 :

일반적으로 최종 정적 필드는 수정할 수 없습니다. 그러나 System.in, System.outSystem.err레거시 이유, 방법에 의해 변경 될 수 있어야, 최종 정적 필드이다 System.setIn, System.setOut하고 System.setErr. 이러한 필드 를 일반 최종 필드와 구별하기 위해 쓰기 금지 된 필드라고합니다.

컴파일러는 이러한 필드를 다른 최종 필드와 다르게 처리해야합니다. 예를 들어, 일반 최종 필드의 읽기는 동기화에 "면역"됩니다. 잠금 또는 휘발성 읽기와 관련된 장벽은 최종 필드에서 읽는 값에 영향을 미치지 않습니다. 쓰기 방지 된 필드의 값이 변경되는 것으로 보일 수 있으므로 동기화 이벤트가 영향을 미칩니다. 따라서 의미론은 해당 사용자 코드가 System클래스 에 있지 않는 한 이러한 필드를 사용자 코드로 변경할 수없는 일반 필드로 처리하도록 지시합니다 .

그런데 실제로 final필드를 호출 setAccessible(true)하거나 Unsafe메서드 를 사용하여 리플렉션을 통해 필드를 변경할 수 있습니다 . 이러한 기술은 Hibernate 및 기타 프레임 워크 등에서 deserialization 중에 사용되지만 한 가지 제한이 있습니다. 수정 전에 최종 필드의 값을 본 코드는 수정 후 새로운 값을 볼 수 있다는 보장이 없습니다. 문제의 필드에서 특별한 점은 컴파일러에 의해 특별한 방식으로 처리되기 때문에 이러한 제한이 없다는 것입니다.


4
FSM이 미래의 디자인을 타협하는 멋진 방식으로 레거시 코드를 축복하기를 바랍니다!
썰매

1
>>이 기술은 역 직렬화 중에 사용됩니다 << 지금은 사실이 아닙니다. 역 직렬화는 안전하지 않음 (빠름)을 사용합니다
bestsss

1
필드 setAccessible(true)가 아닌 경우에만 작동하므로 staticdeserialization 또는 코드 복제 작업에 적합하지만 static final필드 를 변경할 수있는 방법이 없다는 것을 이해하는 것이 중요 합니다. 그렇기 때문에 인용 된 텍스트가 " 일반적으로 최종 정적 필드는 수정할 수 없습니다 "로 시작하여 final static이러한 필드 의 특성과 세 가지 예외를 참조합니다. 인스턴스 필드의 경우는 다른 곳에서 논의됩니다.
홀거

나는 그들이 단순히 final수정자를 제거하지 않은 이유가 궁금합니다 . 이 모든 "쓰기 금지"항목보다 간단 해 보입니다. 나는 그것이 큰 변화가 아니라고 확신합니다.
Mark VY

@Holger 정적 최종 필드를 변경하는 방법이 있습니다 (Java 8까지). public class OnePrinter {private static final Integer ONE = 1; public static void printOne () {System.out.println (ONE); }} 그런 다음 Field z = OnePrinter.class.getDeclaredField ( "ONE"); z.setAccessible (true); 필드 f = Field.class.getDeclaredField ( "modifiers"); int modifiers = z.getModifiers (); f.setAccessible (true); f.set (z, modifiers & ~ Modifier.FINAL); z.set (null, 2); OnePrinter.printOne ();
Peter Verhas

30

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에서 단순히 할 수없는 일을 허용합니다. 이는 기본 라이브러리를 사용하기 위해 애플릿에 서명해야하는 요구 사항과 같은 기본 메소드에 제한 사항이있는 이유를 설명합니다.


1
좋아요, 그래서 그것은 순수한 자바 의미론에 대한 백도어입니다. 제가 추가 한 질문의 일부에 대답 final해 주시겠습니까 , 즉 스트림을 재 할당 할 수 있다면 실제로 여기에 어떤 의미가 있습니까?
Jason S

1
System.out = new SomeOtherImp ()와 같은 작업을 수행 할 수 없도록 최종 작업 일 수 있습니다. 그러나 위에서 볼 수 있듯이 기본 접근 방식을 사용하여 setter를 계속 사용할 수 있습니다.
Amir Raminfar

이 경우에 네이티브 setIn0 및 setOut0 메서드에 대한 호출이 최종 변수의 값을 실제로 수정한다고 생각합니다. 네이티브 메서드는 아마도 그렇게 할 수 있습니다 ... 게임에서 치트 코드를 사용하는 것과 같습니다. S
Danilo Tommasina

@Danilo, 그래 수정하지 않습니다 :)
bestsss

@Jason, 직접 설정할 수없는 경우 setIn / setErr을 호출하는 동안 보안 검사가 필요합니다. 모두 공정하고 정사각형입니다. java가 최종 필드를 수정하는 예 : java.util.Random (필드 시드)
bestsss

7

Adam이 말한 것을 확장하기 위해 다음은 impl입니다.

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

setOut0은 다음과 같이 정의됩니다.

private static native void setOut0(PrintStream out);

6

구현에 따라 다릅니다. 마지막 것은 절대 변경되지 않을 수 있지만 실제 출력 스트림에 대한 프록시 / 어댑터 / 데코레이터가 될 수 있습니다. 예를 들어 setOut은 out 멤버가 실제로 쓰는 멤버를 설정할 수 있습니다. 그러나 실제로는 기본적으로 설정됩니다.


1

outSystem 클래스에서 final로 선언 된 클래스 수준 변수입니다. 아래 방법 중 어느 것이 로컬 변수입니다. 우리는 클래스 레벨을 실제로이 메소드에 전달하는 곳이 아닙니다.

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

위 방법의 사용법은 다음과 같습니다.

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

이제 데이터가 파일로 전환됩니다. 이 설명이 이해되기를 바랍니다.

따라서 최종 키워드의 목적을 변경하는 데 네이티브 메서드 또는 반사의 역할이 없습니다.


1
setOut0은 최종 클래스 변수를 수정하고 있습니다.
fgb

0

어떻게하면 소스 코드를 다음과 같이 살펴볼 수 있습니다 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는 "속임수"를 사용할 수 있습니다. ; )


-2

나는 setout0지역 수준 변수를 수정 하고 있다고 생각 하며 out클래스 수준 변수를 수정할 수 없습니다 out.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.