Metode statis di kelas generik?

197

Di Jawa, saya ingin memiliki sesuatu sebagai:

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

Tapi saya mengerti

Tidak dapat membuat referensi statis ke tipe T non-statis

Saya tidak mengerti obat generik di luar penggunaan dasar dan karenanya tidak bisa memahaminya. Tidak membantu bahwa saya tidak dapat menemukan banyak info di internet tentang masalah ini.

Bisakah seseorang mengklarifikasi jika penggunaan semacam itu dimungkinkan, dengan cara yang serupa? Juga, mengapa upaya awal saya tidak berhasil?

André Chalella
sumber

Jawaban:

270

Anda tidak bisa menggunakan parameter tipe generik kelas dalam metode statis atau bidang statis. Parameter tipe kelas hanya dalam cakupan untuk metode instance dan bidang instance. Untuk bidang statis dan metode statis, mereka dibagikan di antara semua instance kelas, bahkan instance dari parameter tipe yang berbeda, jadi jelas mereka tidak dapat bergantung pada parameter tipe tertentu.

Sepertinya masalah Anda tidak perlu menggunakan parameter tipe kelas. Jika Anda menggambarkan apa yang ingin Anda lakukan secara lebih rinci, mungkin kami dapat membantu Anda menemukan cara yang lebih baik untuk melakukannya.

berita baru
sumber
9
Terpilih, jawaban ini sebenarnya menjelaskan masalah poster alih-alih hanya memberikan solusi.
Jorn
7
@Andre: Intuisi Anda tidak berdasar; C # memang memperlakukan obat generik dengan cara ini.
jyoungdev
32
"Untuk bidang statis dan metode statis, mereka dibagikan di antara semua instance kelas, bahkan contoh parameter tipe yang berbeda ..." Aduh! Menendang kacang dengan tipe penghapusan lagi!
BD di Rivenhill
2
jika Anda akan melihat bagaimana kelas generik / metode terlihat setelah kompilasi, Anda akan melihat bahwa atribut generik dihapus. Dan Daftar <Integer> setelah kompilasi terlihat seperti "Daftar". Jadi tidak ada perbedaan antara List <Integer> dan List <Long> setelah kompilasi - keduanya menjadi List.
Dainius
3
Saya pikir dia menjelaskan apa yang dia coba lakukan dengan cukup baik. Jelas dia mencoba mengguncang rampasan dat! hah
astryk
140

Java tidak tahu apa Titu sampai Anda instantiate jenis.

Mungkin Anda bisa menjalankan metode statis dengan menelepon Clazz<T>.doit(something)tetapi sepertinya Anda tidak bisa.

Cara lain untuk menangani berbagai hal adalah dengan memasukkan parameter tipe dalam metode itu sendiri:

static <U> void doIt(U object)

yang tidak memberi Anda batasan yang tepat pada U, tapi itu lebih baik daripada tidak sama sekali ....

Jason S
sumber
Lalu saya akan memanggil metode tanpa menentukan kendala, yaitu Clazz.doIt (objek) bukan Clazz <Object> .doIt (objek), kan? Apakah Anda menganggap itu OK?
André Chalella
Sintaks kedua ada yang lebih tepat, yang Anda butuhkan jika kompilator tidak dapat menyimpulkan jenis nilai pengembalian dari contaxt di mana metode ini dipanggil. Dengan kata lain, jika kompiler memungkinkan Clazz.doIt (objek), maka lakukan itu.
skaffman
1
Saya mencoba Clazz <Object> .doIt (objek) dan mendapat kesalahan waktu kompilasi! + Msgstr "Kesalahan sintaksis pada token, konstruk salah penempatan". Clazz.doIt (objek) berfungsi dengan baik, bahkan tidak peringatan.
André Chalella
8
Gunakan Clazz. <Object> doIt (objek). Saya pikir ini sintaks yang aneh, dibandingkan dengan C ++'s Clazz <int> :: doIt (1)
Crend King
Mengapa Anda mengatakan itu tidak membuat Anda mendapatkan batasan yang tepat pada U?
Adam Burley
46

Saya mengalami masalah yang sama. Saya menemukan jawaban saya dengan mengunduh kode sumber untuk Collections.sortdalam kerangka java. Jawaban yang saya gunakan adalah untuk meletakkan<T> generik dalam metode, bukan dalam definisi kelas.

Jadi ini berhasil:

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

}

Tentu saja, setelah membaca jawaban di atas saya menyadari bahwa ini akan menjadi alternatif yang dapat diterima tanpa menggunakan kelas generik:

public static void quickSort(Comparable[] array, int bottom, int top){
//do it
}
Chris
sumber
8
Meskipun jawaban yang diterima secara teknis benar: inilah yang sebenarnya saya cari ketika Google mengarahkan saya ke pertanyaan ini.
Daftar Jeremy
Alat peraga untuk memberikan contoh yang menyertakan T extends XXXsintaksis.
Ogre Psalm33
3
@ Chris Karena Anda tetap menggunakan obat generik, Anda sebaiknya menggunakannya sepanjang jalan --- yaitu tidak menggunakan jenis mentah seperti Comparable. Coba <T extends Comparable<? super T>>saja.
easoncxz
15

Dimungkinkan untuk melakukan apa yang Anda inginkan dengan menggunakan sintaks untuk metode generik ketika mendeklarasikan doIt()metode Anda (perhatikan penambahan di <T>antara staticdan voiddalam tanda tangan metode doIt()):

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

Saya mendapat editor Eclipse untuk menerima kode di atas tanpa Cannot make a static reference to the non-static type Tkesalahan dan kemudian memperluasnya ke program kerja berikut (lengkap dengan referensi budaya yang agak sesuai usia):

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

Yang mencetak garis-garis ini ke konsol ketika saya menjalankannya:

goyangkan 'kelas com.eclipseoptions.datamanager.Clazz $ KC' !!!
goyangkan 'class com.eclipseoptions.datamanager.Clazz $ SunshineBand' !!!

BD di Rivenhill
sumber
Dalam hal ini, apakah <T> yang kedua menyembunyikan yang pertama?
André Chalella
1
@ AndréNeves, Ya. Yang kedua <T>menutupi yang pertama dengan cara yang sama bahwa dalam class C { int x; C(int x) { ... } }parameter xmenutupi bidang x.
Mike Samuel
Bagaimana menjelaskan output sampel: tampaknya memang ada dua jenis yang terlibat, satu dengan nilai parameter T? Jika T benar-benar menyembunyikan tipe kelas saya akan berharap "kocok kelas itu rampasan 'com.eclipseoptions.datamanager.Clazz" dihasilkan dua kali tanpa parameter.
Pragmateek
1
Itu tidak ada hubungannya dengan topeng. Metode statis tidak bisa diikat oleh tipe generik kelas . Namun demikian, ia dapat menentukan jenis dan batas generiknya sendiri.
mike
Apakah ada cara untuk mendefinisikan tipe statis sekali dan menggunakannya kembali untuk beberapa metode statis? Saya bukan penggemar melakukan static <E extends SomeClass> E foo(E input){return input;}untuk setiap metode ketika saya ingin melakukan sesuatu sepertistatic <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

Saya pikir sintaks ini belum disebutkan (jika Anda ingin metode tanpa argumen):

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

Dan panggilannya:

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

Semoga ini bisa membantu seseorang.

Oreste Viron
sumber
1
Sebenarnya (saya tidak tahu versi Java), ini dimungkinkan bahkan tanpa <String>karena hanya menyimpulkan argumen tipe karena Anda menetapkannya ke variabel. Jika Anda tidak menetapkannya, itu hanya menyimpulkan Object.
Adowrath
Ini adalah solusi sempurna.
Prabhat Ranjan
6

Disebut dengan benar dalam kesalahan: Anda tidak dapat membuat referensi statis ke tipe T. non-statis. Alasannya adalah parameter tipe Tdapat diganti oleh salah satu argumen tipe misalnya Clazz<String>atauClazz<integer> dll Tapi bidang statis / metode yang dimiliki oleh semua non objek-statis dari kelas.

Kutipan berikut diambil dari dokumen :

Bidang statis kelas adalah variabel tingkat kelas yang dibagikan oleh semua objek non-statis kelas. Oleh karena itu, bidang statis dari parameter tipe tidak diperbolehkan. Pertimbangkan kelas berikut:

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

    // ...
}

Jika bidang statis dari jenis parameter dibolehkan, maka kode berikut akan bingung:

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

Karena os bidang statis dibagi melalui telepon, pager, dan pc, apa jenis os yang sebenarnya? Itu tidak bisa Smartphone, Pager, dan TabletPC secara bersamaan. Karenanya, Anda tidak dapat membuat bidang statis dari jenis parameter.

Seperti yang ditunjukkan oleh chris dalam jawabannya, Anda perlu menggunakan tipe parameter dengan metode dan bukan dengan kelas dalam kasus ini. Anda bisa menulis seperti:

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

Sesuatu seperti yang berikut ini akan membuat Anda lebih dekat

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

Sunting: Contoh yang diperbarui dengan lebih detail

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
sumber
Persis seperti yang disarankan Jason S.
André Chalella
@Andre saran sebelumnya tidak memberikan batasan bahwa Anda harus memperpanjang Clazz
ekj
Saya mengerti, tetapi itu tidak menambahkan sesuatu yang berguna kecuali untuk kendala. Dalam posting asli saya, saya ingin dapat melakukan ini tanpa melewati U sebagai parameter.
André Chalella
@Andre Lihat pembaruan saya di atas. Tipe generik hanya didefinisikan dalam definisi metode, dan tidak harus diteruskan saat memanggil metode
ekj
@ekj Terima kasih, saya mengerti sekarang. Maaf, saya mengajukan pertanyaan ini tiga tahun lalu, ketika saya mengerjakan Java setiap hari. Saya mendapatkan ide Anda, meskipun saya pikir Anda mengerti bahwa ini bukan yang saya maksudkan semula. Namun, saya menyukai jawaban Anda.
André Chalella
2

Ketika Anda menentukan tipe generik untuk kelas Anda, JVM tahu tentang itu hanya memiliki turunan kelas Anda, bukan definisi. Setiap definisi hanya memiliki tipe parametrized.

Generik berfungsi seperti templat di C ++, jadi pertama-tama Anda harus membuat instance kelas Anda, lalu gunakan fungsi dengan tipe yang ditentukan.

Marcin Cylke
sumber
2
Java generics sangat berbeda dari template C ++. Kelas generik dikompilasi dengan sendirinya. Bahkan kode setara dalam C ++ akan bekerja (memanggil kode akan terlihat seperti Clazz<int>::doIt( 5 ))
David Rodríguez - dribeas
Saya suka jawaban ini lebih baik daripada yang diterima ... selain dari referensi template C ++, komentar tentang tipe generik hanya untuk satu instance dari kelas bukannya seluruh kelas tepat. Jawaban yang diterima tidak menjelaskan hal ini, dan hanya menyediakan solusi yang tidak ada hubungannya dengan mengapa Anda tidak dapat menggunakan tipe generik kelas dalam metode statis.
Jorn
1

Juga untuk membuatnya secara sederhana, itu terjadi karena properti "Penghapusan" dari obat generik. Yang berarti bahwa meskipun kita mendefinisikan ArrayList<Integer>dan ArrayList<String>, pada waktu kompilasi, ia tetap sebagai dua jenis beton yang berbeda tetapi pada saat runtime JVM menghapus jenis generik dan hanya membuat satu kelas ArrayList, bukan dua kelas. Jadi ketika kita mendefinisikan metode tipe statis atau apa pun untuk generik, itu dibagi oleh semua contoh generik itu, dalam contoh saya itu dibagi oleh keduanya ArrayList<Integer>dan ArrayList<String>. Itulah sebabnya Anda mendapatkan kesalahan. Parameter Tipe Generik dari Kelas Tidak Diizinkan dalam Konteks Statis!


sumber
1

@BD di Rivenhill: Karena pertanyaan lama ini telah mendapat perhatian baru tahun lalu, mari kita lanjutkan sedikit, hanya demi diskusi. Tubuh doItmetode Anda tidak melakukan apa-apa- Tspesifik sama sekali. Ini dia:

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

Jadi, Anda dapat sepenuhnya menghapus semua variabel tipe dan hanya kode

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

Baik. Tapi mari kita kembali lebih dekat ke masalah aslinya. Variabel tipe pertama pada deklarasi kelas adalah redundan. Hanya metode kedua yang diperlukan. Kita mulai lagi, tetapi ini bukanlah jawaban terakhir:

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

Namun, semuanya terlalu ribut tentang apa-apa, karena versi berikut berfungsi dengan cara yang sama. Yang dibutuhkan hanyalah tipe antarmuka pada parameter metode. Tidak ada variabel tipe yang terlihat di mana pun. Apakah itu benar-benar masalah awal?

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

Karena variabel statis dibagikan oleh semua instance kelas. Misalnya jika Anda memiliki kode berikut

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

T tersedia hanya setelah sebuah instance dibuat. Tetapi metode statis dapat digunakan bahkan sebelum instance tersedia. Jadi, parameter tipe umum tidak dapat dirujuk di dalam metode dan variabel statis

Bharat
sumber