Méthode statique dans une classe générique?

197

En Java, j'aimerais avoir quelque chose comme:

class Clazz<T> {
  static void doIt(T object) {
    // ...
  }
}

Mais je reçois

Impossible de faire une référence statique au type non statique T

Je ne comprends pas les génériques au-delà des utilisations de base et ne peux donc pas donner beaucoup de sens à cela. Cela n'aide pas que je n'ai pas pu trouver beaucoup d'informations sur Internet à ce sujet.

Quelqu'un pourrait-il préciser si une telle utilisation est possible, d'une manière similaire? Aussi, pourquoi ma tentative initiale a-t-elle échoué?

André Chalella
la source

Réponses:

270

Vous ne pouvez pas utiliser les paramètres de type générique d'une classe dans des méthodes statiques ou des champs statiques. Les paramètres de type de la classe sont uniquement dans la portée des méthodes d'instance et des champs d'instance. Pour les champs statiques et les méthodes statiques, ils sont partagés entre toutes les instances de la classe, même les instances de paramètres de type différents, donc évidemment ils ne peuvent pas dépendre d'un paramètre de type particulier.

Il ne semble pas que votre problème doive nécessiter l'utilisation du paramètre type de la classe. Si vous décrivez ce que vous essayez de faire plus en détail, nous pouvons peut-être vous aider à trouver une meilleure façon de le faire.

newacct
la source
9
Votée, cette réponse explique en fait le problème de l'affiche au lieu de simplement fournir une solution de contournement.
Jorn
7
@Andre: Votre intuition n'est pas infondée; C # traite en effet les génériques de cette façon.
jyoungdev
32
"Pour les champs statiques et les méthodes statiques, ils sont partagés entre toutes les instances de la classe, même les instances de paramètres de type différents ..." Aïe! Coup de pied dans les noix par effacement de type à nouveau!
BD à Rivenhill le
2
si vous regardez à quoi ressemble la classe / les méthodes génériques après la compilation, vous verrez que l'attribut générique est supprimé. Et List <Integer> après la compilation ressemble à "List". Il n'y a donc pas de différence entre List <Integer> et List <Long> après la compilation - les deux sont devenus List.
Dainius
3
Je pense qu'il a expliqué ce qu'il essayait de faire assez bien. Il est clair qu'il essaie de secouer le butin de dat! hah
astryk
140

Java ne sait pas ce que Tc'est jusqu'à ce que vous instanciez un type.

Vous pouvez peut-être exécuter des méthodes statiques en appelant, Clazz<T>.doit(something)mais il semble que vous ne puissiez pas.

L'autre façon de gérer les choses est de mettre le paramètre type dans la méthode elle-même:

static <U> void doIt(U object)

ce qui ne vous donne pas la bonne restriction sur U, mais c'est mieux que rien ....

Jason S
la source
Ensuite, j'appellerais la méthode sans spécifier de contraintes, c'est-à-dire Clazz.doIt (objet) au lieu de Clazz <Object> .doIt (objet), non? Pensez-vous que ça va?
André Chalella
La deuxième syntaxe est la plus exacte, dont vous avez besoin si le compilateur ne peut pas déduire le type de la valeur de retour à partir du contaxt dans lequel la méthode est appelée. En d'autres termes, si le compilateur autorise Clazz.doIt (objet), faites-le.
skaffman
1
J'ai essayé Clazz <Object> .doIt (object) et j'ai eu une erreur de compilation! "Erreur de syntaxe sur le (s) jeton (s), construction (s) mal placée (s)". Clazz.doIt (objet) fonctionne bien, pas même un avertissement.
André Chalella
8
Utilisez Clazz. <Object> doIt (object). Je pense que c'est une syntaxe étrange, comparée à Clazz de C ++ <int> :: doIt (1)
Crend King
Pourquoi dites-vous que cela ne vous donne pas la bonne restriction sur U?
Adam Burley
46

