Zur Hauptnavigation springen [Alt]+[0] Zum Seiteninhalt springen [Alt]+[1]

Technische Hintergründe zur Programmiersprache Java

Zur Umsetzung der Bildungsstandards wird in diesem Material ausschließlich die Programmiersprache Java verwendet. Für das Verständnis generischer Programmierung und Sammlungen in Java wird zunächst auf Programmierparadigmen im allgemeinen und anschließend auf die Umsetzung in Java eingegangen. Um die Funktionsweise generischer Datentypen und Lambdaausdrücke nachzuvollziehen, wird auf die Typisierung in Java und Schnittstellen näher eingegangen.

Programmierparadigmen

Programmierparadigmen

Abbildung 1: Programmierparadigmen ZPG Informatik [CC BY-SA 4.0 DE], aus 01_hintergrund.pdf

Es gibt verschiedene grundlegende Prinzipien der Programmierung, die als Programmier­paradigmen bezeichnet werden4 . Hierzu gehören auch strukturierte und objektorientierte Programmierung. Jedes Paradigma fordert bestimmte sprachliche Mittel und stellt Algorithmen durch verschiedene Notationsformen dar. Durch die in einer Programmier­sprache zur Verfügung stehenden sprachlichen Mittel, wird das Programmieren nach einem oder mehreren Paradigmen ermöglicht oder ausgeschlossen.

Java ist eine strukturierte, objektorientierte, modulare Programmiersprache, die zudem generische Programmierung ermöglicht und Elemente funktionaler Programmierung bereit stellt.

Paradigma Sprachliche Mittel Notationsformen / Beispiele
Imperative Programmierung Zuweisungen (Speichervariablen), Kontrollstrukturen: Schleifen

Nassi-Shneiderman-Diagramme/Struktogramme

Assembler

Prozedurale Programmierung Lesbar benannte Teilprogramme/Prozeduren, Daten und Objekte verarbeitende Prozeduren getrennt; C, Pascal

Objektorientierte Programmierung

Objekte mit Daten und darauf arbeitenden Methoden als eine Einheit.

UML Diagramme: z.B. Klassendiagramme, Objektdiagramme

Java, Python,Snap!, Scratch

Deklarative Programmierung Kontrollstrukturen: Rekursion und Substitution („Aufruf“) SQL
Funktionale Programmierung

Beschreibung durch Funktionen (mit Funktionsvariablen) die auf Daten arbeiten.

Listen (map-filter-reduce), Lambda, Closures

Objekte werden nicht verändert, sondern neu erstellt.

Begünstigt Parallelisierung

Teile von UML: Aktivitätsdiagramme, Zustandsdiagramme

LISP-basierte Sprachen, ECMAScript (Java-Script)

teils in Java: Funktionale Interfaces, Lambdaausdrücke

Modulare Programmierung

Module

Bei OOP: ADTs (Daten und Funktionen zur Behandlung dieser Daten als Einheit)

Strukturierte Programmierung

Lesbarer Code durch Kontrollstrukturen: Sequenz, Verzweigung, Schleifen

Gültigkeitsbereich von Variablen (Scope)

Generische Programmierung

Algorithmen für mehrere Datentypen verwendbar machen.

Generische Datentypen

In Java: Collections Framework

Tabelle 1: Auswahl an Programmierparadigmen und ihrer sprachlichen Mittel/Notationsformen; Themen aus dem Bildungsplan 2016 sind fett hervorgehoben

Typisierung

Jeder Ausdruck in einem Java Programm ergibt entweder kein Ergebnis (void) oder einen Wert eines Typs, der zur Laufzeit abgeleitet werden kann. Ein Ausdruck muss mit dem Typ des Kontexts, in dem er erscheint (Zieltyp), kompatibel sein. Ansonsten kommt es zu einem Kompilierungsfehler. Der Typ eines Ausdrucks kann abhängig vom Zieltyp sein, das heißt je nach Kontext, in dem der selbe Ausdruck vorkommt, wird vom Compiler ein anderer Typ ermittelt („abgeleitet“). Das „+“ kann zum Beispiel je nach Kontext die Addition von z.B. Integer-Werten oder die Konkatenation von String-Werten bedeuten. Man spricht von:

  • Typinferenz: Typen werden entsprechend ihrem Zieltyp automatisch abgeleitet.
  • Typumwandlung: Stimmt der Typ des Ausdrucks nicht mit dem Zieltyp überein, kann manchmal ohne Kompilierungsfehler eine implizite Umwandlung in den Zieltyp statt finden.
  • Typkonvertierung: Der Zieltyp kann vom Programmierer z.B. mit dem „type cast“-Operator explizit vorgegeben werden. Bei Abweichung vom Zieltyp findet eine explizite Umwandlung in den Zieltyp statt.
  • Typdeklaration: Der Kontext mancher Ausdrücke kann definiert werden. So wird bei der Deklaration einer Variable oder eines Parameters der Zieltyp für jedes weitere Vorkommen des Ausdrucks festgelegt und dann durch Typinferenz abgeleitet.

Bei manchen Typumwandlungen finden Typüberprüfungen oder Typübersetzungen erst zur Laufzeit statt. Kommt es dabei zu einem Laufzeitfehler, wird eine Ausnahme (engl. exception) geworfen.

Eine genaue Beschreibung möglicher Ausdrücke, Kontexte, Typen und Umwandlungen bietet die Java® Language Specification5 .

Eine Typumwandlung kann erweiternd (engl. widening) oder einschränkend (engl. narrowing) sein. Bei einer erweiternden Umwandlung geht sicher keine Information verloren, die Umwandlung kann implizit statt finden. Bei einer einschränkenden Umwandlung kann Information verloren gehen, hier muss eine explizite Konvertierung vorgenommen werden.

Beispiel:

int i = 12.5f;	// einschränkend, weil 12.5 nicht verlustfrei als
	// int dargestellt werden kann → Kompilierungsfehler
int i = (int)12.5f;	// explizite einschränkende Typumwandlung;
	// 12.5 wird auf 12 abgeschnitten.
float f = i;	// implizite erweiternde Typumwandlung, weil jeder int-	// Wert verlustfrei als float dargestellt werden kann.

Bei primitiven Datentypen findet eine Typumwandlung nach in der Sprachspezifikation definierten Regeln statt. Bei der Umwandlung von Referenz-Typen muss eine Verwandtschaft gegeben sein. Als Ausnahme sind hier in Java lediglich die Umwandlung primitiver Typen in einen String-Typ möglich sowie das so genannte Autoboxing, also die Umwandlung primitiven Zahldatentypen (int, short usw) in eine Instanz der jeweiligen Wrapper-Klasse (bspw. int zu Integer).

Beispiel:

String s = "Die Zahl lautet " + i; // Umwandlung int nach String
Integer intObjekt = i; // Autoboxing: Umwandlung int nach Integer

Bei Referenztypen ist eine Umwandlung erweiternd, wenn die Umwandlung von einem Subtyp in die Superklasse geschieht. Die Umwandlung von Referenztypen in ihren Basistyp Object ist immer möglich. Eine Umwandlung ist einschränkend, wenn sie aus Sicht einer Superklasse in die erweiterte Klasse erfolgen soll. Vor allem die einschränkende Konvertierung von Referenztypen birgt die Gefahr von Laufzeitfehlern. Es ist dann oft eine Ausnahmebehandlung im Code notwendig.

Im Kontext von Methoden wird der Zieltyp für formale Parameter bei der Deklaration angegeben. Beim Methodenaufruf ist der Zieltyp dann durch Typinferenz festgelegt. In Tabelle 2 sind einige Fachbegriffe im Methodenkontext festgelegt, wie sie in diesem Dokument verwendet werden.

Wichtig zu erwähnen ist, dass in Java Argumente immer nach dem Prinzip Pass-By-Value übergeben werden, dass heißt es wird immer eine Kopie des konkreten Argumentes an die Methode übergeben. Das Original im aufrufenden Kontext kann nicht durch die Methode geändert werden. Dies gilt auch für Referenztypen. Hier wird allerdings eine Kopie der Referenz übergeben6. Man könnte das Prinzip daher auch Pass-Reference-By-Value nennen.

Fachbegriffe zu Wertparametern in Methoden

Tabelle 2: Fachbegriffe zu Wertparametern in Methoden von ZPG Informatik [CC BY-SA 4.0 DE], aus 01_hintergrund.pdf

Tabelle 2: Fachbegriffe zu Wertparametern in Methoden

Schnittstellen – Interfaces

Im Allgemeinen stellt eine Schnittstelle eine nach außen bekannte Beschreibung einer Blackbox dar, über die mit der Blackbox kommuniziert werden kann. Es ist dabei unerheblich, wie diese Blackbox intern Informationen verarbeitet. So definiert die Java-Spezifikation in Module und Pakete untergliederte Programmierschnittstellen (API: engl. application programming interface). Das Collections-Framework ist zum Beispiel Teil des Paktes java.util, des Moduls java.base und beschreibt neben konkreten, wie abstrakte Klassen auch Algorithmen7. In der objektorientierten Programmierung gibt eine Schnittstelle an, welche Methoden in einer Klasse vorhanden sind, bzw. bei der Implementierung vorhanden sein müssen. Knapp gesagt eine Schnittstelle ist der public-Teil einer Klasse.

