JavaRush /Java Blog /Random-ID /Teori obat generik di Jawa atau cara mempraktekkan tanda ...
Viacheslav
Level 3

Teori obat generik di Jawa atau cara mempraktekkan tanda kurung

Dipublikasikan di grup Random-ID

Perkenalan

Dimulai dengan BEJ 5.0, obat generik ditambahkan ke gudang bahasa Java.
Teori generik di jawa atau cara mempraktekkan tanda kurung - 1

Apa obat generik di Java?

Generik (generalisasi) adalah sarana khusus bahasa Java untuk mengimplementasikan pemrograman umum: pendekatan khusus untuk mendeskripsikan data dan algoritme yang memungkinkan Anda bekerja dengan berbagai jenis data tanpa mengubah deskripsinya. Di situs web Oracle, tutorial terpisah didedikasikan untuk obat generik: “ Pelajaran: Generik ”.

Pertama, untuk memahami obat generik, Anda perlu memahami mengapa obat generik diperlukan dan apa yang disediakannya. Pada tutorial di bagian " Mengapa Menggunakan Generik ?" Dikatakan bahwa salah satu tujuannya adalah pemeriksaan tipe waktu kompilasi yang lebih kuat dan menghilangkan kebutuhan untuk casting eksplisit.
Teori generik di jawa atau cara mempraktekkan tanda kurung - 2
Mari kita persiapkan kompiler java online tutorialspoint favorit kita untuk percobaan . Mari kita bayangkan kode 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);
	}
}
Kode ini akan berjalan dengan baik. Namun bagaimana jika mereka mendatangi kita dan mengucapkan kalimat “Halo, dunia!” dikalahkan dan kamu hanya bisa kembali Halo? Mari kita hapus rangkaian dengan string dari kode ", world!". Tampaknya, apa yang lebih tidak berbahaya? Namun kenyataannya, kita akan menerima kesalahan SELAMA KOMPILASI : error: incompatible types: Object cannot be converted to String Masalahnya adalah dalam kasus kita Daftar menyimpan daftar objek bertipe Objek. Karena String adalah turunan dari Object (karena semua kelas secara implisit diwarisi dari Object di Java), maka memerlukan pemeran eksplisit, yang tidak kami lakukan. Dan ketika menggabungkan, metode statis String.valueOf(obj) akan dipanggil pada objek, yang pada akhirnya akan memanggil metode toString pada Objek. Artinya, Daftar kami berisi Objek. Ternyata ketika kita membutuhkan tipe tertentu, dan bukan Object, kita harus melakukan casting tipe 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);
		}
	}
}
Namun, dalam kasus ini, karena Daftar menerima daftar objek, ia tidak hanya menyimpan String, tetapi juga Integer. Namun yang terburuk adalah dalam hal ini kompiler tidak akan melihat ada yang salah. Dan di sini kita akan menerima kesalahan SELAMA EKSEKUSI (mereka juga mengatakan bahwa kesalahan itu diterima "saat Runtime"). Kesalahannya adalah: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String Setuju, bukan yang paling menyenangkan. Dan semua ini karena kompiler bukanlah kecerdasan buatan dan tidak dapat menebak semua yang dimaksud oleh pemrogram. Untuk memberi tahu kompiler lebih banyak tentang tipe apa yang akan kita gunakan, Java SE 5 memperkenalkan obat generik . Mari kita perbaiki versi kita dengan memberi tahu compiler apa yang kita inginkan:
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 bisa kita lihat, kita tidak lagi memerlukan cast ke String. Selain itu, kami sekarang memiliki tanda kurung sudut yang membingkai obat generik. Sekarang kompiler tidak akan mengizinkan kelas untuk dikompilasi sampai kita menghapus penambahan 123 ke dalam daftar, karena ini adalah bilangan bulat. Dia akan memberitahu kita demikian. Banyak orang menyebut obat generik sebagai "gula sintaksis". Dan mereka benar, karena obat generik memang akan menjadi kasta yang sama ketika dikompilasi. Mari kita lihat bytecode dari kelas yang dikompilasi: dengan casting manual dan menggunakan obat generik:
Teori generik di jawa atau cara mempraktekkan tanda kurung - 3
Setelah kompilasi, semua informasi tentang obat generik akan dihapus. Ini disebut "Penghapusan Tipe" atau " Penghapusan Tipe ". Penghapusan tipe dan generik dirancang untuk memberikan kompatibilitas dengan versi JDK yang lebih lama, sambil tetap memungkinkan kompiler membantu inferensi tipe di versi Java yang lebih baru.
Teori generik di jawa atau cara mempraktekkan tanda kurung - 4

Tipe Mentah atau tipe mentah

