JavaRush /Java блогу /Random-KY /Javaдагы генериктердин теориясы же иш жүзүндө кашааларды ...
Viacheslav
Деңгээл

Javaдагы генериктердин теориясы же иш жүзүндө кашааларды кантип коюу керек

Группада жарыяланган

Киришүү

JSE 5.0 менен баштап, генериктери Java тorнин арсеналына кошулган.
Java тorндеги генериктердин теориясы же иш жүзүндө кашааларды кантип коюу керек - 1

Javaдагы генериктер деген эмне?

Генериктер (жалпылоо) — жалпыланган программалоону ишке ашыруу үчүн Java тorнин атайын каражаты: берorштерди жана алгоритмдерди сыпаттоодогу өзгөчө мамиле, бул ар кандай типтеги маалыматтар менен алардын сыпаттамасын өзгөртпөстөн иштөөгө мүмкүндүк берет. Oracle веб-сайтында генериктерге өзүнчө окуу куралы арналган: “ Сабак: Generics ”.

Биринчиден, генериктерди түшүнүү үчүн, алар эмне үчүн керек экендигин жана алар эмнени камсыз кылышын түшүнүшүңүз керек. Бөлүмдөгү окуу куралында " Эмне үчүн Generics керек ?" Бул максаттардын бири күчтүү компиляция-убакыт түрүн текшерүү жана ачык кастинг зарылдыгын жок кылуу деп айтылат.
Java тorндеги генериктердин теориясы же практикада кашааларды кантип коюу керек - 2
Келгиле, сүйүктүү окуу куралдарыбызды эксперименттер үчүн онлайн java компиляторун даярдайлы . Бул codeду элестетип көрөлү:
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);
	}
}
Бул code жакшы иштейт. Бирок алар бизге келип, «Салам, дүйнө!» деген сөздү айтышсачы? сабалган жана сиз гана кайтара аласыз Hello? Келгиле, codeдон сап менен бириктирүүнү алып салалы ", world!". Мындан зыянсыз эмне болушу мүмкүн окшойт? Бирок, чындыгында, биз КОМПИЛЯЦИЯ ЖӨНҮНДӨ ката алабыз : error: incompatible types: Object cannot be converted to String Эң негизгиси, биздин учурда Тизме Объект түрүндөгү an objectтердин тизмесин сактайт. String Объекттин тукуму болгондуктан (бардык класстар Javaдагы Objectтен кыйыр түрдө тукум кууп өткөндүктөн), ал биз жасаган эмес, ачык-айкын чыгарууну талап кылат. Жана бириктирүүдө String.valueOf(obj) статикалык методу an objectке чакырылат, ал акыры Объекттеги toString ыкмасын чакырат. Башкача айтканда, биздин Тизмеде Объект бар. Көрсө, бизге Объект эмес, белгилүү бир тип керек болсо, типти кастингди өзүбүз жасашыбыз керек болот:
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);
		}
	}
}
Бирок, бул учурда, анткени List an objectтердин тизмесин кабыл алат, ал String гана эмес, бүтүн сандарды да сактайт. Бирок эң жаманы, бул учурда компилятор туура эмес эч нерсе көрбөйт. Жана бул жерде биз АТКАРУУ УЧУРЫНДА ката алабыз (алар ката "Runtime" учурунда алынган деп да айтышат). Ката болот: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String макул эмес, абдан жагымдуу. Мунун баары компилятор жасалма интеллект эмес жана ал программист эмнени айткысы келгенин баарын таба алbyte. Компиляторго биз кандай типтерди колдоно тургандыгыбыз жөнүндө көбүрөөк айтып берүү үчүн, Java SE 5 генериктерди киргизди . Келгиле, компиляторго каалаганыбызды айтып, versionбызды оңдойлу:
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);
		}
	}
}
Көрүнүп тургандай, бизге мындан ары Стрингге актер керек эмес. Мындан тышкары, азыр бизде генериктерди камтыган бурчтуу кашаалар бар. Эми компилятор биз тизмеге 123 кошууну алып салмайынча классты түзүүгө уруксат бербейт, анткени бул бүтүн сан. Ал бизге ошентип айтат. Көптөр генериктерди "синтаксистик кант" деп аташат. Жана алар туура, анткени генериктер түзүлгөндө ошол эле касталарга айланат. Келгиле, түзүлгөн класстардын byte codeун карап көрөлү: кол менен кастинг жана генериктерди колдонуу менен:
Java тorндеги генериктердин теориясы же иш жүзүндө кашааларды кантип коюу керек - 3
Компиляциядан кийин генериктер жөнүндө бардык маалыматтар өчүрүлөт. Бул "Түрдү өчүрүү" же " Түрдү өчүрүү " деп аталат. Типти өчүрүү жана генериктери JDKнын эски versionлары менен артка шайкеш келүүнү камсыз кылуу үчүн иштелип чыккан, ошол эле учурда компиляторго Javaнын жаңыраак versionларында типти чыгарууга жардам берүүгө мүмкүнчүлүк берет.
Java тorндеги генериктердин теориясы же практикада кашааларды кантип коюу керек - 4

