Pertanyaan Apa yang keren tentang generik, mengapa menggunakannya?


Saya pikir saya akan menawarkan softball ini kepada siapa pun yang ingin memukulnya keluar dari taman. Apa itu generik, apa keuntungan dari generik, mengapa, di mana, bagaimana cara saya menggunakannya? Tolong, biarkan itu cukup mendasar. Terima kasih.


75
2017-09-16 21:57


asal


Jawaban:


  • Memungkinkan Anda untuk menulis kode / menggunakan metode pustaka yang aman-jenis, yaitu Daftar <string> dijamin sebagai daftar string.
  • Sebagai hasil dari generik yang digunakan kompilator dapat melakukan pemeriksaan kompilasi pada kode untuk jenis keamanan, yaitu apakah Anda mencoba memasukkan int ke dalam daftar string? Menggunakan ArrayList akan menyebabkan kesalahan runtime yang kurang transparan.
  • Lebih cepat daripada menggunakan objek karena menghindari tinju / unboxing (di mana .net harus mengkonversi jenis nilai untuk tipe referensi atau sebaliknya) atau mentransmisi dari objek ke jenis referensi yang diperlukan.
  • Memungkinkan Anda menulis kode yang berlaku untuk banyak jenis dengan perilaku dasar yang sama, yaitu Kamus <string, int> menggunakan kode dasar yang sama dengan Kamus <DateTime, double>; menggunakan generik, tim kerangka kerja hanya harus menulis satu bagian kode untuk mencapai kedua hasil dengan keuntungan yang sama juga.

112
2017-09-16 22:04



Saya benar-benar benci untuk mengulangi diri saya sendiri. Saya benci mengetik hal yang sama lebih sering daripada saya harus. Saya tidak suka mengulang beberapa kali dengan sedikit perbedaan.

Daripada menciptakan:

class MyObjectList  {
   MyObject get(int index) {...}
}
class MyOtherObjectList  {
   MyOtherObject get(int index) {...}
}
class AnotherObjectList  {
   AnotherObject get(int index) {...}
}

Saya dapat membangun satu kelas yang dapat digunakan kembali ... (dalam kasus di mana Anda tidak ingin menggunakan koleksi mentah untuk beberapa alasan)

class MyList<T> {
   T get(int index) { ... }
}

Saya sekarang 3x lebih efisien dan saya hanya perlu mempertahankan satu salinan. Mengapa TIDAK AKAN Anda ingin mempertahankan lebih sedikit kode?

Ini juga berlaku untuk kelas non-penagihan seperti a Callable<T> atau a Reference<T> yang harus berinteraksi dengan kelas lain. Apakah Anda benar-benar ingin memperpanjang Callable<T> dan Future<T> dan setiap kelas terkait lainnya untuk membuat versi tipe-aman?

Bukan saya.


43
2018-06-04 23:56



Tidak perlu typecast adalah salah satu keuntungan terbesar dari generik Java, karena akan melakukan pengecekan jenis pada saat kompilasi. Ini akan mengurangi kemungkinan ClassCastExceptions yang dapat dilemparkan saat runtime, dan dapat mengarah ke kode yang lebih kuat.

Tapi saya menduga bahwa Anda sepenuhnya sadar akan hal itu.

Setiap kali saya melihat Generik itu memberi   saya sakit kepala. Saya menemukan bagian terbaik dari   Java menjadi kesederhanaan dan minimal   sintaksis dan generik tidak sederhana dan   tambahkan jumlah baru yang signifikan   sintaksis.

Pada awalnya, saya juga tidak melihat manfaat dari generik. Saya mulai belajar Java dari sintaks 1.4 (meskipun Java 5 sudah keluar pada saat itu) dan ketika saya menemukan generik, saya merasa bahwa itu lebih banyak kode untuk ditulis, dan saya benar-benar tidak mengerti manfaatnya.

IDE modern membuat kode menulis dengan generik lebih mudah.

Kebanyakan IDE modern yang bagus cukup pintar untuk membantu penulisan kode dengan generik, terutama dengan penyelesaian kode.

