2009-12-19 6 views
36

Capisco che in questo codice:È una cattiva idea dichiarare un metodo statico finale?

class Foo { 
    public static void method() { 
     System.out.println("in Foo"); 
    } 
} 

class Bar extends Foo { 
    public static void method() { 
     System.out.println("in Bar"); 
    } 
} 

.. il metodo statico in Bar 'nasconde' il metodo statico dichiarato Foo, al contrario di override nel senso polimorfismo.

class Test { 
    public static void main(String[] args) { 
     Foo.method(); 
     Bar.method(); 
    } 
} 

... uscita volontà:

in Foo
in Bar

Ridefinire method() come final in Foo consente di disattivare la possibilità per Bar per nasconderlo. La compilazione non riesce quando si contrassegna il metodo come final.

È considerata una pratica errata dichiarare metodi statici come final, se impedisce alle sottoclassi di ridefinire il metodo in modo intenzionale o inavvertitamente?

(this è una buona spiegazione di ciò che il comportamento di usare final è ..)

+3

dove hai trovato? cattive pratiche? –

+1

Riformatta la domanda;) L'ispezione IntelliJ di default suggerisce che lo è, e non avevo ottenuto una risposta definitiva dalle ricerche di google. – brasskazoo

+1

L'ispezione di NetBeans suggerisce anche che questa è una cattiva pratica. – steve

risposta

34

non ritengo che sia cattiva pratica di segnare un metodo static come final.

Come hai scoperto, final impedirà che il metodo venga nascosto dalle sottoclassi che è molto buona notizia imho.

Sono abbastanza sorpreso dalla sua dichiarazione:

Re-defining method() as final in Foo will disable the ability for Bar to hide it, and re-running main() will output:

in Foo
in Foo

No, che segna il metodo final in Foo impedirà Bar da compilare. Almeno in Eclipse sto ottenendo:

Exception in thread "main" java.lang.Error: Unresolved compilation problem: Cannot override the final method from Foo

Inoltre, penso che la gente dovrebbe sempre richiamare static metodo che li qualifica con il nome della classe anche all'interno della classe stessa:

class Foo 
{ 
    private static final void foo() 
    { 
    System.out.println("hollywood!"); 
    } 

    public Foo() 
    { 
    foo();  // both compile 
    Foo.foo(); // but I prefer this one 
    } 
} 
+0

sì, dovremmo abituarci a marcare tutti i metodi statici pubblici come "finali". – ZhongYu

3

Di solito con classi di utilità - classi con solo metodi statici: non è desiderabile utilizzare l'ereditarietà. per questo motivo potresti voler definire la classe come definitiva per impedire ad altre classi di estenderla. Ciò negherebbe l'inserimento di modificatori finali sui metodi della classe di utilità.

+0

Ottimo punto: uno dei miei obiettivi di refactoring è spostare i metodi statici in una classe di utilità, contrassegnando quella classe come finale è una buona idea. – brasskazoo

1

Potrebbe essere una buona cosa contrassegnare i metodi statici come finali, in particolare se si sta sviluppando un framework che ci si aspetta che gli altri si estendano. In questo modo i tuoi utenti non inavvertitamente finiranno per nascondere i tuoi metodi statici nelle loro classi. Ma se stai sviluppando un framework potresti voler evitare di usare metodi statici per cominciare.

3

Il codice non può essere compilato:

