Home > Informatik > Stufe Q1 > Referenzen > dynamische List

15.43 Offizieller Quelltext der Klasse List

Der komplette Quelltext

Hier nun der komplette Quelltext aus den Materialien zum Zentralabitur NRW.

 /**
 * Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
 * Generische Klasse List
 *
 * Objekt der generischen Klasse List verwalten beliebig viele linear
 * angeordnete Objekte vom Typ ContentType. Auf hoechstens ein Listenobjekt,
 * aktuellesObjekt genannt, kann jeweils zugegriffen werden.
 * Wenn eine Liste leer ist, vollstaendig durchlaufen wurde oder das aktuelle
 * Objekt am Ende der Liste geloescht wurde, gibt es kein aktuelles Objekt.
 * Das erste oder das letzte Objekt einer Liste koennen durch einen Auftrag zum
 * aktuellen Objekt gemacht werden. Ausserdem kann das dem aktuellen Objekt
 * folgende Listenobjekt zum neuen aktuellen Objekt werden.
 * Das aktuelle Objekt kann gelesen, veraendert oder geloescht werden. Ausserdem
 * kann vor dem aktuellen Objekt ein Listenobjekt eingefuegt werden.
 * 
 * @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
 * @version Generisch_06 2015-10-25
 */
public class List {

  /* --------- Anfang der privaten inneren Klasse -------------- */

  private class ListNode {

    private ContentType contentObject;
    private ListNode next;

    /**
     * Ein neues Objekt wird erschaffen. Der Verweis ist leer.
     */
    private ListNode(ContentType pContent) {
      contentObject = pContent;
      next = null;
    }

    /**
     * Der Inhalt des Knotens wird zurueckgeliefert.
     */
    public ContentType getContentObject() {
      return contentObject;
    }

    /**
     * Der Inhalt dieses Kontens wird gesetzt.
     */
    public void setContentObject(ContentType pContent) {
      contentObject = pContent;
    }

    /**
     * Der Nachfolgeknoten wird zurueckgeliefert.
     */
    public ListNode getNextNode() {
      return this.next;
    }

    /**
     * Der Verweis wird auf das Objekt, das als Parameter uebergeben
     * wird, gesetzt.
     */
    public void setNextNode(ListNode pNext) {
      this.next = pNext;
    }

  }

  /* ----------- Ende der privaten inneren Klasse -------------- */

  // erstes Element der Liste
  ListNode first;

  // letztes Element der Liste
  ListNode last;

  // aktuelles Element der Liste
  ListNode current;

  /**
   * Eine leere Liste wird erzeugt.
   */
  public List() {
    first = null;
    last = null;
    current = null;
  }

  /**
   * Die Anfrage liefert den Wert true, wenn die Liste keine Objekte enthaelt,
   * sonst liefert sie den Wert false.
   */
  public boolean isEmpty() {
    // Die Liste ist leer, wenn es kein erstes Element gibt.
    return first == null;
  }

  /**
   * Die Anfrage liefert den Wert true, wenn es ein aktuelles Objekt gibt,
   * sonst liefert sie den Wert false.
   */
  public boolean hasAccess() {
    // Es gibt keinen Zugriff, wenn current auf kein Element verweist.
    return current != null; 
  }

  /**
   * Falls die Liste nicht leer ist, es ein aktuelles Objekt gibt und dieses
   * nicht das letzte Objekt der Liste ist, wird das dem aktuellen Objekt in
   * der Liste folgende Objekt zum aktuellen Objekt, andernfalls gibt es nach
   * Ausfuehrung des Auftrags kein aktuelles Objekt, d.h. hasAccess() liefert
   * den Wert false.
   */
  public void next() {
    if (this.hasAccess()) {
      current = current.getNextNode();
    }
  }

  /**
   * Falls die Liste nicht leer ist, wird das erste Objekt der Liste aktuelles
   * Objekt. Ist die Liste leer, geschieht nichts.
   */
  public void toFirst() {
    if (!isEmpty()) {
      current = first;
    }
  }

  /**
   * Falls die Liste nicht leer ist, wird das letzte Objekt der Liste
   * aktuelles Objekt. Ist die Liste leer, geschieht nichts.
   */
  public void toLast() {
    if (!isEmpty()) {
      current = last;
    }
  }

  /**
   * Falls es ein aktuelles Objekt gibt (hasAccess() == true), wird das
   * aktuelle Objekt zurueckgegeben, andernfalls (hasAccess() == false) gibt
   * die Anfrage den Wert null zurueck.
   */
  public ContentType getContent() {
    if (this.hasAccess()) {
      return current.getContentObject();
    } else {
      return null;
    }
  }

  /**
   * Falls es ein aktuelles Objekt gibt (hasAccess() == true) und pContent
   * ungleich null ist, wird das aktuelle Objekt durch pContent ersetzt. Sonst
   * geschieht nichts.
   */
  public void setContent(ContentType pContent) {
    // Nichts tun, wenn es keinen Inhalt oder kein aktuelles Element gibt.
    if (pContent != null && this.hasAccess()) { 
      current.setContentObject(pContent);
    }
  }

  /**
   * Falls es ein aktuelles Objekt gibt (hasAccess() == true), wird ein neues
   * Objekt vor dem aktuellen Objekt in die Liste eingefuegt. Das aktuelle
   * Objekt bleibt unveraendert.
   * Wenn die Liste leer ist, wird pContent in die Liste eingefuegt und es
   * gibt weiterhin kein aktuelles Objekt (hasAccess() == false). 
   * Falls es kein aktuelles Objekt gibt (hasAccess() == false) und die Liste
   * nicht leer ist oder pContent gleich null ist, geschieht nichts.
   */
  public void insert(ContentType pContent) {
    if (pContent != null) { // Nichts tun, wenn es keinen Inhalt gibt.
      if (this.hasAccess()) { // Fall: Es gibt ein aktuelles Element.

        // Neuen Knoten erstellen.
        ListNode newNode = new ListNode(pContent); 

        if (current != first) { // Fall: Nicht an erster Stelle einfuegen.
          ListNode previous = this.getPrevious(current);
          newNode.setNextNode(previous.getNextNode());
          previous.setNextNode(newNode);
        } else { // Fall: An erster Stelle einfuegen.
          newNode.setNextNode(first);
          first = newNode;
        }

      } else { //Fall: Es gibt kein aktuelles Element.

        if (this.isEmpty()) { // Fall: In leere Liste einfuegen.

          // Neuen Knoten erstellen.
          ListNode newNode = new ListNode(pContent); 

          first = newNode;
          last = newNode;
        }

      }
    }
  }

  /**
   * Falls pContent gleich null ist, geschieht nichts.
   * Ansonsten wird ein neues Objekt pContent am Ende der Liste eingefuegt.
   * Das aktuelle Objekt bleibt unveraendert. 
   * Wenn die Liste leer ist, wird das Objekt pContent in die Liste eingefuegt
   * und es gibt weiterhin kein aktuelles Objekt (hasAccess() == false).
   */
  public void append(ContentType pContent) {
    if (pContent != null) { // Nichts tun, wenn es keine Inhalt gibt.

      if (this.isEmpty()) { // Fall: An leere Liste anfuegen.
        this.insert(pContent);
      } else { // Fall: An nicht-leere Liste anfuegen.

        // Neuen Knoten erstellen.
        ListNode newNode = new ListNode(pContent); 

        last.setNextNode(newNode);
        last = newNode; // Letzten Knoten aktualisieren.
      }

    }
  }

  /**
   * Falls es sich bei der Liste und pList um dasselbe Objekt handelt,
   * pList null oder eine leere Liste ist, geschieht nichts.
   * Ansonsten wird die Liste pList an die aktuelle Liste angehaengt.
   * Anschliessend wird pList eine leere Liste. Das aktuelle Objekt bleibt
   * unveraendert. Insbesondere bleibt hasAccess identisch.
   */
  public void concat(List pList) {
    if (pList != this && pList != null && !pList.isEmpty()) { // Nichts tun,
    // wenn pList und this identisch, pList leer oder nicht existent.

      if (this.isEmpty()) { // Fall: An leere Liste anfuegen.
        this.first = pList.first;
        this.last = pList.last;
      } else { // Fall: An nicht-leere Liste anfuegen.
        this.last.setNextNode(pList.first);
        this.last = pList.last;
      }

      // Liste pList loeschen.
      pList.first = null;
      pList.last = null;
      pList.current = null;
    }
  }

  /**
   * Wenn die Liste leer ist oder es kein aktuelles Objekt gibt (hasAccess()
   * == false), geschieht nichts.
   * Falls es ein aktuelles Objekt gibt (hasAccess() == true), wird das
   * aktuelle Objekt geloescht und das Objekt hinter dem geloeschten Objekt
   * wird zum aktuellen Objekt. 
   * Wird das Objekt, das am Ende der Liste steht, geloescht, gibt es kein
   * aktuelles Objekt mehr.
   */
  public void remove() {
    // Nichts tun, wenn es kein aktuelle Element gibt oder die Liste leer ist.
    if (this.hasAccess() && !this.isEmpty()) { 

      if (current == first) {
        first = first.getNextNode();
      } else {
        ListNode previous = this.getPrevious(current);
        if (current == last) {
          last = previous;
        }
        previous.setNextNode(current.getNextNode());
      }

      ListNode temp = current.getNextNode();
      current.setContentObject(null);
      current.setNextNode(null);
      current = temp;

      //Beim loeschen des letzten Elements last auf null setzen. 
      if (this.isEmpty()) {
        last = null;
      }
    }
  }

  /**
   * Liefert den Vorgaengerknoten des Knotens pNode. Ist die Liste leer, pNode
   * == null, pNode nicht in der Liste oder pNode der erste Knoten der Liste,
   * wird null zurueckgegeben.
   */
  private ListNode getPrevious(ListNode pNode) {
    if (pNode != null && pNode != first && !this.isEmpty()) {
      ListNode temp = first;
      while (temp != null && temp.getNextNode() != pNode) {
        temp = temp.getNextNode();
      }
      return temp;
    } else {
      return null;
    }
  }
  
}

}

Anzeige der Klasse List als Textdatei.

Im Prinzip ist diese Klasse ähnlich implementiert wie die von mir selbst erstellte Klasse List. Die innere Klasse heißt hier ListNode und enthält wesentlich mehr manipulierende und sondierende Methoden als bei meiner Version der Klasse List.

Ein viel wichtigerer Unterschied ist hier die Verwendung generischer Datentypen. Generische Datentypen sind quasi Variable für Datentypen. Man legt sich bei der Implementation also nicht auf einen bestimmten Datentypen wie int, float, Person, Bruch etc. fest, sondern schreibt - in spitzen Klammern -  <ContentType>. Bei meiner Version habe ich stattdessen immer den Datentyp Object angegeben, was auf den ersten Blick das Gleiche leistet.

Wenn nun eine Java-Klasse die NRW-Liste benutzen möchte, muss das List-Attribut dieser Klasse folgendermaßen deklariert werden:

private List<Athlet> liste;

In diesem Beispiel wird eine Liste generiert, die ausschließlich Objekte der Klasse Athlet enthält. Alternativ hätte man hier auch Klassen wir Bruch, Person etc. hinschreiben können. Was aber leider nicht geht, sind primitive Datentypen wie int oder float. Betrachten wir nun folgenden Quelltext:

public class TesteDieListe
{
    List<Athlet> liste;
    
    public TesteDieListe()
    {
        liste = new List();
        
        liste.append(new Athlet("Helmich",12));
        liste.append(new Athlet("Müller",13));
        liste.append(new Athlet("Meier",14));
        liste.append(new Athlet("Schulze",15));
        liste.append(new Athlet("Epp",16));
        liste.append(new Athlet("Penner",17));     
        
        liste.toFirst();
        System.out.println(liste.getContent().getNachName());
    }
}

Hier ist es tatsächlich möglich, ohne Typenumwandlung (Typecasting) direkt auf die Inhalte der Liste zuzugreifen. Das ist ein enormer Vorteil der generischen Programmierung in Java. Hätten wir stattdessen die Liste von mir genommen, in der die Daten als Objekte der Klasse Object gespeichert werden, so müsste der Quelltext folgendermaßen aussehen:

public class TesteDieListe
{
    List<Athlet> liste;
    
    public TesteDieListe()
    {
        liste = new List();
        
        liste.append(new Athlet("Helmich",12));
        liste.append(new Athlet("Müller",13));
        liste.append(new Athlet("Meier",14));
        liste.append(new Athlet("Schulze",15));
        liste.append(new Athlet("Epp",16));
        liste.append(new Athlet("Penner",17));     
        
        liste.toFirst();
        Athlet a = (Athlet) liste.getContent();
        System.out.println(a.getNachName());
    }
}

In der Zeile

 Athlet a = (Athlet) liste.getContent();

wird mit liste.getContent ein allgemeines Objekt aus der Liste extrahiert, dessen Klasse noch nicht festgelegt ist. Erst durch die Typenumwandlung (Athlet) wird aus dem allgmeinen Objekt ein Objekt der Klasse Athlet. Und erst dann kann von diesem Objekt die sondierende Methode getNachName aufgerufen werden. Ein allgmeines Objekt der Klasse Object hat keine sondierende Methode getNachName.

Die Tatsache, dass in der Athleten-Liste ausschließlich Objekte der Klasse Athlet gespeichert werden können, ist gleichzeitig ein Vor- und ein Nachteil.

  • Vorteil: Es können ausschließlich Objekte der Klasse Athlet gespeichert werden. Es ist nicht möglich, aus Versehen andere Datentypen in der Liste unterzubringen.
  • Nachteil: Es können ausschließlich Objekte der Klasse Athlet gespeichert werden. Es ist nicht möglich, bei Bedarf andere Datentypen in der Liste unterzubringen.

Diesem Nachteil kann man aber mit Hilfe von Vererbungs-Techniken begegnen. Man gibt als ContentType einfach die Mutterklasse an, und kann dann auch Objekte aller Tochterklassen in der Liste speichern:

public class TesteDieListe
{
    List<Athlet> liste;
    
    public TesteDieListe()
    {
        liste = new List();
        
        liste.append(new Athlet("Helmich",12));
        liste.append(new Athlet("Müller",13));
        liste.append(new Leichtathlet("Meier","Klaus",14));
        liste.append(new Athlet("Schulze",15));
        liste.append(new Leichtathlet("Epp","Johann",16));
        liste.append(new Athlet("Penner",17));     
        
        liste.toFirst();
        System.out.println(liste.getContent().getNachName());
        liste.next();
        System.out.println(liste.getContent().getNachName());
        liste.next();
        System.out.println(liste.getContent().getNachName());
    }
}

Anzeige dieser Testklasse als Textdatei.

Die Klasse Leichtathlet ist eine Tochterklasse von Athlet. Der Konstruktor von Leichtathlet nimmt drei Parameter entgegen, den Nachnamen, den Vornamen und die Punktezahl. Der getContent-Befehl hat nun kein Problem, das richtige Objekt anzuliefern, wenn der Nachname ausgegeben werden soll. Hätte man aber bei dem ersten Listenobjekt nach dem Vornamen gefragt, also mit

        liste.toFirst();
        System.out.println(liste.getContent().getVorname());

dann hätte es eine Fehlermeldung des Java-Compilers gegeben, weil das erste Listenobjekt der Klasse Athlet angehört und nicht der Klasse Leichtathlet. Die Klasse Athlet besitzt aber keine sondierende Methode getVorname.