J'ai rencontré ce même problème. J'ai trouvé ma réponse en téléchargeant le code source de Collections.sortdans le framework java. La réponse que j'ai utilisée était de mettre le <T>générique dans la méthode, pas dans la définition de classe.

Cela a donc fonctionné:

public class QuickSortArray  {
    public static <T extends Comparable> void quickSort(T[] array, int bottom, int top){
//do it
}

}

Bien sûr, après avoir lu les réponses ci-dessus, j'ai réalisé que ce serait une alternative acceptable sans utiliser de classe générique:

public static void quickSort(Comparable[] array, int bottom, int top){
//do it
}
Chris
la source
8
Alors que la réponse acceptée était techniquement correcte: c'est en fait ce que je cherchais lorsque Google m'a amené à cette question.
Jeremy List
Accessoires pour fournir un exemple qui inclut la T extends XXXsyntaxe.
Ogre Psalm33
3
@Chris Puisque vous utilisez de toute façon des génériques, vous pourriez aussi bien les utiliser tout le long --- à savoir de ne pas utiliser de types bruts comme Comparable. Essayez <T extends Comparable<? super T>>plutôt.
easoncxz
15

Il est possible de faire ce que vous voulez en utilisant la syntaxe des méthodes génériques lors de la déclaration de votre doIt()méthode (notez l'ajout de <T>entre staticet voiddans la signature de méthode de doIt()):

class Clazz<T> {
  static <T> void doIt(T object) {
    // shake that booty
  }
}

J'ai demandé à l'éditeur Eclipse d'accepter le code ci-dessus sans l' Cannot make a static reference to the non-static type Terreur, puis je l'ai étendu au programme de travail suivant (avec une référence culturelle quelque peu adaptée à l'âge):

public class Clazz<T> {
  static <T> void doIt(T object) {
    System.out.println("shake that booty '" + object.getClass().toString()
                       + "' !!!");
  }

  private static class KC {
  }

  private static class SunshineBand {
  }

  public static void main(String args[]) {
    KC kc = new KC();
    SunshineBand sunshineBand = new SunshineBand();
    Clazz.doIt(kc);
    Clazz.doIt(sunshineBand);
  }
}

Qui imprime ces lignes sur la console lorsque je l'exécute:

secouez ce butin 'classe com.eclipseoptions.datamanager.Clazz $ KC' !!!
secouez ce butin 'classe com.eclipseoptions.datamanager.Clazz $ SunshineBand' !!!

BD chez Rivenhill
la source
Dans ce cas, le second <T> cache-t-il le premier?
André Chalella
1
@ AndréNeves, oui. Le second <T>masque le premier de la même manière que dans class C { int x; C(int x) { ... } }le paramètre xmasque le champ x.
Mike Samuel
Comment expliquer la sortie de l'échantillon: il semble qu'il y ait en effet deux types impliqués, un par paramètre T valeur? Si T cachait vraiment le type de classe, je m'attendrais à ce que "secouez ce booty 'classe com.eclipseoptions.datamanager.Clazz" soit sorti deux fois sans paramètres.
Pragmateek
1
Cela n'a rien à voir avec le masquage. Une méthode statique ne peut tout simplement pas être liée par le type générique des classes . Néanmoins, il peut définir ses propres types et limites génériques.
mike
Existe-t-il un moyen de définir une fois le type statique et de le réutiliser pour plusieurs méthodes statiques? Je ne suis pas fan de faire static <E extends SomeClass> E foo(E input){return input;}pour chaque méthode quand je voudrais faire quelque chose commestatic <E extends SomeClass>; //static generic type defined only once and reused static E foo(E input){return input;} static E bar(E input){return input;} //...etc...
HesNotTheStig
14

Je pense que cette syntaxe n'a pas encore été mentionnée (dans le cas où vous voulez une méthode sans arguments):

class Clazz {
  static <T> T doIt() {
    // shake that booty
  }
}

Et l'appel:

String str = Clazz.<String>doIt();

J'espère que cela aidera quelqu'un.

