Tipuri generice

Genericitatea sau mai bine spus, tipurile parametrizate au fost introduce în Java începând cu versiunea 5.0 (identificatorul versiunii fiind de fapt 1.5.0).

Tipurile generice reprezintă un mecanism cu ajutorul căruia, prin intermediul parametrilor, se poate specifica tipul obiectelor cu care o anumită clasă va opera.

Acest concept este similar, într-o oarecare măsură template-urilor din C++. Pentru prezentarea genericității ne vom referi la colecții de obiecte . În versiunile anterioare versiunii 5.0, colecțiile puteau conține orice obiect, existând riscul apariției unei excepții de tipul ClassCastException, datorită introducerii involuntare a unui obiect diferit de tipul obiectelor pentru care a fost folosită colecția. În momentul în care se dorește extragerea obiectului din colecție, cast-ul făcut asupra acestuia are ca rezultat apariția excepției mai sus amintită.

import java.util.ArrayList;
 
public class Test1 {
	static ArrayList a =new ArrayList();
 
	public static void main	(String[] args) {
		a.add("Marius");
		a.add("este student in anul ");
		a.add(new Integer(4));
		a.add("la UTCN");
		a.add("...");
		print();
}
 
	public static void print() {
		for (int i = 0; i < a.size();i++) {
			String s = (String)
			a.get(i);
			System.out.print(s +
			" ");
		}
		System.out.println();
	}
}//the end

Codul anterior se compilează corect, dar la execuţie, în cadrul metodei print() este generată o excepţie ClassCastException datorită faptului că un obiect de tipul Integer nu poate fi convertit la tipul String.

În Java 5 tipul obiectelor care vor fi stocate într-o colecţie poate fi precizat în momentul declarării colecţiei, astfel, codul următor poate fi transcris în Java 5 în:

import java.util.ArrayList;

public class Test2 {
	static ArrayList<String> a =new ArrayList<String>();
	public static void main(String[] args) {
		a.add("Marius");
		a.add("este student in anul ");
		a.add(new Integer(4));
		a.add("la UTCN");
		a.add("...");
		print();
	}
	
public static void print() {
		for (int i = 0; i < a.size();i++) {
			String s = a.get(i);
			System.out.print(s + " ");
		}
		System.out.println();
	}
}//the end

Acest cod nu poate fi compilat deoarece s-a declarat o colecţie care conţine obiecte de tipul String, iar în cadrul metodei main se încearcă adăugarea unui obiect de tipul Integer.

În momentul extragerii unui obiect dintr-o colecţie pentru care s-a specificat tipul obiectelor conţinute, nu mai este necesară conversia rezultatului la tipul dorit. În metoda print, linia de cod a.get(i) returnează un obiect de tipul String.

Declararea tipurilor parametrizate de date

Următoarea aplicație exemplifică modul în care se declară un tip generic:

public class NV <N,V> {
	private N name;
	private V value;
 
	public NV(N name, V value) {
		this.name = name;
		this.value = value;
	}
 
	public N getName() {
		return name;
	}
 
	public V getValue() {
		return value;	
	}
 
	public static void main(String[] args) {
		NV<String, Integer> nv =new NV<String, Integer>	
		("Ionel", new Integer(4));
		System.out.println(nv.getName());
		System.out.println(nv.getValue());
	}
}//the end

După cum se poate observa în cadrul codului anterior, pentru a declara parametrii unei clase se folosesc caracterele “<” şi “>”. Între aceste caractere se află lista parametrilor. O clasă parametrizată poate fi folosită ca şi cum nu ar fi parametrizată, implicit valoarea parametrilor fiind Object. Faptul că anumite clase sunt parametrizate este utilizat doar de compilatorul Java nu şi de maşina virtuală, codul generat de compilatorul Java 5 putând fi executat şi de versiunea 1.4 a platformei.

Domeniul de definiţie al parametrilor unei clase poate fi restrâns, în sensul că poate fi specificat tipul acestora (clasele şi/sau interfeţele din care aceştia trebuie să fie derivaţi).

Se exemplifică modul de restricționare al domeniului de definiție al parametrilor clasei:

public class NVrestrictie <N,V extends Number> {
	private N name;
	private V value;
 
	public NVrestrictie(N name, V value) {
		this.name = name;
		this.value = value;
	}
 
	public N getName() {
		return name;
	}
 
	public V getValue() {
		return value;
	}
}

Un cod care ar conţine o declaraţie de forma: NV<String, Date> nu ar putea fi compilat deoarece cel de-al doilea parametru al clasei trebuie să extindă clasa Number. Domeniul de definiţie al parametrilor poate fi restrâns astfel încât anumiţi parametri să implementeze ai multe interfeţe. O declaraţie în care un parametru implementează mai multe interfeţe poate fi:

public class NV <N,V extends Number & Serializable>

Introducerea tipurilor parametrizate de date a dus la modificarea modului de declaraţie a metodelor, astfel că pot apare secvenţe de cod ca:

void print(NV<? extends X, Y>);

sau

void print(NV<? super X, Y>)

În cadrul primei declaraţii, metoda print() primeşte ca parametru un obiect de tipul NV, iar primul parametru al clasei NV trebuie să fie derivat din X, unde X este un parametru al clasei curente. În cadrul celei de-a doua declaraţii, metoda print() primeşte ca parametru un obiect de tipul NV, iar primul parametru al clasei NV trebuie să fie părintele lui X, unde X este un parametru al clasei curente.

Parametrii claselor pot fi utilizaţi şi pentru a arunca excepţii în Java 5.

public interface Except<E extends Exception> {
	public void doRun() throws E;
}
public class Action implements Except<FileNotFoundException> {
	public void doWork() throws	FileNotFoundException {
		new File("/foo/bar.txt");
	}
}

O clasă poate să extindă o clasă parametrizată şi/sau să suprascrie metode care se folosesc de parametrii clasei. Regula de bază în momentul moştenirii unei clase sau suprascrierii unei metode este aceea că tipurile de date utilizate să fie subtipuri ale tipurilor definite în super-clasa sau într-o super-interfaţă. Dacă înainte de Java 5 metodele care suprascriau alte metode trebuiau să returneze acelaşi tip de date, de această dată trebuie să returneze un tip de dată derivat din tipul de dată al metodei suprascrise.