Método estático em uma classe genérica?

197

Em Java, eu gostaria de ter algo como:

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

Mas eu entendo

Não é possível fazer uma referência estática para o tipo não estático T

Eu não entendo genéricos além dos usos básicos e, portanto, não consigo entender muito isso. Não ajuda que eu não tenha conseguido encontrar muita informação na internet sobre o assunto.

Alguém poderia esclarecer se esse uso é possível, de maneira semelhante? Além disso, por que minha tentativa original não teve êxito?

André Chalella
fonte

Respostas:

270

Você não pode usar parâmetros de tipo genérico de uma classe em métodos estáticos ou campos estáticos. Os parâmetros de tipo da classe estão apenas no escopo para métodos e campos de instância. Para campos estáticos e métodos estáticos, eles são compartilhados entre todas as instâncias da classe, mesmo instâncias de diferentes parâmetros de tipo, portanto, obviamente, eles não podem depender de um parâmetro de tipo específico.

Não parece que seu problema exija o uso do parâmetro de tipo da classe. Se você descrever o que está tentando fazer com mais detalhes, talvez possamos ajudá-lo a encontrar uma maneira melhor de fazê-lo.

newacct
fonte
9
Promovida, essa resposta realmente explica o problema do pôster em vez de apenas fornecer uma solução alternativa.
Jorn
7
@ André: Sua intuição não é infundada; O C # de fato trata os genéricos dessa maneira.
jyoungdev
32
"Para campos estáticos e métodos estáticos, eles são compartilhados entre todas as instâncias da classe, mesmo instâncias de diferentes parâmetros de tipo ..." Ouch! Chutado nas nozes por tipo de apagamento novamente!
BD em Rivenhill 13/04
2
se você olhar como a classe / métodos genéricos cuida da compilação, verá que o atributo genérico é removido. E Listar <Integer> após a compilação se parece com "Lista". Portanto, não há diferenças entre List <Integer> e List <Long> após a compilação - ambas se tornaram List.
Dainius 28/05
3
Acho que ele explicou o que está tentando fazer razoavelmente bem. É claro que ele está tentando sacudir o saque! hah
astryk
140

Java não sabe o que Té até você instanciar um tipo.

Talvez você possa executar métodos estáticos chamando, Clazz<T>.doit(something)mas parece que não pode.

A outra maneira de lidar com as coisas é colocar o parâmetro type no próprio método:

static <U> void doIt(U object)

o que não lhe dá a restrição certa em U, mas é melhor que nada ....

Jason S
fonte
Então eu chamaria o método sem especificar restrições, ou seja, Clazz.doIt (objeto) em vez de Clazz <Object> .doIt (objeto), certo? Você considera que está bem?
André Chalella 01/06/09
A segunda sintaxe é a mais exata, que você precisa se o compilador não puder inferir o tipo do valor de retorno do contaxt no qual o método é chamado. Em outras palavras, se o compilador permitir Clazz.doIt (objeto), faça isso.
21413 skaffman
1
Tentei o Clazz <Object> .doIt (object) e obtive um erro em tempo de compilação! "Erro de sintaxe no (s) token (s), construção (s) extraviada". Clazz.doIt (objeto) funciona bem, nem mesmo um aviso.
André Chalella 01/06/09
8
Use Clazz. <Object> doIt (objeto). Eu acho que é uma sintaxe estranha, comparando com o Clazz do C ++ <int> :: doIt (1)
Crend King
Por que você diz que isso não gera a restrição certa para você?
Adam Burley
46

Eu encontrei esse mesmo problema. Encontrei minha resposta baixando o código-fonte Collections.sortno framework java. A resposta que usei foi colocar o <T>genérico no método, não na definição de classe.

Então, isso funcionou:

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

}

Obviamente, depois de ler as respostas acima, percebi que essa seria uma alternativa aceitável sem o uso de uma classe genérica:

public static void quickSort(Comparable[] array, int bottom, int top){
//do it
}
Chris
fonte
8
Embora a resposta aceita seja tecnicamente correta: é isso que eu estava procurando quando o Google me levou a essa pergunta.
Lista Jeremy
Adereços para fornecer um exemplo que inclui a T extends XXXsintaxe.
achou do post
3
@ Chris Como você está usando genéricos de qualquer maneira, é melhor usá-los por todo o caminho - ou seja, para não usar tipos brutos como Comparable. Tente em <T extends Comparable<? super T>>vez disso.
easoncxz
15

É possível fazer o que você deseja usando a sintaxe para métodos genéricos ao declarar seu doIt()método (observe a adição de <T>entre statice voidna assinatura do método doIt()):

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

Eu obtive o editor Eclipse para aceitar o código acima sem o Cannot make a static reference to the non-static type Terro e o expandi para o seguinte programa de trabalho (completo com uma referência cultural apropriada para a idade):

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

Que imprime essas linhas no console quando eu o executo:

agite esse montante 'classe com.eclipseoptions.datamanager.Clazz $ KC' !!!
agite esse saque 'classe com.eclipseoptions.datamanager.Clazz $ SunshineBand' !!!

BD na Rivenhill
fonte
Nesse caso, o segundo <T> oculta o primeiro?
André Chalella 14/05
1
@ AndréNeves, sim. O segundo <T>mascara o primeiro da mesma maneira que no class C { int x; C(int x) { ... } }parâmetro xmascara o campo x.
Mike Samuel
Como explicar o resultado da amostra: parece que há realmente dois tipos envolvidos, um pelo valor do parâmetro T? Se T estivesse realmente ocultando o tipo de classe, eu esperaria que "agite essa classe de saque", com.eclipseoptions.datamanager.Clazz ", exibida duas vezes sem parâmetros.
precisa saber é o seguinte
1
Não tem nada a ver com mascarar. Um método estático simplesmente não pode ser vinculado pelo tipo genérico de classes . No entanto, ele pode definir seus próprios tipos e limites genéricos.
Mike
Existe uma maneira de definir o tipo estático uma vez e reutilizá-lo para vários métodos estáticos? Eu não sou um fã de fazer static <E extends SomeClass> E foo(E input){return input;}para todos e cada método quando eu gostaria de fazer algo assimstatic <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

Eu acho que essa sintaxe ainda não foi mencionada (no caso de você querer um método sem argumentos):

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

E a ligação:

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

Espero que isso ajude alguém.

Oreste Viron
fonte
1
Na verdade (eu não sei a versão Java), isso é possível mesmo sem o <String>porque simplesmente infere o argumento de tipo porque você o atribui a uma variável. Se você não atribuir, simplesmente deduz Object.
Adowrath
Esta é uma solução perfeita.
Prabhat Ranjan
6

É corretamente mencionado no erro: você não pode fazer uma referência estática para o tipo não estático T. O motivo é que o parâmetro type Tpode ser substituído por qualquer um dos argumentos de tipo, por exemplo, Clazz<String>ou Clazz<integer>etc. Mas os campos / métodos estáticos são compartilhados por todos os objetos estáticos da classe.

O seguinte trecho é retirado do documento :

O campo estático de uma classe é uma variável em nível de classe compartilhada por todos os objetos não estáticos da classe. Portanto, campos estáticos dos parâmetros do tipo não são permitidos. Considere a seguinte classe:

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

    // ...
}

Se campos estáticos dos parâmetros do tipo fossem permitidos, o código a seguir seria confundido:

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

Como o campo estático SO é compartilhado por telefone, pager e pc, qual é o tipo real de SO? Não pode ser Smartphone, Pager e TabletPC ao mesmo tempo. Portanto, você não pode criar campos estáticos dos parâmetros de tipo.

Como corretamente apontado por chris em sua resposta, você precisa usar o parâmetro type com o método e não com a classe neste caso. Você pode escrever como:

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

Algo como o seguinte o aproximaria

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

EDIT: Exemplo atualizado com mais detalhes

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
fonte
Exatamente como Jason S sugeriu.
André Chalella
@Andre a sugestão anterior não fornecer a restrição de que U deve estender Clazz
EKJ
Entendo, mas não adiciona nada de útil, exceto a restrição. Na minha postagem original, eu gostaria de poder fazer isso sem passar U como parâmetro.
André Chalella
@ Andre Veja minha atualização acima. O tipo genérico é definido apenas na definição do método e não precisa ser passado ao chamar o método
ekj
@ekj Obrigado, eu entendo agora. Desculpe, eu fiz essa pergunta três anos atrás, quando eu fazia Java diariamente. Tive sua ideia, mesmo que você entenda que não é exatamente isso que eu pretendia originalmente. No entanto, gostei da sua resposta.
André Chalella
2

Quando você especifica um tipo genérico para sua classe, a JVM sabe que ela possui apenas uma instância da sua classe, não uma definição. Cada definição possui apenas um tipo parametrizado.

Os genéricos funcionam como modelos em C ++; portanto, você deve instanciar sua classe primeiro e depois usar a função com o tipo especificado.

Marcin Cylke
fonte
2
Os genéricos Java são bem diferentes dos modelos C ++. A classe genérica é compilada por si só. Na verdade, o código equivalente em C ++ iria trabalhar (código de chamada seria semelhante Clazz<int>::doIt( 5 ))
David Rodríguez - dribeas
Eu gosto mais desta resposta do que a aceita ... além da referência do modelo C ++, o comentário sobre o tipo genérico é apenas para uma instância da classe em vez de toda a classe. A resposta aceita não explica isso e apenas fornece uma solução alternativa que não tem nada a ver com o motivo pelo qual você não pode usar o tipo genérico da classe em um método estático.
Jorn
1

Também para simplificar, isso acontece devido à propriedade "Erasure" dos genéricos. O que significa que, embora definamos ArrayList<Integer>e ArrayList<String>, no tempo de compilação, ele permanece como dois tipos concretos diferentes, mas no tempo de execução a JVM apaga tipos genéricos e cria apenas uma classe ArrayList em vez de duas classes. Portanto, quando definimos um método de tipo estático ou qualquer coisa para um genérico, ele é compartilhado por todas as instâncias desse genérico, no meu exemplo, é compartilhado por ambos ArrayList<Integer>e ArrayList<String>. É por isso que você recebe o erro. Permitido em um contexto estático!


fonte
1

@BD em Rivenhill: Como essa pergunta antiga ganhou atenção renovada no ano passado, vamos continuar um pouco, apenas por uma questão de discussão. O corpo do seu doItmétodo não faz nada Tespecífico. Aqui está:

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

Então você pode descartar inteiramente todas as variáveis ​​de tipo e apenas codificar

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

Está bem. Mas vamos voltar mais perto do problema original. A primeira variável de tipo na declaração de classe é redundante. Somente o segundo no método é necessário. Aqui vamos nós novamente, mas ainda não é a resposta 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";
      }
}

No entanto, é muito barulho por nada, já que a versão a seguir funciona da mesma maneira. Tudo o que precisa é do tipo de interface no parâmetro method. Não há variáveis ​​de tipo à vista em qualquer lugar. Esse era realmente o 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
fonte
0

Como as variáveis ​​estáticas são compartilhadas por todas as instâncias da classe. Por exemplo, se você está tendo o seguinte código

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

T está disponível somente após a criação de uma instância. Mas métodos estáticos podem ser usados ​​mesmo antes que as instâncias estejam disponíveis. Portanto, parâmetros do tipo genérico não podem ser referenciados dentro de métodos e variáveis ​​estáticos

Bharat
fonte