JavaRush /Java Blogu /Random-AZ /Pişiklər üçün generiklər
Viacheslav
Səviyyə

Pişiklər üçün generiklər

Qrupda dərc edilmişdir
Pişiklər üçün generiklər - 1

Giriş

Bu gün Java haqqında bildiklərimizi xatırlamaq üçün əla gündür. Ən mühüm sənədə görə, yəni. Java Language Specification (JLS - Java Language Specifiaction), Java " Fəsil 4. Növlər, Dəyərlər və Dəyişənlər " fəslində təsvir olunduğu kimi güclü tipli bir dildir . Bu nə deməkdir? Tutaq ki, bizim əsas metodumuz var:
public static void main(String[] args) {
String text = "Hello world!";
System.out.println(text);
}
Güclü yazma təmin edir ki, bu kod tərtib edildikdə, tərtibçi yoxlayacaq ki, əgər mətn dəyişəninin növünü String kimi göstərmişiksə, onda biz onu heç bir yerdə başqa tipli dəyişən kimi (məsələn, Tam ədəd kimi) istifadə etməyə çalışmırıq. . Məsələn, mətn əvəzinə dəyər saxlamağa çalışsaq 2L(yəni String əvəzinə uzun), tərtib zamanı xəta alacağıq:

Main.java:3: error: incompatible types: long cannot be converted to String
String text = 2L;
Bunlar. Güclü yazma sizə obyektlər üzərində əməliyyatların yalnız həmin əməliyyatlar həmin obyektlər üçün qanuni olduqda yerinə yetirilməsini təmin etməyə imkan verir. Buna tip təhlükəsizliyi də deyilir. JLS-də qeyd edildiyi kimi, Java-da iki növ tip var: primitiv tiplər və istinad tipləri. İcmal məqaləsindən ibtidai tiplər haqqında xatırlaya bilərsiniz: " Java-da ibtidai tiplər: Onlar o qədər də primitiv deyillər ." İstinad növləri sinif, interfeys və ya massiv ilə təmsil oluna bilər. Və bu gün biz istinad növləri ilə maraqlanacağıq. Və massivlərdən başlayaq:
class Main {
  public static void main(String[] args) {
    String[] text = new String[5];
    text[0] = "Hello";
  }
}
Bu kod səhvsiz işləyir. Bildiyimiz kimi (məsələn, " Oracle Java Tutorial: Massivlər " dən) massiv yalnız bir növ məlumatı saxlayan konteynerdir. Bu vəziyyətdə - yalnız xətlər. Gəlin seriala String əvəzinə long əlavə etməyə çalışaq:
text[1] = 4L;
Gəlin bu kodu işə salaq (məsələn, Repl.it Online Java Compiler- də ) və xəta alırıq:
error: incompatible types: long cannot be converted to String
Dilin massivi və tip təhlükəsizliyi bizə tipə uyğun olmayanı massivdə saxlamağa imkan vermədi. Bu tip təhlükəsizliyin təzahürüdür. Bizə dedilər: "Xətanı düzəldin, amma o vaxta qədər kodu tərtib etməyəcəyəm." Və bununla bağlı ən vacib şey odur ki, bu proqram işə salındıqda deyil, kompilyasiya zamanı baş verir. Yəni səhvləri "bir gün" deyil, dərhal görürük. Massivlər haqqında xatırladığımız üçün Java Collections Framework haqqında da xatırlayaq . Orada bizim müxtəlif strukturlarımız var idi. Məsələn, siyahılar. Nümunəni yenidən yazaq:
import java.util.*;
class Main {
  public static void main(String[] args) {
    List text = new ArrayList(5);
    text.add("Hello");
    text.add(4L);
    String test = text.get(0);
  }
}
testOnu tərtib edərkən dəyişənin başlanğıc xəttində xəta alacağıq :
incompatible types: Object cannot be converted to String
Bizim vəziyyətimizdə List istənilən obyekti (yəni Object tipli obyekt) saxlaya bilər. Ona görə də tərtibçi deyir ki, belə bir məsuliyyət yükünü üzərinə götürə bilməz. Buna görə də, siyahıdan alacağımız növü açıq şəkildə göstərməliyik:
String test = (String) text.get(0);
Bu göstərici növün çevrilməsi və ya növün ötürülməsi adlanır. İndi indeks 1-də elementi almağa cəhd edənə qədər hər şey yaxşı işləyəcək, çünki Uzun tipdir. Və ədalətli bir səhv alacağıq, lakin artıq proqram işləyərkən (Runtime-da):

type conversion, typecasting
Exception in thread "main" java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.String
Gördüyümüz kimi, burada bir sıra mühüm çatışmazlıqlar var. Birincisi, biz siyahıdan əldə edilən dəyəri String sinfinə “yatırmaq” məcburiyyətindəyik. Razılaşın, bu çirkindir. İkincisi, səhv olarsa, biz bunu yalnız proqram icra edildikdə görəcəyik. Əgər kodumuz daha mürəkkəb olsaydı, belə bir xətanı dərhal aşkar edə bilməzdik. Və tərtibatçılar belə vəziyyətlərdə işi necə asanlaşdırmaq və kodu daha aydın etmək barədə düşünməyə başladılar. Və onlar doğuldular - Generics.
Pişiklər üçün generiklər - 2

Generiklər

Beləliklə, generiklər. Bu nədir? Ümumi, istifadə olunan növləri təsvir etmək üçün xüsusi bir üsuldur, kod tərtibçisi növün təhlükəsizliyini təmin etmək üçün işində istifadə edə bilər. Bu kimi bir şey görünür:
Pişiklər üçün generiklər - 3
Qısa bir nümunə və izahat budur:
import java.util.*;
class Main {
  public static void main(String[] args) {
    List<String> text = new ArrayList<String>(5);
    text.add("Hello");
    text.add(4L);
    String test = text.get(1);
  }
}
Bu misalda biz deyirik ki, bizdə yalnız List, lakin List, yalnız String tipli obyektlərlə işləyir. Və başqaları yox. Mötərizədə nə göstərilibsə, biz onu saxlaya bilərik. Belə "mötərizələr" "bucaqlı mötərizələr" adlanır, yəni. bucaqlı mötərizələr. Tərtibatçı sətirlərin siyahısı ilə işləyərkən (siyahı mətn adlanır) hər hansı səhvlərə yol verib-vermədiyimizi yoxlayacaq. Tərtibatçı görəcək ki, biz həyasızcasına Long-u String siyahısına daxil etməyə çalışırıq. Və tərtib zamanı bir səhv verəcəkdir:
error: no suitable method found for add(long)
Siz String-in CharSequence-in nəslindən olduğunu xatırlamısınız. Və belə bir şey etməyə qərar verin:
public static void main(String[] args) {
	ArrayList<CharSequence> text = new ArrayList<String>(5);
	text.add("Hello");
	String test = text.get(0);
}
Ancaq bu mümkün deyil və biz səhv alacağıq: error: incompatible types: ArrayList<String> cannot be converted to ArrayList<CharSequence> Qəribə görünür, çünki. sətirdə CharSequence sec = "test";heç bir səhv yoxdur. Gəlin bunu anlayaq. Bu davranış haqqında deyirlər: "Generiklər dəyişməzdir." "İnvariant" nədir? Bu barədə Vikipediyada “ Kvariantlıq və ziddiyyət ” məqaləsində deyilənləri bəyənirəm :
Pişiklər üçün generiklər - 4
Beləliklə, İnvariantlıq törəmə növlər arasında irsiyyətin olmamasıdır. Əgər Pişik Heyvanların alt növüdürsə, onda Set<Pişiklər> Set<Heyvanlar> və Set<Heyvanlar> Set<Pişiklər>-in alt növü deyil. Yeri gəlmişkən, Java SE 7-dən başlayaraq " Almaz operatoru " meydana gəldiyini söyləməyə dəyər . Çünki iki bucaqlı mötərizə <> almaz kimidir. Bu, bizə bu kimi generiklərdən istifadə etməyə imkan verir:
public static void main(String[] args) {
  List<String> lines = new ArrayList<>();
  lines.add("Hello world!");
  System.out.println(lines);
}
Bu kod əsasında kompilyator başa düşür ki, əgər sol tərəfdə onun String tipli obyektlərdən ibarət olacağını göstərmişiksə , sağ tərəfdə biz yeni ArrayList-i dəyişənə saxlamaq Lististədiyimizi bildiririk ki , bu da obyekti saxlayacaq. linessol tərəfdə göstərilən növdən. Beləliklə, sol tərəfdəki tərtibçi sağ tərəf üçün tipi başa düşür və ya nəticə çıxarır. Məhz buna görə də bu davranışa ingiliscə tipli nəticə və ya “Tip çıxarışı” deyilir. Diqqətə layiq başqa bir maraqlı şey RAW növləri və ya “xam növlər”dir. Çünki Jeneriklər həmişə mövcud olmayıb və Java mümkün olduqda geriyə uyğunluğu qorumağa çalışır, sonra generiklər heç bir generik göstərilməyən kodla birtəhər işləməyə məcbur olurlar. Bir nümunəyə baxaq:
List<CharSequence> lines = new ArrayList<String>();
Xatırladığımız kimi, belə bir xətt generiklərin dəyişməzliyinə görə tərtib edilməyəcəkdir.
List<Object> lines = new ArrayList<String>();
Və bu da eyni səbəbdən tərtib edilməyəcək.
List lines = new ArrayList<String>();
List<String> lines2 = new ArrayList();
Belə xətlər tərtib edəcək və işləyəcək. Məhz onlarda Raw Tipləri istifadə olunur, yəni. qeyri-müəyyən növlər. Bir daha qeyd etmək lazımdır ki, Raw Tipləri müasir kodda istifadə edilməməlidir.
Pişiklər üçün generiklər - 5

Yazılan siniflər

Beləliklə, tipli siniflər. Gəlin görək öz çap olunmuş sinfimizi necə yaza bilərik. Məsələn, bizdə bir sinif iyerarxiyası var:
public static abstract class Animal {
  public abstract void voice();
}

public static class Cat extends Animal {
  public void voice(){
    System.out.println("Meow meow");
  }
}

public static class Dog extends Animal {
  public void voice(){
    System.out.println("Woof woof");
  }
}
Biz heyvan konteynerini həyata keçirən bir sinif yaratmaq istəyirik. Hər hansı bir sinfi ehtiva edən bir sinif yazmaq mümkün olardı Animal. Bu sadədir, başa düşüləndir, AMMA... it-pişiyi qarışdırmaq pisdir, onlar bir-biri ilə dost deyillər. Bundan əlavə, kimsə belə bir qab alırsa, səhvən pişikləri konteynerdən itlər sürüsünə ata bilər... və bu heç bir yaxşılığa səbəb olmayacaq. Və burada generiklər bizə kömək edəcək. Məsələn, tətbiqi belə yazaq:
public static class Box<T> {
  List<T> slots = new ArrayList<>();
  public List<T> getSlots() {
    return slots;
  }
}
Sinifimiz T adlı generik tərəfindən təyin olunan tipli obyektlərlə işləyəcək. Bu, bir növ ləqəbdir. Çünki Ümumi sinif adında göstərilmişdir, sonra sinfi elan edərkən onu alacağıq:
public static void main(String[] args) {
  Box<Cat> catBox = new Box<>();
  Cat murzik = new Cat();
  catBox.getSlots().add(murzik);
}
Gördüyümüz kimi, bizdə Boxyalnız ilə işləyən olduğunu bildirdik Cat. Kompilyator başa düşdü ki, catBoxgeneric əvəzinə, ümuminin adı göstərildiyi yerdə Tnövü əvəz etməlisiniz : CatT
Pişiklər üçün generiklər - 6
Bunlar. Box<Cat>kompilyator sayəsində slotsəslində nə olması lazım olduğunu başa düşür List<Cat>. Çünki içəridə , ehtiva edən Box<Dog>olacaq . Tip bəyannaməsində bir neçə generik ola bilər, məsələn: slotsList<Dog>
public static class Box<T, V> {
Ümuminin adı hər hansı bir şey ola bilər, baxmayaraq ki, bəzi açıqlanmayan qaydalara riayət etmək tövsiyə olunur - "Növ Parametrlərinin Adlandırılması Konvensiyaları": Element növü - E, açar növü - K, nömrə növü - N, T - növ üçün, V - üçün dəyər növü. Yeri gəlmişkən, generiklərin invariant olduğunu dediyimizi xatırlayın, yəni. miras iyerarxiyasını saxlamayın. Əslində biz buna təsir edə bilərik. Yəni generikləri COvariant etmək imkanımız var, yəni. mirasın eyni qaydada saxlanılması. Bu davranış "Bounded Type" adlanır, yəni. məhdud növlər. Məsələn, sinifimiz Boxbütün heyvanları ehtiva edə bilər, onda biz belə bir ümumi elan edəcəyik:
public static class Box<T extends Animal> {
Yəni sinifə yuxarı hədd qoymuşuq Animal. Açar sözdən sonra bir neçə növ də təyin edə bilərik extends. Bu o demək olacaq ki, işləyəcəyimiz tip hansısa sinfin nəslindən olmalı və eyni zamanda hansısa interfeysi həyata keçirməlidir. Misal üçün:
public static class Box<T extends Animal & Comparable> {
BoxBu halda, varis olmayan Animalvə həyata keçirməyən bir şey qoymağa çalışsaq Comparable, kompilyasiya zamanı səhv alacağıq:
error: type argument Cat is not within bounds of type-variable T
Pişiklər üçün generiklər - 7

Metod Yazma

Generiklər yalnız növlərdə deyil, həm də fərdi üsullarda istifadə olunur. Metodların tətbiqini rəsmi təlimatda görmək olar: " Generics Methods ".

Fon:

Pişiklər üçün generiklər - 8
Gəlin bu şəkilə baxaq. Gördüyünüz kimi, kompilyator metod imzasına baxır və görür ki, biz müəyyən edilməmiş sinifləri giriş kimi qəbul edirik. İmza ilə müəyyən etmir ki, biz bir növ obyekti qaytarırıq, yəni. Obyekt. Buna görə də, məsələn, ArrayList yaratmaq istəyiriksə, bunu etməliyik:
ArrayList<String> object = (ArrayList<String>) createObject(ArrayList.class);
Çıxışın ArrayList olacağını açıq şəkildə yazmalısınız ki, bu da çirkindir və səhv etmək şansını artırır. Məsələn, belə cəfəngiyyatlar yaza bilərik və o, tərtib edəcək:
ArrayList object = (ArrayList) createObject(LinkedList.class);
Kompilyatora kömək edə bilərikmi? Bəli, generiklər bunu etməyə imkan verir. Eyni misala baxaq:
Pişiklər üçün generiklər - 9
Sonra, sadəcə olaraq belə bir obyekt yarada bilərik:
ArrayList<String> object = createObject(ArrayList.class);
Pişiklər üçün generiklər - 10

Wild Card

Oracle-ın Generics Dərsliyinə, xüsusən də " Jokerlər " bölməsinə əsasən , biz sual işarəsi ilə "naməlum növü" təsvir edə bilərik. Wildcard generiklərin bəzi məhdudiyyətlərini azaltmaq üçün lazımlı bir vasitədir. Məsələn, əvvəllər müzakirə etdiyimiz kimi, generiklər dəyişməzdir. Bu o deməkdir ki, bütün siniflər Obyekt növünün nəsli (alt tipləri) olsa da, o, List<любой тип>alt tip deyil List<Object>. AMMA, List<любой тип>bu bir alt növdür List<?>. Beləliklə, aşağıdakı kodu yaza bilərik:
public static void printList(List<?> list) {
  for (Object elem: list) {
    System.out.print(elem + " ");
  }
  System.out.println();
}
Adi generiklər kimi (yəni joker işarələrdən istifadə etmədən), joker işarəli generiklər məhdudlaşdırıla bilər. Yuxarı sərhədli joker simvol tanış görünür:
public static void printCatList(List<? extends Cat> list) {
  for (Cat cat: list) {
    System.out.print(cat + " ");
  }
  System.out.println();
}
Lakin siz onu Aşağı həddə olan joker işarə ilə də məhdudlaşdıra bilərsiniz:
public static void printCatList(List<? super Cat> list) {
Beləliklə, metod bütün pişikləri, eləcə də iyerarxiyada daha yüksəkləri (Obyektə qədər) qəbul etməyə başlayacaq.
Pişiklər üçün generiklər - 11

Silinmə yazın

Generiklər haqqında danışarkən, "Növün silinməsi" haqqında bilməyə dəyər. Əslində tipin silinməsi generiklərin tərtibçi üçün məlumat olması ilə bağlıdır. Proqramın icrası zamanı generiklər haqqında daha çox məlumat yoxdur, buna "silmək" deyilir. Bu silmə ümumi növün xüsusi tiplə əvəz edilməsinə səbəb olur. Əgər ümuminin sərhədi yoxdursa, o zaman Obyekt növü əvəz olunacaq. Əgər sərhəd göstərilibsə (məsələn <T extends Comparable>), o zaman əvəz olunacaq. Budur Oracle Təlimçiliyindən bir nümunə: " Ümumi növlərin silinməsi ":
Pişiklər üçün generiklər - 12
Yuxarıda deyildiyi kimi, bu nümunədə generik Töz sərhəddinə qədər silinir, yəni. əvvəl Comparable.
Pişiklər üçün generiklər - 13

Nəticə

Generiklər çox maraqlı mövzudur. Ümid edirəm bu mövzu sizin üçün maraqlıdır. Xülasə etmək üçün deyə bilərik ki, generiklər tərtibatçıların bir tərəfdən növün təhlükəsizliyini, digər tərəfdən isə çevikliyi təmin etmək üçün tərtibçiyə əlavə məlumat vermək üçün əldə etdikləri əla vasitədir. Əgər maraqlanırsınızsa, bəyəndiyim resursları yoxlamağı təklif edirəm: #Viaçeslav
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION