Кіріспе
JSE 5.0 нұсқасынан бастап генериктер Java тілінің арсеналына қосылды.Java тіліндегі генериктер дегеніміз не?
Генериктер (жалпылаулар) жалпыланған бағдарламалауды жүзеге асыруға арналған Java тілінің арнайы құралы: деректер мен алгоритмдерді сипаттаудың ерекше тәсілі, олардың сипаттамасын өзгертпей әр түрлі деректер түрлерімен жұмыс істеуге мүмкіндік береді. Oracle веб-сайтында генериктерге жеке оқу құралы арналған: “ Сабақ: Generics ”.
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ын қарастырайық: қолмен құю және генериктерді пайдалану:
Шикізат түрлері немесе шикі түрлері
Генериктер туралы айтатын болсақ, бізде әрқашан екі санат болады: терілген типтер (Жалпы типтер) және «шикі» типтер (шикі түрлер). Шикізат түрлері - бұрыштық жақшада «біліктілігі» көрсетілмеген түрлер:<>
ұғымымен байланысты . Өйткені, компилятор оң жақтағы <> белгісін көріп, сол жаққа қарайды, онда мән берілген айнымалының типінің декларациясы орналасқан. Ал осы бөліктен оң жақтағы мәннің қай типте терілетінін түсінеді. Шындығында, егер генерик сол жақта көрсетіліп, оң жағында көрсетілмесе, компилятор түрін шығара алады:
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")
Басу басып шығару, яғни сөзбе-сөз ескертулерді басу деп аударылады. Бірақ ойланыңыз, неліктен сіз оны көрсетуді шештіңіз? Бірінші ережені есте сақтаңыз және теруді қосу қажет болуы мүмкін.
Жалпы әдістер
Генериктер әдістерді теруге мүмкіндік береді. 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ты орындау қатемен орындалмайды.
Жалпы типтер
Сіз тек әдістерді ғана емес, сонымен қатар сыныптардың өзін де тере аласыз. 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 Олар өз кезегінде үш түрге бөлінеді:
- Жоғарғы шектелген қойылмалы таңбалар - < ? Сан > кеңейтеді
- Шексіз қойылмалы таңбалар - < ? >
- Төменгі шектелген қойылмалы таңбалар - < ? супер бүтін >
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>();
Мұрагерлік генериктермен басқаша жұмыс істейтіндіктен:
List<String> list1 = new ArrayList<>();
List<Object> list2 = list1;
Мұнда да бәрі қарапайым. List<String> List<Object> ұрпағы емес, дегенмен String Объектінің ұрпағы.
Финал
Сонымен, біз генериктер туралы жадымызды жаңарттық. Егер олар барлық күшінде сирек қолданылса, кейбір бөлшектер жадтан шығып қалады. Бұл қысқа шолу жадыңызды жаңартуға көмектеседі деп үміттенемін. Үлкен нәтижелерге қол жеткізу үшін мен сізге келесі материалдарды оқуды ұсынамын:- Юрий Ткач: шикізат түрлері - №1 генериктер - кеңейтілген Java
- Мұрагерлік және жалпы кеңейткіштер - Generics №2 - Жетілдірілген Java
- Рекурсивті типті кеңейтім - №3 генериктер - кеңейтілген Java
- Александр Маторин - Белгісіз генериктер
- Java тіліне кіріспе. Генериктер. Қойылмалы таңбалар | Технострим
- O'Reilly: Java Generics және Collections
GO TO FULL VERSION