Oreste Viron
la source
1
En fait (je ne connais pas la version Java), cela est possible même sans le <String>car il infère simplement l'argument type car vous l'assignez à une variable. Si vous ne l'attribuez pas, il en déduit simplement Object.
Adowrath
C'est une solution parfaite.
Prabhat Ranjan
6

Il est correctement mentionné dans l'erreur: vous ne pouvez pas faire de référence statique à un type non statique T. La raison en est que le paramètre type Tpeut être remplacé par n'importe quel argument de type, par exemple Clazz<String>ouClazz<integer> etc. Mais les champs / méthodes statiques sont partagés par tous les non -des objets statiques de la classe.

L'extrait suivant est tiré du doc :

Le champ statique d'une classe est une variable de niveau classe partagée par tous les objets non statiques de la classe. Par conséquent, les champs statiques de paramètres de type ne sont pas autorisés. Considérez la classe suivante:

public class MobileDevice<T> {
    private static T os;

    // ...
}

Si des champs statiques de paramètres de type étaient autorisés, le code suivant serait confondu:

MobileDevice<Smartphone> phone = new MobileDevice<>();
MobileDevice<Pager> pager = new MobileDevice<>();
MobileDevice<TabletPC> pc = new MobileDevice<>();

Étant donné que le système d'exploitation de champ statique est partagé par téléphone, téléavertisseur et ordinateur, quel est le type réel de système d'exploitation? Il ne peut pas s'agir d'un smartphone, d'un pager ou d'un TabletPC en même temps. Vous ne pouvez donc pas créer de champs statiques de paramètres de type.

Comme l'a souligné à juste titre chris dans sa réponse, vous devez utiliser le paramètre type avec la méthode et non avec la classe dans ce cas. Vous pouvez l'écrire comme:

static <E> void doIt(E object) 
akhil_mittal
la source
3

Quelque chose comme ce qui suit vous rapprocherait

class Clazz
{
   public static <U extends Clazz> void doIt(U thing)
   {
   }
}

EDIT: exemple mis à jour avec plus de détails

public abstract class Thingo 
{

    public static <U extends Thingo> void doIt(U p_thingo)
    {
        p_thingo.thing();
    }

    protected abstract void thing();

}

class SubThingoOne extends Thingo
{
    @Override
    protected void thing() 
    {
        System.out.println("SubThingoOne");
    }
}

class SubThingoTwo extends Thingo
{

    @Override
    protected void thing() 
    {
        System.out.println("SuThingoTwo");
    }

}

public class ThingoTest 
{

    @Test
    public void test() 
    {
        Thingo t1 = new SubThingoOne();
        Thingo t2 = new SubThingoTwo();

        Thingo.doIt(t1);
        Thingo.doIt(t2);

        // compile error -->  Thingo.doIt(new Object());
    }
}
ekj
la source
Exactement comme Jason S l'a suggéré.
André Chalella
@Andre la suggestion précédente ne prévoyait pas la restriction selon laquelle U doit étendre Clazz
ekj
Je vois, mais cela n'ajoute rien d'utile à l'exception de la contrainte. Dans mon article d'origine, j'aimerais pouvoir le faire sans passer U comme paramètre.
André Chalella
@Andre Voir ma mise à jour ci-dessus. Le type générique est uniquement défini dans la définition de la méthode et ne doit pas être transmis lors de l'appel de la méthode
ekj
@ekj Merci, je comprends maintenant. Désolé, j'ai posé cette question il y a trois ans, quand je faisais Java quotidiennement. J'ai eu votre idée, même si je pense que vous comprenez que ce n'est pas exactement ce que je voulais à l'origine. Cependant, j'ai bien aimé votre réponse.
André Chalella
2

Lorsque vous spécifiez un type générique pour votre classe, la machine virtuelle Java sait qu'il n'a qu'une instance de votre classe, pas une définition. Chaque définition n'a qu'un type paramétré.

Les génériques fonctionnent comme des modèles en C ++, vous devez donc d'abord instancier votre classe, puis utiliser la fonction avec le type spécifié.

Marcin Cylke
la source
2
Les génériques Java sont assez différents des modèles C ++. La classe générique est compilée par elle-même. En fait, le code équivalent en C ++ fonctionnerait (le code appelant ressemblerait Clazz<int>::doIt( 5 ))
David Rodríguez - dribeas
J'aime mieux cette réponse que la réponse acceptée ... à part la référence du modèle C ++, le commentaire sur le type générique ne concernant qu'une seule instance de la classe au lieu de la classe entière est parfait. La réponse acceptée n'explique pas cela et fournit simplement une solution de contournement qui n'a rien à voir avec la raison pour laquelle vous ne pouvez pas utiliser le type générique de la classe dans une méthode statique.
Jorn
1

En d'autres termes, cela se produit en raison de la propriété "Erasure" des génériques. Ce qui signifie que bien que nous définissions ArrayList<Integer>et ArrayList<String>, au moment de la compilation, cela reste comme deux types concrets différents, mais au moment de l'exécution, la JVM efface les types génériques et crée une seule classe ArrayList au lieu de deux classes. Donc, lorsque nous définissons une méthode de type statique ou quoi que ce soit pour un générique, elle est partagée par toutes les instances de ce générique, dans mon exemple, elle est partagée par les deux ArrayList<Integer>et ArrayList<String>. C'est pourquoi vous obtenez l'erreur. Un paramètre de type générique d'une classe n'est pas Autorisé dans un contexte statique!


la source
1

@BD at Rivenhill: Puisque cette vieille question a retenu l'attention l'année dernière, continuons un peu, juste pour le plaisir de la discussion. Le corps de votre doItméthode ne fait rien de Tspécifique. C'est ici:

public class Clazz<T> {
  static <T> void doIt(T object) {
    System.out.println("shake that booty '" + object.getClass().toString()
                       + "' !!!");
  }
// ...
}

Vous pouvez donc supprimer entièrement toutes les variables de type et simplement coder

public class Clazz {
  static void doIt(Object object) {
    System.out.println("shake that booty '" + object.getClass().toString()
                       + "' !!!");
  }
// ...
}

D'accord. Mais revenons au problème d'origine. La première variable de type sur la déclaration de classe est redondante. Seul le second sur la méthode est nécessaire. C'est reparti, mais ce n'est pas encore la réponse définitive:

public class Clazz  {
  static <T extends Saying> void doIt(T object) {
    System.out.println("shake that booty "+ object.say());
  }

  public static void main(String args[]) {
    Clazz.doIt(new KC());
    Clazz.doIt(new SunshineBand());
  }
}
// Output:
// KC
// Sunshine

interface Saying {
      public String say();
}

class KC implements Saying {
      public String say() {
          return "KC";
      }
}

class SunshineBand implements Saying {
      public String say() {
          return "Sunshine";
      }
}

Cependant, c'est trop de bruit pour rien, car la version suivante fonctionne de la même manière. Tout ce dont il a besoin est le type d'interface sur le paramètre de méthode. Aucune variable de type en vue nulle part. Est-ce vraiment le problème d'origine?

public class Clazz  {
  static void doIt(Saying object) {
    System.out.println("shake that booty "+ object.say());
  }

  public static void main(String args[]) {
    Clazz.doIt(new KC());
    Clazz.doIt(new SunshineBand());
  }
}

interface Saying {
      public String say();
}

class KC implements Saying {
      public String say() {
          return "KC";
      }
}

class SunshineBand implements Saying {
      public String say() {
          return "Sunshine";
      }
}
Hubert Kauker
la source
0

Puisque les variables statiques sont partagées par toutes les instances de la classe. Par exemple, si vous rencontrez le code suivant

class Class<T> {
  static void doIt(T object) {
    // using T here 
  }
}

T n'est disponible qu'après la création d'une instance. Mais des méthodes statiques peuvent être utilisées avant même que des instances ne soient disponibles. Ainsi, les paramètres de type générique ne peuvent pas être référencés dans des méthodes et des variables statiques

Bharat
la source