Test.java:8: method() in Bar cannot override method() in Foo; overridden method is static final public static void method() {

Il messaggio è fuorviante in quanto un metodo statico può, per definizione, non essere sovrascritto.

faccio la seguente quando si scrive codice (non al 100% per tutto il tempo, ma niente qui è "sbagliato":

(La prima serie di "regole" sono fatto per la maggior parte delle cose - alcuni casi particolari sono coperti dopo)

  1. creare un'interfaccia
  2. creare una classe astratta che implementa l'interfaccia
  3. creare classi concrete che estendono la classe astratta
  4. creare concret e le classi che implementa l'interfaccia, ma non estendere la classe astratta
  5. sempre, se possibile, fare tutte le variabili/costanti/parametri dell'interfaccia

Dal momento che un interfaccia non può avere metodi statici non Wind Up con il problema. Se stai andando a fare metodi statici nella classe astratta o in classi concrete, questi devono essere privati, quindi non c'è modo di provare a sovrascriverli.

Casi particolari:

classi di utilità (classi con tutti i metodi statici):

  1. dichiarare la classe come definitiva
  2. dargli un costruttore privato per impedire la creazione accidentale

Se si desidera avere un metodo statico in una classe concreta o astratta che non è privato, si preferisce invece creare una classe di utilità.

classi di valore (una classe che è molto specializzato per tenere in sostanza i dati, come java.awt.Point dove è praticamente in mano valori xey):

  1. non c'è bisogno di creare un'interfaccia
  2. non è necessario creare una classe astratta
  3. classe dovrebbe essere finale
  4. metodi statici non privati ​​sono OK, soprattutto per la costruzione come si potrebbe desiderare di eseguire la memorizzazione nella cache.

Se si segue il consiglio di cui sopra si finirà con un codice piuttosto flessibile che ha anche una separazione delle responsabilità abbastanza pulita.

Una classe di valore di esempio è questa classe Località:

import java.util.ArrayList; 
import java.util.HashMap; 
import java.util.List; 
import java.util.Map; 


public final class Location 
    implements Comparable<Location> 
{ 
    // should really use weak references here to help out with garbage collection 
    private static final Map<Integer, Map<Integer, Location>> locations; 

    private final int row;  
    private final int col; 

    static 
    { 
     locations = new HashMap<Integer, Map<Integer, Location>>(); 
    } 

    private Location(final int r, 
        final int c) 
    { 
     if(r < 0) 
     { 
      throw new IllegalArgumentException("r must be >= 0, was: " + r); 
     } 

     if(c < 0) 
     { 
      throw new IllegalArgumentException("c must be >= 0, was: " + c); 
     } 

     row = r; 
     col = c; 
    } 

    public int getRow() 
    { 
     return (row); 
    } 

    public int getCol() 
    { 
     return (col); 
    } 

    // this ensures that only one location is created for each row/col pair... could not 
    // do that if the constructor was not private. 
    public static Location fromRowCol(final int row, 
             final int col) 
    { 
     Location    location; 
     Map<Integer, Location> forRow; 

     if(row < 0) 
     { 
      throw new IllegalArgumentException("row must be >= 0, was: " + row); 
     } 

     if(col < 0) 
     { 
      throw new IllegalArgumentException("col must be >= 0, was: " + col); 
     } 

     forRow = locations.get(row); 

     if(forRow == null) 
     { 
      forRow = new HashMap<Integer, Location>(col); 
      locations.put(row, forRow); 
     } 

     location = forRow.get(col); 

     if(location == null) 
     { 
      location = new Location(row, col); 
      forRow.put(col, location); 
     } 

     return (location); 
    } 

    private static void ensureCapacity(final List<?> list, 
             final int  size) 
    { 
     while(list.size() <= size) 
     { 
      list.add(null); 
     } 
    } 

    @Override 
    public int hashCode() 
    { 
     // should think up a better way to do this... 
     return (row * col); 
    } 

    @Override 
    public boolean equals(final Object obj) 
    { 
     final Location other; 

     if(obj == null) 
     { 
      return false; 
     } 

     if(getClass() != obj.getClass()) 
     { 
      return false; 
     } 

     other = (Location)obj; 

     if(row != other.row) 
     { 
      return false; 
     } 

     if(col != other.col) 
     { 
      return false; 
     } 

     return true; 
    } 

    @Override 
    public String toString() 
    { 
     return ("[" + row + ", " + col + "]"); 
    } 

    public int compareTo(final Location other) 
    { 
     final int val; 

     if(row == other.row) 
     { 
      val = col - other.col; 
     } 
     else 
     { 
      val = row - other.row; 
     } 

     return (val); 
    } 
} 
0

ho incontrato uno svantaggio di utilizzare metodi finali che utilizzano AOP di primavera e MVC. Stavo cercando di utilizzare l'AOP di primavera inserito nei ganci di sicurezza attorno a uno dei metodi in AbstractFormController che è stato dichiarato definitivo. Penso che la primavera stesse usando la libreria di bcel per l'iniezione in classi e ci fosse qualche limitazione lì.

0

Quando creo classi di utilità pure, dichiaro quindi con un costruttore privato in modo che non possano essere estese.Quando creo classi normali, dichiaro i miei metodi statici se non usano nessuna delle variabili di istanza di classe (o, in alcuni casi, anche se lo fossero, io passerei gli argomenti nel metodo e lo renderemo statico, è più facile vedere cosa sta facendo il metodo). Questi metodi sono dichiarati statici ma sono anche privati: sono lì solo per evitare la duplicazione del codice o per rendere il codice più facile da capire.

Detto questo, non ricordo di aver eseguito il caso in cui si dispone di una classe che ha metodi statici pubblici e che può/deve essere estesa. Ma, in base a quanto riportato qui, dichiarerei i suoi metodi statici definitivi.

5

Se ho un metodo di public static, allora è spesso già si trova in un cosiddetto classe di utilità con solo static metodi. Esempi di auto-spiegazione sono StringUtil, SqlUtil, IOUtil, ecc. Queste classi di utilità sono già dichiarate final e fornite con un costruttore private. Per esempio.

public final class SomeUtil { 

    private SomeUtil() { 
     // Hide c'tor. 
    } 

    public static SomeObject doSomething(SomeObject argument1) { 
     // ... 
    } 

    public static SomeObject doSomethingElse(SomeObject argument1) { 
     // ... 
    } 

} 

In questo modo non è possibile ignorarli.

Se il vostro non si trova in una sorta di classe di utilità, quindi metterei in dubbio il valore del modificatore public. Non dovrebbe essere private? Altrimenti basta spostarlo in una classe di utilità. Non confondere le classi "normali" con i metodi public static. In questo modo non è inoltre necessario contrassegnarli final.

Un altro caso è una sorta di classe fabbrica astratta, che restituisce concrete implementazioni di sé tramite un metodo public static. In tal caso sarebbe perfettamente logico marcare il metodo final, non si vuole che le implementazioni concrete siano in grado di sovrascrivere il metodo.

+0

Penso che questo dovrebbe essere contrassegnato come la risposta corretta personalmente. Questo è esattamente quello che avrei suggerito. Finalizza la classe, non i singoli metodi. –

31

I metodi statici sono una delle funzionalità più confuse di Java. Le migliori pratiche ci sono per risolvere il problema e fare tutti i metodi statici final è una di queste migliori pratiche!

Il problema con metodi statici è che

  • non sono metodi di classe, ma le funzioni globali preceduto da un nome di classe
  • è strano che essi sono "ereditate" per sottoclassi
  • è sorprendente che non possono essere sovrascritte ma nascosti
  • è totalmente rotto che possono essere chiamati con un esempio come ricevitore

quindi si dovrebbe

  • sempre chiamarli con il loro classe come ricevitore
  • sempre chiamarli con la classe dichiarando solo come ricevitore
  • sempre farli (o la classe dichiarando) final

e dovresti

  • mai chiamarli con un'istanza come ricevitore
  • mai li chiamano con una sottoclasse di loro classe dichiarando come ricevitore
  • mai ridefinirli in sottoclassi

 

NB: il la seconda versione del tuo programma dovrebbe fallire in un errore di compilazione. Presumo che il tuo IDE stia nascondendo questo fatto da te!

+0

Risposta molto bella! A proposito, la seconda versione fallisce davvero la compilazione nel mio IDE. Modificato la domanda per chiarire questo. – brasskazoo

+0

ne sei assolutamente sicuro: rendili sempre (o la classe dichiarante) finale - se rendi definitive le class final, automaticamente tutti i metodi pubblici saranno definitivi, ma questo vale anche per i metodi STATIC? –

+0

Se la classe è definitiva, non può essere sottoclasse, quindi ne consegue che tutti i metodi sono definitivi. Per metodo statico ciò significa che non può esistere un metodo di sottoclasse che li nasconda, il che equivale a rendere il metodo definitivo ma non la classe. – akuhn

1

La maggior parte di questo numero final risale all'epoca in cui le macchine virtuali erano piuttosto stupide/conservatrici. Allora, se hai contrassegnato un metodo final, significa (tra le altre cose) che la VM può essere in linea, evitando le chiamate ai metodi. Questo non è il caso da un tempo lungo (o lungo doppio: P): http://java.sun.com/developer/technicalArticles/Networking/HotSpot/inlining.html.

ho immagino che l'ispezione Idea/Netbeans si avverte, perché pensa che si desidera utilizzare la parola chiave final per l'ottimizzazione e pensano che non sono a conoscenza del fatto che è non necessario con le macchine virtuali moderna.

Solo i miei due centesimi ...

0

Perché metodi statici sono le proprietà della classe e sono chiamati con il nome della classe, piuttosto che di oggetto. Se rendiamo finale anche il metodo della classe genitore, non verrà sovraccaricato in quanto i metodi finali non consentono di modificare la sua posizione di memoria, ma possiamo aggiornare il membro dati finale nella stessa posizione di memoria ...