JavaRush /Java блогу /Random-KY /Мышыктар үчүн генерик
Viacheslav
Деңгээл

Мышыктар үчүн генерик

Группада жарыяланган
Мышыктар үчүн генериктер - 1

Киришүү

Бүгүн Java жөнүндө билгендерибизди эстеп калуу үчүн эң сонун күн. Эң маанилүү documentке ылайык, б.а. Java Language Specification (JLS - Java Language Specifiaction), 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тер боюнча операциялар ошол an objectтер үчүн мыйзамдуу болгондо гана аткарылышын камсыз кылууга мүмкүндүк берет. Бул түрү коопсуздук деп да аталат. JLSте айтылгандай, Javaда типтердин эки категориясы бар: примитивдүү типтер жана маалымдама түрлөрү. Карап чыгуу макаласынан примитивдик типтер жөнүндө эстей аласыз: " Java'дагы примитивдик типтер: Алар анчалык примитивдик эмес ." Шилтемелердин түрлөрү класс, интерфейс же массив менен көрсөтүлүшү мүмкүн. Ал эми бүгүн биз шилтеме түрлөрүнө кызыкдар болот. Жана массивдерден баштайлы:
class Main {
  public static void main(String[] args) {
    String[] text = new String[5];
    text[0] = "Hello";
  }
}
Бул code катасыз иштейт. Белгилүү болгондой (мисалы, " Oracle Java Tutorial: Массивдерден "), массив бир гана типтеги маалыматтарды сактаган контейнер. Бул учурда - сызыктар гана. Саптын ордуна массивге 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 каалаган an objectти (б.а. Object түрүндөгү an object) сактай алат. Андыктан мындай жоопкерчorкти өз мойнуна ала албастыгын айтат түзүүчү. Ошондуктан, биз тизмеден ала турган түрүн так көрсөтүшүбүз керек:
String test = (String) text.get(0);
Бул көрсөткүч типти конversionлоо же типти кастинг деп аталат. Эми 1-декс боюнча элементти алууга аракет кылмайынча баары жакшы иштейт, анткени ал Long түрү болуп саналат. Жана биз адилет ката алабыз, бирок программа иштеп жатканда (Runtime):

type conversion, typecasting
Exception in thread "main" java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.String
Биз көрүп тургандай, бул жерде бир нече маанилүү кемчorктери бар. Биринчиден, биз тизмеден алынган маанини String классына "ташууга" мажбурбуз. Макул, бул чиркин. Экинчиден, ката болсо, программа аткарылганда гана көрөбүз. Эгерде биздин code татаалыраак болсо, биз мындай катаны дароо байкабай калышыбыз мүмкүн. Жана иштеп чыгуучулар мындай кырдаалдарда ишти кантип жеңилдетүү жана codeду ачык-айкын кылуу жөнүндө ойлоно башташты. Жана алар төрөлгөн - Generics.
Мышыктар үчүн генериктер - 2

Generics

Ошентип, генериктер. Бул не? Генерик – бул колдонулган типтерди сүрөттөөнүн өзгөчө жолу, аны 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, бирок ListСтринг түрүндөгү an objectтер менен ГАНА иштейт деп айтабыз. Жана башкалар жок. Жөн эле кашаада көрсөтүлгөн нерсе, биз аны сактай алабыз. Мындай "кашалар" "бурчтуу кашаалар" деп аталат, б.а. бурчтуу кашаалар. Компилятор саптардын тизмеси (тизме текст деп аталат) менен иштөөдө ката кетирбегенибизди текшерет. Компилятор Лонгду Стринг тизмесине коюуга уятсыздык менен аракет кылып жатканыбызды көрөт. Ал эми компиляция учурунда ал ката берет:
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";каталар жок. Келгиле, аны аныктап көрөлү. Алар бул жүрүм-турум жөнүндө: "Генериктер өзгөрүлбөйт" дешет. "Инвариант" деген эмне? Мага бул тууралуу Wikipediaдагы “ Ковариация жана контрварианс ” макаласында айтылганы жагат :
Мышыктар үчүн генериктер - 4
Ошентип, Инварианттык - бул туунду типтердин ортосунда тукум куучулуктун жоктугу. Эгерде Мышык Жаныбарлардын чакан түрү болсо, анда Set<Cats> Set<Animals> жана Set<Animals> Set<Cats>тин чакан түрү эмес. Айтмакчы, 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келет , ал дагы an object сактай турган linesсол жагында көрсөтүлгөн түрдөгү. Ошентип, сол тараптан түзүлгөн компилятор оң тараптын түрүн түшүнөт же тыянак чыгарат. Мына ошондуктан бул жүрүм-турум англисче "Type Inference" же "Type Inference" деп аталат. Белгилей кетчү дагы бир кызыктуу нерсе - 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 деген генерик тарабынан белгиленген типтеги an objectтер менен иштейт. Бул псевдонимдин бир түрү. Анткени Жалпы класстын аталышында көрсөтүлгөн, андан кийин классты жарыялоодо биз аны алабыз:
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
Келгиле, бул сүрөттү карап көрөлү. Көрүнүп тургандай, компилятор методдун кол тамгасын карап, биз кандайдыр бир аныкталбаган классты киргизүү катары алып жатканыбызды көрөт. Бул кандайдыр бир an objectти кайтарып жатканыбызды кол коюу менен аныктаbyte, б.а. Объект. Ошондуктан, эгерде биз 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's Tutorial on Generics, өзгөчө " Wildcards " бөлүмүнө ылайык , биз суроо белгиси менен "белгисиз түрдү" сүрөттөй алабыз. Wildcard генериктердин кээ бир чектөөлөрүн жеңилдетүү үчүн ыңгайлуу курал. Мисалы, биз мурда талкуулагандай, генериктер инвариант болуп саналат. Бул бардык класстар Объект түрүнүн урпактары (подтиптер) болсо да, ал 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) {
Ошентип, ыкма бардык мышыктарды кабыл ала баштайт, ошондой эле иерархияда жогору (an objectке чейин).
Мышыктар үчүн генериктер - 11

Өчүрүүнү жазыңыз

Генериктер жөнүндө айтсак, "Түрдү өчүрүү" жөнүндө билүү керек. Чынында, типти өчүрүү - бул генериктердин компилятор үчүн маалымат экендиги жөнүндө. Программанын аткарылышы учурунда генериктер жөнүндө маалымат жок, бул "тазалоо" деп аталат. Бул өчүрүү жалпы типтин конкреттүү түргө алмаштырылышына алып келет. Эгерде генериктин чеги жок болсо, анда Объект түрү алмаштырылат. Эгерде чек көрсөтүлгөн болсо (мисалы <T extends Comparable>), анда ал алмаштырылат. Бул жерде Oracle's Tutorial үлгүсү: " Жалпы типтерди өчүрүү ":
Мышыктар үчүн генериктер - 12
Жогоруда айтылгандай, бул мисалда генерик Tанын чегине чейин өчүрүлөт, б.а. чейин Comparable.
Мышыктар үчүн генериктер - 13

Корутунду

Generics абдан кызыктуу тема болуп саналат. Бул тема сизди кызыктырат деп үмүттөнөм. Жыйынтыктап айтканда, генериктер бир жагынан типтин коопсуздугун, экинчи жагынан ийкемдүүлүктү камсыз кылуу үчүн компиляторду кошумча маалымат менен камсыздоо үчүн иштеп чыгуучулар алган эң сонун курал деп айта алабыз. Жана эгер сизди кызыктырса, анда мен сизге жаккан ресурстарды карап чыгууну сунуштайм: #Вячеслав
Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION