web-dev-qa-db-fra.com

Est-ce une mauvaise idée de déclarer une méthode statique finale?

Je comprends cela dans ce code:

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

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

.. la méthode statique dans Bar 'masque' la méthode statique déclarée dans Foo, par opposition à la remplacer dans le sens du polymorphisme.

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

... affichera:

à Foo
en barre

Redéfinir method() en tant que final dans Foo désactivera la possibilité pour Bar de le masquer et la ré-exécution de main() générera:

à Foo
dans Foo

( Edit : la compilation échoue lorsque vous marquez la méthode avec final et ne s'exécute à nouveau que lorsque je supprime Bar.method())}

Est-il considéré comme une mauvaise pratique de déclarer des méthodes statiques avec la variable final si elle empêche les sous-classes de redéfinir la méthode de manière intentionnelle ou par inadvertance?

( this est une bonne explication du comportement de l'utilisation de final ..)

36
brasskazoo

Je ne considère pas comme une mauvaise pratique de marquer une méthode static comme final.

Comme vous l'avez découvert, final empêchera la méthode d'être cachée par les sous-classes ce qui est une très bonne nouvelle imho .

Je suis assez surpris par votre déclaration:

Redéfinir method () comme final dans Foo désactive la possibilité de le masquer par Bar, et la réexécution de main () produira:

à Foo
dans Foo 

Non, marquer la méthode avec final dans Foo empêchera Bar de compiler. Au moins dans Eclipse, je reçois:

Exception dans le fil "principal" Java.lang.Error: Problème de compilation non résolu: Impossible de remplacer la méthode finale de Foo

De plus, je pense que les gens devraient toujours invoquer la méthode static en les qualifiant avec le nom de la classe, même dans la classe elle-même:

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

  public Foo()
  {
    foo();      // both compile
    Foo.foo();  // but I prefer this one
  }
}
36
Gregory Pakosz

Les méthodes statiques sont l’une des fonctionnalités les plus déroutantes de Java. Les meilleures pratiques existent pour résoudre ce problème, et rendre toutes les méthodes statiques final est l'une de ces meilleures pratiques!

Le problème avec les méthodes statiques est que 

  • ce ne sont pas des méthodes de classe, mais des fonctions globales préfixées par un nom de classe
  • il est étrange qu'ils soient "hérités" de sous-classes
  • il est surprenant qu'ils ne puissent pas être remplacés mais cachés
  • il est totalement cassé qu'ils peuvent être appelés avec une instance en tant que récepteur

donc vous devriez

  • toujours les appeler avec leur classe en tant que récepteur
  • appelez-les toujours avec la classe déclarante en tant que récepteur
  • toujours les rendre (ou la classe déclarante) final

et vous devriez 

  • jamais les appeler avec une instance en tant que destinataire
  • never appelez-les avec une sous-classe de leur classe déclarante en tant que récepteur
  • jamais redéfinis-les en sous-classes

NB: la deuxième version de votre programme devrait échouer une erreur de compilation. Je suppose que votre IDE vous cache ce fait!

31
akuhn

Si j'ai une méthode public static, elle est souvent déjà située dans une soi-disant classe d'utilitaires} avec uniquement des méthodes static. Les exemples auto-explicatifs sont StringUtil, SqlUtil, IOUtil, etc. Ces classes d'utilitaires sont déjà déclarées final et sont fournies avec un constructeur private. Par exemple.

public final class SomeUtil {

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

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

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

}

De cette façon, vous ne pouvez pas les remplacer.

Si le vôtre ne se trouve pas dans le genre d'une classe d'utilitaires, je remettrais en question la valeur du modificateur public. Ne devrait-il pas être private? Sinon, déplacez-le vers une classe utilitaire. Ne pas encombrer les classes "normales" avec les méthodes public static. De cette façon, vous n'avez pas non plus besoin de les marquer final

Un autre cas est une sorte de classe de fabrique abstraite, qui renvoie des implémentations concrètes de self via une méthode public static. Dans un tel cas, il serait parfaitement logique de marquer la méthode final, vous ne voulez pas que les implémentations concrètes puissent remplacer la méthode.

6
BalusC

Habituellement, avec des classes d'utilitaires (classes avec uniquement des méthodes statiques), il n'est pas souhaitable d'utiliser l'héritage. Pour cette raison, vous voudrez peut-être définir la classe en tant que finale pour empêcher d'autres classes de l'étendre. Cela empêcherait de placer des modificateurs finaux sur vos méthodes de classe d'utilitaires.

4
mR_fr0g

Le code ne compile pas:

Test.Java:8: method () dans Bar ne peut pas Surcharger method () dans Foo; surchargée méthode est statique finale public static void, méthode () {

Le message est trompeur car une méthode statique peut, par définition, ne jamais être remplacée.

Je fais ce qui suit lors du codage (pas à 100% tout le temps, mais rien ici n’est "faux":

(La première série de "règles" est faite pour la plupart des choses - certains cas spéciaux sont couverts après)

  1. créer une interface
  2. créer une classe abstraite qui implémente l'interface
  3. créer des classes concrètes qui étendent la classe abstraite
  4. créer des classes concrètes qui implémentent l'interface mais ne développent pas la classe abstraite
  5. toujours, si possible, créer toutes les variables/constantes/paramètres de l'interface

Puisqu'une interface ne peut pas avoir de méthodes statiques, le problème n'est pas résolu. Si vous voulez créer des méthodes statiques dans la classe abstraite ou dans des classes concrètes, elles doivent être privées, il n’ya aucun moyen de les remplacer.

Cas spéciaux:

Classes utilitaires (classes avec toutes les méthodes statiques):

  1. déclarer la classe comme finale
  2. lui donner un constructeur privé pour éviter la création accidentelle

Si vous voulez avoir une méthode statique dans une classe concrète ou abstraite qui n'est pas privée, vous voudrez probablement plutôt créer une classe utilitaire. 

Les classes de valeur (une classe très spécialisée pour essentiellement contenir des données, comme Java.awt.Point où elle contient à peu près les valeurs x et y):

  1. pas besoin de créer une interface
  2. pas besoin de créer une classe abstraite
  3. la classe devrait être finale 
  4. les méthodes statiques non privées sont acceptables, en particulier pour la construction, car vous souhaiterez peut-être effectuer la mise en cache.

Si vous suivez les conseils ci-dessus, vous obtiendrez un code assez flexible, avec une séparation assez nette des responsabilités.

Un exemple de classe de valeur est cette classe d'emplacement:

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);
    }
}
3
TofuBeer

Il peut être intéressant de marquer les méthodes statiques comme définitives, en particulier si vous développez un cadre que vous attendez d’autres extensions. Ainsi, vos utilisateurs ne cacheront pas par inadvertance vos méthodes statiques dans leurs classes. Mais si vous développez un framework, vous voudrez peut-être éviter d'utiliser des méthodes statiques pour commencer.

1
Aditya

La plupart de ce problème final remonte à l'époque où les VM étaient plutôt stupides/conservatrices. À l'époque, si vous aviez marqué une méthode final, cela signifiait (entre autres) que le VM pouvait la mettre en ligne, évitant ainsi les appels de méthode. Ce n'est pas le cas depuis un temps long, long (ou long double: P): http://Java.Sun.com/developer/technicalArticles/Networking/HotSpot/inlining.html .

Isupposel'inspection Idea/Netbeans vous avertit, car elle pense que vous souhaitez utiliser le mot clé final pour l'optimisation et vous pensez que vous ignorez le fait qu'elle n'est pas nécessaire. VM modernes.

Juste mes deux cents...

1
Gergely Szilagyi

J'ai rencontré un inconvénient à l'utilisation des méthodes finales utilisant AOP et MVC de Spring. J'essayais d'utiliser l'AOP de Spring dans les crochets de sécurité autour de l'une des méthodes de AbstractFormController, qui a été déclarée finale. Je pense que spring utilisait la bibliothèque bcel pour l'injection en classe et qu'il y avait certaines limites.

0
BillMan

Parce que les méthodes statiques sont les propriétés de la classe et qu’elles sont appelées avec le nom de la classe plutôt que de l’objet. Si nous rendons également la méthode de la classe parent finale, elle ne sera pas surchargée, car les méthodes finales ne permettent pas de changer l'emplacement de la mémoire, mais nous pouvons mettre à jour le membre de données final au même emplacement de la mémoire ...

0
Aasif Ali

Lorsque je crée des classes d'utilitaires pures, je déclare alors avec un constructeur privé afin qu'elles ne puissent pas être étendues. Lors de la création de classes normales, je déclare mes méthodes statiques si elles n'utilisent aucune des variables d'instance de classe (ou, dans certains cas, même si elles l'étaient, je transmettrais les arguments de la méthode et la rendrais statique, il est plus facile de voir ce que fait la méthode). Ces méthodes sont déclarées statiques mais sont également privées. Elles existent uniquement pour éviter la duplication de code ou pour rendre le code plus facile à comprendre.

Ceci étant dit, je ne me souviens pas d'avoir rencontré une classe qui possède des méthodes statiques publiques et qui peut/devrait être étendue. Mais, sur la base de ce qui a été rapporté ici, je déclarerais ses méthodes statiques comme définitives.

0
Ravi Wallau