Statische Methode in einer generischen Klasse?


197

In Java hätte ich gerne etwas wie:

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

Aber ich verstehe

Es kann kein statischer Verweis auf den nicht statischen Typ T erstellt werden

Ich verstehe Generika nicht über die grundlegenden Verwendungszwecke hinaus und kann daher nicht viel Sinn daraus machen. Es hilft nicht, dass ich im Internet nicht viele Informationen zu diesem Thema finden konnte.

Könnte jemand auf ähnliche Weise klären, ob eine solche Verwendung möglich ist? Warum war mein ursprünglicher Versuch ebenfalls erfolglos?

Antworten:


270

Sie können die generischen Typparameter einer Klasse nicht in statischen Methoden oder statischen Feldern verwenden. Die Typparameter der Klasse sind nur für Instanzmethoden und Instanzfelder gültig. Bei statischen Feldern und statischen Methoden werden sie von allen Instanzen der Klasse gemeinsam genutzt, auch von Instanzen unterschiedlicher Typparameter. Daher können sie offensichtlich nicht von einem bestimmten Typparameter abhängen.

Es scheint nicht so, als ob Ihr Problem die Verwendung des Typparameters der Klasse erfordern sollte. Wenn Sie detaillierter beschreiben, was Sie versuchen, können wir Ihnen vielleicht dabei helfen, einen besseren Weg zu finden.


9
Diese Antwort erklärt das Problem des Posters, anstatt nur eine Problemumgehung bereitzustellen.
Jorn

7
@Andre: Ihre Intuition ist nicht unbegründet; C # behandelt Generika tatsächlich so.
Jyoungdev

32
"Bei statischen Feldern und statischen Methoden werden sie von allen Instanzen der Klasse gemeinsam genutzt, auch von Instanzen unterschiedlicher Typparameter ..." Autsch! Wieder in die Nüsse getreten durch Löschen!
BD in Rivenhill

2
Wenn Sie sehen, wie generische Klassen / Methoden nach der Kompilierung aussehen, werden Sie feststellen, dass das generische Attribut entfernt wurde. Und List <Integer> nach dem Kompilieren sieht aus wie "List". Es gibt also keinen Unterschied zwischen List <Integer> und List <Long> nach der Kompilierung - beide wurden zu List.
Dainius

3
Ich denke, er hat erklärt, was er versucht, ziemlich gut zu machen. Es ist klar, dass er versucht, die Beute zu schütteln! hah
astryk

140

Java weiß nicht, was Tist, bis Sie einen Typ instanziieren.

Vielleicht können Sie statische Methoden durch Aufrufen ausführen, Clazz<T>.doit(something)aber es hört sich so an, als könnten Sie das nicht.

Die andere Möglichkeit, mit Dingen umzugehen, besteht darin, den Typparameter in die Methode selbst einzufügen:

static <U> void doIt(U object)

Das bringt dir nicht die richtige Einschränkung für U, aber es ist besser als nichts ...


Dann würde ich die Methode aufrufen, ohne irgendwelche Einschränkungen anzugeben, dh Clazz.doIt (Objekt) anstelle von Clazz <Objekt> .doIt (Objekt), richtig? Halten Sie das für OK?
André Chalella

Die zweite Syntax dort ist die genauere, die Sie benötigen, wenn der Compiler den Typ des Rückgabewerts nicht aus dem Kontext ableiten kann, in dem die Methode aufgerufen wird. Mit anderen Worten, wenn der Compiler Clazz.doIt (Objekt) zulässt, dann tun Sie das.
Skaffman

1
Ich habe Clazz <Object> .doIt (Objekt) ausprobiert und einen Fehler beim Kompilieren erhalten! "Syntaxfehler bei Token, falsch platzierten Konstrukten". Clazz.doIt (Objekt) funktioniert jedoch einwandfrei, nicht einmal eine Warnung.
André Chalella

8
Verwenden Sie Clazz. <Object> doIt (Objekt). Ich denke, es ist eine seltsame Syntax im Vergleich zu C ++ 's Clazz <int> :: doIt (1)
Crend King

Warum sagst du, dass es dir nicht die richtige Einschränkung für U gibt?
Adam Burley

46

Ich bin auf dasselbe Problem gestoßen. Ich fand meine Antwort, indem ich den Quellcode für Collections.sortim Java-Framework herunterlud . Die Antwort, die ich verwendet habe, war, das <T>Generikum in die Methode und nicht in die Klassendefinition einzufügen.

Das hat also funktioniert:

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

}

Nachdem ich die obigen Antworten gelesen hatte, wurde mir klar, dass dies eine akzeptable Alternative wäre, ohne eine generische Klasse zu verwenden:

public static void quickSort(Comparable[] array, int bottom, int top){
//do it
}

8
Die akzeptierte Antwort war zwar technisch korrekt: Dies ist tatsächlich das, wonach ich gesucht habe, als Google mich zu dieser Frage führte.
Jeremy List

Requisiten für die Bereitstellung eines Beispiels, das die T extends XXXSyntax enthält.
Oger Psalm33

3
@Chris Da Sie ohnehin Generika verwenden, können Sie sie genauso gut vollständig verwenden - und zwar nicht, um Rohtypen wie zu verwenden Comparable. Versuchen Sie es <T extends Comparable<? super T>>stattdessen.
easoncxz

15

Sie können tun, was Sie möchten, indem Sie beim Deklarieren Ihrer doIt()Methode die Syntax für generische Methoden verwenden (beachten Sie das Hinzufügen von <T>zwischen staticund voidin der Methodensignatur von doIt()):

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

Ich habe den Eclipse-Editor dazu gebracht, den obigen Code ohne Cannot make a static reference to the non-static type TFehler zu akzeptieren, und ihn dann auf das folgende Arbeitsprogramm erweitert (komplett mit einem altersgerechten kulturellen Bezug):

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

Was druckt diese Zeilen auf der Konsole, wenn ich es ausführe:

Schütteln Sie diese Beute 'Klasse com.eclipseoptions.datamanager.Clazz $ KC' !!!
Schütteln Sie diese Beute 'Klasse com.eclipseoptions.datamanager.Clazz $ SunshineBand' !!!


Versteckt in diesem Fall das zweite <T> das erste?
André Chalella

1
@ AndréNeves, ja. Der zweite <T>maskiert den ersten auf dieselbe Weise wie class C { int x; C(int x) { ... } }der Parameter xdas Feld maskiert x.
Mike Samuel

Wie erklärt man die Ausgabe der Stichprobe: Es scheint, dass tatsächlich zwei Typen beteiligt sind, einer nach Parameter T-Wert? Wenn T den Klassentyp wirklich verstecken würde, würde ich erwarten, dass "diese Beuteklasse com.eclipseoptions.datamanager.Clazz schütteln" zweimal ohne Parameter ausgegeben wird.
Pragmateek

1
Es hat nichts mit Maskierung zu tun. Eine statische Methode kann einfach nicht an den generischen Typ der Klasse gebunden werden . Trotzdem kann es seine eigenen generischen Typen und Grenzen definieren.
Mike

Gibt es eine Möglichkeit, den statischen Typ einmal zu definieren und für mehrere statische Methoden wiederzuverwenden? Ich bin kein Fan von static <E extends SomeClass> E foo(E input){return input;}für jede einzelne Methode, wenn ich etwas tun möchte wiestatic <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

Ich denke, diese Syntax wurde noch nicht erwähnt (falls Sie eine Methode ohne Argumente wünschen):

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

Und der Anruf:

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

Hoffe das hilft jemandem.


1
Eigentlich (ich kenne die Java-Version nicht) ist dies auch ohne möglich, <String>da es einfach auf das Typargument folgt, weil Sie es einer Variablen zuweisen. Wenn Sie es nicht zuweisen, schließt es einfach Object.
Adowrath

Dies ist eine perfekte Lösung.
Prabhat Ranjan

6

Es ist richtig in dem Fehler erwähnt: Sie keinen statischen Verweis auf nicht-statischen Typ T. Der Grund machen können , ist der Typparameter Tdurch eine des Typargument zB ersetzt werden kann Clazz<String>oder Clazz<integer>etc. Aber statische Felder / Methoden , die von allen nicht geteilt werden -statische Objekte der Klasse.

Der folgende Auszug stammt aus dem Dokument :

Das statische Feld einer Klasse ist eine Variable auf Klassenebene, die von allen nicht statischen Objekten der Klasse gemeinsam genutzt wird. Daher sind statische Felder von Typparametern nicht zulässig. Betrachten Sie die folgende Klasse:

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

    // ...
}

Wenn statische Felder mit Typparametern zulässig wären, wäre der folgende Code verwirrt:

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

Was ist der tatsächliche Betriebssystemtyp, da das statische Feld os von Telefon, Pager und PC gemeinsam genutzt wird? Es können nicht gleichzeitig Smartphone, Pager und TabletPC sein. Sie können daher keine statischen Felder mit Typparametern erstellen.

Wie Chris in seiner Antwort zu Recht betont hat, müssen Sie den Typparameter für die Methode und in diesem Fall nicht für die Klasse verwenden. Sie können es schreiben wie:

static <E> void doIt(E object) 

3

So etwas wie das Folgende würde dich näher bringen

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

BEARBEITEN: Beispiel mit mehr Details aktualisiert

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

Genau wie Jason S vorgeschlagen hat.
André Chalella

@Andre der vorherige Vorschlag enthielt nicht die Einschränkung, dass U Clazz verlängern muss
ekj

Ich verstehe, aber es fügt nichts Nützliches hinzu, außer der Einschränkung. In meinem ursprünglichen Beitrag möchte ich dies tun können, ohne U als Parameter zu übergeben.
André Chalella

@Andre Siehe mein Update oben. Der generische Typ wird nur in der Methodendefinition definiert und muss beim Aufruf der Methode nicht übergeben werden
ekj

@ekj Danke, ich verstehe jetzt. Entschuldigung, ich habe diese Frage vor drei Jahren gestellt, als ich täglich Java machte. Ich habe Ihre Idee, obwohl ich glaube, Sie verstehen, dass dies nicht genau das ist, was ich ursprünglich beabsichtigt hatte. Ihre Antwort hat mir jedoch gefallen.
André Chalella

2

Wenn Sie einen generischen Typ für Ihre Klasse angeben, weiß JVM, dass nur eine Instanz Ihrer Klasse vorhanden ist, keine Definition. Jede Definition hat nur einen parametrisierten Typ.

Generika funktionieren wie Vorlagen in C ++, daher sollten Sie zuerst Ihre Klasse instanziieren und dann die Funktion mit dem angegebenen Typ verwenden.


2
Java-Generika unterscheiden sich erheblich von C ++ - Vorlagen. Die generische Klasse wird von selbst kompiliert. Tatsächlich würde der entsprechende Code in C ++ funktionieren (aufrufender Code würde aussehen Clazz<int>::doIt( 5 ))
David Rodríguez - dribeas

Diese Antwort gefällt mir besser als die akzeptierte ... Abgesehen von der C ++ - Vorlagenreferenz ist der Kommentar, dass der generische Typ nur für eine Instanz der Klasse anstelle der gesamten Klasse gilt, genau richtig. Die akzeptierte Antwort erklärt dies nicht und bietet lediglich eine Problemumgehung, die nichts damit zu tun hat, warum Sie den generischen Typ der Klasse nicht in einer statischen Methode verwenden können.
Jorn

1

Einfach ausgedrückt geschieht dies aufgrund der "Erasure" -Eigenschaft der Generika. Dies bedeutet, dass wir zwar definieren ArrayList<Integer>und ArrayList<String>zur Kompilierungszeit zwei verschiedene konkrete Typen beibehalten, die JVM jedoch zur Laufzeit generische Typen und löscht Erstellt nur eine ArrayList-Klasse anstelle von zwei Klassen. Wenn wir also eine statische Typmethode oder etwas anderes für ein Generikum definieren, wird sie von allen Instanzen dieses Generikums gemeinsam genutzt. In meinem Beispiel wird sie von beiden ArrayList<Integer>gemeinsam genutzt. ArrayList<String>Aus diesem Grund wird der Fehler angezeigt. Ein generischer Typparameter einer Klasse ist nicht In einem statischen Kontext erlaubt!


1

@BD bei Rivenhill: Da diese alte Frage im letzten Jahr erneut Beachtung gefunden hat, lassen Sie uns ein wenig weitermachen, nur um zu diskutieren. Der Körper Ihrer doItMethode macht überhaupt nichts TSpezifisches. Hier ist es:

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

Sie können also alle Typvariablen und nur Code vollständig löschen

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

OK. Aber kommen wir dem ursprünglichen Problem näher. Die erste Typvariable in der Klassendeklaration ist redundant. Es wird nur die zweite Methode benötigt. Jetzt geht es wieder los, aber es ist noch nicht die endgültige Antwort:

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

Es ist jedoch allzu viel Aufhebens um nichts, da die folgende Version genauso funktioniert. Es wird lediglich der Schnittstellentyp für den Methodenparameter benötigt. Nirgendwo sind Typvariablen in Sicht. War das wirklich das ursprüngliche 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";
      }
}

0

Da statische Variablen von allen Instanzen der Klasse gemeinsam genutzt werden. Zum Beispiel, wenn Sie folgenden Code haben

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

T ist erst verfügbar, nachdem eine Instanz erstellt wurde. Statische Methoden können jedoch verwendet werden, noch bevor Instanzen verfügbar sind. Daher können generische Typparameter nicht in statischen Methoden und Variablen referenziert werden

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.