Tipurile generice simplifică lucrul cu colecții, permițând tipizarea elementelor acestora. Definirea unui tip generic se realizează prin specificarea între paranteze unghiulare a unui tip de date Java, efectul fiind impunerea tipului respectiv pentru toate elementele colecției.
class Association<K, V> { private K key; private V value; public Association(K key, V value) { this.key = key; this.value = value; } public K getKey() { return this.key; } public V getValue() { return this.value; } }
Sintaxa <K, V> este folosită pentru a defini tipuri formale în cadrul definiției clasei. Aceste tipuri pot fi folosite în mod asemănător cu tipurile uzuale. În momentul în care invocăm efectiv o structură generică, ele vor fi înlocuite cu tipurile efective.
class Test { public static void main(String args[]) { Association<String, String> map1; map1 = new Association<String, String>("CC", "POO"); String s1 = map1.getKey(); Association<String, Integer> map2; map2 = new Association<String, Integer>("POO", 2015); String s2 = map2.getKey(); int nr = map2.getValue(); } }
class Test { public static void main(String args[]) { Association<String, Integer> map1; // Operația 1 map1 = new Association<String, Integer>("POO", 2015); Association<Object, Object> map2; // Operația 2 map2 = map1; // EROARE! } }
Există cazuri în care dorim să adăugăm într-o structură de date generică elemente care au un tip cu anumite proprietăți.
abstract class AVector<E extends Number> extends AbstractList<E> { abstract public boolean add(E obj); abstract public E get(int index); abstract public Enumeration<E> elements(); abstract public Iterator<E> iterator(); abstract public ListIterator<E> listIterator(); }
Sintaxa <E extends Number> indică faptul că tipul E este o subclasă a lui Number (sau chiar Number). Această restricție face imposibilă instanțierea unui obiect care să conțină elemente de tip String, deoarece String nu este o subclasă a lui Number.
Cuvântul cheie extends este folosit și în cazul în care dorim să indicăm un tip ce implementează o anumită interfață:
class Map<K extends Number, V extends Set<K>> extends Association<K, V> { public Map(K key, V value) { super(key, value); } public static void main(String args[]) { Map<Double, TreeSet<Double>> obj; TreeSet<Double> v = new TreeSet<Double>(); v.add(new Double(2.5)); obj = new Map<Double, TreeSet<Double>>(1.2, v); Double x = obj.getKey(); System.out.println(x); TreeSet<Double> set = obj.getValue(); System.out.println(set); } }
Wildcard-urile sunt utilizate atunci când dorim să întrebuințăm o structură generică drept parametru într-o funcție și nu dorim să limităm tipul de date din colecția respectivă.
// Fără wildcard - restrictiv (doar Collection<Object>) void boolean containsAll(Collection<Object> c) { for (Object e : c) { if (!this.contains(e)) { return false; } } return true; } // Cu wildcard - flexibil (orice Collection) void boolean containsAll(Collection<?> c) { for (Object e : c) { if (!this.contains(e)) { return false; } } return true; }
class TestClass { public static void main(String args[]) { // Operație permisă Collection<?> c = new LinkedList<Integer>(); // Eroare la compilare! c.add(new Object()); } }
Singurul element care poate fi adăugat este null, întrucât acesta este membru al oricărui tip referință. Operațiile de tip getter sunt posibile, întrucât rezultatul acestora poate fi mereu interpretat drept Object.
Mecanismul bazat pe Bounded Wildcards permite introducerea unor restricții asupra tipurilor ce pot înlocui un wildcard.
class TestClass { public static void printType(Set<? extends Number> set) { for (Number item : set) { System.out.println(item.getClass()); } } public static void main(String args[]) { Set<Number> set = new HashSet<Number>(); set.add(new Integer(5)); set.add(new Double(7.2)); set.add(new Float(10.5)); TestClass.printType(set); } }
Java ne oferă posibilitatea scrierii de metode generice (având un tip-parametru) pentru a facilita prelucrarea unor structuri generice.
class TestClass { // Metoda 1 - CORECTĂ public <T> void metoda1(T[] a, Collection<T> c) { for (T o : a) { // Operație permisă c.add(o); } } // Metoda 2 - EROARE la compilare public void metoda2(Object[] a, Collection<?> c) { for (Object o : a) { // Eroare la compilare! c.add(o); } } }
Exemplu cu bounded wildcards în metode generice:
class TestClass { List<Number> dest; public <T> void copy(Set<T> dest, List<? extends T> src) { for (T o : src) { dest.add(o); } } public <T extends Number> boolean addAll(Collection<T> c) { for (T o : c) { dest.add(o); } return true; } }
De multe ori în cadrul unei aplicații apare următorul scenariu: aveți de implementat o anumită funcționalitate care trebuie să poată fi extinsă fără să știți în momentul proiectării în ce mod va fi extinsă.
Exemplu: Lucrați la un joc de tipul shooter. Aveți o listă de arme pe care un user le poate achiziționa și utiliza. La un moment dat observați că încasările din joc încep să scadă odată ce alternativele de pe piață oferă mai multe feature-uri pentru armele lor.
Rezolvarea: Creați o clasă abstractă wrapper peste o instanță de armă. Dacă de exemplu vreți ca toate armele voastre să poată fi mortale dar silențioase, creați clasa WeaponSilencer ce implementează interfața de bază a armelor și are un obiect de tip armă intern. În metoda fire, în decorator, setați eventual sunetul jocului mai încet.
CipherOutputStream cos = new CipherOutputStream(new FileOutputStream("file"), c); PrintWriter pw = new PrintWriter(new OutputStreamWriter(cos)); pw.println("Stand and unfold yourself"); pw.close();
Participanți: