2009-12-19 12 views
36

entiendo que en este código:¿Es una mala idea declarar un método estático final?

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

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

.. el método estático en Bar 'pieles' el método estático declarada en Foo, a diferencia de lo primordial en el sentido de polimorfismo.

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

... es la salida:

in Foo
in Bar

Redefiniendo method() como final en Foo deshabilitará la capacidad para Bar de ocultarlo. La compilación falla cuando marca el método como final.

¿Se considera una mala práctica declarar métodos estáticos como final, si detiene las subclases para redefinir el método intencionalmente o sin querer?

(this es una buena explicación de lo que el comportamiento de uso de final es ..)

+3

¿dónde lo obtuviste? ¿mala práctica? –

+1

Reformó la pregunta;) La inspección por defecto de IntelliJ lo sugiere y no recibí una respuesta definitiva de las búsquedas de Google. – brasskazoo

+1

La inspección de NetBeans también sugiere que esta es una mala práctica. – steve

Respuesta

34

no considero que es una mala práctica para marcar un método static como final.

Como descubriste, final evitará que el método quede oculto por las subclases que son muy buenas noticias imho.

Estoy bastante sorprendido por su estado de cuenta:

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, marcando el método que final en Foo evitará Bar de compilación. Al menos en Eclipse que estoy recibiendo:

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

Además, creo que la gente siempre debe invocar static método de clasificación con el nombre de la clase, incluso dentro de la propia clase:

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í, deberíamos hacernos el hábito de marcar todos los métodos estáticos públicos como 'finales'. – ZhongYu

3

lo general, con clases de utilidad - clases con solo métodos estáticos: no es deseable usar la herencia. por esta razón, es posible que desee definir la clase como definitiva para evitar que otras clases la amplíen. Esto negaría poner modificadores finales en sus métodos de clase de utilidad.

+0

Buen punto: una de mis metas de refactorización es mover los métodos estáticos a una clase de utilidad, y marcar esa clase como final es una buena idea. – brasskazoo

1

Podría ser una buena idea marcar los métodos estáticos como definitivos, especialmente si está desarrollando un marco que espera que otros extiendan. De esta forma, sus usuarios no terminarán ocultando inadvertidamente sus métodos estáticos en sus clases. Pero si está desarrollando un marco, es posible que desee evitar el uso de métodos estáticos para empezar.

3

El código no se compila:

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

El mensaje es engañoso ya que un método estático puede, por definición, no puede anular.