Чийки түрлөрү же чийки түрлөрү

Генериктер жөнүндө сөз кылганда, бизде ар дайым эки категория бар: терилген типтер (Жалпы типтер) жана "чийки" типтер (Чийки түрлөрү). Чийки түрлөрү - бурчтуу кашааларда "квалификациясы" көрсөтүлбөгөн түрлөр:
Java тorндеги генериктердин теориясы же практикада кашааларды кантип коюу керек - 5
Терилген түрлөрү "тактоо" көрсөтүү менен, тескерисинче болуп саналат:
Java тorндеги генериктердин теориясы же практикада кашааларды кантип коюу керек - 6
Көрүнүп тургандай, биз скриншотто жебе менен белгиленген адаттан тыш дизайнды колдондук. Бул Java SE 7де кошулган атайын синтаксис жана ал алмаз дегенди билдирген " бриллиант " деп аталат. Неге? Сиз бриллианттын формасы менен тармал кашаанын формасынын окшоштугун түзө аласыз: Алмаз синтаксиси " Тип тыянак<> " түшүнүгү менен да байланыштуу , же типтүү жыйынтык. Анткени, компилятор оң жагында <> көрүп, маани берилген өзгөрмөнүн түрүнүн декларациясы жайгашкан сол жагын карайт. Жана ушул бөлүктөн ал оң жактагы маани кандай типте терилгенин түшүнөт. Чындыгында, эгерде генерик сол тарапта көрсөтүлүп, оң жагында көрсөтүлбөсө, компилятор түрүн чыгара алат:
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);
	}
}
Бирок, бул генериктер менен жаңы стилдин жана аларсыз эски стилдин аралашмасы болмок. Жана бул өтө жагымсыз. Жогорудагы codeду түзүүдө биз төмөнкү билдирүүнү алабыз: Note: HelloWorld.java uses unchecked or unsafe operations. Чынында, бул жерде дегеле алмазды кошуунун эмне үчүн зарылчылыгы бар экендиги түшүнүксүз окшойт. Бирок бул жерде бир мисал:
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);
	}
}
Эсибизде болгондой, ArrayListте коллекцияны киргизүү катары кабыл алган экинчи конструктор да бар. Ал эми айлакерлик мына ушунда. Алмаз синтаксиси болбосо, компилятор алданып жатканын түшүнбөйт, ал эми алмаз менен түшүнөт. Ошондуктан, №1 эреже : терилген түрлөрүн колдонсок, ар дайым алмаз синтаксисин колдонуңуз. Болбосо, биз чийки түрүн колдонгон жерде жок болуп кетүү коркунучу бар. Журналда "текшерилбеген же кооптуу операцияларды колдонот" деген эскертүүлөрдү болтурбоо үчүн колдонулуп жаткан ыкма же класс боюнча атайын annotationны көрсөтсөңүз болот: @SuppressWarnings("unchecked") Suppress басуу, башкача айтканда, сөзмө-сөз эскертүүлөрдү басуу деп которулат. Бирок ойлонуп көр, эмне үчүн аны көрсөтүүнү чечтиң? №1 эрежени эстеп, балким, терүүнү кошуу керек.
Java тorндеги генериктердин теориясы же практикада кашааларды кантип коюу керек - 7

Жалпы методдор

Генериктер методдорду терүүгө мүмкүндүк берет. Oracle окуу куралында бул өзгөчөлүккө арналган өзүнчө бөлүм бар: “ Жалпы методдор ”. Бул окуу куралынан синтаксисти эстеп калуу маанилүү:
  • бурчтуу кашаанын ичинде терилген параметрлердин тизмесин камтыйт;
  • терилген параметрлердин тизмеси кайтарылган ыкмага чейин барат.
Келгиле, бир мисал карап көрөлү:
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 классын карасаңыз, анда биз эки терилген ыкманы көрөбүз. Типти жыйынтыктоо менен, типтин аныктамасын түз компиляторго бере алабыз же аны өзүбүз аныктай алабыз. Эки вариант тең мисалда берилген. Айтмакчы, синтаксис, эгер сиз ойлонуп көрсөңүз, логикалуу. Методду терүүдө биз методдон АЛДЫНДА жалпыны белгилейбиз, анткени методдон кийин жалпыны колдонсок, Java кайсы түрүн колдонуу керектигин аныктай алbyte. Ошондуктан, биз адегенде жалпы T колдонобуз деп жарыялайбыз, андан кийин биз бул генерикти кайтарабыз деп айтабыз. Албетте, Util.<Integer>getValue(element, String.class)бул ката менен ишке ашпай калат incompatible types: Class<String> cannot be converted to Class<Integer>. Терилген ыкмаларды колдонууда, түрүн өчүрүү жөнүндө дайыма эстен чыгарбоо керек. Келгиле, бир мисал карап көрөлү:
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);
		}
    }
}
Бул сонун иштейт. Бирок компилятор чакырылган методдун Integer түрү бар экенин түшүнгөндө гана. Консолдун чыгышын төмөнкү сап менен алмаштыралы: System.out.println(Util.getValue(element) + 1); Жана биз ката алабыз: экorк оператор '+' үчүн жаман операнд түрлөрү, биринчи тип: Объект , экинчи түрү: int Башкача айтканда, типтер өчүрүлгөн. Компилятор эч ким түрүн көрсөтпөгөнүн көрөт, түрү Объект катары көрсөтүлгөн жана codeдун аткарылышы ката менен ишке ашпай калат.
Java тorндеги генериктердин теориясы же практикада кашааларды кантип коюу керек - 8

Жалпы түрлөрү

Сиз методдорду гана эмес, класстарды да тере аласыз. Oracle колдонмосунда буга арналган " Жалпы типтер " бөлүмү бар . Келгиле, бир мисал карап көрөлү:
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);
		}
	}
}
Бул жерде баары жөнөкөй. Эгерде биз классты колдонсок, жалпы класстын аталышынан кийин келтирилет. Эми бул класстын мисалын негизги методдо түзөлү:
public static void main(String []args) {
	SomeType<String> st = new SomeType<>();
	List<String> list = Arrays.asList("test");
	st.test(list);
}
Бул жакшы иштейт. Компилятор сандар тизмеси жана String түрүндөгү Коллекция бар экенин көрөт. Бирок биз генериктерди өчүрүп, муну кылсакчы:
SomeType st = new SomeType();
List<String> list = Arrays.asList("test");
st.test(list);
Биз катаны алабыз: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer кайра өчүрүүнү териңиз. Класста генерик жок болгондуктан, компилятор биз Тизмеге өткөндүктөн, List<Бүтүн> менен метод туурараак деп чечет. А биз ката менен жыгылабыз. Ошондуктан, эреже №2: Эгерде класс терилген болсо, ар дайым generic ичинде түрүн көрсөтүңүз .

Чектөөлөр

Биз генериктерде көрсөтүлгөн түрлөргө чектөө колдоно алабыз. Мисалы, контейнер киргизүү катары Санды гана кабыл алышын каалайбыз. Бул өзгөчөлүк Oracle окуу куралында Чектелген типтин параметрлери бөлүмүндө сүрөттөлөт . Келгиле, бир мисал карап көрөлү:
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");
    }
}
Көрүнүп тургандай, биз Сан классы/интерфейси жана анын тукумдары болуу үчүн жалпы типти чектедик. Кызыгы, сиз классты гана эмес, интерфейстерди да көрсөтө аласыз. Мисалы: public static class NumberContainer<T extends Number & Comparable> { Generics ошондой эле Wildcard түшүнүгүнө ээ https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html Алар, өз кезегинде, үч түргө бөлүнөт: Get Put принциби деп аталган Wildcards үчүн колдонулат . Алар төмөнкү формада көрсөтүлүшү мүмкүн:
Java тorндеги генериктердин теориясы же практикада кашааларды кантип коюу керек - 9
Бул принцип PECS (Producer Extends Consumer Super) принциби деп да аталат. Сиз Habré жөнүндө көбүрөөк макалада окуй аласыз “ Java API колдонууга жарамдуулугун жакшыртуу үчүн жалпы коймочокторду колдонуу ”, ошондой эле stackoverflow боюнча эң сонун талкуудан: “ Generics Java-да жабдыктарды колдонуу ”. Бул жерде Java булагынан кичинекей бир мисал - Collections.copy ыкмасы:
Java тorндеги генериктердин теориясы же практикада кашааларды кантип коюу керек - 10
Кантип иштебей турганынын кичинекей бир мисалы:
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);
}
Бирок кеңейтүүнү суперге алмаштырсаңыз, баары жакшы болот. Биз тизмени чыгаруудан мурун маани менен толтургандыктан, ал биз үчүн керектөөчү, б.а. керектөөчү. Ошондуктан, биз супер колдонобуз.

Мурас

Генериктердин дагы бир адаттан тыш өзгөчөлүгү бар - алардын тукум куучулук. Генериктердин мурасы Oracle окуу куралында " Генерикалар, мурастоо жана субтиптер " бөлүмүндө сүрөттөлөт . Эң негизгиси төмөндөгүлөрдү эстен чыгарбоо жана ишке ашыруу. Биз муну кыла албайбыз:
List<CharSequence> list1 = new ArrayList<String>();
Анткени мурас генериктер менен башкача иштейт:
Java тorндеги генериктердин теориясы же кашаларды практикада кантип коюу керек - 11
Жана бул жерде ката менен ишке ашпай турган дагы бир жакшы мисал:
List<String> list1 = new ArrayList<>();
List<Object> list2 = list1;
Бул жерде да баары жөнөкөй. List<String> List<Object> тукуму эмес, бирок String Объекттин тукуму.

Финал

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