Ketika berbicara tentang obat generik, kami selalu memiliki dua kategori: tipe yang diketik (Jenis Generik) dan tipe “mentah” (Tipe Mentah). Tipe mentah adalah tipe tanpa menentukan “kualifikasi” dalam tanda kurung siku:
Teori generik di jawa atau cara mempraktekkan tanda kurung - 5
Jenis yang diketik adalah kebalikannya, dengan indikasi "klarifikasi":
Teori generik di jawa atau cara mempraktekkan tanda kurung - 6
Seperti yang bisa kita lihat, kami menggunakan desain yang tidak biasa, ditandai dengan panah di tangkapan layar. Ini adalah sintaksis khusus yang ditambahkan di Java SE 7, dan disebut " diamond ", yang artinya berlian. Mengapa? Anda dapat menggambar analogi antara bentuk wajik dan bentuk kurung kurawal: <> Sintaks wajik juga diasosiasikan dengan konsep " Type Inference ", atau inferensi tipe. Lagi pula, kompiler, melihat <> di sebelah kanan, melihat ke sisi kiri, di mana deklarasi tipe variabel yang diberi nilai berada. Dan dari bagian ini dia memahami tipe apa yang diketikkan nilai di sebelah kanan. Faktanya, jika generik ditentukan di sisi kiri dan tidak ditentukan di sisi kanan, kompiler akan dapat menyimpulkan tipenya:
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);
	}
}
Namun, ini akan menjadi campuran gaya baru dengan obat generik dan gaya lama tanpa obat generik. Dan ini sangat tidak diinginkan. Saat mengkompilasi kode di atas kita akan menerima pesan: Note: HelloWorld.java uses unchecked or unsafe operations. Faktanya, sepertinya tidak jelas mengapa kita perlu menambahkan berlian di sini. Tapi ini contohnya:
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 memiliki konstruktor kedua yang mengambil koleksi sebagai masukan. Dan disinilah letak kebohongannya. Tanpa sintaks diamond, kompiler tidak memahami bahwa ia sedang ditipu, tetapi dengan diamond ia memahaminya. Oleh karena itu, aturan #1 : selalu gunakan sintaks berlian jika kita menggunakan tipe yang diketik. Jika tidak, kami berisiko kehilangan tempat kami menggunakan tipe mentah. Untuk menghindari peringatan di log yang “menggunakan operasi yang tidak dicentang atau tidak aman” Anda dapat menentukan anotasi khusus pada metode atau kelas yang digunakan: @SuppressWarnings("unchecked") Suppress diterjemahkan sebagai penekan, yang secara harfiah berarti menyembunyikan peringatan. Namun pikirkan mengapa Anda memutuskan untuk menunjukkannya? Ingat aturan nomor satu dan mungkin Anda perlu menambahkan pengetikan.
Teori generik di jawa atau cara mempraktekkan tanda kurung - 7

Metode Generik

Generik memungkinkan Anda mengetik metode. Ada bagian terpisah yang didedikasikan untuk fitur ini dalam tutorial Oracle: “ Metode Generik ”. Dari tutorial ini, penting untuk mengingat sintaksnya:
  • menyertakan daftar parameter yang diketik di dalam tanda kurung sudut;
  • daftar parameter yang diketik diletakkan sebelum metode yang dikembalikan.
Mari kita lihat sebuah 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, kita melihat dua metode yang diketik di dalamnya. Dengan inferensi tipe, kita bisa memberikan definisi tipe langsung ke compiler, atau kita bisa menentukannya sendiri. Kedua opsi disajikan dalam contoh. Omong-omong, sintaksnya cukup logis jika Anda memikirkannya. Saat mengetik suatu metode, kita menentukan metode generik SEBELUM metode tersebut karena jika kita menggunakan metode generik setelah metode tersebut, Java tidak akan dapat mengetahui tipe mana yang akan digunakan. Oleh karena itu, pertama-tama kami mengumumkan bahwa kami akan menggunakan T generik, dan kemudian kami mengatakan bahwa kami akan mengembalikan generik ini. Secara alami, Util.<Integer>getValue(element, String.class)itu akan gagal karena kesalahan incompatible types: Class<String> cannot be converted to Class<Integer>. Saat menggunakan metode yang diketik, Anda harus selalu ingat tentang penghapusan tipe. Mari kita lihat sebuah 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 bekerja dengan baik. Tetapi hanya selama kompiler memahami bahwa metode yang dipanggil memiliki tipe Integer. Mari kita ganti keluaran konsol dengan baris berikut: System.out.println(Util.getValue(element) + 1); Dan kita mendapatkan kesalahan: tipe operan buruk untuk operator biner '+', tipe pertama: Objek, tipe kedua: int Artinya, tipe telah dihapus. Kompiler melihat bahwa tidak ada yang menentukan tipenya, tipenya ditentukan sebagai Objek dan eksekusi kode gagal karena kesalahan.
Teori generik di jawa atau cara mempraktekkan tanda kurung - 8

Tipe Generik

Anda tidak hanya dapat mengetikkan metode, tetapi juga kelas itu sendiri. Oracle memiliki bagian “ Jenis Generik ” yang didedikasikan untuk ini dalam panduan mereka. Mari kita lihat sebuah 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 sederhana di sini. Jika kita menggunakan suatu kelas, generiknya dicantumkan setelah nama kelas. Sekarang mari kita buat sebuah instance dari kelas ini dalam metode utama:
public static void main(String []args) {
	SomeType<String> st = new SomeType<>();
	List<String> list = Arrays.asList("test");
	st.test(list);
}
Ini akan bekerja dengan baik. Kompiler melihat bahwa ada Daftar angka dan Koleksi bertipe String. Namun bagaimana jika kita menghapus obat generiknya dan melakukan ini:
SomeType st = new SomeType();
List<String> list = Arrays.asList("test");
st.test(list);
Kami akan mendapatkan kesalahan: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer Ketik penghapusan lagi. Karena kelas tidak lagi memiliki generik, kompiler memutuskan bahwa sejak kita meneruskan Daftar, metode dengan Daftar<Integer> lebih tepat. Dan kita terjatuh karena sebuah kesalahan. Oleh karena itu, aturan #2: Jika suatu kelas diketik, selalu tentukan tipenya dalam generik .

Pembatasan

Kita dapat menerapkan pembatasan pada tipe yang ditentukan dalam obat generik. Misalnya, kita ingin container hanya menerima Number sebagai input. Fitur ini dijelaskan dalam Tutorial Oracle di bagian Parameter Tipe Terikat . Mari kita lihat sebuah 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 membatasi tipe generik menjadi kelas/antarmuka Number dan turunannya. Menariknya, Anda tidak hanya dapat menentukan kelas, tetapi juga antarmuka. Misalnya: public static class NumberContainer<T extends Number & Comparable> { Generik juga memiliki konsep Wildcard https://docs.Oracle.com/javase/tutorial/java/generics/wildcards.html Mereka, pada gilirannya, dibagi menjadi tiga jenis: Prinsip Get Put yang disebut berlaku untuk Wildcard . Mereka dapat dinyatakan dalam bentuk berikut:
Teori generik di jawa atau cara mempraktekkan tanda kurung - 9
Prinsip ini disebut juga dengan prinsip PECS (Producer Extends Consumer Super). Anda dapat membaca lebih lanjut tentang Habré di artikel “ Menggunakan wildcard generik untuk meningkatkan kegunaan Java API ”, serta dalam diskusi yang sangat bagus tentang stackoverflow: “ Menggunakan wildcard di Generics Java ”. Berikut ini contoh kecil dari sumber Java - metode Collections.copy:
Teori generik di jawa atau cara mempraktekkan tanda kurung - 10
Nah, contoh kecil bagaimana ini TIDAK akan berhasil:
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);
}
Namun jika Anda mengganti extends dengan super, semuanya akan baik-baik saja. Karena kita mengisi daftar dengan nilai sebelum mengeluarkannya, bagi kita itu adalah konsumen, yaitu konsumen. Oleh karena itu, kami menggunakan super.

Warisan

Ada ciri lain yang tidak biasa dari obat generik - warisannya. Pewarisan obat generik dijelaskan dalam tutorial Oracle di bagian " Generik, Warisan, dan Subtipe ". Yang utama adalah mengingat dan menyadari hal-hal berikut. Kami tidak dapat melakukan ini:
List<CharSequence> list1 = new ArrayList<String>();
Karena warisan bekerja secara berbeda dengan obat generik:
Teori generik di jawa atau cara mempraktekkan tanda kurung - 11
Dan inilah contoh bagus lainnya yang akan gagal karena kesalahan:
List<String> list1 = new ArrayList<>();
List<Object> list2 = list1;
Semuanya juga sederhana di sini. List<String> bukan merupakan turunan dari Daftar<Objek>, meskipun String merupakan turunan dari Object.

Terakhir

Jadi kami menyegarkan ingatan kami tentang obat generik. Jika mereka jarang digunakan dengan sekuat tenaga, beberapa detail akan hilang dari ingatan. Semoga ulasan singkat ini membantu menyegarkan ingatan Anda. Dan untuk hasil yang lebih baik, saya sangat menyarankan Anda membaca materi berikut ini: #Viacheslav
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION