2013-08-08 13 views
9

Fare riferimento al codice riportato di seguito. Quando eseguo il codice, sono in grado di modificare il valore di una variabile non statica finale. Ma se provo a cambiare il valore di una variabile statica finale, getta java.lang.IllegalAccessException.modifica delle variabili finali tramite la riflessione, perché differenza tra variabile finale statica e non statica

La mia domanda è perché non genera un'eccezione anche in caso di variabile finale non statica o viceversa. Perché la differenza?

import java.lang.reflect.Field; 
import java.util.Random; 

public class FinalReflection { 

    final static int stmark = computeRandom(); 
    final int inmark = computeRandom(); 

    public static void main(String[] args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { 
     FinalReflection obj = new FinalReflection(); 
     System.out.println(FinalReflection.stmark); 
     System.out.println(obj.inmark); 
     Field staticFinalField = FinalReflection.class.getDeclaredField("stmark"); 
     Field instanceFinalField = FinalReflection.class.getDeclaredField("inmark"); 
     staticFinalField.setAccessible(true); 
     instanceFinalField.setAccessible(true); 

     instanceFinalField.set(obj, 100); 
     System.out.println(obj.inmark); 

     staticFinalField.set(FinalReflection.class, 101); 
     System.out.println(FinalReflection.stmark); 

    } 

    private static int computeRandom() { 
     return new Random().nextInt(5); 
    } 
} 
+1

Ho pubblicato il codice che non fornisce un'eccezione. Ma è sicuramente un hack. –

risposta

10
FinalReflectionobj = new FinalReflection(); 
System.out.println(FinalReflection.stmark); 
System.out.println(obj.inmark); 
Field staticFinalField = FinalReflection.class.getDeclaredField("stmark"); 
Field instanceFinalField = FinalReflection.class.getDeclaredField("inmark"); 
staticFinalField.setAccessible(true); 
instanceFinalField.setAccessible(true); 

//EXTRA CODE 
//Modify the final using reflection 
Field modifiersField = Field.class.getDeclaredField("modifiers"); 
modifiersField.setAccessible(true); 
modifiersField.setInt(staticFinalField, staticFinalField.getModifiers() & ~Modifier.FINAL); 


instanceFinalField.set(obj, 100); 
System.out.println(obj.inmark); 
staticFinalField.set(FinalReflection.class, 101); 
System.out.println(FinalReflection.stmark); 

Questa soluzione non viene senza alcuni svantaggi, esso non può funzionare in tutti i casi:

In caso un campo final viene inizializzato a una costante nella dichiarazione campo compilazione, modifiche al campo final potrebbe non essere visibile, dal momento che gli usi di tale campo finale vengono sostituiti in fase di compilazione con la costante di compilazione.

Un altro problema è che la specifica consente l'ottimizzazione aggressiva dei campi final. All'interno di un thread, è possibile riordinare le letture di un campo final con quelle modifiche di un campo final che non si verificano nel costruttore. More su questo è anche spiegato in questa domanda simile.

+0

@assylias non vedi la parte codice aggiuntivo –

+2

@assylias questo ti permetterà di cambiare anche il campo finale statico se non c'è un gestore sicurezza. –

+0

Si noti che questo non funzionerà con le primitive finali statiche inizializzate con un'espressione costante. – assylias

0

Per la finale, può essere assegnato diversi valori in fase di esecuzione quando inizializzato.

Class Test{  
public final int a; 
} 

Test t1 = new Test(); 
t1.a = 10; 
Test t2 = new Test(); 
t1.a = 20; 

Così ogni istanza ha un valore diverso del campo a.

Per la finale statica, tutte le istanze condividono lo stesso valore e non possono essere modificate dopo la prima inizializzazione.

Class TestStatic{ 
    public static final int a; 
} 

Test t1 = new Test(); 
t1.a = 10; 
Test t2 = new Test(); 
t1.a = 20; // ERROR, CAN'T BE ALTERED AFTER THE FIRST INITIALIZATION. 
+0

+1. Sì, sono d'accordo con quello che stai dicendo. –

+0

Ma lui usa solo una singola istanza della classe. E una variabile finale non statica non è modificabile dopo il primo incarico. Quindi questa spiegazione è sbagliata. –

2

Il javadoc è chiaro:

Se il campo sottostante è definitiva, il metodo genera un IllegalAccessException meno setAccessible (true) è riuscita per questo oggetto Campo e il campo è non-statico.

Da una prospettiva JLS, non è specificato l'esatto comportamento di riflessione come dovrebbe funzionare, ma in JLS 17.5.4:

Normalmente, un campo che è definitiva e statico non può essere modificato.

Una soluzione è remove the final modifier through reflection.

+1

Risposta eccellente, ma penso che la domanda sia piuttosto: perché i designer java hanno deciso questo? – morgano

+0

@morgano se questo è il caso, SO probabilmente non è il posto migliore per chiedere! Certamente causa tutti i tipi di problemi. Ad esempio, le costanti primitive sono allineate al momento della compilazione, quindi non è possibile cambiarle in fase di esecuzione a meno che non si modifichi il bytecode sottostante. – assylias

+0

@assylias grazie per l'eccellente informazione. ma sì, la mia domanda era poco allineata a ciò che diceva Morgano. Ma sì, non potrei essere più d'accordo sul fatto che SO potrebbe non essere il posto migliore per chiederlo. Mi verrà in mente la prossima volta. In ogni caso, l'essere primitivo in linea vale sia per variabili finali statiche che non statiche. prova a eseguire il valore di codice modificato 5 invece di computeRandom(). – veritas