Szia! A Java Genericsről fogunk beszélni. Azt kell mondanom, hogy sokat fogsz tanulni! Nemcsak ez a lecke, hanem a következő néhány óra is a generikus terápiának lesz szentelve. Ha tehát érdeklődik a generikumok iránt, ma szerencsés napja van: sok mindent megtudhat a generikumok jellemzőiről. És ha nem, akkor mondjon le és lazítson! :) Ez egy nagyon fontos téma, és ezt tudni kell. Kezdjük az egyszerűvel: a „mit” és a „miért”.
A könyv 23. fejezetének nagyon beszédes a címe: "Ne használj nyers típusokat az új kódban" Erre kell emlékezned. Általános osztályok használatakor soha ne alakítson általános típust nyers típussá .
Sok sikert a tanuláshoz! :)
Mik azok a Java Generics?
Az általánosok olyan típusok, amelyeknek van paraméterük. Az általános típus létrehozásakor nem csak egy típust kell megadnia, hanem azt az adattípust is, amellyel dolgozni fog. Gondolom, a legszembetűnőbb példa már eszedbe jutott: ArrayList! Általában így készítünk egyet egy programban:
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> myList1 = new ArrayList<>();
myList1.add("Test String 1");
myList1.add("Test String 2");
}
}
Ahogy sejthető, ennek a listának az a sajátossága, hogy nem tudunk mindent belerakni: kizárólag String objektumokkal működik. Most vessünk egy kis kitérőt a Java történetébe, és próbáljunk meg válaszolni a „miért?” kérdésre. Ehhez megírjuk az ArrayList osztály saját egyszerűsített változatát. A listánk csak azt tudja, hogyan kell adatokat hozzáadni egy belső tömbhöz és lekérni az adatokat:
public class MyListClass {
private Object[] data;
private int count;
public MyListClass() {
this.data = new Object[10];
this.count = 0;
}
public void add(Object o) {
this.data[count] = o;
count++;
}
public Object[] getData() {
return data;
}
}
Tegyük fel, hogy azt szeretnénk, hogy a listánk csak egész számokat tároljon. Nem általános típust használunk. Nem akarunk explicit "instanceof Integer " ellenőrzést beépíteni az add() metódusba. Ha megtennénk, akkor az egész osztályunk csak az Integer -re lenne alkalmas , és a világ összes többi adattípusához hasonló osztályt kellene írnunk! Bízunk a programozóinkban, és csak megjegyzést kell hagynunk a kódban, hogy megbizonyosodjunk arról, hogy nem adnak hozzá semmit, amit nem szeretnénk:
// Use this class ONLY with the Integer data type
public void add(Object o) {
this.data[count] = o;
count++;
}
Az egyik programozó figyelmen kívül hagyta ezt a megjegyzést, és véletlenül több karakterláncot is elhelyezett egy számlistában, majd kiszámolta az összegüket:
public class Main {
public static void main(String[] args) {
MyListClass list = new MyListClass();
list.add(100);
list.add(200);
list.add("Lolkek");
list.add("Shalala");
Integer sum1 = (Integer) list.getData()[0] + (Integer) list.getData()[1];
System.out.println(sum1);
Integer sum2 = (Integer) list.getData()[2] + (Integer) list.getData()[3];
System.out.println(sum2);
}
}
Konzol kimenet:
300
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at Main.main (Main.java:14)
Mi a legrosszabb része ennek a helyzetnek? Természetesen nem a programozó figyelmetlensége miatt. A legrosszabb az, hogy a helytelen kód a programunk fontos helyére került, és sikeresen lefordítottuk. Most nem kódírás közben fogunk találkozni a hibával, hanem csak tesztelés közben (és ez a legjobb eset!). A hibák javítása a fejlesztés későbbi szakaszaiban sokkal többe kerül – mind pénzben, mind időben. Pontosan ez az, ahol a generikusok hasznunkra válnak: egy általános osztály segítségével a szerencsétlen programozó azonnal észleli a hibát. A program egyszerűen nem áll le!
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> myList1 = new ArrayList<>();
myList1.add(100);
myList1.add(100);
myList1.add ("Lolkek"); // Error!
myList1.add("Shalala"); // Error!
}
}
A programozó azonnal rájön a hibájára, és azonnal jobban lesz. Egyébként nem kellett saját List osztályt létrehoznunk, hogy láthassuk ezt a fajta hibát. Egyszerűen távolítsa el a szögletes zárójeleket, és írja be ( <Integer> ) egy közönséges ArrayList-ből!
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List list = new ArrayList();
list.add(100);
list.add(200);
list.add("Lolkek");
list.add("Shalala");
System.out.println((Integer) list.get(0) + (Integer) list.get(1));
System.out.println((Integer) list.get(2) + (Integer) list.get(3));
}
}
Konzol kimenet:
300
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at Main.main(Main.java:16)
Más szavakkal, még a Java „natív” mechanizmusait használva is elkövethetünk ilyen hibát, és nem biztonságos gyűjteményt hozhatunk létre. Ha azonban beillesztjük ezt a kódot egy IDE-be, figyelmeztetést kapunk: "Nem ellenőrzött hívás az add(E)-hez a java.util.List nyers típusának tagjaként" Azt mondják, hogy valami elromolhat egy elem hozzáadásakor. olyan gyűjteményhez, amelyből hiányzik az általános típus. De mit jelent a „nyers típus” kifejezés? A nyers típus egy általános osztály, amelynek típusát eltávolították. Más szavakkal, a List myList1 egy nyers típus . A nyers típus ellentéte egy általános típus – egy általános osztály a paraméterezett típus(ok) jelzésével . Például List<String> myList1. Felmerülhet a kérdés, hogy a nyelv miért engedi meg a nyers típusok használatát ? Az ok egyszerű. A Java készítői meghagyták a nyers típusok támogatását a nyelvben, hogy elkerüljék a kompatibilitási problémákat. Mire a Java 5.0 megjelent (ebben a verzióban jelentek meg először az általánosságok), sok kódot már írtak nyers típusokkal . Ennek eredményeként ez a mechanizmus ma is támogatott. A leckéken többször is megemlítettük Joshua Bloch klasszikus könyvét, az „Effective Java” (Hatékony Java). A nyelv egyik megalkotójaként könyvében nem hagyta ki a nyers és általános típusokat .
Általános módszerek
A Java lehetővé teszi az egyes metódusok paraméterezését úgynevezett általános metódusok létrehozásával. Mennyire hasznosak az ilyen módszerek? Mindenekelőtt abban hasznosak, hogy különböző típusú metódusparaméterekkel dolgozhatunk. Ha ugyanaz a logika biztonságosan alkalmazható különböző típusokra, akkor egy általános módszer nagyszerű megoldás lehet. Tekintsük ezt egy nagyon egyszerű példának: Tegyük fel, hogy van egy myList1 nevű listánk . Szeretnénk eltávolítani az összes értéket a listáról, és minden üres helyet kitölteni új értékekkel. Így néz ki az osztályunk egy általános módszerrel:
public class TestClass {
public static <T> void fill(List<T> list, T val) {
for (int i = 0; i < list.size(); i++)
list.set(i, val);
}
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
strings.add("Old String 1");
strings.add("Old String 2");
strings.add("Old String 3");
fill(strings, "New String");
System.out.println(strings);
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
fill(numbers, 888);
System.out.println(numbers);
}
}
Ügyeljen a szintaxisra. Kicsit szokatlannak tűnik:
public static <T> void fill(List<T> list, T val)
<T>-t írunk a visszatérési típus elé. Ez azt jelzi, hogy általános módszerrel van dolgunk. Ebben az esetben a metódus 2 paramétert fogad be bemenetként: a T objektumok listáját és egy másik különálló T objektumot. A <T> használatával paraméterezzük a metódus paramétertípusait: nem adhatunk át egy karakterláncot és egy egész számot. A karakterláncok és egy karakterlánc listája, egész számok és egész számok listája, saját Cat objektumaink listája és egy másik Cat objektum – ezt kell tennünk. A main() metódus azt szemlélteti, hogy a fill() metódus hogyan használható könnyen különböző típusú adatokkal való munkavégzésre. Először a metódust használjuk a karakterláncok és egy karakterlánc bemeneti listájával, majd az egészek és egy egész számok listájával. Konzol kimenet:
[New String, New String, New String] [888, 888, 888]
Képzeljük el, ha nem léteznének általános metódusaink, és szükségünk lenne a fill() metódus logikájára 30 különböző osztályhoz. Ugyanazt a metódust 30-szor kellene leírnunk különböző adattípusokhoz! De az általános módszereknek köszönhetően újra felhasználhatjuk kódunkat! :)
Általános osztályok
Ön nem korlátozódik a szabványos Java-könyvtárak általános osztályaira – létrehozhatja saját osztályait! Íme egy egyszerű példa:
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
Box<String> stringBox = new Box<>();
stringBox.set("Old String");
System.out.println(stringBox.get());
stringBox.set("New String");
System.out.println(stringBox.get());
stringBox.set(12345); // Compilation error!
}
}
A Box<T> osztályunk egy általános osztály. Ha a létrehozás során hozzárendelünk egy adattípust ( <T> ), abban már nem tudunk más típusú objektumokat elhelyezni. Ez látható a példában. Objektumunk létrehozásakor jeleztük, hogy a Strings-szel működne:
Box<String> stringBox = new Box<>();
És az utolsó kódsorban, amikor megpróbáljuk a 12345-ös számot beírni a dobozba, fordítási hibát kapunk! Ilyen egyszerű! Létrehoztuk saját általános osztályunkat! :) Ezzel a mai lecke véget is ér. De nem mondunk búcsút a generikus gyógyszereknek! A következő leckékben a fejlettebb funkciókról fogunk beszélni, szóval ne menj el! ) A tanultak megerősítése érdekében javasoljuk, hogy nézzen meg egy videóleckét a Java-tanfolyamról
GO TO FULL VERSION