System.out
jest zadeklarowany jako public static final PrintStream out
.
Ale możesz zadzwonić, System.setOut()
aby go ponownie przypisać.
Co? Jak to możliwe, jeśli tak jest final
?
(ten sam punkt dotyczy System.in
i System.err
)
A co ważniejsze, jeśli możesz zmutować publiczne statyczne pola końcowe, co to oznacza, jeśli chodzi o gwarancje (jeśli w ogóle), które final
Ci dają? (Nigdy nie zdawałem sobie sprawy, że System.in/out/err zachowuje się jak final
zmienne)
Odpowiedzi:
JLS 17.5.4 Pola chronione przed zapisem :
Zwykle końcowe pola statyczne nie mogą być modyfikowane. Jednak
System.in
,System.out
iSystem.err
są ostatecznymi polami statycznymi, które ze starszych powodów muszą mieć możliwość zmiany za pomocą metodSystem.setIn
,System.setOut
iSystem.setErr
. Odnosimy się do tych pól jako chronionych przed zapisem, aby odróżnić je od zwykłych pól końcowych.Kompilator musi traktować te pola inaczej niż inne końcowe pola. Na przykład odczyt zwykłego pola końcowego jest „odporny” na synchronizację: bariera związana z blokadą lub odczytem zmiennym nie musi wpływać na wartość odczytywaną z pola końcowego. Ponieważ wartość pól chronionych przed zapisem może się zmieniać, zdarzenia synchronizacji powinny mieć na nie wpływ. Dlatego semantyka nakazuje, aby te pola były traktowane jak zwykłe pola, których nie można zmienić za pomocą kodu użytkownika, chyba że ten kod użytkownika znajduje się w
System
klasie.
Nawiasem mówiąc, w rzeczywistości możesz mutować final
pola przez odbicie, wywołując setAccessible(true)
je (lub używając Unsafe
metod). Takie techniki są używane podczas deserializacji, przez Hibernate i inne frameworki itp., Ale mają jedno ograniczenie: kod, który widział wartość końcowego pola przed modyfikacją, nie gwarantuje, że zobaczy nową wartość po modyfikacji. Cechą szczególną tych pól jest to, że są one wolne od tego ograniczenia, ponieważ są one traktowane w specjalny sposób przez kompilator.
setAccessible(true)
działa tylko dla static
pól niebędących polami, co sprawia, że nadaje się do zadania pomocy w deserializacji lub klonowaniu kodu, ale nie ma możliwości zmiany static final
pól. Dlatego cytowany tekst zaczyna się od słów „ Zwykle końcowe pola statyczne nie mogą być modyfikowane ”, odnosząc się do final static
charakteru tych pól i trzech wyjątków. W innym miejscu omówiono przypadek pól instancji.
final
modyfikatora; wydaje się prostsze niż wszystkie te „chronione przed zapisem” rzeczy. Jestem pewien, że to nie jest przełomowa zmiana.
Odpowiedzi:
Java używa natywnej metody do implementacji setIn()
, setOut()
a setErr()
.
Na moim JDK1.6.0_20 setOut()
wygląda to tak:
public static void setOut(PrintStream out) {
checkIO();
setOut0(out);
}
...
private static native void setOut0(PrintStream out);
Nadal nie możesz "normalnie" ponownie przypisać final
zmiennych, a nawet w tym przypadku nie możesz bezpośrednio zmieniać przypisania pola (tj. Nadal nie możesz skompilować " System.out = myOut
"). Metody natywne pozwalają na pewne rzeczy, których po prostu nie można wykonać w zwykłej Javie, co wyjaśnia, dlaczego istnieją ograniczenia dotyczące metod natywnych, takie jak wymóg podpisania apletu w celu korzystania z natywnych bibliotek.
źródło
final
ma tutaj jakieś znaczenie?
Aby rozszerzyć to, co powiedział Adam, oto implikacja:
public static void setOut(PrintStream out) {
checkIO();
setOut0(out);
}
a setOut0 jest zdefiniowane jako:
private static native void setOut0(PrintStream out);
źródło
Zależy od realizacji. Ostatni może nigdy się nie zmienić, ale może być proxy / adapterem / dekoratorem dla rzeczywistego strumienia wyjściowego, setOut może na przykład ustawić element członkowski, do którego faktycznie zapisuje element wyjściowy. W praktyce jednak jest ustawiony natywnie.
źródło
out
która jest zadeklarowana jako final w klasie System jest zmienny poziom klasy. gdzie jak out, który jest w poniższej metodzie, jest zmienną lokalną. nie jesteśmy w miejscu, w którym przekazujemy poziom klasy, który jest w rzeczywistości ostatnim poziomem tej metody
public static void setOut(PrintStream out) {
checkIO();
setOut0(out);
}
użycie powyższej metody jest następujące:
System.setOut(new PrintStream(new FileOutputStream("somefile.txt")));
teraz dane zostaną przekierowane do pliku. Mam nadzieję, że to wyjaśnienie ma sens.
Więc nie ma tu roli natywnych metod lub refleksji w zmianie celu końcowego słowa kluczowego.
źródło
Jeśli chodzi o to, jak, możemy spojrzeć na kod źródłowy, aby 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);
}
...
Innymi słowy, JNI potrafi „oszukiwać”. ; )
źródło
Myślę, że setout0
modyfikuje zmienną na poziomie lokalnym out
, nie może modyfikować zmiennej na poziomie klasy out
.
źródło