2011-05-10 24 views
73

System.out dichiarato come public static final PrintStream out.java: "final" System.out, System.in e System.err?

Ma è possibile chiamare System.setOut() per riassegnarlo.

Huh? Com'è possibile se è final?

(stesso punto vale per System.in e System.err)

E ancora più importante, se si può mutare i campi finali statici pubblici, che cosa significa questo per quanto riguarda le garanzie (se presenti) che final ti dà? (Non ho mai capito né aspettato System.in/out/err comportava come final variabili)

+3

I campi finali non godono di molti vantaggi dalla JVM stessa, sebbene siano controllati rigorosamente dal verificatore. Ci sono modi per modificare anche i campi finali, ma non tramite il codice java standard (poiché è un oggetto del verificatore). È fatto tramite Unsafe ed esposto in java tramite Field.set (richiede true accessibile), che viene compilato per le cose non sicure menzionate. Anche JNI può farlo, quindi la JVM non è così appassionata nel tentativo di ottimizzare ... {forse avrei dovuto strutturare il commento come risposta ma meh} – bestsss

risposta

54

JLS 17.5.4 Write Protected Fields:

Normalmente, campi statici finali non possono essere modificati. Tuttavia, System.in, System.out e System.err sono campi statici finali che, per motivi legacy, devono essere autorizzati a essere modificati dai metodi System.setIn, System.setOut e System.setErr. Ci riferiamo a questi campi come protetti da scrittura per distinguerli dai normali campi finali.

Il compilatore deve trattare questi campi in modo diverso dagli altri campi finali. Ad esempio, una lettura di un campo finale ordinario è "immune" alla sincronizzazione: la barriera coinvolta in un blocco o una lettura volatile non deve influire sul valore letto da un campo finale. Poiché il valore dei campi protetti da scrittura può essere visto cambiare, gli eventi di sincronizzazione dovrebbero avere un effetto su di essi. Pertanto, la semantica impone che questi campi vengano trattati come normali campi che non possono essere modificati dal codice utente, a meno che tale codice utente non sia nella classe System.

Tra l'altro, in realtà si può mutare final campi tramite riflessione chiamando setAccessible(true) su di loro (o utilizzando metodi Unsafe). Tali tecniche vengono utilizzate durante la deserializzazione, da Hibernate e altri framework, ecc., Ma hanno una limitazione: codice che ha visto il valore del campo finale prima che la modifica non sia garantita per vedere il nuovo valore dopo la modifica. La particolarità dei campi in questione è che sono esenti da questa limitazione poiché sono trattati in modo speciale dal compilatore.

+4

aha ............ –

+4

Possa l'FSM benedire il codice legacy per il bel modo in cui compromette il design futuro! – ArtB

+1

>> Questa tecnica viene utilizzata durante la deserializzazione << non è vero ora, la desereliazazione utilizza Unsafe (più veloce) – bestsss

28

Java utilizza un metodo nativo per implementare setIn(), setOut() e setErr().

Sul mio JDK1.6.0_20, setOut() assomiglia a questo:

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

... 

private static native void setOut0(PrintStream out); 

non è ancora possibile "normalmente" riassegnare final variabili, e anche in questo caso, non si sta riassegnando direttamente sul campo (cioè non è ancora possibile compilare "System.out = myOut"). I metodi nativi consentono alcune cose che semplicemente non possono essere eseguite in Java normale, il che spiega il motivo per cui esistono restrizioni con metodi nativi come il requisito che un'applet sia firmata per poter utilizzare le librerie native.

+0

AAA mi hai battuto :) –

+1

OK, quindi è un back door attorno alla pura semantica di Java ... potresti rispondere alla parte della domanda che ho aggiunto, vale a dire se riesci a riassegnare i flussi, che 'final' ha davvero qualche significato qui? –

+1

Probabilmente è definitivo, quindi non si può fare qualcosa come System.out = new SomeOtherImp(). Ma puoi ancora usare i setter usando l'approccio nativo come vedi sopra. –

7

Per estendere su ciò detto Adam, ecco l'impl:

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

e setOut0 è definito come:

private static native void setOut0(PrintStream out); 
6

dipende dall'implementazione. L'ultimo potrebbe non cambiare mai, ma potrebbe essere un proxy/adattatore/decoratore per il flusso di output effettivo, setOut potrebbe ad esempio impostare un membro a cui il membro esterno scrive effettivamente. In pratica, tuttavia, è impostato in modo nativo.

1

il out che è dichiarato come finale in System class è una variabile di livello di classe. dove come fuori che è nel metodo sottostante è una variabile locale. stiamo precisamente passando livello di classe quale è in realtà una finale uno in questo metodo

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

utilizzo del metodo di cui sopra è la seguente:

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

adesso i dati saranno essere deviato al file. spero che questa spiegazione abbia un senso.

Quindi nessun ruolo di metodi nativi o riflessioni qui nel cambiare lo scopo della parola chiave finale.

+1

setOut0 sta modificando la variabile di classe, che è definitiva. – fgb

0

Per quanto riguarda il modo, siamo in grado di dare un'occhiata al codice sorgente per 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); 
} 

... 

In altre parole, JNI può "barare". ;)

Problemi correlati