JavaRush /Java блогы /Random-KK /Мысықтарға арналған генериктер
Viacheslav
Деңгей

Мысықтарға арналған генериктер

Топта жарияланған
Мысықтарға арналған генериктер - 1

Кіріспе

Бүгін Java туралы білетінімізді еске түсіретін тамаша күн. Ең маңызды құжатқа сәйкес, яғни. Java тілінің спецификациясы (JLS - Java тілінің ерекшелігі), Java " 4 тарау. Түрлер, мәндер және айнымалылар " тарауында сипатталғандай қатты терілген тіл болып табылады . Бұл нені білдіреді? Бізде негізгі әдіс бар делік:
public static void main(String[] args) {
String text = "Hello world!";
System.out.println(text);
}
Күшті теру бұл codeты құрастырған кезде компилятор мәтіндік айнымалының түрін String ретінде көрсетсек, оны басқа түрдегі айнымалы мән ретінде (мысалы, бүтін сан ретінде) еш жерде қолдануға тырыспағанымызды тексереді. . Мысалы, мәтіннің орнына мәнді сақтауға тырыссақ 2L(яғни Жолдың орнына ұзын), компиляция уақытында қате пайда болады:

Main.java:3: error: incompatible types: long cannot be converted to String
String text = 2L;
Анау. Күшті теру нысандардағы операциялардың сол an objectілер үшін заңды болған кезде ғана орындалуын қамтамасыз етуге мүмкіндік береді. Мұны типтік қауіпсіздік деп те атайды. JLS-те айтылғандай, Java-да типтердің екі санаты бар: қарабайыр типтер және анықтамалық типтер. Қарапайым түрлер туралы шолу мақаласынан есте сақтауға болады: “ Java тіліндегі қарабайыр типтер: олар соншалықты қарабайыр емес .” Сілтеме түрлері класс, интерфейс немесе массив арқылы ұсынылуы мүмкін. Ал бүгін біз анықтамалық типтерге қызығушылық танытамыз. Ал массивтерден бастайық:
class Main {
  public static void main(String[] args) {
    String[] text = new String[5];
    text[0] = "Hello";
  }
}
Бұл code қатесіз жұмыс істейді. Біз білетіндей (мысалы, « Oracle Java оқулығы: массивтер ») массив тек бір түрдегі деректерді сақтайтын контейнер болып табылады. Бұл жағдайда - тек сызықтар. Жолдың орнына массивке long қосып көрейік:
text[1] = 4L;
Осы codeты іске қосып көрейік (мысалы, Repl.it Online Java Compiler ішінде ) және қатені алайық:
error: incompatible types: long cannot be converted to String
Тілдің массиві мен түрінің қауіпсіздігі бізге типке сәйкес келмейтінді массивке сақтауға мүмкіндік бермеді. Бұл қауіпсіздік түрінің көрінісі. Бізге: «Қатені түзетіңіз, бірақ мен оған дейін codeты құрастырмаймын» деді. Ең бастысы, бұл бағдарлама іске қосылған кезде емес, құрастыру кезінде болады. Яғни, біз қателерді «бір күні» емес, бірден көреміз. Массивтер туралы есте қалғандықтан, Java Collections Framework туралы да еске түсірейік . Онда бізде әртүрлі құрылымдар болды. Мысалы, тізімдер. Мысалды қайта жазайық:
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);
  }
}
testОны құрастырған кезде біз айнымалыны баптандыру жолында қате аламыз :
incompatible types: Object cannot be converted to String
Біздің жағдайда, List кез келген нысанды сақтай алады (яғни Object типті an object). Сондықтан құрастырушы мұндай жауапкершілік жүгін өз мойнына ала алмайтынын айтады. Сондықтан біз тізімнен алатын түрін нақты көрсетуіміз керек:
String test = (String) text.get(0);
Бұл көрсеткіш типті түрлендіру немесе типті құю деп аталады. Енді біз 1-декстегі элементті алуға тырысқанша бәрі жақсы жұмыс істейді, өйткені ол Long түріне жатады. Біз әділ қатені аламыз, бірақ бағдарлама жұмыс істеп тұрған кезде (Runtime ішінде):

type conversion, typecasting
Exception in thread "main" java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.String
Көріп отырғанымыздай, мұнда бірнеше маңызды кемшіліктер бар. Біріншіден, біз тізімнен алынған мәнді String класына «тұсқауға» мәжбүрміз. Келісіңіз, бұл шіркін. Екіншіден, қате болған жағдайда оны бағдарлама орындалған кезде ғана көреміз. Егер codeымыз күрделірек болса, мұндай қатені бірден байқамауымыз мүмкін. Ал әзірлеушілер мұндай жағдайларда жұмысты қалай жеңілдету және codeты айқынырақ ету туралы ойлана бастады. Және олар дүниеге келді - Generics.
Мысықтарға арналған генериктер - 2

Генериктер

Сонымен, генериктер. Бұл не? Генерик - бұл тип қауіпсіздігін қамтамасыз ету үшін code құрастырушы өз жұмысында пайдалана алатын, қолданылатын типтерді сипаттаудың ерекше тәсілі. Бұл келесідей көрінеді:
Мысықтарға арналған генериктер - 3
Мұнда қысқаша мысал және түсініктеме берілген:
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);
  }
}
ListБұл мысалда бізде жай ғана емес, тек ListString типті нысандармен жұмыс істейтінін айтамыз . Және басқалары жоқ. Жақшада не көрсетілген болса, біз оны сақтай аламыз. Мұндай «жақшалар» «бұрыштық жақшалар» деп аталады, яғни. бұрыштық жақшалар. Компилятор жолдар тізімімен (тізім мәтін деп аталады) жұмыс істегенде қателіктер жібергенімізді тексереді. Компилятор Long дегенді жолдар тізіміне қоюға тырысып жатқанымызды көреді. Ал компиляция уақытында ол қате береді:
error: no suitable method found for add(long)
Сіз String CharSequence ұрпағы екенін есте ұстаған боларсыз. Және келесідей нәрсені жасауды шешіңіз:
public static void main(String[] args) {
	ArrayList<CharSequence> text = new ArrayList<String>(5);
	text.add("Hello");
	String test = text.get(0);
}
Бірақ бұл мүмкін емес және біз қатені аламыз: error: incompatible types: ArrayList<String> cannot be converted to ArrayList<CharSequence> Біртүрлі көрінеді, өйткені. жолда CharSequence sec = "test";қателер жоқ. Оны анықтап көрейік. Олар бұл мінез-құлық туралы айтады: «Генериктер инвариантты». «Инвариант» дегеніміз не? Маған бұл туралы Википедиядағы « Коварианттық және контрварианс » мақаласында айтылғаны ұнайды :
Мысықтарға арналған генериктер - 4
Сонымен, Инварианттылық – туынды түрлер арасында тұқым қуалаушылықтың болмауы. Егер мысық Жануарлардың ішкі түрі болса, онда Жиын<Мысықтар> жиынының<Жануарлар> жиынының және жиын<Жануарлар> жиынының<Мысықтардың ішкі түрі емес. Айтпақшы, Java SE 7-ден бастап «Гауһар операторы » пайда болғанын айту керек. Өйткені екі бұрыштық жақша <> алмас тәрізді. Бұл бізге келесідей генериктерді пайдалануға мүмкіндік береді:
public static void main(String[] args) {
  List<String> lines = new ArrayList<>();
  lines.add("Hello world!");
  System.out.println(lines);
}
Осы codeқа сүйене отырып, компилятор түсінеді, егер біз оның сол жағында String типті an objectілерді қамтитынын көрсетсек , онда оң жағында біз жаңа ArrayList-ті айнымалыға сақтағымыз Listкелетінін білдіреді , ол сонымен бірге нысанды сақтайды. linesсол жағында көрсетілген түрдегі. Сондықтан сол жақтағы компилятор оң жақтың түрін түсінеді немесе шығарады. Сондықтан бұл әрекетті типтік қорытынды немесе ағылшын тілінде «Түр туралы қорытынды» деп атайды. Тағы бір айта кететін қызықты нәрсе - RAW түрлері немесе «шикі түрлері». Өйткені Генериктер әрқашан болған емес, және Java мүмкіндігінше кері үйлесімділікті сақтауға тырысады, содан кейін генериктер қандай да бір түрде ешқандай генерик көрсетілмеген codeпен жұмыс істеуге мәжбүр болады. Мысал көрейік:
List<CharSequence> lines = new ArrayList<String>();
Біздің есімізде, мұндай жол генериктердің инварианттылығына байланысты құрастырылмайды.
List<Object> lines = new ArrayList<String>();
Бұл да дәл сол себепті құрастырылмайды.
List lines = new ArrayList<String>();
List<String> lines2 = new ArrayList();
Мұндай жолдар құрастырылады және жұмыс істейді. Дәл оларда шикізат түрлері қолданылады, яғни. анықталмаған түрлері. Тағы да айта кететін жайт, шикі түрлерді заманауи codeта ҚОЛДАНБАУ КЕРЕК.
Мысықтарға арналған генериктер - 5

Терілген сыныптар

Сонымен, терілген сыныптар. Өзіміз терілген сыныпты қалай жазуға болатынын көрейік. Мысалы, бізде класс иерархиясы бар:
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");
  }
}
Біз жануарларға арналған контейнерді жүзеге асыратын класс жасағымыз келеді. Кез келген бар сыныпты жазуға болады Animal. Бұл қарапайым, түсінікті, БІРАҚ... ит пен мысықты араластыру жаман, олар бір-бірімен дос емес. Сонымен қатар, егер біреу мұндай контейнерді алса, ол контейнерден мысықтарды қателесіп иттердің қорабына лақтыруы мүмкін ... және бұл жақсылыққа әкелмейді. Бұл жерде генериктер бізге көмектеседі. Мысалы, іске асыруды былай жазайық:
public static class Box<T> {
  List<T> slots = new ArrayList<>();
  public List<T> getSlots() {
    return slots;
  }
}
Біздің сынып T деп аталатын генерикпен көрсетілген типтегі нысандармен жұмыс істейді. Бұл бүркеншік аттың бір түрі. Өйткені Жалпы мән класс атауында көрсетілген, содан кейін біз оны сыныпты жариялағанда аламыз:
public static void main(String[] args) {
  Box<Cat> catBox = new Box<>();
  Cat murzik = new Cat();
  catBox.getSlots().add(murzik);
}
Көріп отырғанымыздай, бізде бар екенін көрсеттік Box, ол тек -мен жұмыс істейді Cat. Компилятор catBoxгенериктің орнына жалпы атау көрсетілген жерде Tтүрді ауыстыру керек екенін түсінді : CatT
Мысықтарға арналған генериктер - 6
Анау. Box<Cat>компилятордың арқасында ол slotsшын мәнінде қандай болуы керек екенін түсінеді List<Cat>. Өйткені Box<Dog>ішінде болады slots, қамтитын List<Dog>. Түр туралы мәлімдемеде бірнеше генериктер болуы мүмкін, мысалы:
public static class Box<T, V> {
Жалпы атау кез келген болуы мүмкін, дегенмен кейбір айтылмаған ережелерді сақтау ұсынылады – «Түр параметрлерін атау конвенциялары»: Элемент түрі - E, кілт түрі - K, сан түрі - N, T - түр үшін, V - мән түрі үшін . Айтпақшы, генериктердің инвариантты екенін айтқанымызды есте сақтаңыз, яғни. мұра иерархиясын сақтамаңыз. Шын мәнінде, біз бұған әсер ете аламыз. Яғни, бізде генериктерді COvariant жасауға мүмкіндік бар, яғни. мұраны сол тәртіпте сақтау. Бұл мінез-құлық «Шектелген тип» деп аталады, яғни. шектеулі түрлері. Мысалы, біздің сыныпта Boxбарлық жануарлар болуы мүмкін, содан кейін біз келесідей жалпылама жариялаймыз:
public static class Box<T extends Animal> {
Яғни, біз сыныптың жоғарғы шегін белгіледік Animal. Сондай-ақ кілт сөзден кейін бірнеше түрін көрсетуге болады extends. Бұл біз жұмыс істейтін тип қандай да бір класстың ұрпағы болуы керек және сонымен бірге кейбір интерфейсті жүзеге асыруы керек дегенді білдіреді. Мысалы:
public static class Box<T extends Animal & Comparable> {
Бұл жағдайда Boxмұрагер болып табылмайтын Animalжәне орындалмайтын нәрсені қоюға тырысатын болсақ Comparable, онда компиляция кезінде біз қатені аламыз:
error: type argument Cat is not within bounds of type-variable T
Мысықтарға арналған генериктер - 7

Теру әдісі

Генериктер тек қана түрлерде емес, жеке әдістерде де қолданылады. Әдістерді қолдануды ресми оқулықтан көруге болады: « Generics Methods ».

Фон:

Мысықтарға арналған генериктер - 8
Мына суретке назар аударайық. Көріп отырғаныңыздай, компилятор әдіс қолтаңбасына қарайды және біз кіріс ретінде кейбір анықталмаған сыныпты алып жатқанымызды көреді. Ол қандай да бір нысанды қайтаратынымызды қол қою арқылы анықтамайды, яғни. Нысан. Сондықтан, егер біз ArrayList жасағымыз келсе, онда мұны істеу керек:
ArrayList<String> object = (ArrayList<String>) createObject(ArrayList.class);
Шығару ArrayList болатынын анық жазуыңыз керек, ол ұсқынсыз және қателесу мүмкіндігін қосады. Мысалы, біз мұндай бос сөзді жаза аламыз және ол құрастырады:
ArrayList object = (ArrayList) createObject(LinkedList.class);
Компиляторға көмектесе аламыз ба? Иә, генериктер бізге мұны істеуге мүмкіндік береді. Сол мысалды қарастырайық:
Мысықтарға арналған генериктер - 9
Содан кейін біз an objectіні келесідей жасай аламыз:
ArrayList<String> object = createObject(ArrayList.class);
Мысықтарға арналған генериктер - 10

Wildcard

Oracle компаниясының Generics бойынша оқу құралына сәйкес, атап айтқанда, « Жылжымалы таңбалар » бөлімі, біз сұрақ белгісі бар «белгісіз түрді» сипаттай аламыз. Қойылмалы таңба генериктердің кейбір шектеулерін жеңілдететін ыңғайлы құрал болып табылады. Мысалы, біз бұрын талқылағанымыздай, генериктер инвариантты. Бұл барлық сыныптар Объект түрінің ұрпақтары (ішкі түрлері) болғанымен, ол List<любой тип>ішкі түрі емес дегенді білдіреді List<Object>. БІРАҚ, List<любой тип>бұл ішкі түрі List<?>. Сондықтан біз келесі codeты жаза аламыз:
public static void printList(List<?> list) {
  for (Object elem: list) {
    System.out.print(elem + " ");
  }
  System.out.println();
}
Тұрақты генериктер сияқты (яғни қойылмалы таңбаларды қолданбай), қойылмалы таңбалары бар генериктерді шектеуге болады. Жоғарғы шектелген қойылмалы таңба таныс көрінеді:
public static void printCatList(List<? extends Cat> list) {
  for (Cat cat: list) {
    System.out.print(cat + " ");
  }
  System.out.println();
}
Бірақ сіз оны Төменгі шек қойылмалы таңбасымен де шектей аласыз:
public static void printCatList(List<? super Cat> list) {
Осылайша, әдіс барлық мысықтарды, сондай-ақ иерархияда жоғарырақ (Нысанға дейін) қабылдай бастайды.
Мысықтарға арналған генериктер - 11

Өшіру теріңіз

Генериктер туралы айтатын болсақ, «Түрді өшіру» туралы білу керек. Шын мәнінде, типті өшіру генериктердің компиляторға арналған ақпарат екендігі туралы. Бағдарламаны орындау кезінде генериктер туралы қосымша ақпарат болмайды, бұл «өшіру» деп аталады. Бұл өшіру жалпы түрдің нақты түрмен ауыстырылуына әсер етеді. Егер генериктің шекарасы болмаса, онда Нысан түрі ауыстырылады. Егер шекара көрсетілген болса (мысалы <T extends Comparable>), онда ол ауыстырылады. Міне, Oracle оқулығының мысалы: " Жалпы типтерді өшіру ":
Мысықтарға арналған генериктер - 12
Жоғарыда айтылғандай, бұл мысалда генерик Tоның шекарасына дейін өшіріледі, яғни. бұрын Comparable.
Мысықтарға арналған генериктер - 13

Қорытынды

Генериктер өте қызықты тақырып. Бұл тақырып сізді қызықтырады деп үміттенемін. Қорытындылай келе, генериктерді бір жағынан типтің қауіпсіздігін және екінші жағынан икемділігін қамтамасыз ету үшін компиляторға қосымша ақпарат беру үшін әзірлеушілер алған тамаша құрал деп айта аламыз. Егер сізді қызықтыратын болса, мен сізге ұнаған ресурстарды тексеруді ұсынамын: #Вячеслав
Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION