JavaRush /Java blogi /Random-UZ /Java-da generiklar nazariyasi yoki qavslarni amalda qo'yi...
Viacheslav
Daraja

Java-da generiklar nazariyasi yoki qavslarni amalda qo'yish

Guruhda nashr etilgan

Kirish

JSE 5.0 dan boshlab, generiklar Java tili arsenaliga qo'shildi.
Java-da generiklar nazariyasi yoki amalda qavslarni qo'yish - 1

Java'da generiklar nima?

Jeneriklar (umumlashtirishlar) umumlashtirilgan dasturlashni amalga oshirish uchun Java tilining maxsus vositalaridir: ma'lumotlar va algoritmlarni tavsiflashning maxsus yondashuvi, bu sizga har xil turdagi ma'lumotlar bilan ularning tavsifini o'zgartirmasdan ishlash imkonini beradi. Oracle veb-saytida generiklarga alohida o'quv qo'llanma bag'ishlangan: " Dars: Generics ".

Birinchidan, generiklarni tushunish uchun nima uchun ular umuman kerakligini va ular nimani taqdim etishini tushunishingiz kerak. Bo'limidagi o'quv qo'llanmada " Nega umumiy foydalanish kerak ?" Aytilishicha, maqsadlardan biri kompilyatsiya vaqtini kuchliroq tekshirish va aniq kastingga bo'lgan ehtiyojni bartaraf etishdir.
Java-da generiklar nazariyasi yoki qavslarni amalda qo'yish - 2
Keling, sevimli darsliklarimizni tajribalar uchun onlayn java kompilyatorini tayyorlaylik . Keling, ushbu kodni tasavvur qilaylik:
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);
	}
}
Bu kod yaxshi ishlaydi. Ammo ular bizning oldimizga kelib, "Salom, dunyo!" iborasini aytishsa nima bo'ladi? kaltaklangan va siz faqat qaytishingiz mumkin Salom? Keling, koddan satr bilan bog'lanishni olib tashlaymiz ", world!". Aftidan, nima zararsizroq bo'lishi mumkin? Lekin, aslida, biz KOMPILATsiya vaqtida xatoga yo'l qo'yamiz : error: incompatible types: Object cannot be converted to String Gap shundaki, bizning holatimizda Ro'yxat Ob'ekt turidagi ob'ektlar ro'yxatini saqlaydi. String Ob'ektning avlodi bo'lganligi sababli (barcha sinflar Java'da Ob'ektdan bilvosita meros qilib olinganligi sababli), u biz bajarmagan aniq tasvirni talab qiladi. Va birlashtirganda, statik usul String.valueOf(obj) ob'ektga chaqiriladi va u oxir-oqibat Ob'ektdagi toString usulini chaqiradi. Ya'ni Ro'yxatimizda Ob'ekt mavjud. Ma'lum bo'lishicha, bizga ob'ekt emas, balki ma'lum bir tur kerak bo'lsa, biz o'zimiz turdagi kastingni bajarishimiz kerak bo'ladi:
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);
		}
	}
}
Biroq, bu holatda, chunki List ob'ektlar ro'yxatini qabul qiladi, u nafaqat Stringni, balki butun sonni ham saqlaydi. Ammo eng yomoni shundaki, bu holda kompilyator hech qanday noto'g'ri narsani ko'rmaydi. Va bu erda biz IJRO QILISHDA xatoga duch kelamiz (ular xato "Runtime" da olinganligini ham aytishadi). Xato bo'ladi: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String rozi bo'ling, eng yoqimli emas. Va bularning barchasi, chunki kompilyator sun'iy intellekt emas va u dasturchi nazarda tutgan hamma narsani taxmin qila olmaydi. Kompilyatorga qanday turlardan foydalanishimiz haqida ko'proq ma'lumot berish uchun Java SE 5 generiklarni taqdim etdi . Keling, kompilyatorga nimani xohlayotganimizni aytib, versiyamizni to'g'rilaymiz:
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);
		}
	}
}
Ko'rib turganimizdek, String uchun aktyorlar endi kerak emas. Bundan tashqari, endi bizda generiklarni ramkalash uchun burchakli qavslar mavjud. Endi biz ro'yxatga 123 qo'shimchasini olib tashlamagunimizcha, kompilyator sinfni kompilyatsiya qilishga ruxsat bermaydi, chunki bu butun son. U bizga shunday aytadi. Ko'p odamlar generiklarni "sintaktik shakar" deb atashadi. Va ular to'g'ri, chunki generiklar tuzilganda haqiqatan ham xuddi shu kastalarga aylanadi. Keling, kompilyatsiya qilingan sinflarning bayt kodini ko'rib chiqaylik: qo'lda quyish va generiklardan foydalanish:
Java-da generiklar nazariyasi yoki qavslarni amalda qo'yish - 3
Kompilyatsiyadan so'ng, generiklar haqidagi barcha ma'lumotlar o'chiriladi. Bu "Tipni o'chirish" yoki " Tipni o'chirish " deb ataladi . Turlarni o'chirish va umumiy xususiyatlar JDK ning eski versiyalari bilan orqaga qarab muvofiqlikni ta'minlash uchun mo'ljallangan, shu bilan birga kompilyatorga Java-ning yangi versiyalarida turdagi xulosalar chiqarishda yordam berishga imkon beradi.
Java-da generiklar nazariyasi yoki qavslarni amalda qo'yish - 4

Xom turlari yoki xom turlari

Jeneriklar haqida gapirganda, biz doimo ikkita toifaga egamiz: yoziladigan turlar (Umumiy turlar) va "xom" turlar (xom turlari). Xom turlar burchakli qavslarda "malakali" ko'rsatilmagan turlardir:
Java-da generiklar nazariyasi yoki qavslarni amalda qo'yish - 5
Yozilgan turlar aksincha, "aniqlash" belgisi bilan:
Java-da generiklar nazariyasi yoki qavslarni amalda qo'yish - 6
Ko'rib turganimizdek, biz skrinshotda o'q bilan belgilangan g'ayrioddiy dizayndan foydalandik. Bu Java SE 7 da qo'shilgan maxsus sintaksis bo'lib, u " olmos " deb ataladi, ya'ni olmos. Nega? Siz olmos shakli va jingalak qavslar shakli o'rtasida o'xshashlik chizishingiz mumkin: Olmos sintaksisi " Tip xulosasi " yoki turdagi xulosalar <> tushunchasi bilan ham bog'liq . Axir, kompilyator o'ngdagi <> ni ko'rib, chap tomonga qaraydi, bu erda qiymat berilgan o'zgaruvchining turi deklaratsiyasi joylashgan. Va bu qismdan u o'ngdagi qiymat qaysi turdagi terilganligini tushunadi. Aslida, agar chap tomonda umumiy belgi ko'rsatilgan bo'lsa va o'ng tomonda ko'rsatilmagan bo'lsa, kompilyator turni aniqlashi mumkin:
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);
	}
}
Biroq, bu yangi uslubning generiklar va ularsiz eski uslubning aralashmasi bo'ladi. Va bu juda istalmagan. Yuqoridagi kodni kompilyatsiya qilishda biz quyidagi xabarni olamiz: Note: HelloWorld.java uses unchecked or unsafe operations. Aslida, nega bu erda olmos qo'shish kerakligi umuman tushunarsiz ko'rinadi. Lekin bu erda bir misol:
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);
	}
}
Esda tutganimizdek, ArrayList-da to'plamni kiritish sifatida qabul qiluvchi ikkinchi konstruktor ham mavjud. Va bu erda yolg'on yotadi. Olmos sintaksisi bo'lmasa, kompilyator aldanayotganini tushunmaydi, ammo olmos bilan tushunadi. Shuning uchun, №1 qoida : agar biz terilgan turlardan foydalansak, har doim olmos sintaksisidan foydalaning. Aks holda, biz xom turini ishlatadigan joyni yo'qotish xavfi bor. Jurnalda "tekshirilmagan yoki xavfli operatsiyalarni ishlatadigan" ogohlantirishlarga yo'l qo'ymaslik uchun foydalanilayotgan usul yoki sinf bo'yicha maxsus izohni belgilashingiz mumkin: @SuppressWarnings("unchecked") Suppress bostirish, ya'ni tom ma'noda ogohlantirishlarni bostirish deb tarjima qilinadi. Ammo o'ylab ko'ring, nega buni ko'rsatishga qaror qildingiz? Birinchi qoidani eslang va siz yozishni qo'shishingiz kerak bo'lishi mumkin.
Java tilidagi generiklar nazariyasi yoki amalda qavslar qo'yish - 7

Umumiy usullar

Generics sizga usullarni kiritish imkonini beradi. Oracle qo'llanmasida ushbu xususiyatga bag'ishlangan alohida bo'lim mavjud: " Umumiy usullar ". Ushbu qo'llanmadan sintaksisni eslab qolish muhim:
  • burchakli qavslar ichida yozilgan parametrlar ro'yxatini o'z ichiga oladi;
  • terilgan parametrlar ro'yxati qaytarilgan usuldan oldin o'tadi.
Keling, bir misolni ko'rib chiqaylik:
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));
		}
    }
}
Agar siz Util sinfiga qarasangiz, unda ikkita terilgan usulni ko'ramiz. Turni aniqlash orqali biz to'g'ridan-to'g'ri kompilyatorga tur ta'rifini berishimiz yoki uni o'zimiz belgilashimiz mumkin. Ikkala variant ham misolda keltirilgan. Aytgancha, agar siz bu haqda o'ylab ko'rsangiz, sintaksis juda mantiqiy. Usulni yozayotganda, biz generic FORE usulini belgilaymiz, chunki agar biz usuldan keyin genericdan foydalansak, Java qaysi turdan foydalanishni aniqlay olmaydi. Shuning uchun, biz birinchi navbatda umumiy T dan foydalanishimizni e'lon qilamiz, keyin esa bu generikni qaytarmoqchimiz. Tabiiyki, Util.<Integer>getValue(element, String.class)u xato bilan muvaffaqiyatsiz bo'ladi incompatible types: Class<String> cannot be converted to Class<Integer>. Yozilgan usullardan foydalanganda siz har doim turni o'chirish haqida eslab qolishingiz kerak. Keling, bir misolni ko'rib chiqaylik:
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);
		}
    }
}
Bu ajoyib ishlaydi. Ammo kompilyator chaqirilgan usul Integer turiga ega ekanligini tushunsagina. Konsol chiqishini quyidagi qator bilan almashtiramiz: System.out.println(Util.getValue(element) + 1); Va biz xatoga duch kelamiz: ikkilik operator '+' uchun noto'g'ri operand turlari, birinchi turdagi: Ob'ekt , ikkinchi turdagi: int Ya'ni, turlar o'chirildi. Kompilyator hech kim turni ko'rsatmaganligini ko'radi, turi Ob'ekt sifatida ko'rsatilgan va kodning bajarilishi xato bilan bajarilmaydi.
Теория дженериков в Java or How на практике ставить скобки - 8

Umumiy turlari

Siz nafaqat usullarni, balki sinflarni ham yozishingiz mumkin. Oracle o'z qo'llanmasida bunga bag'ishlangan " Umumiy turlar " bo'limiga ega. Keling, bir misolni ko'rib chiqaylik:
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);
		}
	}
}
Bu erda hamma narsa oddiy. Agar biz sinfdan foydalansak, generic sinf nomidan keyin ro'yxatga olinadi. Keling, asosiy usulda ushbu sinfning namunasini yaratamiz:
public static void main(String []args) {
	SomeType<String> st = new SomeType<>();
	List<String> list = Arrays.asList("test");
	st.test(list);
}
Bu yaxshi ishlaydi. Kompilyator raqamlar ro'yxati va String tipidagi To'plam mavjudligini ko'radi. Ammo agar biz generiklarni o'chirib tashlasak va buni qilsak nima bo'ladi:
SomeType st = new SomeType();
List<String> list = Arrays.asList("test");
st.test(list);
Biz xatoga duch kelamiz: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer qayta o'chirishni yozing. Sinfda endi umumiy bo'lmaganligi sababli, kompilyator biz Ro'yxatni o'tkazganimizdan beri List<Integer> usuli ko'proq mos keladi, deb qaror qiladi. Va biz xato bilan yiqilib tushamiz. Shuning uchun 2-qoida: Agar sinf yozilsa, har doim genericda turni belgilang .

Cheklovlar

Biz generiklarda ko'rsatilgan turlarga cheklov qo'yishimiz mumkin. Masalan, biz konteyner faqat Raqamni kiritish sifatida qabul qilishini xohlaymiz. Ushbu xususiyat Oracle Qo'llanmasida Chegaralangan turdagi parametrlar bo'limida tasvirlangan . Keling, bir misolni ko'rib chiqaylik:
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");
    }
}
Ko'rib turganingizdek, biz umumiy turni Raqamlar sinfi/interfeysi va uning avlodlari bo'lish uchun cheklab qo'ydik. Qizig'i shundaki, siz nafaqat sinfni, balki interfeyslarni ham belgilashingiz mumkin. Misol uchun: public static class NumberContainer<T extends Number & Comparable> { Generics shuningdek, Wildcard tushunchasiga ega https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html Ular, o'z navbatida, uch turga bo'linadi: Get Put printsipi joker belgilar uchun amal qiladi . Ular quyidagi shaklda ifodalanishi mumkin:
Теория дженериков в Java or How на практике ставить скобки - 9
Ushbu tamoyil PECS (Producer Extends Consumer Super) printsipi deb ham ataladi. Habré haqida ko'proq ma'lumotni " Java API-ning qulayligini yaxshilash uchun umumiy joker belgilardan foydalanish " maqolasida , shuningdek, stackoverflow bo'yicha ajoyib muhokamada o'qishingiz mumkin: " Generics Java'da joker belgilardan foydalanish ". Java manbasidan kichik bir misol - Collections.copy usuli:
Теория дженериков в Java or How на практике ставить скобки - 10
Xo'sh, qanday qilib ishlamasligiga kichik misol:
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);
}
Ammo agar siz uzatmalarni super bilan almashtirsangiz, hammasi yaxshi bo'ladi. Ro'yxatni chiqarishdan oldin uni qiymat bilan to'ldirganimiz uchun u biz uchun iste'molchi, ya'ni iste'molchidir. Shuning uchun biz superdan foydalanamiz.

Meros olish

Jeneriklarning yana bir g'ayrioddiy xususiyati bor - ularning merosi. Jeneriklarni meros qilib olish Oracle qo'llanmasida " Generiklar, meros va kichik tiplar " bo'limida tasvirlangan . Asosiysi, quyidagilarni yodda tutish va amalga oshirishdir. Biz buni qila olmaymiz:
List<CharSequence> list1 = new ArrayList<String>();
Chunki meros generiklar bilan boshqacha ishlaydi:
Теория дженериков в Java or How на практике ставить скобки - 11
Va bu erda xato bilan muvaffaqiyatsiz bo'ladigan yana bir yaxshi misol:
List<String> list1 = new ArrayList<>();
List<Object> list2 = list1;
Bu erda ham hamma narsa oddiy. List<String> List<Object> ning avlodi emas, garchi String Ob'ektning avlodi bo'lsa.

Final

Shunday qilib, biz generiklar haqidagi xotiramizni yangiladik. Agar ular barcha kuchlarida kamdan-kam ishlatilsa, ba'zi tafsilotlar xotiradan chiqib ketadi. Umid qilamanki, bu qisqa sharh xotirangizni yangilashga yordam beradi. Va kattaroq natijalarga erishish uchun men sizga quyidagi materiallarni o'qishni tavsiya qilaman: #Viacheslav
Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION