CodeGym /Blog Java /rawak /Java Generics: cara menggunakan kurungan bersudut dalam a...
John Squirrels
Tahap
San Francisco

Java Generics: cara menggunakan kurungan bersudut dalam amalan

Diterbitkan dalam kumpulan

pengenalan

Bermula dengan JSE 5.0, generik telah ditambahkan pada senjata bahasa Java.

Apakah generik dalam java?

Generik ialah mekanisme khas Java untuk melaksanakan pengaturcaraan generik — cara untuk menerangkan data dan algoritma yang membolehkan anda bekerja dengan jenis data yang berbeza tanpa mengubah perihalan algoritma. Laman web Oracle mempunyai tutorial berasingan khusus untuk generik: " Pelajaran ". Untuk memahami generik, anda perlu mengetahui sebab ia diperlukan dan apa yang mereka berikan. Bahagian " Mengapa Gunakan Generik? " dalam tutorial mengatakan bahawa beberapa tujuan adalah pemeriksaan jenis yang lebih kuat pada masa penyusunan dan penghapusan keperluan untuk hantaran eksplisit. Generik dalam Java: cara menggunakan kurungan bersudut dalam amalan - 1Mari kita bersedia untuk beberapa ujian dalam pengkompil java dalam talian Tutorialspoint kami yang dikasihi . Katakan anda mempunyai kod berikut:

import java.util.*;
public class HelloWorld {
	public static void main(String []args) {
		List list = new ArrayList();
		list.add("Hello");
		String text = list.get(0) + ", world!";
		System.out.print(text);
	}
}
Kod ini akan berjalan dengan baik. Tetapi bagaimana jika bos datang kepada kami dan berkata bahawa "Hello, dunia!" adalah frasa yang terlalu digunakan dan anda mesti membalas hanya "Hello"? Kami akan mengalih keluar kod yang menggabungkan ", dunia!" Ini nampaknya tidak berbahaya, bukan? Tetapi kami sebenarnya mendapat ralat PADA MASA KOMPILE:

error: incompatible types: Object cannot be converted to String
Masalahnya ialah dalam Senarai kami menyimpan Objek. String ialah turunan Object (kerana semua kelas Java secara tersirat mewarisi Object ), yang bermaksud kita memerlukan pelakon eksplisit, tetapi kita tidak menambah satu. Semasa operasi penggabungan, kaedah String.valueOf(obj) statik akan dipanggil menggunakan objek. Akhirnya, ia akan memanggil kaedah toString kelas Objek . Dengan kata lain, Senarai kami mengandungi Objek . Ini bermakna di mana sahaja kita memerlukan jenis tertentu (bukan Object ), kita perlu melakukan penukaran jenis itu sendiri:

import java.util.*;
public class HelloWorld {
	public static void main(String []args) {
		List list = new ArrayList();
		list.add("Hello!");
		list.add(123);
		for (Object str : list) {
		    System.out.println("-" + (String)str);
		}
	}
}
Walau bagaimanapun, dalam kes ini, kerana Senarai mengambil objek, ia boleh menyimpan bukan sahaja String s, tetapi juga Integer s. Tetapi perkara yang paling teruk ialah pengkompil tidak melihat apa-apa yang salah di sini. Dan sekarang kita akan mendapat ralat AT RUN TIME (dikenali sebagai "ralat masa jalan"). Ralat akan menjadi:

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Anda mesti bersetuju bahawa ini tidak begitu baik. Dan semua ini kerana pengkompil bukanlah kecerdasan buatan yang mampu sentiasa meneka dengan betul niat pengaturcara. Java SE 5 memperkenalkan generik untuk membolehkan kami memberitahu pengkompil tentang niat kami — tentang jenis yang akan kami gunakan. Kami membetulkan kod kami dengan memberitahu pengkompil apa yang kami mahu:

import java.util.*;
public class HelloWorld {
	public static void main(String []args) {
		List<String> list = new ArrayList<>();
		list.add("Hello!");
		list.add(123);
		for (Object str : list) {
		    System.out.println("-" + str);
		}
	}
}
Seperti yang anda lihat, kami tidak lagi memerlukan cast ke String . Di samping itu, kami mempunyai kurungan sudut mengelilingi hujah jenis. Sekarang pengkompil tidak akan membenarkan kami menyusun kelas sehingga kami mengalih keluar baris yang menambah 123 pada senarai, kerana ini ialah Integer . Dan ia akan memberitahu kita begitu. Ramai orang memanggil generik "gula sintaksis". Dan mereka betul, kerana selepas generik disusun, mereka benar-benar menjadi jenis penukaran yang sama. Mari lihat kod bait bagi kelas yang disusun: satu yang menggunakan pelakon eksplisit dan yang menggunakan generik: Generik di Jawa: cara menggunakan kurungan bersudut dalam amalan - 2Selepas penyusunan, semua generik dipadamkan. Ini dipanggil " penghapusan jenis". Jenis pemadaman dan generik direka bentuk agar serasi ke belakang dengan versi lama JDK sambil pada masa yang sama membenarkan pengkompil membantu dengan definisi jenis dalam versi baharu Java.

Jenis mentah

Bercakap tentang generik, kami sentiasa mempunyai dua kategori: jenis parameter dan jenis mentah. Jenis mentah ialah jenis yang menghilangkan "penjelasan jenis" dalam kurungan sudut: Generik dalam Java: cara menggunakan kurungan bersudut dalam amalan - 3Jenis berparameter, di tangan, termasuk "penjelasan": Generik di Jawa: cara menggunakan kurungan bersudut dalam amalan - 4Seperti yang anda lihat, kami menggunakan binaan luar biasa, ditandakan dengan anak panah dalam tangkapan skrin. Ini adalah sintaks khas yang telah ditambahkan pada Java SE 7. Ia dipanggil " berlian ". kenapa? Tanda kurung sudut membentuk berlian: <> . Anda juga harus tahu bahawa sintaks berlian dikaitkan dengan konsep " jenis inferens ". Lagipun, pengkompil, melihat <>di sebelah kanan, lihat di sebelah kiri pengendali tugasan, di mana ia mencari jenis pembolehubah yang nilainya diberikan. Berdasarkan perkara yang ditemui dalam bahagian ini, ia memahami jenis nilai di sebelah kanan. Malah, jika jenis generik diberikan di sebelah kiri, tetapi bukan di sebelah kanan, pengkompil boleh membuat kesimpulan jenis:

import java.util.*;
public class HelloWorld {
	public static void main(String []args) {
		List<String> list = new ArrayList();
		list.add("Hello, World");
		String data = list.get(0);
		System.out.println(data);
	}
}
Tetapi ini mencampurkan gaya baharu dengan generik dan gaya lama tanpanya. Dan ini sangat tidak diingini. Apabila menyusun kod di atas, kami mendapat mesej berikut:

Note: HelloWorld.java uses unchecked or unsafe operations
Sebenarnya, sebab mengapa anda perlu menambah berlian di sini nampaknya tidak dapat difahami. Tetapi inilah contoh:

import java.util.*;
public class HelloWorld {
	public static void main(String []args) {
		List<String> list = Arrays.asList("Hello", "World");
		List<Integer> data = new ArrayList(list);
		Integer intNumber = data.get(0);
		System.out.println(data);
	}
}
Anda akan ingat bahawa ArrayList mempunyai pembina kedua yang mengambil koleksi sebagai hujah. Dan di sinilah tersembunyi sesuatu yang jahat. Tanpa sintaks berlian, pengkompil tidak memahami bahawa ia sedang ditipu. Dengan sintaks berlian, ia berfungsi. Jadi, Peraturan #1 ialah: sentiasa gunakan sintaks berlian dengan jenis parameter. Jika tidak, kami berisiko kehilangan tempat kami menggunakan jenis mentah. Untuk menghapuskan amaran "menggunakan operasi yang tidak ditanda atau tidak selamat", kami boleh menggunakan anotasi @SuppressWarnings("unchecked") pada kaedah atau kelas. Tetapi fikirkan mengapa anda memutuskan untuk menggunakannya. Ingat peraturan nombor satu. Mungkin anda perlu menambah hujah jenis.

Kaedah Generik Java

Generik membolehkan anda mencipta kaedah yang jenis parameter dan jenis pulangan diparameterkan. Bahagian berasingan dikhaskan untuk keupayaan ini dalam tutorial Oracle: " Kaedah Generik ". Adalah penting untuk mengingati sintaks yang diajar dalam tutorial ini:
  • ia termasuk senarai parameter jenis di dalam kurungan sudut;
  • senarai parameter jenis pergi sebelum jenis pulangan kaedah.
Mari lihat contoh:

import java.util.*;
public class HelloWorld {
	
    public static class Util {
        public static <T> T getValue(Object obj, Class<T> clazz) {
            return (T) obj;
        }
        public static <T> T getValue(Object obj) {
            return (T) obj;
        }
    }

    public static void main(String []args) {
		List list = Arrays.asList("Author", "Book");
		for (Object element : list) {
		    String data = Util.getValue(element, String.class);
		    System.out.println(data);
		    System.out.println(Util.<String>getValue(element));
		}
    }
}
Jika anda melihat kelas Util , anda akan melihat bahawa ia mempunyai dua kaedah generik. Terima kasih kepada kemungkinan jenis inferens, kita boleh sama ada menunjukkan jenis terus kepada pengkompil, atau kita boleh menentukannya sendiri. Kedua-dua pilihan dibentangkan dalam contoh. Ngomong-ngomong, sintaks sangat masuk akal jika anda memikirkannya. Apabila mengisytiharkan kaedah generik, kami menentukan parameter jenis SEBELUM kaedah, kerana jika kami mengisytiharkan parameter jenis selepas kaedah, JVM tidak akan dapat mengetahui jenis yang hendak digunakan. Oleh itu, kami mula-mula mengisytiharkan bahawa kami akan menggunakan parameter jenis T , dan kemudian kami mengatakan bahawa kami akan mengembalikan jenis ini. Sememangnya, Util.<Integer>getValue(elemen, String.class) akan gagal dengan ralat:jenis tidak serasi: Class<String> tidak boleh ditukar kepada Class<Integer> . Apabila menggunakan kaedah generik, anda harus sentiasa ingat jenis pemadaman. Mari lihat contoh:

import java.util.*;
public class HelloWorld {
	
    public static class Util {
        public static <T> T getValue(Object obj) {
            return (T) obj;
        }
    }

    public static void main(String []args) {
		List list = Arrays.asList(2, 3);
		for (Object element : list) {
		    System.out.println(Util.<Integer>getValue(element) + 1);
		}
    }
}
Ini akan berjalan dengan baik. Tetapi hanya selagi pengkompil memahami bahawa jenis pulangan kaedah yang dipanggil ialah Integer . Gantikan pernyataan output konsol dengan baris berikut:

System.out.println(Util.getValue(element) + 1);
Kami mendapat ralat:

bad operand types for binary operator '+', first type: Object, second type: int.
Dalam erti kata lain, pemadaman jenis telah berlaku. Pengkompil melihat bahawa tiada siapa yang menentukan jenis, jadi jenis ditunjukkan sebagai Objek dan kaedah gagal dengan ralat.

Kelas generik

Bukan sahaja kaedah boleh diparameterkan. Kelas pun boleh. Bahagian "Jenis Generik" tutorial Oracle dikhaskan untuk ini. Mari kita pertimbangkan contoh:

public static class SomeType<T> {
	public <E> void test(Collection<E> collection) {
		for (E element : collection) {
			System.out.println(element);
		}
	}
	public void test(List<Integer> collection) {
		for (Integer element : collection) {
			System.out.println(element);
		}
	}
}
Semuanya mudah di sini. Jika kita menggunakan kelas generik, parameter jenis ditunjukkan selepas nama kelas. Sekarang mari kita buat contoh kelas ini dalam kaedah utama :

public static void main(String []args) {
	SomeType<String> st = new SomeType<>();
	List<String> list = Arrays.asList("test");
	st.test(list);
}
Kod ini akan berjalan dengan baik. Pengkompil melihat bahawa terdapat Senarai nombor dan Koleksi Rentetan . Tetapi bagaimana jika kita menghapuskan parameter jenis dan melakukan ini:

SomeType st = new SomeType();
List<String> list = Arrays.asList("test");
st.test(list);
Kami mendapat ralat:

java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
Sekali lagi, ini adalah pemadaman jenis. Memandangkan kelas tidak lagi menggunakan parameter jenis, pengkompil memutuskan bahawa, kerana kami lulus List , kaedah dengan List<Integer> adalah paling sesuai. Dan kita gagal dengan kesilapan. Oleh itu, kami mempunyai Peraturan #2: Jika anda mempunyai kelas generik, sentiasa nyatakan parameter jenis.

Sekatan

Kita boleh menyekat jenis yang dinyatakan dalam kaedah generik dan kelas. Sebagai contoh, katakan kita mahu bekas menerima hanya Nombor sebagai hujah jenis. Ciri ini diterangkan dalam bahagian Parameter Jenis Terhad dalam tutorial Oracle. Mari lihat contoh:

import java.util.*;
public class HelloWorld {
	
    public static class NumberContainer<T extends Number> {
        private T number;
    
        public NumberContainer(T number) { this.number = number; }
    
        public void print() {
            System.out.println(number);
        }
    }

    public static void main(String []args) {
		NumberContainer number1 = new NumberContainer(2L);
		NumberContainer number2 = new NumberContainer(1);
		NumberContainer number3 = new NumberContainer("f");
    }
}
Seperti yang anda lihat, kami telah mengehadkan parameter jenis kepada kelas/antara muka Nombor atau keturunannya. Ambil perhatian bahawa anda boleh menentukan bukan sahaja kelas, tetapi juga antara muka. Sebagai contoh:

public static class NumberContainer<T extends Number & Comparable> {
Generik juga menyokong kad bebas Mereka dibahagikan kepada tiga jenis: Penggunaan kad bebas anda harus mematuhi prinsip Dapatkan-Put . Ia boleh dinyatakan seperti berikut:
  • Gunakan kad bebas lanjutan apabila anda hanya mendapat nilai daripada struktur.
  • Gunakan kad bebas super apabila anda hanya meletakkan nilai ke dalam struktur.
  • Dan jangan gunakan wildcard apabila anda berdua ingin mendapatkan dan meletakkan dari/ke struktur.
Prinsip ini juga dipanggil prinsip Producer Extends Consumer Super (PECS). Berikut ialah contoh kecil daripada kod sumber untuk kaedah Collections.copy Java : Generik di Jawa: cara menggunakan kurungan bersudut dalam amalan - 5Dan berikut ialah contoh kecil perkara yang TIDAK AKAN berfungsi:

public static class TestClass {
	public static void print(List<? extends String> list) {
		list.add("Hello, World!");
		System.out.println(list.get(0));
	}
}

public static void main(String []args) {
	List<String> list = new ArrayList<>();
	TestClass.print(list);
}
Tetapi jika anda menggantikan extends dengan super , maka semuanya baik-baik saja. Kerana kami mengisi senarai dengan nilai sebelum memaparkan kandungannya, ia adalah pengguna . Sehubungan itu, kami menggunakan super.

Warisan

Generik mempunyai satu lagi ciri menarik: warisan. Cara pewarisan berfungsi untuk generik diterangkan di bawah " Generik, Warisan dan Subjenis " dalam tutorial Oracle. Yang penting ingat dan kenali perkara berikut. Kami tidak boleh melakukan ini:

List<CharSequence> list1 = new ArrayList<String>();
Kerana warisan berfungsi secara berbeza dengan generik: Generik dalam Java: cara menggunakan kurungan bersudut dalam amalan - 6Dan berikut ialah satu lagi contoh bagus yang akan gagal dengan ralat:

List<String> list1 = new ArrayList<>();
List<Object> list2 = list1;
Sekali lagi, semuanya mudah di sini. List<String> bukan keturunan List<Object> , walaupun String adalah keturunan Object . Untuk mengukuhkan perkara yang anda pelajari, kami cadangkan anda menonton pelajaran video daripada Kursus Java kami

Kesimpulan

Jadi kami telah menyegarkan ingatan kami mengenai generik. Jika anda jarang memanfaatkan sepenuhnya keupayaan mereka, beberapa butiran menjadi kabur. Saya harap ulasan ringkas ini telah membantu meningkatkan ingatan anda. Untuk hasil yang lebih baik, saya amat mengesyorkan agar anda membiasakan diri dengan bahan berikut:
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION