2015-05-13 5 views
17

Ho il seguente esempio:Calling base e metodi statici derivati ​​di un tipo di variabile

class Ideone 
{ 
    public static void main (String[] args) throws java.lang.Exception 
    { 
     A<ConcreteErrorHandler> a = new A<ConcreteErrorHandler>(); 
     a.m(); //Exception here! 
    } 

    public static class AbstractErrorHandler { 
     public static void handle(){ 
      throw new UnsupportedOperationException("Not implemented"); 
     } 
    } 

    public static class ConcreteErrorHandler extends AbstractErrorHandler{ 
     public static void handle(){ 
      System.out.println("Concrete handler"); 
     } 
    } 

    public static class A<T extends AbstractErrorHandler>{ 
     public void m(){ 
      T.handle(); 
     } 
    } 
} 

IDEONE

Perché il metodo della classe di base è chiamato, ma non del derivato? Le firme dei metodi handle() sono perfettamente uguali. So che i metodi statici non ereditano, ma non dovrebbe essere generato un errore in fase di compilazione nel mio caso?

Qualcuno potrebbe spiegare questo comportamento?

risposta

10

La ragione di ciò è che il compilatore non sa quale sottotipo esatto di AbstractErrorHandler sostituirà T a runtime. Questo è il motivo per cui lega il metodo T.handle() al metodo AbstractErrorHandler.handle().

Il problema qui è che si sta mescolando l'ereditarietà con le funzioni static delle classi in Java.

Al fine di avere questo lavoro (correttamente), è necessario sbarazzarsi del modificatore static per le .handle() metodi e mantenere un'istanza T nella classe A. Questa istanza di T (in fase di runtime) sarà una sottoclasse specifica di AbstractErrorHandler e quindi verrà eseguito il metodo effettivo.handle().

Ad esempio:

class Ideone { 
    public static void main(String[] args) throws java.lang.Exception { 
     A<ConcreteErrorHandler> a = new A<ConcreteErrorHandler>(new ConcreteErrorHandler()); 
     a.m(); 
    } 

    public static class AbstractErrorHandler { 
     public void handle() { 
      throw new UnsupportedOperationException("Not implemented"); 
     } 
    } 

    public static class ConcreteErrorHandler extends AbstractErrorHandler { 
     public void handle() { 
      System.out.println("Concrete handler"); 
     } 
    } 

    public static class A<T extends AbstractErrorHandler> { 

     T instance; 

     A(T instance) { 
      this.instance = instance; 
     } 

     public void m() { 
      instance.handle(); 
     } 
    } 
} 
+0

Ma ho fornito il tipo esatto al momento della compilazione. 'A a = new A ();' Il tipo noto al momento della compilazione. –

+0

Hai fornito il tipo fuori dall'ambito del metodo 'm'. – MadConan

+3

@ St.Antario I generici Java non sono modelli come in C++. La dichiarazione di classe non conosce la parametrizzazione che gli dai da qualche altra parte. – Radiodef

2

Credo che sia perché static è di classe scope e si sta dicendo al compilatore di utilizzare AbstractErrorHandler implicitamente utilizzando T extends AbstractErrorHandler.

Il runtime assumerà il livello di classe più alto poiché la cancellazione del tipo si verifica in fase di esecuzione.

L'attuazione di m utilizza solo T che è un AbstractErrorHandler, nonostante il fatto che si è dichiarato di essere il tipo concreto nel metodo principale, che non è nel campo di applicazione del metodo di m.

+0

Non vedo come spiega nulla. Onestamente, mi aspettavo di vedere il riferimento JLS o alcuni. –

3

Perché in fondo il metodo m sarà compilato in

public void m(){ 
    AbstractErrorHandler.handle(); 
} 
+2

Perché, ho fornito il tipo al momento della compilazione? –

+0

No, non l'hai fatto. Hai fornito 'T'. Mentre potrebbe essere possibile fare in modo che il compilatore faccia ciò che pensi dovrebbe, chiaramente non lo fa. – MadConan

+1

Mentre è vero, la tua risposta è già evidente dalla domanda. L'OP chiede invece * perché * questo accada. – Radiodef

6

4.4. Type Variables ci dice che:

I membri di un tipo di variabile X con bound T & I1 & ... & In sono i membri del tipo di intersezione T & I1 & ... & Invisualizzati nel punto in cui la variabile di tipo viene dichiarata.

Pertanto i membri di T extends AbstractErrorHandler sono membri di AbstractErrorHandler. T.handle(); fa riferimento a AbstractErrorHandler.handle();.

2

Il motivo è perché si utilizza farmaci generici e Java metodi statici che sono non nascosto sovrascritto. Al momento della compilazione l'unica informazione conosciuta è la classe AbstractErrorHandler (i generici funzionano a tempo di compilazione in java, non esiste un bytecode con informazioni generiche) e il metodo chiamato è quello della classe. Se si modifica il metodo di gestione del form in "istanza", l'implementazione chiamata è quella "corretta" (poiché il metodo è sovrascritto, non nascosto) come nell'esempio seguente.

class Ideone 
{ 
    public static void main (String[] args) throws java.lang.Exception 
    { 
     A<AbstractErrorHandler> a = new A<AbstractErrorHandler>(); 
     a.m(new ConcreteErrorHandler()); //Exception here! 
    } 

    public static class AbstractErrorHandler { 
     public void handle(){ 
      throw new UnsupportedOperationException("Not implemented"); 
     } 
    } 

    public static class ConcreteErrorHandler extends AbstractErrorHandler{ 
     public void handle(){ 
      System.out.println("Concrete handler"); 
     } 
    } 

    public static class A<T extends AbstractErrorHandler>{ 
     public void m(T t){ 
      t.handle(); 
     } 
    } 
} 
4

La cancellazione di un parametro di tipo limitata è il limite (e, nel caso di un incrocio legato, il primo tipo il limite). Quindi nel tuo caso, T extends AbstractErrorHandler viene cancellato per AbstractErrorHandler e il metodo è efficace sostituito dal seguente:

public void m() { AbstractErrorHandler.handle(); } 

Si veda ad esempio JLS 4.6

La cancellazione di una variabile di tipo (§ 4.4) è la cancellazione del suo limite più a sinistra.