Die Programmiersprache Java bietet mit dem sprachlichen Mittel eines Interface eine Möglichkeit eine solche Schnittstelle zu definieren. Im Englischen ist der Begriff für eine allgemeine Schnittstelle (im Sinne des public-Teils einer Klasse) der selbe wie für das sprachliche Mittel. Im Deutschen lässt sich dies jedoch differenzieren. In diesem Dokument wird für das sprachliche Mittel der Begriff Interface verwendet, ansonsten der Begriff Schnittstelle.

Interfaces ermöglichen es dem Programmierer, so wie er es mit Klassen tut, selbst Typen zu definieren. Interfaces geben wie abstrakte Klassen die minimale Typsignatur für alle Subtypen vor, mit dem Unterschied selbst keine Implementierung (Ausnahme: static und default Methoden)8 zu besitzen. Von einem Interface kann wie von einer abstrakte Klassen keine Instanz erzeugt werden.

Ein Interface wird wie folgt deklariert:

Modifikator interface Schnittstelle
{
    final Typ konstante = Wert;
    Modifikator Rückgabetyp methode( Parameterliste );
}

Das Interface besitzt nur Methodensignaturen und Konstanten. Bei den Methoden kann der Modifikator static oder default sein. Die Methoden sind automatisch public, private ist nicht erlaubt.

Um einen Subtyp zu deklarieren geht man wie folgt vor:

Modifikator class Klasse extends Superklasse implements Schnittstelle
{
    public Rückgabetyp methode( Parameterliste )
    {
        // Anweisungen
    }
}

Mit dem Schlüsselwort implements wird angegeben, welche Interfaces die Klasse implementiert. Dies können anders als beim Erweitern einer Klasse, auch mehrere sein (mit Komma getrennt hintereinander aufgelistet). Implementiert eine Klasse ein Interface, muss sie alle Methoden, die von der Schnittstelle vorgegeben sind, implementieren. Diese sind per Definition public. Es kann hierbei nicht zu Konflikten kommen, da jedes Interface nur Signaturen und keine Implementierungen vorgibt.

Interfaces können auch, wie Klassen, eine Vererbungshierarchie besitzen. Dort gelten die selben Regeln wie bei Klassen.

Der große Vorteil von Interfaces liegt darin, dass sie flexibler („Mehrfachimplementierung“) als abstrakte Klassen („keine Mehrfachvererbung“) sind und damit bestens geeignet einen Zieltyp mit klarer Minimalsignatur vorzugeben. Allerdings muss die komplette Signatur im Gegensatz zur Erweiterung abstrakter Klassen bei der Implementierung eines Interfaces erneut aufgeschrieben werden. Als Ersatz für abstrakte Klassen sind Interfaces daher nicht gedacht.

Für das Verständnis generischer Sammlungen und Lambdaausdrücke ist es unverzichtbar, im Unterricht Interfaces zu thematisieren, auch wenn diese nicht explizit im Bildungsplan erwähnt werden. Für die Schülerinnen und Schüler ist es aber kein weiter Weg, Interfaces zu verstehen. Im Unterrichtsverlauf müssen sie lediglich Interfaces als Zieltyp identifizieren und selbst keine definieren oder implementieren.

 

4https://www.itwissen.info/Programmierparadigma-programming-paradigm.html (ausgewertet am 22.12.2020)

5 https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html (ausgewertet am 22.12.2020)

6 Eine gute Erklärung für Pass-Reference-By-Value: https://www.codejava.net/java-core/the-java-language/java-variables-passing-examples-pass-by-value-or-pass-by-reference (ausgewertet 3.2.2021)

7 https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/doc-files/coll-reference.html (ausgewertet 3.2.2021)

8 Default-Methoden ermöglichen Rückwärtskompatibilität, Static-Methoden werden wie statische Methoden in regulären Klassen mit dem Interface anstatt einem Objekt assoziiert und daher von allen Objekten die das Interface implementieren geteilt. Auf beide wird hier nicht weiter eingegangen. Für eine genauere Beschreibung mit Beispielen siehe https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html (ausgewertet am 3.2.2012)

 

Hintergrundinformationen: Herunterladen [odt][370 KB]

Hintergrundinformationen: Herunterladen [pdf][480 KB]

 

Weiter zu Generische Datentypen