JavaRush /Blog Java /Random-MS /Teori generik di Jawa atau cara meletakkan tanda kurung d...

Teori generik di Jawa atau cara meletakkan tanda kurung dalam amalan

Diterbitkan dalam kumpulan

pengenalan

Bermula dengan JSE 5.0, generik telah ditambahkan pada arsenal bahasa Java.
Teori generik di Jawa atau cara meletakkan tanda kurung dalam amalan - 1

Apakah generik di Jawa?

Generik (umum) ialah cara khas bahasa Java untuk melaksanakan pengaturcaraan umum: pendekatan khas untuk menerangkan data dan algoritma yang membolehkan anda bekerja dengan jenis data yang berbeza tanpa mengubah penerangannya. Di tapak web Oracle, tutorial berasingan didedikasikan untuk generik: " Pelajaran: Generik ".

Pertama, untuk memahami generik, anda perlu memahami mengapa ia diperlukan sama sekali dan apa yang mereka sediakan. Dalam tutorial dalam bahagian " Mengapa Menggunakan Generik ?" Dikatakan bahawa salah satu tujuan adalah pemeriksaan jenis masa kompilasi yang lebih kuat dan menghapuskan keperluan untuk pemutus eksplisit.
Teori generik di Jawa atau cara meletakkan tanda kurung dalam amalan - 2
Mari sediakan pengkompil java dalam talian tutorialspoint kegemaran kami untuk percubaan . Mari bayangkan kod ini:
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 mereka datang kepada kami dan berkata bahawa frasa "Hello, dunia!" dipukul dan anda hanya boleh membalas Hello? Mari kita keluarkan gabungan dengan rentetan daripada kod ", world!". Nampaknya apa yang lebih tidak berbahaya? Tetapi sebenarnya, kami akan menerima ralat SEMASA KOMPILASI : error: incompatible types: Object cannot be converted to String Perkaranya ialah dalam kes kami Senarai menyimpan senarai objek jenis Objek. Memandangkan String ialah keturunan Object (memandangkan semua kelas diwarisi secara tersirat daripada Object di Java), ia memerlukan cast eksplisit, yang tidak kami lakukan. Dan apabila menggabungkan, kaedah statik String.valueOf(obj) akan dipanggil pada objek, yang akhirnya akan memanggil kaedah toString pada Objek. Iaitu, Senarai kami mengandungi Objek. Ternyata di mana kita memerlukan jenis tertentu, dan bukan Objek, kita perlu melakukan jenis pemutus 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 menerima senarai objek, ia menyimpan bukan sahaja String, tetapi juga Integer. Tetapi perkara yang paling teruk ialah dalam kes ini pengkompil tidak akan melihat apa-apa yang salah. Dan di sini kami akan menerima ralat SEMASA PELAKSANAAN (mereka juga mengatakan bahawa ralat telah diterima "pada Runtime"). Ralatnya ialah: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String Setuju, bukan yang paling menyenangkan. Dan semua ini adalah kerana pengkompil bukan kecerdasan buatan dan ia tidak dapat meneka semua yang dimaksudkan oleh pengaturcara. Untuk memberitahu pengkompil lebih lanjut tentang jenis yang akan kami gunakan, Java SE 5 memperkenalkan generik . Mari kita betulkan versi kita dengan memberitahu pengkompil apa yang kita 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 kita lihat, kita tidak lagi memerlukan pelakon untuk String. Di samping itu, kami kini mempunyai kurungan sudut yang membingkai generik. Sekarang pengkompil tidak akan membenarkan kelas disusun sehingga kami mengalih keluar penambahan 123 pada senarai, kerana ini ialah Integer. Dia akan memberitahu kita begitu. Ramai orang memanggil generik "gula sintaksis". Dan mereka betul, kerana generik memang akan menjadi kasta yang sama apabila disusun. Mari kita lihat kod bait kelas yang disusun: dengan pemutus manual dan menggunakan generik:
Teori generik di Jawa atau cara meletakkan tanda kurung dalam amalan - 3
Selepas penyusunan, sebarang maklumat tentang generik dipadamkan. Ini dipanggil "Jenis Pemadaman" atau " Jenis Pemadaman ". Pemadaman jenis dan generik direka bentuk untuk menyediakan keserasian ke belakang dengan versi lama JDK, sementara masih membenarkan pengkompil membantu dengan inferens jenis dalam versi Java yang lebih baharu.
Teori generik di Jawa atau cara meletakkan tanda kurung dalam amalan - 4

Jenis Mentah atau jenis mentah

Apabila bercakap tentang generik, kita sentiasa mempunyai dua kategori: jenis ditaip (Jenis Generik) dan jenis "mentah" (Jenis Mentah). Jenis mentah ialah jenis tanpa menyatakan "kelayakan" dalam kurungan sudut:
Teori generik di Jawa atau cara meletakkan tanda kurung dalam amalan - 5
Jenis yang ditaip adalah sebaliknya, dengan petunjuk "penjelasan":
Teori generik di Jawa atau cara meletakkan tanda kurung dalam amalan - 6
Seperti yang kita dapat lihat, kami menggunakan reka bentuk yang luar biasa, ditandai dengan anak panah dalam tangkapan skrin. Ini ialah sintaks khas yang telah ditambahkan dalam Java SE 7, dan ia dipanggil " berlian ", yang bermaksud berlian. kenapa? Anda boleh melukis analogi antara bentuk berlian dan bentuk pendakap kerinting: <> Sintaks berlian juga dikaitkan dengan konsep " Jenis Inferens ", atau jenis inferens. Lagipun, pengkompil, melihat <> di sebelah kanan, melihat di sebelah kiri, di mana pengisytiharan jenis pembolehubah yang nilainya diberikan terletak. Dan dari bahagian ini dia memahami jenis apa yang ditaip nilai di sebelah kanan. Malah, jika generik ditentukan di sebelah kiri dan tidak dinyatakan di sebelah kanan, pengkompil akan dapat 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);
	}
}
Walau bagaimanapun, ini akan menjadi campuran gaya baharu dengan generik dan gaya lama tanpanya. Dan ini sangat tidak diingini. Apabila menyusun kod di atas kami akan menerima mesej: Note: HelloWorld.java uses unchecked or unsafe operations. Malah, nampaknya tidak jelas mengapa terdapat keperluan untuk menambah berlian di sini sama sekali. 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);
	}
}
Seperti yang kita ingat, ArrayList juga mempunyai pembina kedua yang mengambil koleksi sebagai input. Dan di sinilah letaknya penipuan. Tanpa sintaks berlian, pengkompil tidak memahami bahawa ia sedang ditipu, tetapi dengan berlian ia faham. Oleh itu, peraturan #1 : sentiasa gunakan sintaks berlian jika kita menggunakan jenis yang ditaip. Jika tidak, kami berisiko hilang tempat kami menggunakan jenis mentah. Untuk mengelakkan amaran dalam log yang "menggunakan operasi tidak bertanda atau tidak selamat", anda boleh menentukan anotasi khas pada kaedah atau kelas yang digunakan: @SuppressWarnings("unchecked") Sekat diterjemahkan sebagai penindas, iaitu, secara literal, untuk menyekat amaran. Tetapi fikirkan mengapa anda memutuskan untuk menunjukkannya? Ingat peraturan nombor satu dan mungkin anda perlu menambah menaip.
Teori generik di Jawa atau cara meletakkan tanda kurung dalam amalan - 7

Kaedah Generik

Generik membolehkan anda menaip kaedah. Terdapat bahagian berasingan khusus untuk ciri ini dalam tutorial Oracle: " Kaedah Generik ". Daripada tutorial ini, adalah penting untuk mengingati sintaks:
  • termasuk senarai parameter yang ditaip di dalam kurungan sudut;
  • senarai parameter yang ditaip pergi sebelum kaedah yang dikembalikan.
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, kami melihat dua kaedah ditaip di dalamnya. Dengan jenis inferens, kami boleh memberikan definisi jenis terus kepada pengkompil, atau kami boleh menentukannya sendiri. Kedua-dua pilihan dibentangkan dalam contoh. Dengan cara ini, sintaksnya agak logik jika anda memikirkannya. Apabila menaip kaedah, kami menentukan generik SEBELUM kaedah kerana jika kami menggunakan generik selepas kaedah, Java tidak akan dapat mengetahui jenis yang hendak digunakan. Oleh itu, kami mula-mula mengumumkan bahawa kami akan menggunakan generik T, dan kemudian kami mengatakan bahawa kami akan mengembalikan generik ini. Sememangnya, Util.<Integer>getValue(element, String.class)ia akan gagal dengan ralat incompatible types: Class<String> cannot be converted to Class<Integer>. Apabila menggunakan kaedah ditaip, anda harus sentiasa ingat tentang pemadaman jenis. 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);
		}
    }
}
Ia akan berfungsi dengan baik. Tetapi hanya selagi pengkompil memahami bahawa kaedah yang dipanggil mempunyai jenis Integer. Mari gantikan output konsol dengan baris berikut: System.out.println(Util.getValue(element) + 1); Dan kita mendapat ralat: jenis operan buruk untuk operator binari '+', jenis pertama: Objek , jenis kedua: int Iaitu, jenis telah dipadamkan. Pengkompil melihat bahawa tiada siapa yang menentukan jenisnya, jenisnya ditentukan sebagai Objek dan pelaksanaan kod gagal dengan ralat.
Теория дженериков в Java or How на практике ставить скобки - 8

