JavaRush /Java блогы /Random-KK /Java тіліндегі генериктердің теориясы немесе жақшаларды і...
Viacheslav
Деңгей

Java тіліндегі генериктердің теориясы немесе жақшаларды іс жүзінде қалай қою керек

Топта жарияланған

Кіріспе

JSE 5.0 нұсқасынан бастап генериктер Java тілінің арсеналына қосылды.
Java тіліндегі генериктер теориясы немесе жақшаларды іс жүзінде қалай қою керек - 1

Java тіліндегі генериктер дегеніміз не?

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

Біріншіден, генериктерді түсіну үшін олардың не үшін қажет екенін және олар не беретінін түсіну керек. « Неліктен генериктерді пайдалану керек ?» бөліміндегі оқулықта. Мақсаттардың бірі - компиляция уақытының түрін неғұрлым күшті тексеру және нақты кастинг қажеттілігін жою деп айтылады.
Java тіліндегі генериктер теориясы немесе жақшаларды практикада қалай қою керек - 2
Тәжірибелер үшін сүйікті оқулықтарды дайындап көрейік . Мына 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 жақсы жұмыс істейді. Бірақ олар бізге келіп, «Сәлем, әлем!» деген сөзді айтса ше? ұрып-соғып, сіз тек қайтара аласыз Сәлем? Жолмен байланыстыруды codeтан алып тастаймыз ", world!". Бұдан зиянсыз не болуы мүмкін сияқты? Бірақ шын мәнінде, біз ҚҰРАСТЫРУ ҮРІСІНДЕ қате аламыз : error: incompatible types: Object cannot be converted to String Біздің жағдайда Тізімде Object типті нысандардың тізімі сақталады. String Объектінің ұрпағы болғандықтан (барлық сыныптар Java тіліндегі Object-тен жанама түрде мұраланғандықтан), ол біз жасамаған айқын трансляцияны қажет етеді. Ал біріктіру кезінде String.valueOf(obj) статикалық әдісі нысанда шақырылады, ол ақыр соңында Нысандағы 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 нысандар тізімін қабылдайды, ол тек String емес, сонымен қатар бүтін сандарды сақтайды. Бірақ ең сорақысы, бұл жағдайда компилятор қате ештеңе көрмейді. Міне, біз ОРЫНДАУ ҮРЕСІНДЕ қатені аламыз (олар қате «Орындалу уақытында» алынды деп те айтады). Қате болады: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String Келісемін, ең жағымды емес. Мұның бәрі компилятор жасанды интеллект емес және ол бағдарламашы айтқанның бәрін болжай алмайды. Компиляторға қандай түрлерді қолданатынымыз туралы көбірек айту үшін Java SE 5 генериктерді енгізді . Компиляторға не қалайтынымызды айту арқылы нұсқамызды түзетейік:
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);
		}
	}
}
Көріп отырғанымыздай, бізге енді String үшін актерлер қажет емес. Бұған қоса, қазір бізде генериктерді жақтайтын бұрыштық жақшалар бар. Енді компилятор тізімге 123 қосымшасын алып тастамайынша сыныпты құрастыруға рұқсат бермейді, өйткені бұл бүтін сан. Ол бізге солай айтады. Көптеген адамдар генериктерді «синтаксистік қант» деп атайды. Және олар дұрыс, өйткені генериктер құрастырылған кезде дәл сол касталарға айналады. Құрастырылған сыныптардың byte codeын қарастырайық: қолмен құю және генериктерді пайдалану:
Java тіліндегі генериктер теориясы немесе жақшаларды іс жүзінде қалай қою керек – 3
Құрастырудан кейін генериктер туралы кез келген ақпарат жойылады. Бұл «Түрді өшіру» немесе « Түрді өшіру » деп аталады. Түрді өшіру және генериктері JDK ескі нұсқаларымен кері үйлесімділікті қамтамасыз етуге арналған, сонымен бірге компиляторға Java-ның жаңа нұсқаларында типті анықтауға көмектесуге мүмкіндік береді.
Java тіліндегі генериктер теориясы немесе жақшаларды практикада қалай қою керек – 4

Шикізат түрлері немесе шикі түрлері

Генериктер туралы айтатын болсақ, бізде әрқашан екі санат болады: терілген типтер (Жалпы типтер) және «шикі» типтер (шикі түрлер). Шикізат түрлері - бұрыштық жақшада «біліктілігі» көрсетілмеген түрлер:
Java тіліндегі генериктер теориясы немесе жақшаларды іс жүзінде қалай қою керек – 5
Терілген түрлер керісінше, «нақтылау» белгісімен:
Java тіліндегі генериктер теориясы немесе жақшаларды практикада қалай қою керек – 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") Басу басып шығару, яғни сөзбе-сөз ескертулерді басу деп аударылады. Бірақ ойланыңыз, неліктен сіз оны көрсетуді шештіңіз? Бірінші ережені есте сақтаңыз және теруді қосу қажет болуы мүмкін.
Java тіліндегі генериктер теориясы немесе жақшаларды іс жүзінде қалай қою керек – 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 қай типті пайдалану керектігін анықтай алмайды. Сондықтан, біз алдымен жалпы 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); Және біз қатені аламыз: екілік оператор '+' үшін нашар операнд түрлері, бірінші түрі: Object , екінші түрі: int Яғни, типтер өшірілді. Компилятор түрін ешкім көрсетпегенін көреді, түрі Объект ретінде көрсетіледі және codeты орындау қатемен орындалмайды.
Теория дженериков в Java or How на практике ставить скобки - 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<Integer> әдісі қолайлырақ деп шешеді. Ал біз қателікпен құлаймыз. Сондықтан, №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 деп аталатын принцип қойылмалы таңбаларға қолданылады . Оларды келесі формада көрсетуге болады:
Теория дженериков в Java or How на практике ставить скобки - 9
Бұл принцип PECS (Producer Extends Consumer Super) принципі деп те аталады. Сіз Habré туралы толығырақ « Java API пайдалану мүмкіндігін жақсарту үшін жалпы қойылмалы таңбаларды пайдалану » мақаласынан , сондай-ақ stackoverflow туралы тамаша талқылаудан оқи аласыз: « Generics Java-да қойылмалы таңбаларды пайдалану ». Міне, Java көзінен алынған шағын мысал - Collections.copy әдісі:
Теория дженериков в Java or How на практике ставить скобки - 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 How на практике ставить скобки - 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