2015-03-24 12 views
10

Questa classe:Perché una classe non può estendere una classe nidificata statica che si verifica al suo interno?

public class OuterChild extends OuterChild.InnerParent { 
    public static class InnerParent { 
    } 
} 

fallisce la compilazione:

$ javac OuterChild.java 
OuterChild.java:1: error: cyclic inheritance involving OuterChild 
public class OuterChild extends OuterChild.InnerParent { 
    ^
1 error 

perché OuterChild sarebbe "dipendere" stessa, perché (per §8.1.4 "Superclasses and Subclasses" of The Java Language Specification, Java SE 8 Edition) una classe dipende direttamente su qualsiasi tipo che "riportato in [ è] extends o implements clausola [& hellip;] come qualificatore nella forma pienamente qualificata di un nome superclasse o superinterfaccia. "

Ma io non capisco davvero la motivazione qui. Qual è la dipendenza problematica? E 'solo per coerenza con il caso in cui InnerParent non sono stati static (e quindi finirebbe con un'istanza di sé stessa che include lessicamente)?

+2

@downvoter: attenzione a spiegare perché? – ruakh

risposta

5

Questo sembra essere un caso d'angolo abbastanza nefasto, in quanto vi è un number of bugs relativo all'ereditarietà ciclica, che spesso porta a cicli infiniti, stack overflow e OOM nel compilatore. Ecco alcune citazioni rilevanti che possono offrire una certa comprensione:

Bug 4326631:

questo esempio è non legale, e questo è reso evidente nella prossima 2 ° edizione del linguaggio Java Specification. Le classi allo stesso tempo relative sia all'ereditarietà sia all'involucro sono problematiche, tuttavia il whitepaper delle classificazioni originali non ha risolto adeguatamente il problema, , né i compilatori precedenti alla 1.3 implementavano una politica coerente. Nell'edizione JLS 2nd , la regola contro l'ereditarietà ciclica è stata estesa per proibire a una classe o un'interfaccia da "dipendente" su se stessa, direttamente o indirettamente. Un tipo dipende non solo dai tipi che estende o implementa, ma anche dai tipi che servono come qualificatori all'interno dei nomi di tali tipi.

Bug 6695838:

Le due dichiarazioni di classe sono infatti ciclica; di conseguenza a JLS 8.1.4 abbiamo che:

Foo dipende Foo $ INTF (Foo $ INTF appare nella clausola attrezzi di Foo)
Foo $ INTF dipende Moo $ INTF (Moo $ INTF appare nella clausola di Foo $ INTF si estende)
Foo $ Intf dipende da Foo (Foo appare come un qualificatore nella clausola extends di Foo $ Intf)

Per transitività, abbiamo che Foo dipende da se stesso; in quanto tale il codice dovrebbe essere rifiutato con un errore in fase di compilazione.

Bug 8041994:

Indietreggiando, il direttamente-dipende rapporto per le classi e interfacce è stato introdotto nel JLS2 per chiarire JLS1 e coprire superclassi/superinterfacce nidificati classi (ad esempio AB nella descrizione) .

Bug 6660289:

Questo problema è dovuto all'ordine in cui javac eseguire attribuzione di tipo variabile attribuzione limiti classe wrt.

1) attribuzione di classe esterna < T estende Outer.Inner>
1a) attribuzione di Outer innesca attribuzione del tipo di Outer variabile
2) attribuzione di Outer.T
2a) attribuzione di Outer.T trigger attribuzione della sua dichiarata vincolato
3) attribuzione di classe Outer.Inner < S estende T>
3a) attribuzione di Outer.Inner innesca attribuzione del tipo di Outer.Inner variabile
4) attribuzione di Outer.Inner < S>
4a) Attribuzione di Oute r.Inner.S innesca l'attribuzione del limite dichiarato
5) Attribuzione di Outer.T - questo non fa altro che restituire il tipo di T; come puoi vedere, in questa fase non è stato ancora impostato il limite di T sull'oggetto che rappresenta il tipo di T.

In un secondo momento, per ogni variabile di tipo attribuito, javac esegue un controllo per garantire che il limite di un la variabile tipo data non introduce l'ereditarietà ciclica. Ma abbiamo visto che nessun limite è impostato per Outer.T; per questo motivo javac si blocca con un NPE quando tenta di rilevare un ciclo nell'albero ereditario indotto dal limite dichiarato di Outer.Inner.S.

Bug 6663588:

limiti di tipo variabile possono riferirsi a classi appartenenti ad un albero ereditario ciclico che provoca il processo di risoluzione per immettere un ciclo durante la ricerca per i simboli.

Per tua domanda specifica di "qual è la dipendenza problematica?" Sembra essere un complesso in fase di compilazione caso limite risoluzione simbolo, e la soluzione introdotta nel JLS2 è stato quello di vietare semplicemente cicli introdotte dal tipi di qualificazione così come i supertipi reali.

In altre parole questo potrebbe teoricamente essere fatto per funzionare con miglioramenti appropriati al compilatore, ma fino a quando qualcuno arriva e fa in modo che ciò accada è più pratico semplicemente bandire questa relazione insolita nelle specifiche del linguaggio.

+0

Grazie! Ora ho letto i bug che hai trovato e mentre loro non dicono esplicitamente qualcosa del tipo "è stato difficile, quindi abbiamo rinunciato", sono d'accordo con la tua valutazione sul fatto che ciò sembra essere il sottoterra. :-P – ruakh

+0

Sì, ho cercato qualcosa nel processo di sviluppo/documentazione JLS2 che spiegava il motivo del cambiamento, ma non ho trovato nulla di esplicito. Immagino che investigare le mailing list appropriate possa svelare qualcosa come "Ugh, lascia che sia vietato e fatto!" :) – dimo414

2

Un SWAG istruito: Perché la JVM deve prima caricare la classe genitore, che include un comando per caricare la classe interna. La classe interna è definita dal CL dopo la classe esterna è definita in modo che eventuali riferimenti ai campi o ai metodi della classe esterna siano risolvibili. Cercando di estendere l'esterno dall'interno, chiede alla JVM di compilare l'interno prima dell'esterno, creando così un problema con l'uovo e la gallina. Il problema ha le sue radici nel fatto che una classe interna può fare riferimento ai valori di campo della sua classe esterna, a condizione che vengano rispettate le regole attorno all'ambito e all'istanziazione (statico v. Non statico). A causa di questa possibilità, la JVM dovrebbe essere garantita che in nessun momento nulla nella classe interna tenterà di accedere o mutare qualsiasi riferimento di campo o oggetto nella classe esterna. Può solo trovarlo compilando entrambe le classi, la prima esterna, ma ha bisogno di questa informazione prima della alla compilazione per essere certi che non ci sarà un problema di ambito o istanza di qualche tipo. Quindi è un catch-22.

+1

Si noti che questo problema si verifica in fase di compilazione, non in fase di runtime, quindi non ha nulla a che fare con l'ordine di inizializzazione della classe JVM. Infatti se la JVM determina che ha bisogno di inizializzare una classe interna statica, * non * ha bisogno di inizializzare la classe esterna prima - vedi [JLS §12.4.1] (https://docs.oracle.com/javase/specs/ JLS/SE8/html/JLS-12.html # JLS-12.4.1). – dimo414

Problemi correlati