Berikut ini contoh pembuatan Map<String, Integer> dengan HashMap. Kode yang harus saya ketik adalah:

Map<String, Integer> m = new HashMap<String, Integer>();

Dan memang, itu banyak untuk mengetik hanya untuk membuat yang baru HashMap. Namun, dalam kenyataannya, saya hanya perlu mengetik sebanyak ini sebelum Eclipse tahu apa yang saya butuhkan:

Map<String, Integer> m = new Ha  Ctrl+Ruang

Benar, saya memang perlu memilih HashMap dari daftar kandidat, tetapi pada dasarnya IDE tahu apa yang harus ditambahkan, termasuk jenis generik. Dengan alat yang tepat, menggunakan obat generik tidak terlalu buruk.

Selain itu, karena jenisnya diketahui, ketika mengambil elemen dari koleksi generik, IDE akan bertindak seolah-olah objek tersebut sudah menjadi objek dari tipe yang dideklarasikan - tidak perlu melakukan casting untuk IDE untuk mengetahui apa jenis objeknya. aku s.

Keuntungan utama dari generik berasal dari caranya bermain dengan baik dengan fitur Java 5 baru. Berikut ini contoh pelemparan bilangan bulat ke a Set dan menghitung totalnya:

Set<Integer> set = new HashSet<Integer>();
set.add(10);
set.add(42);

int total = 0;
for (int i : set) {
  total += i;
}

Dalam potongan kode itu, ada tiga fitur baru Java 5 yang ada:

Pertama, generik dan autoboxing primitif memungkinkan baris berikut:

set.add(10);
set.add(42);

Bilangan bulat 10 diautobox menjadi suatu Integer dengan nilai 10. (Dan sama untuk 42). Lalu itu Integer dilempar ke dalam Set yang dikenal untuk dipegang Integers. Mencoba melempar String akan menyebabkan kesalahan kompilasi.

Selanjutnya, untuk setiap loop mengambil ketiganya:

for (int i : set) {
  total += i;
}

Pertama, itu Set mengandung Integerdigunakan untuk setiap loop. Setiap elemen dinyatakan sebagai int dan itu diizinkan sebagai Integer tidak kembali ke kotak primitif int. Dan fakta bahwa unboxing ini terjadi diketahui karena generik digunakan untuk menentukan yang ada Integers diadakan di Set.

Generik dapat menjadi perekat yang menyatukan fitur-fitur baru yang diperkenalkan di Java 5, dan itu hanya membuat pengkodean lebih sederhana dan lebih aman. Dan sebagian besar waktu IDE cukup pintar untuk membantu Anda dengan saran yang bagus, jadi umumnya, itu tidak akan lebih banyak mengetik.

Dan sejujurnya, seperti yang bisa dilihat dari Set Sebagai contoh, saya merasa bahwa memanfaatkan fitur Java 5 dapat membuat kode lebih ringkas dan kuat.

Edit - Contoh tanpa generik

Berikut ini adalah ilustrasi di atas Set Misalnya tanpa menggunakan obat generik. Itu mungkin, tetapi tidak terlalu menyenangkan:

Set set = new HashSet();
set.add(10);
set.add(42);

int total = 0;
for (Object o : set) {
  total += (Integer)o;
}

(Catatan: Kode di atas akan menghasilkan peringatan konversi yang tidak dicentang pada waktu kompilasi.)

Saat menggunakan koleksi non-generik, jenis yang dimasukkan ke dalam koleksi adalah objek bertipe Object. Oleh karena itu, dalam contoh ini, a Object adalah apa yang sedang terjadi added ke set.

set.add(10);
set.add(42);

Pada baris di atas, autoboxing sedang dimainkan - primitif int nilai 10 dan 42 sedang diautobox Integer objek, yang ditambahkan ke Set. Namun, perlu diingat, itu Integer objek sedang ditangani sebagai Objects, karena tidak ada informasi jenis untuk membantu compiler mengetahui jenis apa Set harus diharapkan.

for (Object o : set) {

Ini adalah bagian yang sangat penting. Alasan untuk setiap loop bekerja adalah karena Setmengimplementasikan Iterable antarmuka, yang mengembalikan sebuah Iterator dengan informasi jenis, jika ada. (Iterator<T>, itu adalah.)

Namun, karena tidak ada informasi jenis, maka Set akan mengembalikan Iterator yang akan mengembalikan nilai dalam Set sebagai Objects, dan itulah mengapa elemen yang diambil dalam untuk setiap loop harus bertipe Object.

Sekarang itu Object diambil dari Set, perlu dilemparkan ke Integer manual untuk melakukan penambahan:

  total += (Integer)o;

Di sini, typecast dilakukan dari Object ke sebuah Integer. Dalam hal ini, kami tahu ini akan selalu berhasil, tetapi typecasting manual selalu membuat saya merasa ini adalah kode yang rapuh yang dapat rusak jika perubahan kecil dibuat di mana saja. (Saya merasa bahwa setiap typecast adalah a ClassCastException menunggu untuk terjadi, tapi saya ngelantur ...)

Itu Integer sekarang tidak dimasukkan ke kotak int dan diizinkan untuk melakukan penambahan ke dalam int variabel total.

Saya harap saya dapat mengilustrasikan bahwa fitur baru Java 5 adalah mungkin untuk digunakan dengan kode non-generik, tetapi itu tidak bersih dan lurus ke depan seperti menulis kode dengan generik. Dan, menurut pendapat saya, untuk memanfaatkan sepenuhnya fitur-fitur baru di Java 5, seseorang harus melihat ke dalam generik, jika setidaknya, memungkinkan untuk pemeriksaan waktu-kompilasi untuk mencegah typecast yang tidak valid untuk melemparkan pengecualian pada saat runtime.


18
2018-06-05 00:24



Jika Anda mencari database bug Java sebelum 1,5 dirilis, Anda akan menemukan tujuh kali lebih banyak bug NullPointerException dari ClassCastException. Jadi sepertinya itu bukan fitur yang bagus untuk menemukan bug, atau setidaknya bug yang bertahan setelah pengujian asap sedikit.

Bagi saya, keuntungan besar dari generik adalah bahwa mereka mendokumentasikan dalam kode informasi jenis penting. Jika saya tidak ingin informasi jenis itu didokumentasikan dalam kode, maka saya akan menggunakan bahasa yang diketik secara dinamis, atau setidaknya bahasa dengan inferensi jenis yang lebih implisit.

Menjaga koleksi objek untuk dirinya sendiri bukanlah gaya yang buruk (tetapi gaya umum adalah secara efektif mengabaikan enkapsulasi). Ini lebih tergantung pada apa yang Anda lakukan. Melewatkan koleksi ke "algoritme" sedikit lebih mudah untuk diperiksa (pada atau sebelum waktu kompilasi) dengan generik.


15
2018-06-04 23:43



Generik di Jawa memfasilitasi polimorfisme parametrik. Dengan menggunakan parameter jenis, Anda dapat meneruskan argumen ke jenis. Sama seperti metode seperti String foo(String s) memodelkan beberapa perilaku, tidak hanya untuk string tertentu, tetapi untuk string apa pun s, jadi sejenisnya List<T> memodelkan beberapa perilaku, tidak hanya untuk tipe tertentu, tetapi untuk jenis apa pun. List<T> mengatakan itu untuk jenis apa pun T, ada jenisnya List elemen-elemennya Ts. Begitu List sebenarnya adalah a ketik konstruktor. Dibutuhkan jenis sebagai argumen dan membangun tipe lain sebagai hasilnya.

Berikut adalah beberapa contoh tipe generik yang saya gunakan setiap hari. Pertama, antarmuka generik yang sangat berguna:

public interface F<A, B> {
  public B f(A a);
}

Antarmuka ini mengatakan itu untuk sekitar dua tipe, A dan B, ada fungsi (disebut f) yang membutuhkan A dan mengembalikan a B. Saat Anda menerapkan antarmuka ini, A dan B dapat berupa jenis apa pun yang Anda inginkan, selama Anda menyediakan fungsi f yang mengambil yang pertama dan mengembalikan yang terakhir. Berikut contoh penerapan antarmuka:

F<Integer, String> intToString = new F<Integer, String>() {
  public String f(int i) {
    return String.valueOf(i);
  }
}

Sebelum generik, polimorfisme dicapai oleh subclassing menggunakan extends kata kunci. Dengan generik, kita sebenarnya dapat menyingkirkan subclassing dan menggunakan polimorfisme parametik. Sebagai contoh, pertimbangkan kelas paramateris (generik) yang digunakan untuk menghitung kode hash untuk jenis apa pun. Alih-alih menimpa Object.hashCode (), kita akan menggunakan kelas generik seperti ini:

public final class Hash<A> {
  private final F<A, Integer> hashFunction;

  public Hash(final F<A, Integer> f) {
    this.hashFunction = f;
  }

  public int hash(A a) {
    return hashFunction.f(a);
  }
}

Ini jauh lebih fleksibel daripada menggunakan warisan, karena kita bisa tetap dengan tema menggunakan komposisi dan polimorfisme parametrik tanpa mengunci hierarki rapuh.

Obat generik Jawa tidak sempurna. Anda dapat mengabstraksi tipe-tipe yang ada, tetapi Anda tidak dapat menganalogikan jenis konstruktor, misalnya. Artinya, Anda dapat mengatakan "untuk tipe T apa pun", tetapi Anda tidak bisa mengatakan "untuk tipe T apa pun yang mengambil tipe parameter A".

Saya menulis artikel tentang batasan-batasan ini generik Jawa, di sini.

Satu kemenangan besar dengan generik adalah mereka membiarkan Anda menghindari subclassing. Subclassing cenderung menghasilkan hierarki kelas rapuh yang aneh untuk diperluas, dan kelas yang sulit dipahami secara individual tanpa melihat seluruh hirarki.

Sebelum generik Anda mungkin memiliki kelas seperti Widget diperpanjang oleh FooWidget, BarWidget, dan BazWidget, dengan generik Anda dapat memiliki satu kelas umum Widget<A> yang membutuhkan Foo, Bar atau Baz di konstruktor untuk memberi Anda Widget<Foo>, Widget<Bar>, dan Widget<Baz>.


10
2018-06-05 00:39



Generics menghindari pukulan kinerja tinju dan unboxing. Pada dasarnya, lihat ArrayList vs List <T>. Keduanya melakukan hal-hal inti yang sama, tetapi Daftar <T> akan jauh lebih cepat karena Anda tidak harus kotak ke / dari objek.


7
2017-09-16 22:00



Saya hanya menyukai mereka karena mereka memberi Anda cara cepat untuk menentukan tipe kustom (seperti saya menggunakannya).

Jadi misalnya, bukan mendefinisikan struktur yang terdiri dari string dan integer, dan kemudian harus mengimplementasikan seluruh kumpulan objek dan metode tentang cara mengakses larik struktur tersebut dan seterusnya, Anda dapat membuat Kamus

Dictionary<int, string> dictionary = new Dictionary<int, string>();

Dan compiler / IDE melakukan sisa angkat berat. Kamus secara khusus memungkinkan Anda menggunakan jenis pertama sebagai kunci (tidak ada nilai yang berulang).


5
2017-09-16 22:02



Manfaat terbaik untuk Generics adalah penggunaan kembali kode. Katakanlah Anda memiliki banyak objek bisnis, dan Anda akan menulis kode yang sangat mirip untuk setiap entitas untuk melakukan tindakan yang sama. (I.E Linq ke operasi SQL).

Dengan generik, Anda dapat membuat kelas yang akan dapat beroperasi dengan memberikan salah satu jenis yang mewarisi dari kelas dasar tertentu atau mengimplementasikan antarmuka yang diberikan seperti:

public interface IEntity
{

}

public class Employee : IEntity
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int EmployeeID { get; set; }
}

public class Company : IEntity
{
    public string Name { get; set; }
    public string TaxID { get; set }
}

public class DataService<ENTITY, DATACONTEXT>
    where ENTITY : class, IEntity, new()
    where DATACONTEXT : DataContext, new()
{

    public void Create(List<ENTITY> entities)
    {
        using (DATACONTEXT db = new DATACONTEXT())
        {
            Table<ENTITY> table = db.GetTable<ENTITY>();

            foreach (ENTITY entity in entities)
                table.InsertOnSubmit (entity);

            db.SubmitChanges();
        }
    }
}

public class MyTest
{
    public void DoSomething()
    {
        var dataService = new DataService<Employee, MyDataContext>();
        dataService.Create(new Employee { FirstName = "Bob", LastName = "Smith", EmployeeID = 5 });
        var otherDataService = new DataService<Company, MyDataContext>();
            otherDataService.Create(new Company { Name = "ACME", TaxID = "123-111-2233" });

    }
}

Perhatikan penggunaan kembali layanan yang sama dengan Tipe yang berbeda dalam metode DoSomething di atas. Benar-benar elegan!

Ada banyak alasan bagus lainnya untuk menggunakan obat generik untuk pekerjaan Anda, ini adalah favorit saya.


5
2017-09-17 04:03



  • Koleksi yang diketik - bahkan jika Anda tidak ingin menggunakannya, Anda mungkin harus berurusan dengan mereka dari perpustakaan lain, sumber lain.

  • Pengetikan generik dalam pembuatan kelas:

    kelas publik Foo <T> { publik T dapatkan () ...

  • Menghindari casting - Saya selalu menyukai hal-hal seperti

    Pembanding baru {    public int compareTo (Object o) {      if (o instance of classIcareAbout) ...

Di mana Anda pada dasarnya memeriksa suatu kondisi yang seharusnya hanya ada karena antarmuka dinyatakan dalam bentuk objek.

Reaksi awal saya terhadap generik sama dengan Anda - "terlalu berantakan, terlalu rumit". Pengalaman saya adalah bahwa setelah menggunakannya sedikit Anda terbiasa dengan mereka, dan kode tanpa mereka terasa kurang jelas ditentukan, dan hanya kurang nyaman. Selain itu, seluruh dunia jawa menggunakan mereka sehingga Anda harus menyelesaikan program itu pada akhirnya, bukan?


5
2018-06-04 23:39



Untuk memberikan contoh yang baik. Bayangkan Anda memiliki kelas yang disebut Foo

public class Foo
{
   public string Bar() { return "Bar"; }
}

Contoh 1 Sekarang Anda ingin memiliki koleksi objek Foo. Anda memiliki dua opsi, LIst atau ArrayList, keduanya bekerja dengan cara yang sama.

Arraylist al = new ArrayList();
List<Foo> fl = new List<Foo>();

//code to add Foos
al.Add(new Foo());
f1.Add(new Foo());

Dalam kode di atas, jika saya mencoba menambahkan kelas FireTruck, bukan Foo, ArrayList akan menambahkannya, tetapi Daftar Umum Foo akan menyebabkan pengecualian untuk dibuang.

Contoh dua.

Sekarang Anda memiliki dua daftar array dan Anda ingin memanggil fungsi Bar () pada masing-masing. Karena ArrayList hte diisi dengan Objek, Anda harus mentransmisikannya sebelum Anda dapat memanggil bilah. Tetapi karena Generic List of Foo hanya dapat berisi Foos, Anda dapat memanggil Bar () langsung pada mereka.

foreach(object o in al)
{
    Foo f = (Foo)o;
    f.Bar();
}

foreach(Foo f in fl)
{
   f.Bar();
}

5
2018-06-04 23:46



Apakah Anda pernah menulis metode (atau kelas) di mana konsep kunci dari metode / kelas tidak terikat erat dengan jenis data spesifik dari variabel parameter / contoh (pikirkan daftar yang ditautkan, fungsi maks / min, pencarian biner , dll.).

Apakah Anda tidak pernah berharap Anda dapat menggunakan kembali algorthm / kode tanpa menggunakan cut-n-paste reuse atau mengorbankan pengetikan-kuat (misalnya saya ingin List String, bukan List hal-hal yang saya berharap adalah string!)?

Itu sebabnya kamu harus ingin menggunakan generik (atau sesuatu yang lebih baik).


4
2018-06-05 00:33