Método estático en una clase genérica?

197

En Java, me gustaría tener algo como:

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

Pero consigo

No se puede hacer una referencia estática al tipo T no estático

No entiendo los genéricos más allá de los usos básicos y, por lo tanto, no tiene mucho sentido. No ayuda que no haya podido encontrar mucha información en Internet sobre el tema.

¿Podría alguien aclarar si tal uso es posible, de manera similar? Además, ¿por qué fracasó mi intento original?

André Chalella
fuente

Respuestas:

270

No puede usar los parámetros de tipo genérico de una clase en métodos estáticos o campos estáticos. Los parámetros de tipo de la clase solo están dentro del alcance de los métodos de instancia y los campos de instancia. Para campos estáticos y métodos estáticos, se comparten entre todas las instancias de la clase, incluso instancias de diferentes parámetros de tipo, por lo que obviamente no pueden depender de un parámetro de tipo particular.

No parece que su problema deba requerir el uso del parámetro de tipo de la clase. Si describe lo que está tratando de hacer con más detalle, tal vez podamos ayudarlo a encontrar una mejor manera de hacerlo.

newacct
fuente
9
Votado, esta respuesta en realidad explica el problema del afiche en lugar de solo proporcionar una solución alternativa.
Jorn
77
@Andre: Tu intuición no es infundada; C # realmente trata los genéricos de esta manera.
jyoungdev
32
"Para campos estáticos y métodos estáticos, se comparten entre todas las instancias de la clase, incluso instancias de diferentes parámetros de tipo ..." ¡Ay! Pateado en las nueces por tipo de borrado de nuevo!
BD en Rivenhill
2
si observa cómo se ve la clase / métodos genéricos después de la compilación, verá que se elimina ese atributo genérico. Y List <Integer> después de la compilación se parece a "List". Entonces, no hay diferencia entre List <Integer> y List <Long> después de la compilación, ambos se convirtieron en List.
Dainius
3
Creo que él explicó lo que está tratando de hacer bastante bien. ¡Está claro que está tratando de sacudir ese botín! ja
astryk
140

Java no sabe qué Tes hasta que crea una instancia de un tipo.

Tal vez pueda ejecutar métodos estáticos llamando, Clazz<T>.doit(something)pero parece que no puede.

La otra forma de manejar las cosas es poner el parámetro de tipo en el método mismo:

static <U> void doIt(U object)

lo que no te da la restricción correcta en U, pero es mejor que nada ...

Jason S
fuente
Entonces llamaría al método sin especificar ninguna restricción, es decir, Clazz.doIt (objeto) en lugar de Clazz <Object> .doIt (objeto), ¿verdad? ¿Consideras que está bien?
André Chalella 01 de
La segunda sintaxis es la más exacta, que necesita si el compilador no puede inferir el tipo del valor de retorno del contacto en el que se llama el método. En otras palabras, si el compilador permite Clazz.doIt (objeto), entonces hazlo.
skaffman 01 de
1
¡Intenté Clazz <Object> .doIt (objeto) y obtuve un error en tiempo de compilación! "Error de sintaxis en token (s), construcciones mal ubicadas". Clazz.doIt (objeto) funciona bien, ni siquiera una advertencia.
André Chalella 01 de
8
Utilice Clazz. <Object> doIt (objeto). Creo que es una sintaxis extraña, en comparación con Clazz de C ++ <int> :: doIt (1)
Crend King
¿Por qué dices que no te da la restricción correcta sobre U?
Adam Burley
46

Me encontré con este mismo problema. Encontré mi respuesta descargando el código fuente Collections.sorten el marco de Java. La respuesta que usé fue poner el <T>genérico en el método, no en la definición de la clase.

Entonces esto funcionó:

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

}

Por supuesto, después de leer las respuestas anteriores, me di cuenta de que esta sería una alternativa aceptable sin usar una clase genérica:

public static void quickSort(Comparable[] array, int bottom, int top){
//do it
}
Chris
fuente
8
Si bien la respuesta aceptada era técnicamente correcta: esto es en realidad lo que estaba buscando cuando Google me llevó a esta pregunta.
Jeremy List
Props para proporcionar un ejemplo que incluye la T extends XXXsintaxis.
Ogre Psalm33
3
@Chris Ya que de todos modos estás usando genéricos, también puedes usarlos todo el tiempo, es decir, no usar tipos sin procesar como Comparable. Intenta en su <T extends Comparable<? super T>>lugar.
easoncxz
15

Es posible hacer lo que quiera usando la sintaxis para métodos genéricos al declarar su doIt()método (observe la adición de <T>entre staticy voiden la firma del método de doIt()):

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

Obtuve el editor Eclipse para aceptar el código anterior sin el Cannot make a static reference to the non-static type Terror y luego lo expandí al siguiente programa de trabajo (completo con referencias culturales algo apropiadas para la edad):

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

Lo que imprime estas líneas en la consola cuando lo ejecuto:

agite ese botín 'class com.eclipseoptions.datamanager.Clazz $ KC' !!!
agite ese botín 'class com.eclipseoptions.datamanager.Clazz $ SunshineBand' !!!

BD en Rivenhill
fuente
En este caso, ¿el segundo <T> oculta el primero?
André Chalella
1
@ AndréNeves, sí. El segundo <T>enmascara al primero de la misma manera que en class C { int x; C(int x) { ... } }el parámetro xenmascara el campo x.
Mike Samuel
Cómo explicar la salida de la muestra: ¿parece que hay dos tipos involucrados, uno por el valor del parámetro T? Si T realmente estuviera ocultando el tipo de clase, esperaría "sacudir ese botín 'class com.eclipseoptions.datamanager.Clazz" emitido dos veces sin parámetros.
Pragmateek
1
No tiene nada que ver con el enmascaramiento. Un método estático simplemente no puede estar vinculado por el tipo genérico de clases . Sin embargo, puede definir sus propios tipos y límites genéricos.
Mike
¿Hay alguna manera de definir el tipo estático una vez y reutilizarlo para varios métodos estáticos? No soy fanático de hacer static <E extends SomeClass> E foo(E input){return input;}para todos y cada uno de los métodos cuando me gustaría hacer algo comostatic <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

Creo que esta sintaxis aún no se ha mencionado (en el caso de que desee un método sin argumentos):

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

Y la llamada:

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

Espero que esto ayude a alguien.

Oreste Viron
fuente
1
En realidad (no sé la versión de Java), esto es posible incluso sin el <String>porque simplemente infiere el argumento de tipo porque lo asigna a una variable. Si no lo asigna, simplemente infiere Object.
Adowrath
Esta es una solución perfecta.
Prabhat Ranjan
6

Se menciona correctamente en el error: no se puede hacer una referencia estática a T. tipo no estática La razón es el parámetro de tipo Tpuede ser sustituido por cualquiera de los argumentos por ejemplo, tipo Clazz<String>o Clazz<integer>etc, pero los campos estáticos / métodos son compartidos por todos los no -objetos estáticos de la clase.

El siguiente extracto se toma del documento :

El campo estático de una clase es una variable de nivel de clase compartida por todos los objetos no estáticos de la clase. Por lo tanto, los campos estáticos de parámetros de tipo no están permitidos. Considere la siguiente clase:

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

    // ...
}

Si se permitieran campos estáticos de parámetros de tipo, se confundiría el siguiente código:

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

Debido a que el campo estático os es compartido por teléfono, buscapersonas y pc, ¿cuál es el tipo real de sistema operativo? No puede ser Smartphone, Pager y TabletPC al mismo tiempo. Por lo tanto, no puede crear campos estáticos de parámetros de tipo.

Como acertadamente señaló Chris en su respuesta , debe usar el parámetro type con el método y no con la clase en este caso. Puedes escribirlo como:

static <E> void doIt(E object) 
akhil_mittal
fuente
3

Algo como lo siguiente te acercaría

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

EDITAR: ejemplo actualizado con más detalles

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
fuente
Exactamente como lo sugirió Jason S.
André Chalella
@Andre la sugerencia anterior no proporcionó la restricción de que U debe extender Clazz
ekj
Ya veo, pero no agrega nada útil, excepto la restricción. En mi publicación original, me gustaría poder hacer esto sin pasar U como parámetro.
André Chalella
@Andre Vea mi actualización arriba. El tipo genérico solo se define en la definición del método, y no tiene que pasarse cuando se llama al método
ekj
@ekj Gracias, entiendo ahora. Lo siento, hice esta pregunta hace tres años, cuando estaba haciendo Java diariamente. Tengo tu idea, aunque creo que entiendes que esto no es exactamente lo que pretendía originalmente. Sin embargo, me gustó tu respuesta.
André Chalella
2

Cuando especifica un tipo genérico para su clase, JVM sabe que solo tiene una instancia de su clase, no una definición. Cada definición solo tiene un tipo parametrizado.

Los genéricos funcionan como plantillas en C ++, por lo que primero debe crear una instancia de su clase, luego usar la función con el tipo que se especifica.

Marcin Cylke
fuente
2
Los genéricos de Java son bastante diferentes de las plantillas de C ++. La clase genérica se compila por sí misma. De hecho, el código equivalente en C ++ funcionaría (sería el código de llamada Clazz<int>::doIt( 5 ))
David Rodríguez - dribeas
Me gusta esta respuesta mejor que la aceptada ... aparte de la referencia de plantilla de C ++, el comentario sobre el tipo genérico es solo para una instancia de la clase en lugar de toda la clase es perfecto. La respuesta aceptada no explica esto, y solo proporciona una solución alternativa que no tiene nada que ver con por qué no puede usar el tipo genérico de la clase en un método estático.
Jorn
1

También para decirlo en términos simples, ocurre debido a la propiedad "Erasure" de los genéricos. Lo que significa que aunque definimos ArrayList<Integer>y ArrayList<String>, en el momento de la compilación, permanece como dos tipos concretos diferentes, pero en el tiempo de ejecución la JVM borra los tipos genéricos y crea solo una clase ArrayList en lugar de dos clases. Entonces, cuando definimos un método de tipo estático o cualquier cosa para un genérico, es compartido por todas las instancias de ese genérico, en mi ejemplo es compartido por ambos ArrayList<Integer>y. ArrayList<String>Es por eso que obtiene el error. Un parámetro de tipo genérico de una clase no es Permitido en un contexto estático!


fuente
1

@BD en Rivenhill: Dado que esta vieja pregunta ha recibido una atención renovada el año pasado, sigamos un poco, solo por el bien de la discusión. El cuerpo de su doItmétodo no hace nada Tespecífico. Aquí está:

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

Por lo tanto, puede descartar por completo todas las variables de tipo y solo codificar

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

Okay. Pero volvamos más cerca del problema original. La primera variable de tipo en la declaración de clase es redundante. Solo se necesita el segundo en el método. Aquí vamos de nuevo, pero aún no es la respuesta final:

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";
      }
}

Sin embargo, es demasiado alboroto por nada, ya que la siguiente versión funciona de la misma manera. Todo lo que necesita es el tipo de interfaz en el parámetro del método. No hay variables de tipo a la vista en ningún lado. ¿Era realmente ese el problema original?

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
fuente
0

Dado que las variables estáticas son compartidas por todas las instancias de la clase. Por ejemplo, si tiene el siguiente código

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

T está disponible solo después de crear una instancia. Pero los métodos estáticos se pueden usar incluso antes de que haya instancias disponibles. Por lo tanto, no se puede hacer referencia a los parámetros de tipo genérico dentro de los métodos y variables estáticos

Bharat
fuente