2012-02-24 9 views
29

Per this answer e this answer, i metodi statici Java non sono virtuali e non possono essere sovrascritti. Intuitivamente, quindi, questo dovrebbe funzionare (anche se nel 99% dei casi si tratta di programmazione pericoloso):Perché Java impone la compatibilità del tipo restituito per i metodi statici sottoposti a override?

class Foo 
{ 
    public static String frob() { 
     return "Foo"; 
    } 
} 

class Bar extends Foo 
{ 
    public static Number frob() { 
     return 123; 
    } 
} 

Tuttavia, in pratica, questo si ottiene:

Foo.java:10: frob() in Bar cannot override frob() in Foo; attempting to use incompatible return type 
found : java.lang.Number 
required: java.lang.String 
    public static Number frob() { 
         ^

Ingenuamente, sembra che Foo.frob() e Bar.frob() non dovrebbe avere nulla a che fare l'uno con l'altro; ma Java insiste che lo facciano. Perché?

(Nb:. Non voglio sentire perché sarebbe una cattiva idea per codificare in questo modo, voglio sentire che cosa è in Java e/o la progettazione JVM che rende questa restrizione necessario)


aggiornato per aggiungere: per coloro che pensano che il compilatore sta per confondersi chiamando metodi statici sulle istanze, se si consente questo: non lo farà. Ha già di capire questo nel caso in cui il metodo firme sono compatibili:

class Foo 
{ 
    static String frob() { 
     return "Foo"; 
    } 
} 

class Bar extends Foo 
{ 
    static String frob() { 
     return "Bar"; 
    } 
} 

class Qux { 
    public static void main(String[] args) { 
     Foo f = new Foo(); 
     Foo b = new Bar(); 
     Bar b2 = new Bar(); 

     System.out.println(f.frob()); 
     System.out.println(b.frob()); 
     System.out.println(b2.frob()); 
    } 
} 

si ottiene:

Foo 
Foo 
Bar 

La domanda è: qual è la ragione concreta per cui non poteva come facilmente (nel caso incompatibili firme) ottenere:

Foo 
Foo 
123 

risposta

8

si consideri il seguente:

public class Foo { 
    static class A { 
    public static void doThing() { 
     System.out.println("the thing"); 
    } 
    } 

    static class B extends A { 

    } 

    static class C extends B { 
    public static void doThing() { 
     System.out.println("other thing"); 
    } 
    } 

    public static void main(String[] args) { 
    A.doThing(); 
    B.doThing(); 
    C.doThing(); 
    } 
} 

Esegui! Compila e stampa

the thing 
the thing 
other thing 

metodi statici sorta di ereditano - nel senso che B.doThing si traduce in una chiamata a A.doThing - e possono sorta di essere sovrascritto.

Sembra che fosse principalmente una chiamata di giudizio per il JLS. Il modo più specifico in cui JLS sembra risolvere questo problema è section 8.2, che semplicemente non dice che i metodi statici non sono ereditati da.

+0

Woah, amico, amico. – Tom

+0

Non sono sicuro che questa sia stata necessariamente la migliore decisione possibile - ha portato a disagio come [questo codice in Guava] (http://code.google.com/p/guava-libraries/source/browse/guava/ src/com/google/common/collect/ImmutableSortedSetFauxverideShim.java) - ma non è una decisione irragionevole, e presumo che abbiano avuto ragioni per questa chiamata al giudizio che non ho ancora pensato. –

+2

'myB' chiamerà il' doThing() 'di tipo' myB'. –

2

È perché in Java, un metodo particolare è chiamato in base al tipo di tempo di esecuzione dell'oggetto e non al tipo di tempo di compilazione di esso. Tuttavia, i metodi statici sono metodi di classe e quindi l'accesso ad essi viene sempre risolto durante la compilazione utilizzando solo le informazioni sul tipo di tempo di compilazione. Cioè, che cosa accadrebbe se si potesse compilare il codice di cui sopra e l'utilizzo come questo

Foo bar = new Bar(); 
bar.frob(); 
+2

Questo non risponde alla domanda. Nel vostro esempio mi aspetterei che 'Foo.frob()' sia chiamato come 'bar' è un' Foo'. –

+0

@SteveKuo Ma le normali regole di firma Java entrano in gioco in questo caso; i metodi ereditati non possono avere gli stessi parametri e tipi di ritorno diversi. –

3

JLS 8.4.2 Method Signature, brevemente:

due metodi hanno la stessa firma se hanno lo stesso nome e argomenti tipi.

Non si dice nulla di staticità.I metodi statici possono essere richiamati tramite un'istanza (o un riferimento null): come deve essere risolto il metodo se si fa riferimento a una sottoclasse tramite una dichiarazione della superclasse?

+2

Il compilatore è già in grado di risolvere questo problema - vedere il mio commento su [risposta di Ryan Shillington] (http://stackoverflow.com/a/9439532/27358). –

+1

@DavidMoles Quindi è semplicemente "perché Java non consente ai metodi di avere la stessa firma ma tipi di ritorno diversi". Per quanto riguarda il razionale, dovresti chiedere a Gosling/ecc. ma la maggior parte risolve "è un modo semplice per eliminare un particolare tipo di errore dell'utente". –

1

Bene, la JVM potrebbe probabilmente essere fatta per permetterlo, ma parliamo del perché è una pessima idea dal punto di vista del compilatore.

I dati di istanza (incluso il tipo di istanza) non è qualcosa che è un semplice problema da risolvere in fase di compilazione. Ovviamente è ben noto in fase di runtime. Se ho una barra variabile di tipo Bar, e chiamo s = bar.frob(), il compilatore dovrebbe decodificare la barra dei tipi per vedere se il valore restituito è accettabile. Se determinare il tipo in fase di compilazione è un problema molto difficile, ciò rende il compilatore inefficiente nella migliore delle ipotesi. Nel peggiore dei casi la risposta è errata e si verificano errori di runtime che dovrebbero essere stati rilevati in fase di compilazione.

+3

Non compro questo argomento. Il compilatore ha già una regola per questo. Se 'b' è dichiarato come' Bar', quindi 'b.frob()' è 'Bar.frob()', e se 'b' è dichiarato come' Foo', allora anche se 'b' è * in realtà * un'istanza di 'Bar',' b.frob() 'è' Foo.frob() '. Questo è facile da dimostrare se si crea un esempio in cui i tipi sono compatibili. –

+0

(Domanda modificata per aggiungere un esempio.) –

+0

Per quanto riguarda la JVM, cercherà nella gerarchia di ereditarietà una corrispondenza esatta che include il tipo di ritorno. Ignora i "tipi di ritorno covarianti" e simili. –

Problemi correlati