hago lo siguiente al codificar (no el 100% todo el tiempo, pero nada aquí es "malo":

(El primer conjunto de "reglas" se llevan a cabo para la mayoría de las cosas - algunos casos especiales se cubren después)

  1. crear una interfaz
  2. crear una clase abstracta que implementa la interfaz
  3. crear clases concretas que amplían la clase abstracta
  4. crear concret clases de correos que implementa la interfaz, pero no extiende la clase abstracta
  5. siempre, si es posible, hacer que todas las variables/constantes/parámetros de la interfaz

Desde una interfaz no puede tener métodos estáticos que no terminan con el problema Si va a hacer métodos estáticos en la clase abstracta o en clases concretas, deben ser privados, entonces no hay forma de intentar anularlos.

casos especiales:

clases de utilidad (clases con todos los métodos estáticos):

  1. declarar la clase como definitiva
  2. le dan un constructor privado para evitar la creación accidental

Si desea tener un método estático en una clase concreta o abstracta que no sea privada, probablemente desee crear una clase de utilidad en su lugar.

clases de valores (una clase que es muy especializado para mantener esencialmente datos, como java.awt.Point donde se mantiene bastante mucho valores X e Y):

  1. hay necesidad de crear una interfaz
  2. no es necesario crear una clase abstracta
  3. clase debe ser final
  4. métodos estáticos no privados son correctos, especialmente para la construcción, ya que es posible que desee realizar el almacenamiento en caché.

Si sigue los consejos anteriores, terminará con un código bastante flexible que también tiene una separación bastante clara de responsabilidades.

Un ejemplo de clase de valor es esta clase Lugar:

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

me encontré con un detrimento a la utilización de métodos finales utilizando AOP y MVC de primavera. Intenté utilizar el AOP de Spring en los ganchos de seguridad alrededor de uno de los métodos en el AbstractFormController que fue declarado final. Creo que Spring estaba usando la biblioteca de Bcel para inyección en clases y había algunas limitaciones allí.

0

Cuando creo clases de utilidad puras, declaro entonces con un constructor privado para que no se puedan extender.Al crear clases normales, declaro que mis métodos son estáticos si no están usando ninguna de las variables de instancia de la clase (o, en algunos casos, incluso si lo fueran, pasaría los argumentos en el método y lo convertiría en estático, es más fácil verlo qué está haciendo el método). Estos métodos se declaran estáticos pero también son privados: están ahí para evitar la duplicación de código o para hacer que el código sea más fácil de entender.

Dicho esto, no recuerdo haber encontrado el caso en el que tiene una clase que tiene métodos públicos estáticos y que puede/debe extenderse. Pero, en base a lo que se informó aquí, declararía que sus métodos estáticos son definitivos.

5

Si tengo un método public static, entonces con frecuencia ya se encuentra en el llamado clase de utilidad con sólo static métodos. Los ejemplos autoexplicativos son StringUtil, SqlUtil, IOUtil, etcétera. Esas clases de utilidad ya están declaradas final y se suministran con un constructor private. P.ej.

public final class SomeUtil { 

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

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

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

} 

De esta manera no puede anularlos.

Si el suyo no se encuentra en una especie de clase de utilidad, entonces cuestionaría el valor del modificador public. ¿No debería ser private? De lo contrario, muévelo a alguna clase de utilidad. No desordene las clases "normales" con los métodos public static. De esta forma, tampoco necesita marcarlos final.

Otro caso es un tipo de clase de fábrica abstracta, que devuelve implementaciones concretas de uno mismo a través de un método public static. En tal caso, tendría sentido marcar el método final, no desea que las implementaciones concretas puedan anular el método.

+0

Creo que esto debería marcarse como la respuesta correcta personalmente. Esto es exactamente lo que hubiera sugerido. Finalice la clase, no los métodos individuales. –

31

Los métodos estáticos son una de las características más confusas de Java. Las mejores prácticas están ahí para solucionar esto, y hacer todos los métodos estáticos final es una de estas mejores prácticas.

El problema con los métodos estáticos es que

  • no son métodos de clase, pero las funciones globales precedido de un nombre de clase
  • es extraño que se "heredan" a las subclases
  • es sorprendente que no pueden ser anulados, pero ocultos
  • está totalmente roto que pueden ser llamados con un ejemplo como receptor

lo tanto, debe

  • siempre llamarlos con su clase como receptor
  • siempre llamar a la clase declarando sólo como receptor
  • siempre que sean (o la clase declarar) final

y usted debe

  • nunca llamarlos con una instancia como receptor
  • Nunca los llaman con una subclase de la clase que se declara como receptor
  • Nunca redefinir en subclases

 

NB: la la segunda versión de tu programa debería fallar un error de compilación. ¡Supongo que tu IDE te oculta este hecho!

+0

Muy buena respuesta! Por cierto, la segunda versión falla la compilación en mi IDE. Editó la pregunta para aclarar esto. – brasskazoo

+0

de lo que estás absolutamente seguro: siempre los haces (o la clase declarante) como definitiva; si haces que la clase declarante sea definitiva, automáticamente todos los métodos públicos serán definitivos, pero ¿eso también se aplica a los métodos STATIC? –

+0

Si la clase es final no puede ser subclases, por lo que se deduce que todos los métodos son finales también. Para el método estático, esto significa que no puede haber un método de subclase que los oculte, lo que equivale a hacer que el método sea final, pero no la clase. – akuhn

1

La mayor parte de esta edición de final se remonta a la época en que los VM-s eran bastante tontos/conservadores. En aquel entonces, si marcaba un método final, significaba (entre otras cosas) que la VM podía alinearlo, evitando llamadas a métodos. Ese no es el caso ya que un tiempo largo (o largo doble: P): http://java.sun.com/developer/technicalArticles/Networking/HotSpot/inlining.html.

I supongo que Idea de inspección/Netbeans le advierte, ya que piensa que desea utilizar la palabra clave final para la optimización y piensan que no son conscientes del hecho de que es innecesario con máquinas virtuales moderna.

Sólo mis dos centavos ...

0

Debido a que los métodos estáticos son las propiedades de la clase y que son llamados con el nombre de la clase y no de objeto. Si también hacemos que el método de clase principal sea final, no se sobrecargará ya que los métodos finales no permiten cambiar su ubicación de memoria, pero podemos actualizar el miembro de datos final en la misma ubicación de memoria ...

Cuestiones relacionadas