Fehlermeldungen

Fehlermeldungen beim Compilieren

  1. Missing return statement (in Eclipse: „This method must return a result of type...“)

    Diese Fehlermeldung tritt auf, wenn eine Methode deklariert, einen bestimmten Rückgabetyp zu haben (also nicht als void deklariert ist), der Compiler aber nicht sicherstellen kann, dass tatsächlich ein entsprechender Wert zurückgegeben wird.

    Im einfachsten Fall liegt das daran, dass schlicht eine entsprechende Return-Anweisung fehlt.

    Es kann aber auch sein, dass die Return-Anweisung zwar vorhanden ist, die Methode aber auch regulär beendet werden kann, ohne dass die Anweisung ausgeführt wird. Ein typisches Beispiel ist das folgende, in dem im Fall einer Division durch 0 die auftretende Exception abgefangen wird – die Methode also nicht abrupt beendet wird, sondern regulär – aber im Catch-Block eben kein Wert zurückgegeben wird:

        int dividiere(int dividend, int divisor) {
            try {
                return dividend / divisor;
            } catch (ArithmeticException e) {
                e.printStackTrace();
            }
        }
  2. Non-static method /field ... cannot be referenced from a static context (in Eclipse: „Cannot make a static reference to the non-static method / field ...“)

    Siehe dazu im Kapitel „Variable / Attribut / Klassenattribut / lokale Variable“ den Abschnitt „Warum darf eine Klassenmethode nur auf Klassenattribute zugreifen?“ (das dort bzgl. Attributen Gesagte gilt analog für Exemplarmethoden) sowie das komplette Kapitel „Verwendung von static“.

  3. „The method m() of type X should be tagged with @Override since it actually overrides a superinterface / superclass method“ (Warnung in Eclipse)

    Seit Java 1.6 gibt es in Java die Möglichkeit, sog Annotations zu verwenden. Von diesen sind einige bereits „in Java eingebaut“, eine davon ist @Override. Wenn Sie diese Annotation vor eine Methodendeklaration setzen, dokumentieren Sie damit explizit, dass Ihre Methode eine Methode eines Supertyps überschreibt. Sollte das dann aber gar nicht der Fall sein, etwa, weil Sie versehentlich eine andere Parameteranzahl oder andere Parametertypen gewählt haben als bei der Methode, die Sie überschreiben wollten, wird der Compiler Ihnen eine Fehlermeldung geben.

    Um nun das volle Potential von @Override auszunutzen, ist es sinnvoll, diese Annotation konsequent zu verwenden und sich ein fehlendes @Override bei einer Methode, welche eine Superklassenmethode überschreibt, zumindest als Warnung melden zu lassen. Bei dem Eclipse-Workspace, den Sie auf http://feu.mpaap.de/eclipse/index.html herunterladen können, ist diese Einstellung bereits vorgenommen und wenn Sie in einem Subtyp einer Methode eines Supertyps überschreiben, ohne diese Absicht durch @Override explizit zu dokumentieren, erhalten Sie die oben genannte Warnung.

Fehlermeldungen beim Ausführen

  1. NullPointerException (NPE)

    Eine NPE tritt genau dann auf, wenn man versucht, das „Objekt hinter einer Referenz“ anzusprechen, also zu „dereferenzieren“, man aber in Wirklichkeit die leere Referenz „null“ vor sich hat, es also gar kein referenziertes Objekt gibt.

    Dieses Dereferenzieren findet statt, wenn auf eine Methode oder ein Attribut eines Objekts zugegriffen werden soll. Man kann den Punkt als Dereferenzierungsoperator lesen, analog zu den entsprechenden Operatoren z.B. in C oder Pascal. Außerdem findet eine Dereferenzierung statt, wenn man auf ein Element eines Arrays zugreift: Auch ein Array ist ja ein Objekt und eine Referenz, von der man glaubt, sie verweise auf einen Array, kann natürlich auch den Wert null haben.

    Wenn man eine NullPointerException bekommt, ist der erste Schritt also, herauszufinden, welche Referenz, die man zu dereferenzieren versucht, eigentlich den Wert null hat. Dazu bietet es sich an, die betreffende Zeile so zu zerlegen, dass man pro Zeile nur noch eine Dereferenzierung hat. Lautet die Zeile, in der die NPE auftritt beispielsweise

        String name = allPersons.getPersonArray()[2].getName();

    so gibt es drei Dereferenzierungen und man könnte die Zeile wie folgt zerlegen:

        Person[] persons = allPersons.getPersonArray();
        Person person = persons[2];
        String name = person.getName();

    Führt man nun das Programm erneut aus, kann man diesmal an der Zeilennummer der Exception genau erkennen, welche Referenz den Wert null hatte. Dann muss man „nur“ noch herausfinden, warum sie diesen Wert hat.

    Ein paar typische Ursachen:

    • Man hat ein Attribut eines Referenztyps zwar deklariert, aber nicht initialisiert, ihr also kein Objekt zugewiesen. Damit hat es per automatischer Initialisierung den Wert null.

    • Ein Attribut eines Referenztyps sollte an einer anderen als der Deklarationsstelle initialisiert werden, dabei wurde aber versehentlich eine erneute Deklaration vorgenommen, also eine lokale Variable deklariert. Die Initialisierung betrifft dann natürlich diese lokale Variable; das Attribut behält den Wert null.

    • Ein Referenztyp-Array wurde zwar erzeugt, die einzelnen Objekte aber nicht. Damit haben die Elemente des Arrays per automatischer Initialisierung den Wert null. Näheres siehe hier.

    • Man will auf ein Attribut zugreifen, hat aber versehentlich eine lokale Variable oder einen formalen Parameter gleichen Namens deklariert. Diese Variable verdeckt das Attribut, so dass man auf die lokale Variable bzw. den formalen Parameter zugreift, die dann natürlich einen anderen Wert hat, als man erwartete, z.B. null.

  2. ArrayIndexOutOfBoundsException (AIOOBE)

    Siehe hierzu im Kapitel „Arrays“ den Abschnitt „Ich bekomme eine ArrayIndexOutOfBoundsException. Was hat es damit auf sich?

  3. ClassNotFoundException (CNFE)

    Eine CNFE bedeutet, dass die Laufzeitumgebung eine Klasse nicht laden konnte, welche sie zur Ausführung des Programms benötigte.

    Die häufigste Ursache dieser Exception ist ein falsch gesetzter Classpath. Der Classpath sagt der Laufzeitumgebung, wo sie nach Klassen zu suchen hat. Ist der Classpath falsch gesetzt, werden z.B. Klassen im aktuellen Verzeichnis nicht gefunden, wo normalerweise automatisch gesucht wird.

    Die systemweite Umgebungsvariable CLASSPATH sollte aber aus verschiedenen Gründen ohnehin nicht gesetzt werden. Wenn zu bestimmten Zwecken eine explizite Angabe des Classpath nötig ist, sollte dazu beim Start des Programms der Aufrufparameter -classpath verwendet werden.

    Eine weitere Ursache besteht darin, dass bei der Exemplarerzeugung per Reflection (z.B. im Adressbuchbeispiel aus der Newsgroup) der Methode forName() der Klasse Class ein String als Klassenname übergeben wird, dem die Laufzeitumgebung keine Klasse zuordnen kann. Wenn es sich nicht um einen einfachen Tippfehler handelt, wurde hier meist die Package-Angabe vergessen, die zum vollständigen Klassennamen aber dazugehört.

    Im Kontext des Kurses 1618 begegnet einem die CNFE meist im Zusammenhang mit RMI (Kurseinheit 7). Dazu einige Erläuterungen:

    Damit die RMI-Registry (welche selbst ein Java-Programm ist) die benötigten Server-Klassen findet, gibt es mehrere Möglichkeiten:

    • Man benötigt die RMI-Registry nur für genau ein Serverprogramm

      Dann kann man sie aus dem Verzeichnis heraus starten, aus dem man auch das Serverprogramm startet (unter der Annahme, dass über diesen Pfad auch das Remote-Interface erreichbar ist). Einfacher ist es dann aber, die Registry direkt aus dem Serverprogramm zu starten. Dazu genügt folgende Zeile:

          java.rmi.registry.LocateRegistry.createRegistry(1099);

      Nachteil: Diese RMI-Registry wird zusammen mit dem Serverprogramm beendet, deswegen ist diese Lösung ungeeignet, wenn mehrere Programme die Registry nutzen sollen.

    • Die Registry wird für mehrere Serverprogramme verwendet.

      In diesem Fall ist das Serverprogramm dafür zuständig, der Registry mitzuteilen, wo die von ihr benötigten Klassen liegen. Dazu übergibt man beim Start des Serverprogramms diese Information mit Hilfe des VM-Parameters -Djava.rmi.server.codebase. Das könnte z.B. so aussehen:

          java -Djava.rmi.server.codebase=file:/D:/1618/bin/pufferServer.RingPufferServer

      Wenn es sich bei der übergebenen Codebase um ein Verzeichnis handelt, muss an dessen Ende ein Slash stehen.

    In der Praxis wird man oft wesentlich komplexere Szenarien haben, bei denen es erwünscht ist, dass die zum Client und/oder zum Server gehörigen Klassen dynamisch von der jeweils anderen oder von dritter Seite geladen werden können. RMI unterstützt derlei durch die Möglichkeit, Klassen dynamisch von Servern laden zu können. Einen guten überblick über das dynamische Laden von Klassen im Kontext von RMI bietet diese Seite. Das dort Beschriebene geht allerdings weit über die Anforderungen des Kurses hinaus.

  4. IllegalMonitorStateException (IMSE)

    Eine IMSE tritt genau dann auf, wenn ein Thread wait(), notify() oder notifyAll() auf einem Objekt aufruft, dessen Monitor dieser Thread nicht besitzt. Der Aufruf der besagten Methoden richtet sich ja immer an ein ganz bestimmtes Objekt, auf dessen Warteschlange er sich bezieht.

    Der einfachste solche Fall ist, dass man eine der drei Methoden in einem Bereich aufruft, der überhaupt nicht synchronisiert ist.

    Dann gibt es den Fall, dass man eine der drei Methoden zwar in einem synchronisierten Bereich aufruft, aber auf einem anderen Objekt als dem (bzw. einem von denen) auf dem (bzw. denen) der Bereich synchronisiert ist.

    Von diesem zweiten Fall gibt es noch eine besonders subtile Variante, nämlich dass man einer der Methoden zwar auf der Variablen aufruft, über die auch die Synchronisation erfolgte, diese Variable aber inzwischen ein anderes Objekt referenziert. Um dies zu verhindern, werden zur Synchronisation, so weit sie nicht auf „this“ erfolgt, meist als final deklarierte Variablen (also Konstanten) verwendet, da sich hier das referenzierte Objekt nicht ändern kann. Vorsicht in diesem Zusammenhang bei Arrays: Die Deklaration einer Variablen eines Array-Typs als final verhindert ja nicht, dass den Elementen des Arrays neue Werte zugewiesen werden.