Java: Mesin Virtual dan Generik

135

Sebelum saya melihat melalui struktur data generik saya untuk indeks nilai, saya ingin melihat apakah ini adalah contoh dari jenis yang thistelah ditentukan.

Tapi Eclipse mengeluh ketika saya melakukan ini:

@Override
public int indexOf(Object arg0) {
    if (!(arg0 instanceof E)) {
        return -1;
    }

Ini adalah pesan kesalahan:

Tidak dapat melakukan instanceof terhadap parameter tipe E. Sebagai gantinya gunakan Objek penghapusan karena informasi tipe umum akan terhapus saat runtime

Apa cara yang lebih baik untuk melakukannya?

Nick Heiner
sumber

Jawaban:

79

Pesan kesalahan mengatakan itu semua. Saat runtime, jenisnya hilang, tidak ada cara untuk memeriksanya.

Anda bisa menangkapnya dengan membuat pabrik untuk objek Anda seperti ini:

 public static <T> MyObject<T> createMyObject(Class<T> type) {
    return new MyObject<T>(type);
 }

Dan kemudian di toko konstruktor objek jenis itu, sangat variabel sehingga metode Anda bisa terlihat seperti ini:

        if (arg0 != null && !(this.type.isAssignableFrom(arg0.getClass()))
        {
            return -1;
        }
Yishai
sumber
3
Saya pikir Anda tidak mau Class.isAssignableFrom.
Tom Hawtin - tackline
@ Tom, saya menulis ini tadi malam dari memori, dan saya memperbaikinya untuk benar-benar lulus kelas (ya!) Tetapi sebaliknya, saya tidak mengerti mengapa Anda tidak menginginkannya (mungkin saya perlu lebih banyak kopi pagi ini, saya ' m hanya di piala pertama saya).
Yishai
Saya dengan Tom. Bisakah Anda mengklarifikasi itu? Menggunakan isAssignableFrom () akan menjadi pilihan saya untuk pekerjaan itu. Mungkin saya melewatkan sesuatu?
luis.espinal
6
@luis, komentar Tom mungkin berarti saya harus menggunakan isInstance () dan melewatkan parameter arg0 yang sebenarnya. Ini memiliki keuntungan menghindari cek nol.
Yishai
1
Saya memiliki situasi yang serupa tetapi saya tidak dapat sepenuhnya memahami jawabannya. Bisakah Anda menjelaskan lebih baik untuk boneka seperti saya?
Rubens Mariuzzo
40

Dua opsi untuk pengecekan tipe runtime dengan obat generik:

Opsi 1 - Rusak konstruktor Anda

Mari kita asumsikan Anda mengganti indexOf (...), dan Anda ingin memeriksa tipe hanya untuk kinerja, untuk menyelamatkan diri Anda sendiri iterasi seluruh koleksi.

Buat konstruktor kotor seperti ini:

public MyCollection<T>(Class<T> t) {

    this.t = t;
}

Kemudian Anda dapat menggunakan isAssignableFrom untuk memeriksa jenisnya.

public int indexOf(Object o) {

    if (
        o != null &&

        !t.isAssignableFrom(o.getClass())

    ) return -1;

//...

Setiap kali Anda membuat instance objek, Anda harus mengulang sendiri:

new MyCollection<Apples>(Apples.class);

Anda mungkin memutuskan itu tidak layak. Dalam implementasi ArrayList.indexOf (...) , mereka tidak memeriksa apakah jenisnya cocok.

Opsi 2 - Biarkan gagal

Jika Anda perlu menggunakan metode abstrak yang memerlukan jenis tidak dikenal Anda, maka semua yang Anda inginkan adalah agar kompiler berhenti menangis tentang instanceof . Jika Anda memiliki metode seperti ini:

protected abstract void abstractMethod(T element);

Anda bisa menggunakannya seperti ini:

public int indexOf(Object o) {

    try {

        abstractMethod((T) o);

    } catch (ClassCastException e) {

//...

Anda melempar objek ke T (tipe generik Anda), hanya untuk menipu kompiler. Para pemain Anda tidak melakukan apa-apa saat runtime , tetapi Anda masih akan mendapatkan ClassCastException ketika Anda mencoba untuk memasukkan tipe objek yang salah ke dalam metode abstrak Anda.

CATATAN 1: Jika Anda melakukan gips yang tidak dicentang tambahan dalam metode abstrak Anda, ClassCastExceptions Anda akan terjebak di sini. Itu bisa baik atau buruk, jadi pikirkan baik-baik.

CATATAN 2: Anda mendapatkan cek nol gratis ketika Anda menggunakan instanceof . Karena Anda tidak dapat menggunakannya, Anda mungkin perlu memeriksa nol dengan tangan kosong.

SharkAlley
sumber
18

Posting lama, tetapi cara sederhana untuk melakukan pengecekan instanceOf generik.

public static <T> boolean isInstanceOf(Class<T> clazz, Class<T> targetClass) {
    return clazz.isInstance(targetClass);
}
Jonas Pedersen
sumber
21
Tidak jelas apa yang sebenarnya Anda kontribusikan di sini.
Andrew
12

Asalkan kelas Anda memperluas kelas dengan parameter generik, Anda juga bisa mendapatkan ini saat runtime melalui refleksi, dan kemudian menggunakannya untuk perbandingan, yaitu

class YourClass extends SomeOtherClass<String>
{

   private Class<?> clazz;

   public Class<?> getParameterizedClass()
   {
      if(clazz == null)
      {
         ParameterizedType pt = (ParameterizedType)this.getClass().getGenericSuperclass();
          clazz = (Class<?>)pt.getActualTypeArguments()[0];
       }
       return clazz;
    }
}

Dalam kasus di atas, pada saat runtime Anda akan mendapatkan String.class dari getParameterizedClass (), dan cache sehingga Anda tidak mendapatkan overhead refleksi setelah beberapa pemeriksaan. Perhatikan bahwa Anda bisa mendapatkan tipe parameter lain berdasarkan indeks dari metode ParameterizedType.getActualTypeArguments ().

terryscotttaylor
sumber
7

Saya memiliki masalah yang sama dan inilah solusi saya (sangat rendah hati, @george: kali ini mengkompilasi DAN bekerja ...).

Masalah saya ada di dalam kelas abstrak yang mengimplementasikan Observer. Pembaruan metode kebakaran yang dapat diobservasi (...) dengan kelas Object yang dapat berupa segala jenis Object.

Saya hanya ingin menangani Objek tipe T

Solusinya adalah meneruskan kelas ke konstruktor agar dapat membandingkan tipe saat runtime.

public abstract class AbstractOne<T> implements Observer {

  private Class<T> tClass;
    public AbstractOne(Class<T> clazz) {
    tClass = clazz;
  }

  @Override
  public void update(Observable o, Object arg) {
    if (tClass.isInstance(arg)) {
      // Here I am, arg has the type T
      foo((T) arg);
    }
  }

  public abstract foo(T t);

}

Untuk implementasinya kita hanya perlu meneruskan Kelas ke konstruktor

public class OneImpl extends AbstractOne<Rule> {
  public OneImpl() {
    super(Rule.class);
  }

  @Override
  public void foo(Rule t){
  }
}
hsaturn
sumber
5

Atau Anda bisa menangkap upaya gagal untuk melemparkan ke E eg.

public int indexOf(Object arg0){
  try{
    E test=(E)arg0;
    return doStuff(test);
  }catch(ClassCastException e){
    return -1;
  }
}
ben
sumber
1
+1 Solusi rekayasa non-over pragmatis untuk pemeriksaan sederhana! Saya berharap saya bisa memilih ini lebih
higuaro
24
Ini tidak akan berhasil. E dihapus saat runtime, sehingga para pemain tidak akan gagal, Anda hanya akan mendapatkan peringatan kompilator tentang hal itu.
Yishai
1

Secara teknis Anda tidak harus melakukannya, itulah gunanya obat generik, sehingga Anda dapat melakukan pengecekan tipe kompilasi:

public int indexOf(E arg0) {
   ...
}

tetapi kemudian @Override mungkin menjadi masalah jika Anda memiliki hierarki kelas. Kalau tidak, lihat jawaban Yishai.

Jason S
sumber
yeah, antarmuka Daftar menuntut agar fungsi tersebut mengambil parameter objek.
Nick Heiner
Anda menerapkan Daftar? Mengapa Anda tidak mengimplementasikan Daftar <E>?
Jason S
(Lihat misalnya deklarasi ArrayList: java.sun.com/j2se/1.5.0/docs/api/java/util/ArrayList.html )
Jason S
@Rosarch: metode indexOf () daftar antarmuka tidak mengharuskan argumen yang diberikan adalah jenis yang sama dengan objek yang Anda cari. itu hanya harus .equals () untuk itu, dan objek dari tipe yang berbeda dapat .equals () satu sama lain. Ini adalah masalah yang sama seperti untuk metode hapus (), lihat: stackoverflow.com/questions/104799/...
newacct
1
[[[malu-malu]]] tidak masalah, saya pikir indexOf () membutuhkan E sebagai parameter daripada Object. (mengapa mereka melakukan itu ???!!)
Jason S
1

Tipe runtime objek adalah kondisi yang relatif arbitrer untuk difilter. Saya sarankan menjauhkan kesembronoan dari koleksi Anda. Ini hanya dicapai dengan meminta delegasi koleksi Anda ke filter melewati konstruksi.

public interface FilterObject {
     boolean isAllowed(Object obj);
}

public class FilterOptimizedList<E> implements List<E> {
     private final FilterObject filter;
     ...
     public FilterOptimizedList(FilterObject filter) {
         if (filter == null) {
             throw NullPointerException();
         }
         this.filter = filter;
     }
     ...
     public int indexOf(Object obj) {
         if (!filter.isAllows(obj)) {
              return -1;
         }
         ...
     }
     ...
}

     final List<String> longStrs = new FilterOptimizedList<String>(
         new FilterObject() { public boolean isAllowed(Object obj) {
             if (obj == null) {
                 return true;
             } else if (obj instanceof String) {
                 String str = (String)str;
                 return str.length() > = 4;
             } else {
                 return false;
             }
         }}
     );
Tom Hawtin - tackline
sumber
(Meskipun Anda mungkin ingin melakukan pemeriksaan contoh jenis jika Anda menggunakan Comparatoratau serupa.)
Tom Hawtin - tackline