Jenis Generik

Anda boleh menaip bukan sahaja kaedah, tetapi juga kelas sendiri. Oracle mempunyai bahagian " Jenis Generik " khusus untuk ini dalam panduan mereka. Mari lihat 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 disenaraikan 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);
}
Ia akan berfungsi dengan baik. Pengkompil melihat bahawa terdapat Senarai nombor dan Koleksi String jenis. Tetapi bagaimana jika kita memadamkan generik dan melakukan ini:
SomeType st = new SomeType();
List<String> list = Arrays.asList("test");
st.test(list);
Kami akan mendapat ralat: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer Taipkan pemadaman sekali lagi. Memandangkan kelas tidak lagi mempunyai generik, pengkompil memutuskan bahawa sejak kami lulus Senarai, kaedah dengan List<Integer> adalah lebih sesuai. Dan kita jatuh dengan kesilapan. Oleh itu, peraturan #2: Jika kelas ditaip, sentiasa nyatakan jenis dalam generik .

Sekatan

Kita boleh menggunakan sekatan pada jenis yang dinyatakan dalam generik. Sebagai contoh, kami mahu bekas hanya menerima Nombor sebagai input. Ciri ini diterangkan dalam Tutorial Oracle dalam bahagian Parameter Jenis Terhad . 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 jenis generik untuk menjadi kelas/antara muka Nombor dan keturunannya. Menariknya, anda boleh menentukan bukan sahaja kelas, tetapi juga antara muka. Sebagai contoh: public static class NumberContainer<T extends Number & Comparable> { Generik juga mempunyai konsep Wildcard https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html Mereka pula dibahagikan kepada tiga jenis: Prinsip Get Put yang dipanggil digunakan untuk Wildcards . Mereka boleh dinyatakan dalam bentuk berikut:
Теория дженериков в Java or How на практике ставить скобки - 9
Prinsip ini juga dipanggil prinsip PECS (Producer Extends Consumer Super). Anda boleh membaca lebih lanjut tentang Habré dalam artikel " Menggunakan kad bebas generik untuk meningkatkan kebolehgunaan API Java ", serta dalam perbincangan yang sangat baik tentang stackoverflow: " Menggunakan kad bebas dalam Java Generik ". Berikut ialah contoh kecil daripada sumber Java - kaedah Collections.copy:
Теория дженериков в Java or How на практике ставить скобки - 10
Nah, contoh kecil bagaimana ia 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 extend dengan super, semuanya akan baik-baik saja. Oleh kerana kami mengisi senarai dengan nilai sebelum mengeluarkannya, ia adalah pengguna untuk kami, iaitu pengguna. Oleh itu, kami menggunakan super.

Warisan

Terdapat satu lagi ciri generik yang luar biasa - warisan mereka. Warisan generik diterangkan dalam tutorial Oracle dalam bahagian " Generik, Warisan dan Subjenis ". Perkara utama adalah untuk mengingati dan menyedari perkara berikut. Kami tidak boleh melakukan ini:
List<CharSequence> list1 = new ArrayList<String>();
Kerana warisan berfungsi secara berbeza dengan generik:
Теория дженериков в Java or How на практике ставить скобки - 11
Dan inilah satu lagi contoh yang baik yang akan gagal dengan ralat:
List<String> list1 = new ArrayList<>();
List<Object> list2 = list1;
Semuanya mudah di sini juga. List<String> bukan keturunan List<Object>, walaupun String ialah keturunan Object.

Akhir

Jadi kami menyegarkan ingatan kami tentang generik. Jika mereka jarang digunakan dalam semua kuasa mereka, beberapa butiran hilang dari ingatan. Saya harap ulasan ringkas ini membantu menyegarkan ingatan anda. Dan untuk hasil yang lebih baik, saya amat mengesyorkan agar anda membiasakan diri dengan bahan berikut: #Viacheslav
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION