Naar inhoud springen

Genericiteit

Uit Wikipedia, de vrije encyclopedie

Genericiteit of generics is een voorziening voor programmeertalen die generiek programmeren toestaat. Dit betekent dat algoritmes kunnen worden geschreven in een bepaalde syntaxis waarbij de algoritmes adaptief zijn maar tevens nog door de compiler geïnstantieerd kunnen worden. Zo kunnen algoritmes generiek worden geschreven zonder snelheidsverlies. Een sorteeralgoritme hoeft zich met behulp van generiek programmeren niet bezig te houden met wat voor datatype het precies sorteert, maar alleen hoe het dat doet.

Generiek programmeren ligt dicht tegen meta-programmeren aan, een techniek waarbij aan de hand van bepaalde broncode weer nieuwe broncode wordt geprogrammeerd, die daarna wordt gecompileerd. Bij generiek programmeren gaat het echter om een syntactische en semantische uitbreiding in de programmeertaal en wordt er niet direct 'nieuwe' broncode gegenereerd.

Genericiteit werd rond 1970 onderdeel van een aantal programmeertalen, zoals CLU, Ada. Later werd de techniek ook onderdeel van veel andere object-georiënteerde talen, zoals C++, C#, Eiffel en Java (vanaf versie 1.5).

Voorbeelden van generiek programmeren

[bewerken | brontekst bewerken]

Geparametriseerde klasse

[bewerken | brontekst bewerken]

Een veelgebruikte toepassing van generiek programmeren is het schrijven van 'container'-klassen, waarin allerlei objecten kunnen worden opgeslagen. Tijdens het schrijven van de container (bijvoorbeeld een lijst, een hashtable of een associatieve array) hoeft het type object dat wordt opgeslagen niet bekend te zijn. Een voorbeeld in C++:

template<typename T>
class List
{ 
  // standaard lijstlogica
};
List<Animal> listOfAnimals;
List<Car> listOfCars;

In de hierboven geschetste 'List'-klasse wordt het te gebruiken type T genoemd tijdens de definitie van de lijst-klasse, en wordt deze door de compiler als het ware gesubstitueerd door Animal of Car op het moment dat deze de typenaam List<Animal> tegenkomt.

Geparametriseerde functie

[bewerken | brontekst bewerken]

Een voorbeeld van een generiek algoritme in C++ is het volgende:

template<typename T>
T& highest(const T& a, const T& b)
{
  if(a > b)
    return a;
  else
    return b;
}

Hierbij is nog niet bekend wat het type is van de parameters a en b, maar wordt wel het algoritme beschreven. Als we bovenstaande functie willen gebruiken voor getallen, dan kan dat simpelweg zo:

int x = 10;
int y = 50;
int z = highest(x,y);

Waarbij z de waarde 50 zal krijgen. Uit de gegeven parameters (x en y, allebei int, geheel getal) leidt de compiler het type voor de generieke parameter T af. Als in plaats van twee ints bijvoorbeeld een string en een float als parameter worden gegeven kan de compiler geen type T bepalen: het type van de eerste en tweede parameter moet namelijk gelijk zijn (of converteerbaar naar een gemeenschappelijke T).

Geparametriseerde superklasse

[bewerken | brontekst bewerken]

Een derde manier van generiek programmeren die vaak wordt toegepast (in Java en C++) is het parametriseren van de superklasse. Stel, we hebben een interface Comparable, die een methode compare definieert:

interface Comparable {
  public int compare(Object b);
}

public class Integer implements Comparable {
  private int value;
     
  public int compare(Object b)
  {
    return value > (Integer) b;
  }
}

De compare-methode kan nu alle objecten vergelijken die subklassen zijn van Object (In Java zijn alle klassen dat). Als de parameter b van type Integer is, gaat de vergelijking goed. Het probleem is alleen dat het mogelijk is voor b een instantie van een compleet ander type dan Integer te geven. De methode zal dus moeten controleren of b van het juiste type is (namelijk Integer):

public int compare(Object b)
{
  if(b instanceof Integer)
    return value > (Integer) b;
  else
    throw new IllegalArgumentException("Parameter b heeft een verkeerd type");
}

Met generiek programmeren kan dit probleem op een nette manier opgelost worden:

interface Comparable<T> {
  public int compare(T b);
}

public class Integer implements Comparable<Integer>
{
  private int value;
     
  public int compare(Integer b)
  {
    return value > b;
  }
}

Nu is de interface, Comparable, generiek gemaakt. Integer implementeert niet zomaar Comparable, maar implementeert Comparable<Integer>. Alle T's in de definitie van Comparable worden gesubstitueerd door Integer. Het type van parameter b kan nu tijdens het compileren worden gecontroleerd, en er kan at-runtime geen fout meer optreden door het gebruik van een verkeerd type. Omdat de superklasse van Integer geparametriseerd is met Integer zelf, spreken we van een geparametriseerde superklasse.