Metoda statyczna w klasie ogólnej?

197

W Javie chciałbym mieć coś takiego:

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

Ale rozumiem

Nie można dokonać statycznego odniesienia do niestatycznego typu T.

Nie rozumiem leków generycznych poza podstawowymi zastosowaniami, a zatem nie mam większego sensu. Nie pomaga to, że nie byłem w stanie znaleźć wielu informacji na ten temat w Internecie.

Czy ktoś mógłby wyjaśnić, czy takie użycie jest możliwe w podobny sposób? Ponadto, dlaczego moja pierwotna próba się nie powiodła?

André Chalella
źródło

Odpowiedzi:

270

Nie można używać ogólnych parametrów klasy w metodach statycznych lub polach statycznych. Parametry typu klasy znajdują się w zakresie tylko dla metod instancji i pól instancji. W przypadku pól statycznych i metod statycznych są one wspólne dla wszystkich instancji klasy, nawet instancji różnych parametrów typu, więc oczywiście nie mogą zależeć od określonego parametru typu.

Nie wydaje się, że Twój problem powinien wymagać użycia parametru type klasy. Jeśli bardziej szczegółowo opisujesz to, co próbujesz zrobić, może pomożemy Ci znaleźć lepszy sposób.

newacct
źródło
9
Uznana za pozytywną odpowiedź ta wyjaśnia problem plakatu, a nie tylko obejście problemu.
Jorn
7
@Andre: Twoja intuicja nie jest bezpodstawna; C # faktycznie traktuje generyczne w ten sposób.
jyoungdev
32
„W przypadku pól statycznych i metod statycznych są one wspólne dla wszystkich instancji klasy, nawet instancji o różnych parametrach typu…” Ojej! Ponownie wykopałem orzechy według typu skasowania!
BD w Rivenhill
2
jeśli spojrzysz, jak wygląda ogólna klasa / metody po kompilacji, zobaczysz, że ogólny atrybut został usunięty. A List <Integer> po kompilacji wygląda jak „List”. Nie ma więc różnicy między List <Integer> a List <Long> po kompilacji - oba stały się List.
Dainius
3
Myślę, że wyjaśnił, co próbuje zrobić całkiem dobrze. Oczywiste jest, że próbuje wstrząsnąć łupem danych! hah
astryk
140

Java nie wie co T jest, dopóki nie utworzysz instancji typu.

Może możesz wykonać metody statyczne, wywołując Clazz<T>.doit(something) ale brzmi to tak, jakbyś nie mógł.

Innym sposobem radzenia sobie z tym jest umieszczenie parametru type w samej metodzie:

static <U> void doIt(U object)

co nie zapewnia właściwego ograniczenia U, ale jest lepsze niż nic ....

Jason S.
źródło
Następnie wywołałbym metodę bez określania jakichkolwiek ograniczeń, tj. Clazz.doIt (obiekt) zamiast Clazz <Object> .doIt (obiekt), prawda? Czy uważasz, że to w porządku?
André Chalella
Druga składnia jest bardziej dokładna, której potrzebujesz, jeśli kompilator nie może wywnioskować typu wartości zwracanej z contaxt, w którym wywoływana jest metoda. Innymi słowy, jeśli kompilator pozwala na Clazz.doIt (obiekt), zrób to.
skaffman
1
Próbowałem Clazz <Object> .doIt (obiekt) i dostałem błąd podczas kompilacji! Msgstr "Błąd składni tokenów (-ów), źle umieszczony konstrukt (y)”. Clazz.doIt (obiekt) działa dobrze, nawet bez ostrzeżenia.
André Chalella
8
Użyj Clazz. <Obiekt> doIt (obiekt). Myślę, że to dziwna składnia w porównaniu do Clazza C ++ <int> :: doIt (1)
Crend King 1'12
Dlaczego mówisz, że nie zapewnia ci to właściwego ograniczenia U?
Adam Burley
46

Natrafiłem na ten sam problem. Znalazłem odpowiedź, pobierając kod źródłowy do Collections.sortframeworku Java. Odpowiedziałem, że użyłem <T>ogólny w metodzie, a nie w definicji klasy.

Więc to zadziałało:

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

}

Oczywiście po przeczytaniu powyższych odpowiedzi zdałem sobie sprawę, że byłaby to akceptowalna alternatywa bez użycia klasy ogólnej:

public static void quickSort(Comparable[] array, int bottom, int top){
//do it
}
Chris
źródło
8
Chociaż zaakceptowana odpowiedź była technicznie poprawna: właśnie tego szukałem, gdy Google zaprowadziło mnie do tego pytania.
Jeremy List
Rekwizyty za przykład, który zawiera T extends XXXskładnię.
Ogre Psalm33,
3
@Chris Ponieważ i tak używasz ogólnych, równie dobrze możesz używać ich przez cały czas - mianowicie nie używać surowych typów takich jak Comparable. Spróbuj <T extends Comparable<? super T>>zamiast tego.
easoncxz,
15

Można zrobić to, co chcesz, używając składni dla metod ogólnych podczas deklarowania doIt()metody (zwróć uwagę na dodanie <T>między statici voidw podpisie metody doIt()):

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

Poprosiłem edytor Eclipse, aby zaakceptował powyższy kod bez Cannot make a static reference to the non-static type Tbłędu, a następnie rozszerzyłem go na następujący program roboczy (wraz z odniesieniami do kultury nieco dostosowanymi do wieku):

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

Które drukuje te linie do konsoli po uruchomieniu:

potrząśnij tym łupem „klasa com.eclipseoptions.datamanager.Clazz $ KC” !!!
potrząśnij tym łupem „klasa com.eclipseoptions.datamanager.Clazz $ SunshineBand” !!!

BD w Rivenhill
źródło
Czy w takim przypadku drugi <T> ukrywa pierwszy?
André Chalella
1
@ AndréNeves, Tak. Drugi <T>maskuje pierwszy w taki sam sposób, jak w class C { int x; C(int x) { ... } }parametrze xmaskuje pole x.
Mike Samuel
Jak wyjaśnić wynik próby: wydaje się, że w grę wchodzą rzeczywiście dwa typy, jeden według wartości parametru T? Jeśli T naprawdę ukrywał typ klasy, spodziewałbym się, że „wstrząśnij klasą booty” com.eclipseoptions.datamanager.Clazz ”wyprowadzony dwukrotnie bez parametrów.
Pragmateek
1
Nie ma to nic wspólnego z maskowaniem. Metoda statyczna po prostu nie może być związana typem klasowym klasy . Niemniej jednak może definiować własne typy ogólne i granice.
Mike
Czy istnieje sposób jednokrotnego zdefiniowania typu statycznego i ponownego użycia go dla kilku metod statycznych? Nie jestem fanem robienia static <E extends SomeClass> E foo(E input){return input;}każdej metody, gdy chciałbym zrobić coś takiegostatic <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

Myślę, że ta składnia nie została jeszcze wspomniana (w przypadku, gdy chcesz metodę bez argumentów):

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

I połączenie:

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

Mam nadzieję, że komuś to pomoże.

Oreste Viron
źródło
1
W rzeczywistości (nie znam wersji Java) jest to możliwe nawet bez, <String>ponieważ po prostu wprowadza argument typu, ponieważ przypisujesz go do zmiennej. Jeśli go nie przypisasz, po prostu wnioskuje Object.
Adowrath,
To idealne rozwiązanie.
Prabhat Ranjan
6

Jest to poprawnie wymienione w błędzie: nie można odwoływać się statycznie do niestatycznego typu T. Powodem jest to, że parametr type Tmożna zastąpić dowolnym argumentem type, np. Clazz<String>LubClazz<integer> itd. Ale wszystkie pola / metody statyczne są wspólne -statyczne obiekty klasy.

Poniższy fragment pochodzi z dokumentu :

Pole statyczne klasy jest zmienną na poziomie klasy wspólną dla wszystkich niestatycznych obiektów klasy. Dlatego pola statyczne parametrów typu nie są dozwolone. Rozważ następującą klasę:

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

    // ...
}

Gdyby dozwolone były pola statyczne parametrów typu, następujący kod zostałby pomylony:

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

Ponieważ statyczny system operacyjny jest współdzielony przez telefon, pager i komputer, jaki jest rzeczywisty typ systemu operacyjnego? Nie może to być jednocześnie Smartfon, Pager i TabletPC. Dlatego nie można tworzyć pól statycznych o parametrach typu.

Jak słusznie zauważył Chris w swojej odpowiedzi , musisz użyć parametru type z metodą, a nie z klasą w tym przypadku. Możesz to napisać w następujący sposób:

static <E> void doIt(E object) 
akhil_mittal
źródło
3

Coś takiego może przybliżyć cię

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

EDYCJA: Zaktualizowano przykład bardziej szczegółowo

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
źródło
Dokładnie tak, jak sugerował Jason S.
André Chalella
@Andre poprzednia sugestia nie przewidywała ograniczenia, że ​​U musi przedłużyć Clazz
ekj
Rozumiem, ale nie dodaje nic użytecznego oprócz ograniczenia. W moim oryginalnym poście chciałbym móc to zrobić bez podawania parametru U jako parametru.
André Chalella
@Andre Zobacz moją aktualizację powyżej. Typ ogólny jest zdefiniowany tylko w definicji metody i nie musi być przekazywany podczas wywoływania metody
ekj
@ ekj Dziękuję, teraz rozumiem. Przepraszam, zadałem to pytanie trzy lata temu, kiedy codziennie tworzyłem Javę. Wpadłem na twój pomysł, chociaż myślę, że rozumiesz, że nie do końca tak zamierzałem. Jednak podobała mi się twoja odpowiedź.
André Chalella
2

Gdy określisz typ ogólny dla swojej klasy, JVM wie o tym, że ma tylko instancję klasy, a nie definicję. Każda definicja ma tylko sparametryzowany typ.

Generyczne działają jak szablony w C ++, więc powinieneś najpierw utworzyć instancję swojej klasy, a następnie użyć funkcji z określonym typem.

Marcin Cylke
źródło
2
Generyczne Java są zupełnie inne niż szablony C ++. Klasa ogólna jest kompilowana samodzielnie. W rzeczywistości działałby równoważny kod w C ++ (wyglądałby kod wywołujący Clazz<int>::doIt( 5 ))
David Rodríguez - dribeas
Podoba mi się ta odpowiedź bardziej niż ta zaakceptowana ... oprócz odwołania do szablonu C ++, komentarz na temat typu ogólnego jest tylko dla jednej instancji klasy zamiast dla całej klasy. Przyjęta odpowiedź nie wyjaśnia tego, a jedynie zapewnia obejście, które nie ma nic wspólnego z tym, dlaczego nie można użyć typu ogólnego klasy w metodzie statycznej.
Jorn
1

Mówiąc prościej, dzieje się tak ze względu na właściwość „Wymaż” generyków. Oznacza to, że chociaż definiujemy ArrayList<Integer>i ArrayList<String>, w czasie kompilacji, pozostają jako dwa różne konkretne typy, ale w czasie wykonywania JVM usuwa typy ogólne i tworzy tylko jedną klasę ArrayList zamiast dwóch klas. Więc kiedy zdefiniujemy metodę typu statycznego lub cokolwiek innego dla rodzaju ogólnego, jest ona współdzielona przez wszystkie instancje tego typu ogólnego, w moim przykładzie jest wspólna dla obu ArrayList<Integer>i ArrayList<String>dlatego otrzymujesz błąd. Parametr typu ogólnego nie jest parametrem Dozwolone w kontekście statycznym!


źródło
1

@BD w Rivenhill: Ponieważ w zeszłym roku to stare pytanie zyskało nowe zainteresowanie, kontynuujmy trochę ze względu na dyskusję. Ciało twojej doItmetody nie robi nic Tspecyficznego. Oto on:

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

Możesz więc całkowicie upuścić wszystkie zmienne typu i po prostu kodować

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

Dobrze. Wróćmy jednak do pierwotnego problemu. Pierwsza zmienna typu w deklaracji klasy jest redundantna. Potrzebna jest tylko druga metoda. Zaczynamy od nowa, ale to jeszcze nie ostateczna odpowiedź:

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

Jednak jest to zbyt wielkie zamieszanie o nic, ponieważ następująca wersja działa tak samo. Wszystko czego potrzebuje to typ interfejsu parametru parametru. Nigdzie nie widać żadnych typów zmiennych. Czy to był naprawdę oryginalny problem?

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
źródło
0

Ponieważ zmienne statyczne są wspólne dla wszystkich instancji klasy. Na przykład, jeśli masz następujący kod

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

T jest dostępny tylko po utworzeniu instancji. Ale można zastosować metody statyczne, nawet zanim instancje będą dostępne. Zatem do parametrów typu ogólnego nie można odwoływać się w metodach statycznych i zmiennych

Bharat
źródło