JavaRush /Java Blogu /Random-AZ /Java-da generiklər nəzəriyyəsi və ya praktikada mötərizəl...
Viacheslav
Səviyyə

Java-da generiklər nəzəriyyəsi və ya praktikada mötərizələrin necə qoyulması

Qrupda dərc edilmişdir

Giriş

JSE 5.0-dan başlayaraq, generiklər Java dili arsenalına əlavə edildi.
Java-da generiklər nəzəriyyəsi və ya praktikada mötərizələrin necə qoyulması - 1

Java-da generiklər nədir?

Generics (ümumiləşdirmələr) ümumiləşdirilmiş proqramlaşdırmanın həyata keçirilməsi üçün Java dilinin xüsusi vasitələridir: məlumatların və alqoritmlərin təsvirinə xüsusi yanaşma, müxtəlif növ məlumatlarla onların təsvirini dəyişdirmədən işləməyə imkan verir. Oracle veb-saytında generiklərə ayrıca dərslik həsr olunub: “ Dərs: Generiklər ”.

Birincisi, generikləri başa düşmək üçün onların ümumiyyətlə niyə lazım olduğunu və nə təmin etdiyini başa düşməlisiniz. " Niyə generiklərdən istifadə etməlisiniz ?" bölməsindəki təlimatda. Məqsədlərdən birinin daha güclü kompilyasiya vaxtının yoxlanılması və açıq tökmə ehtiyacının aradan qaldırılması olduğu deyilir.
Java-da generiklər nəzəriyyəsi və ya praktikada mötərizələrin necə qoyulması - 2
Təcrübələr üçün sevimli tutorialspoint onlayn java kompilyatorumuzu hazırlayaq . Bu kodu təsəvvür edək:
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 yaxşı işləyəcək. Bəs onlar bizə gəlib desələr ki, “Salam, dünya!” döyüldü və yalnız qayıda bilərsən Salam? Gəlin koddan sətir ilə birləşməni çıxaraq ", world!". Belə görünür ki, daha zərərsiz nə ola bilər? Amma əslində biz TƏRƏB ETMƏ ZAMANI xəta alacağıq : error: incompatible types: Object cannot be converted to String İş ondadır ki, bizim vəziyyətimizdə List Object tipli obyektlərin siyahısını saxlayır. String Obyektin nəslindən olduğu üçün (bütün siniflər Java-da Obyektdən dolayı miras alındığı üçün) o, açıq-aydın cast tələb edir, biz bunu etmədik. Və birləşdirildikdə obyektdə String.valueOf(obj) statik metodu çağırılacaq, bu da son nəticədə Obyektdə toString metodunu çağıracaq. Yəni Siyahımızda Obyekt var. Belə çıxır ki, Obyektə deyil, konkret tipə ehtiyac duyduğumuz yerdə biz özümüz tip tökmə etməli olacağıq:
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);
		}
	}
}
Lakin, bu halda, çünki List obyektlərin siyahısını qəbul edir, yalnız String deyil, həm də Tam ədədi saxlayır. Amma ən pisi odur ki, bu halda kompilyator heç nəyi səhv görməyəcək. Və burada icra zamanı xəta alacağıq (onlar da deyirlər ki, xəta “İş zamanı” alındı). Səhv olacaq: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String Razılaşın, ən xoş deyil. Bütün bunlar ona görədir ki, kompilyator süni intellekt deyil və proqramçının demək istədiyi hər şeyi təxmin edə bilmir. Hansı növlərdən istifadə edəcəyimizi kompilyatora daha çox izah etmək üçün Java SE 5 generikləri təqdim etdi . Tərtibçiyə istədiyimizi söyləyərək versiyamızı düzəldək:
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);
		}
	}
}
Gördüyümüz kimi, String-in heyətinə artıq ehtiyacımız yoxdur. Bundan əlavə, indi bizdə generikləri çərçivəyə salan bucaqlı mötərizələr var. İndi biz siyahıya 123 əlavəsini silməyincə kompilyator sinfin tərtib edilməsinə icazə verməyəcək, çünki bu Tam ədəddir. Bizə belə deyəcək. Bir çox insanlar generikləri "sintaktik şəkər" adlandırırlar. Və onlar haqlıdırlar, çünki generiklər tərtib edildikdə həqiqətən eyni kastalara çevriləcəkdir. Gəlin tərtib edilmiş siniflərin bayt koduna baxaq: əl ilə tökmə və generiklərdən istifadə etməklə:
Java-da generiklər nəzəriyyəsi və ya praktikada mötərizələrin necə qoyulması - 3
Tərtib edildikdən sonra generiklər haqqında hər hansı məlumat silinir. Bu, "Növün Silinməsi" və ya " Növün Silinməsi " adlanır . Növlərin silinməsi və generiklər JDK-nın köhnə versiyaları ilə geriyə uyğunluğu təmin etmək üçün nəzərdə tutulmuşdur, eyni zamanda tərtibçiyə Java-nın daha yeni versiyalarında tip nəticəsinə kömək etmək imkanı verir.
Java-da generiklər nəzəriyyəsi və ya praktikada mötərizələrin necə qoyulması - 4

Xam növlər və ya xam növlər

Generiklər haqqında danışarkən, biz həmişə iki kateqoriyaya sahibik: tipli tiplər (Ümumi Tiplər) və "xam" növlər (Raw Tiplər). Xam növlər bucaqlı mötərizədə “ixtisas” göstərilməyən növlərdir:
Java-da generiklər nəzəriyyəsi və ya praktikada mötərizələrin necə qoyulması - 5
Yazılan növlər "aydınlaşdırma" işarəsi ilə əksinədir:
Java-da generiklər nəzəriyyəsi və ya praktikada mötərizələrin necə qoyulması - 6
Gördüyümüz kimi, ekran görüntüsündə ox ilə işarələnmiş qeyri-adi dizayndan istifadə etdik. Bu, Java SE 7-də əlavə edilmiş xüsusi sintaksisdir və o, almaz mənasını verən " almaz " adlanır. Niyə? Siz brilyantın forması ilə qıvrımlı mötərizələrin forması arasında bənzətmə çəkə bilərsiniz: Almaz sintaksisi həm də " Növ haqqında nəticə çıxarma<> " anlayışı ilə əlaqələndirilir . Axı, sağda <> görən kompilyator, dəyərin təyin olunduğu dəyişənin növünün elanının yerləşdiyi sol tərəfə baxır. Və bu hissədən o, sağdakı dəyərin hansı tipdə yazıldığını başa düşür. Əslində, sol tərəfdə generik göstərilibsə və sağ tərəfdə göstərilməyibsə, kompilyator növü nəticə çıxara biləcək:
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);
	}
}
Bununla belə, bu, yeni üslubun generiklərlə və onlarsız köhnə üslubun qarışığı olardı. Və bu son dərəcə arzuolunmazdır. Yuxarıdakı kodu tərtib edərkən biz mesaj alacağıq: Note: HelloWorld.java uses unchecked or unsafe operations. Əslində, ümumiyyətlə, burada almaz əlavə etməyə nə üçün ehtiyac olduğu aydın deyil. Ancaq burada bir nümunə var:
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);
	}
}
Xatırladığımız kimi, ArrayList-də kolleksiyanı giriş kimi qəbul edən ikinci konstruktor da var. Və burada hiylə var. Almaz sintaksisi olmadan tərtibçi aldandığını başa düşmür, almazla isə başa düşür. Buna görə də, qayda №1 : yazı tiplərindən istifadə etsək, həmişə almaz sintaksisindən istifadə edin. Əks halda, xam tipdən istifadə etdiyimiz yeri itirmək riskimiz var. Jurnalda “yoxlanılmamış və ya təhlükəli əməliyyatlardan istifadə edən” xəbərdarlığın qarşısını almaq üçün istifadə olunan metod və ya sinif üzrə xüsusi annotasiya qeyd edə bilərsiniz: @SuppressWarnings("unchecked") Suppress bastırmaq kimi tərcümə olunur, yəni hərfi mənada xəbərdarlıqları basdırmaq üçün. Ancaq düşünün, niyə bunu göstərmək qərarına gəldiniz? Bir nömrəli qaydanı xatırlayın və bəlkə də yazı əlavə etməlisiniz.
Java-da generiklər nəzəriyyəsi və ya praktikada mötərizələrin necə qoyulması - 7

Ümumi üsullar

Generiklər metodları yazmağa imkan verir. Oracle təlimatında bu xüsusiyyətə həsr olunmuş ayrıca bölmə var: “ Ümumi Metodlar ”. Bu dərslikdən sintaksisi yadda saxlamaq vacibdir:
  • bucaqlı mötərizədə yazılmış parametrlərin siyahısını ehtiva edir;
  • yazılan parametrlərin siyahısı qaytarılan metoddan əvvəl gedir.
Bir misala baxaq:
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));
		}
    }
}
Util sinfinə baxsanız, onda iki tipli metod görürük. Növ haqqında nəticə çıxarmaqla biz tip tərifini birbaşa kompilyatora verə bilərik və ya onu özümüz təyin edə bilərik. Hər iki variant nümunədə təqdim olunur. Yeri gəlmişkən, bu barədə düşünsəniz, sintaksis olduqca məntiqlidir. Metod yazarkən biz metoddan ƏVVƏL ümumiliyi təyin edirik, çünki metoddan sonra genericdən istifadə etsək, Java hansı növü istifadə edəcəyini anlaya bilməyəcək. Ona görə də biz əvvəlcə ümumi T-dən istifadə edəcəyimizi elan edirik, sonra isə bu ümumini geri qaytaracağımızı deyirik. Təbii ki, Util.<Integer>getValue(element, String.class)bir səhvlə uğursuz olacaq incompatible types: Class<String> cannot be converted to Class<Integer>. Yazılı üsullardan istifadə edərkən, tipin silinməsini həmişə xatırlamalısınız. Bir misala baxaq:
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);
		}
    }
}
Əla işləyəcək. Ancaq kompilyator çağırılan metodun Tam ədədə malik olduğunu başa düşdüyü müddətcə. Konsol çıxışını aşağıdakı sətirlə əvəz edək: System.out.println(Util.getValue(element) + 1); Və biz xəta alırıq: ikili operator '+' üçün pis operand tipləri, birinci tip: Obyekt , ikinci tip: int Yəni tiplər silindi. Kompilyator görür ki, heç kim növü təyin etməyib, tip Obyekt kimi göstərilib və kodun icrası xəta ilə uğursuz olur.
Java-da generiklər nəzəriyyəsi və ya praktikada mötərizələrin necə qoyulması - 8

Ümumi növlər

Siz yalnız metodları deyil, həm də siniflərin özlərini yaza bilərsiniz. Oracle-ın təlimatında buna həsr olunmuş “ Ümumi növlər ” bölməsi var. Bir misala baxaq:
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);
		}
	}
}
Burada hər şey sadədir. Bir sinifdən istifadə etsək, ümumi sinif adından sonra qeyd olunur. İndi əsas metodda bu sinfin nümunəsini yaradaq:
public static void main(String []args) {
	SomeType<String> st = new SomeType<>();
	List<String> list = Arrays.asList("test");
	st.test(list);
}
Yaxşı işləyəcək. Kompilyator görür ki, nömrələr siyahısı və String tipli Kolleksiya var. Bəs generikləri silib bunu etsək nə olar:
SomeType st = new SomeType();
List<String> list = Arrays.asList("test");
st.test(list);
Səhv alacağıq: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer Yenidən silmə yazın. Sinfin artıq ümumi olmadığına görə, tərtibçi qərar verir ki, biz Listdən keçdiyimiz üçün List<Integer> ilə metod daha uyğundur. Və bir səhvlə yıxılırıq. Buna görə də, qayda №2: Əgər sinif yazılacaqsa, həmişə tipini generic-də göstərin .

Məhdudiyyətlər

Biz generiklərdə göstərilən növlərə məhdudiyyət tətbiq edə bilərik. Məsələn, konteynerin yalnız Nömrəni giriş kimi qəbul etməsini istəyirik. Bu xüsusiyyət Oracle Təlimində Məhdudlaşdırılmış Tip Parametrləri bölməsində təsvir edilmişdir . Bir misala baxaq:
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");
    }
}
Gördüyünüz kimi, biz ümumi növü Nömrə sinfi/interfeysi və onun törəmələri kimi məhdudlaşdırdıq. Maraqlıdır ki, siz yalnız sinfi deyil, həm də interfeysləri təyin edə bilərsiniz. Məsələn: public static class NumberContainer<T extends Number & Comparable> { Generics də Wildcard anlayışına malikdir https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html Onlar da öz növbəsində üç növə bölünür: Get Put prinsipi Joker simvollara aiddir . Onlar aşağıdakı formada ifadə edilə bilər:
Java-da generiklər nəzəriyyəsi və ya praktikada mötərizələrin necə qoyulması - 9
Bu prinsipə PECS (Producer Extends Consumer Super) prinsipi də deyilir. Habré haqqında daha ətraflı “ Java API-nin istifadəsini yaxşılaşdırmaq üçün ümumi joker işarələrdən istifadə ” məqaləsində , həmçinin stackoverflow haqqında əla müzakirədə oxuya bilərsiniz: “ Generics Java-da joker işarələrin istifadəsi ”. Java mənbəyindən kiçik bir nümunə - Collections.copy metodu:
Java-da generiklər nəzəriyyəsi və ya praktikada mötərizələrin necə qoyulması - 10
Yaxşı, bunun necə işləməyəcəyinə dair kiçik bir nümunə:
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);
}
Amma uzantıları super ilə əvəz etsəniz, hər şey yaxşı olacaq. Siyahını çıxarmazdan əvvəl onu dəyərlə doldurduğumuz üçün o, bizim üçün istehlakçı, yəni istehlakçıdır. Ona görə də super istifadə edirik.

Miras

Jeneriklərin başqa bir qeyri-adi xüsusiyyəti var - onların irsiyyəti. Generiklərin varisliyi Oracle təlimatında " Generics, miras və alt növlər " bölməsində təsvir edilmişdir . Əsas odur ki, aşağıdakıları yadda saxlamaq və həyata keçirməkdir. Bunu edə bilmərik:
List<CharSequence> list1 = new ArrayList<String>();
Çünki miras generiklərlə fərqli işləyir:
Java-da generiklər nəzəriyyəsi və ya praktikada mötərizələrin necə qoyulması - 11
Və burada bir səhv ilə uğursuz olacaq başqa bir yaxşı nümunə var:
List<String> list1 = new ArrayList<>();
List<Object> list2 = list1;
Burada da hər şey sadədir. List<String> List<Object>-in nəsli deyil, baxmayaraq ki, String Obyektin nəslindəndir.

Final

Beləliklə, biz generiklər haqqında yaddaşımızı təzələdik. Nadir hallarda bütün gücü ilə istifadə olunursa, bəzi detallar yaddaşdan çıxır. Ümid edirəm ki, bu qısa baxış yaddaşınızı yeniləməyə kömək edir. Daha böyük nəticələr üçün aşağıdakı materialları oxumağınızı şiddətlə tövsiyə edirəm: #Viaçeslav
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION