2016-02-23 13 views
11

In questo momento sto implementando un metodo che ha un parametro del tipo Classe e questo metodo restituisce un valore booleano se l'oggetto della classe data richiede un'istanza della classe che lo include per essere istanziato.Java: come determinare se una classe locale definita in un blocco di inizializzazione richiede un'istanza di inclusione per l'istanziazione?

Questo metodo attualmente funziona nel modo seguente:

if (clazz.getEnclosingClass() == null) { 
     return false; 
    } 
    if (clazz.isAnonymousClass() || clazz.isMemberClass()) { 
     return !Modifier.isStatic(clazz.getModifiers()); 
    } 
    if (clazz.getEnclosingConstructor() != null) { 
     return true; 
    } 
    final Method enclosingMethod = clazz.getEnclosingMethod(); 
    if (enclosingMethod != null) { 
     return !Modifier.isStatic(enclosingMethod.getModifiers()); 
    } 

Per spiegare il motivo per cui è stato progettato come tale:

  1. Prima si verifica se si tratta di una classe di primo livello, in caso affermativo, l'algoritmo può sicuro restituire false
  2. Se la classe è anonima o una classe membro, richiede un'istanza di chiusura se non è statica (una classe anynmous è statica automaticamente se è dichiarata in un costruttore statico/metodo/blocco inizializzatore)
  3. La classe può ora essere assunta come una classe locale (ignorando array e primitive), quindi è definita in un costruttore, un metodo o un inizializzatore. Tuttavia, a differenza di una classe anonima, una classe locale non viene mai considerata statica, ma richiede comunque un'istanza di inclusione se la classe locale è definita in un blocco non statico.
  4. Un costruttore non è mai statica, quindi in questo caso tornare vero
  5. Se è definita in un metodo, restituirà true se il metodo non è statico

ho bisogno passaggio 6 per determinare se la classe locale risiede in un blocco di inizializzazione statico o in un blocco di inizializzazione di istanze, quindi ho finito con l'implementazione per questa funzione.

Quindi ecco dove l'API di riflessione è un po 'corta. Non esiste alcun metodo Class.getEnclosingInitializer() o simile, né esiste una classe che rappresenta un inizializzatore nel pacchetto di riflessione.

Non è un blocco di inizializzazione un membro di una classe? Nella specifica java 1.8 l'interfaccia Membro ha solo le classi di implementazione Campo, Eseguibile (con sottoclassi Costruttore e Metodo), e poi c'è MemberName che è fuori dal campo di applicazione per la maggior parte degli utenti di riflessione.

Non sono sicuro che le persone dietro le specifiche abbiano dimenticato questa situazione e le classi locali dovrebbero essere statiche se dichiarate in un metodo statico/inizializzatore (come le classi anonime). Ma mi sembra che manchi questa ultima dose di coerenza da questo punto di vista.

Così qualcuno ha un'idea su come accertare in quale tipo di blocco di inizializzazione viene dichiarata la classe locale?

io non sono davvero appassionato di scavare attraverso i campi per una sintetica uno di un tipo che è uguale è che racchiude classe, o loop attraverso la sua costruttori di qualcosa di simile a quello (nota a margine: oggetti Parameter da Constructor.getParameters() restituiscono sempre false su isImplicit() e isSynthetic() non importa quello che provo ... sembra solo sbagliato). Quindi se posso evitare tali soluzioni sarebbe fantastico.

+0

sembra più una discussione che una domanda. –

+0

Beh, ho bisogno di un modo per determinare X, e usare il metodo Y è qualcosa che desidero evitare, quindi la mia domanda è "qual è il metodo Z per determinare X di cui non sono a conoscenza?". – grimmeld

risposta

2

Un puzzle interessante, ma temo che non abbia una soluzione che soddisfi le vostre esigenze. Una volta che i file di origine sono stati compilati in bytecode, i dettagli relativi alla classe che racchiude l'ambito vengono persi. Ricorda che JVM non è solo per il linguaggio Java, mentre il problema descritto è per lo più specifico della lingua.


Ho bisogno passaggio 6 per determinare se la classe locale risiede sia in un statico Initializer blocco o un grado Initializer Block

Nessuna di dette informazioni è disponibile in fase di esecuzione. Il file di classe ha l'attributo EnclosingMethod per le classi locali o anonime (JVMS §4.7.7). Tuttavia, non c'è modo di distinguere tra l'inclusione dell'inizializzatore di istanze e l'inizializzatore statico. La specifica dice esplicitamente che

«method_index deve essere zero se la classe attuale è stato immediatamente racchiuso nel codice sorgente da un inizializzatore esempio, inizializzatore statico, esempio inizializzazione variabile o una classe inizializzazione variabile».

Considerare questi due casi:

class Outer { 
    { 
     // Local class inside instance initializer 
     class Local {} 
    } 
} 

vs.

class Outer { 
    static { 
     // Local class inside static initializer 
     class Local { 
      final Outer this$0; 

      Local(Outer outer) { 
       this$0 = outer; 
      } 
     } 
    } 
} 

Essi sono compilati a quasi lo stesso file di classe. L'unica differenza è che il campo this$0 ha il flag SYNTHETIC nel primo caso, ma non nel secondo. Ma controllare questo flag è esattamente quello che vuoi evitare. (Perché?)


classi locali in realtà dovrebbe essere statico se dichiarato in una statica metodo/inizializzazione (come le classi anonime)

direi che né le classi locali né anonime dovrebbe mai essere statico. Inoltre, linguaggio Java Specification impone loro di essere non statico:

  • Il modificatore static riguarda solo le classi aderenti, non verso l'alto livello o corsi locali o anonimi (JLS §8.1.1).
  • Una classe anonima è sempre una classe interiore; non è mai statico (JLS §15.9.5).

Quindi, è chiaramente una violazione specifica che getModifiers() a volte restituisce STATIC per una classe anonima. C'è un bug JDK-8034044 che è stato corretto nel prossimo JDK 9. E che rompe il passo 2 del tuo algoritmo.


Ok, cosa fare allora?Dipende da quello che in realtà intende per

se l'oggetto di classe richiede un'istanza è allegando classe per essere un'istanza

direi, la definizione di cui sopra implica che tutti i costruttori di una classe ha un argomento in più di racchiudere il tipo di classe. In questo caso non esiste in effetti alcun modo per creare un'istanza di una classe senza un'istanza della classe che li include.

Tuttavia, se si desidera veramente distinguere tra inizializzatore e inizializzatore statico, l'unica possibilità (come ho mostrato sopra) è di cercare un campo con il flag SYNTHETIC.

+0

Per quanto riguarda il non voler utilizzare il campo sintetico per il controllo: non ero sicuro dell'affidabilità dell'utilizzo di tale metodo. Ma sembra che il mio metodo fino a questo punto fosse inaffidabile all'inizio poiché avevo fatto delle ipotesi basate sui risultati piuttosto che sulle specifiche, causandomi di dipendere da un bug che verrà corretto in una futura implementazione di Java. Grazie per la spiegazione dettagliata e il tempo che hai dedicato alla ricerca. – grimmeld

0

Un blocco di inizializzazione non è qualcosa che esiste in un file di classe compilato. Il codice in blocchi di inizializzazione statici viene inserito in un metodo statico chiamato <clinit>, mentre il codice in blocchi di inizializzazione non statici viene inserito in ogni costruttore della classe. Pertanto, per una classe locale definita in un blocco di inizializzazione non statico, getEnclosingConstructor() dovrebbe restituire un valore non null.

+0

Ma restituisce nulla nel mio caso però. Potrebbe esserci qualcosa di sbagliato in me IDE? Sto usando netbeans 8.1 – grimmeld

+1

Il blocco di inizializzazione dell'istanza non appartiene a nessun costruttore a livello di lingua. Considera una classe con più costruttori - quale restituirà 'getEnclosingConstructor()'? – apangin