Sitzung: Jeden Freitag in der Vorlesungszeit ab 16 Uhr c. t. im MAR 0.005. In der vorlesungsfreien Zeit unregelmäßig (Jemensch da?). Macht mit!

Martin Häcker/Java Kurs/Tag 2/Handout

Das ist ein erster Entwurf - ein reiner Braindump, also bitte immer her mit Verbesserungsvorschlägen!

Objektorientierung

Objektorientierung ist eine Programmiertechnik - also wie man eine Programmiersprache benutzt und damit von der jeweiligen Programmiersprache völlig unabhängig.

Manche Programmiersprachen haben aber Funktionen eingebaut die es erleichtern Objektorientiert zu programmieren.

Letztlich wendet man aber nur die Kombination zweier Techniken an, Modularisierung sowohl von Methoden wie von Daten - wenn sie in den gleichen Einheiten modularisiert werden.

Modularisierung

Das ist das Handwerkszeug des Informatikers. Wenn ein Programm gut modularisiert ist, dann kann es wachsen. Bzw. große Programme sind überhaupt nur möglich weil der dahinter stehende Code modularisiert ist.

Modularisierung von Code

Genau das was ihr bisher gemacht habt - eine neue Datei, darin eine "class BlahBlub { ... }" ist ein Modul. Zumindest eine der Möglichkeiten die Java bietet ein Modul zu erzeugen.

Wenn ihr also Code wie diesen habt:

 class PointModule {
	static boolean arePointsEqual(float ax, float ay, float bx, float by) {
		return ax == bx && ay == by;
	}
	
	static float distanceBetweenPoints(float ax, float ay, float bx, float by) {
		return sqrt(pow(ax-bx, 2) + pow(ay-by, 2));
	}
	
	static float sqrt(float positiveNumber) {
		return Math.sqrt(positiveNumber);
	}
	
	static float pow(float base, float exponent) {
		return Math.pow(base, exponent);
	}
}

Dann macht es eben sinn diese Funktionen zusammen zu betrachten, da man sie für sich gesehen verstehen kann, ohne den rest des Programms zu kennen.

Danach kann man aber genau umgekehrt verfahren und den rest des Programms ohne dieses Modul verstehen, da man nur mit Punkten und deren Eigenschaften arbeitet, nicht aber wie das genau umgesetzt ist.

Allerdings muss man sich bemühen nur Funktionen in ein Modul einzubauen, die auch wirklich zusammen gehören. Hier im Beispiel etwa gibt es zwei Funktionen "sqrt" und "pow", die nichts mit Punkten zu tun haben, sondern generelle Konzepte der Mathematik darstellen. Daher sollte man sie eben auch in ein eigenes Modul "MathModule" einbauen, damit sie auch seperat verständlich sind.

// File PointModule.java
class PointModule {
	static boolean arePointsEqual(float ax, float ay, float bx, float by) {
		return ax == bx && ay == by;
	}
	
	static float distanceBetweenPoints(float ax, float ay, float bx, float by) {
		return MathModule.sqrt(MathModule.pow(ax-bx, 2) 
			+ MathModule.pow(ay-by, 2));
	}	
}

// File MathModule.java
class MathModule {
	static float sqrt(float positiveNumber) {
		return Math.sqrt(positiveNumber);
	}
	
	static float pow(float base, float exponent) {
		return Math.pow(base, exponent);
	}
}

Das ist wie man es machen sollte - das ist so wichtig das sogar Sun den eigenen Code genau so aufteilt, es existiert von Sun ein Module "Math" das genau so aufgebaut ist, und das ihr genau so verwenden könnt wie ich es hier schon getan habe um "MathModule" selber zu impolementieren. Will man also die Quadratwurzel einer Zahl berechnen, ruft man Math.sqrt(aNumber) aufrufen.

Java bietet noch mehr möglichkeiten Module aufzubauen, etwa Packages und Sichtbarkeitskontrolle. Das werden wir hier aber nicht behandeln, das ist für euch zum Recherchieren.

Modularisierung von Daten

Hier kommt zum ersten mal neue Syntax ins Spiel - wenn auch wirklich wenig.

Die Idee ist simpel, man kann Variablen nicht nur innerhalb von Methoden definieren, sondern auch ausserhalb, nämlich innerhalb einer Klasse. Zum Beispiel so:

// File Point.java
class Point {
	float x;
	float y;
}

// File PointModule.java
class PointModule {
	static Point createPoint(float x, float y) {
		Point aPoint = new Point();
		aPoint.x = x;
		aPoint.y = y;
		return aPoint;
	}
	
	static boolean arePointsEqual(Point aPoint, Point bPoint) {
		return aPoint.x == bPoint.x
			&& aPoint.y == bPoint.y;
	}
	
	static float distanceBetweenPoints(Point aPoint, Point bPoint) {
		return Math.sqrt(Math.pow(aPoint.x - bPoint.x, 2)
			+ Math.pow(aPoint.y - bPoint.y));
	}
	
	static void printPoint(Point aPoint) {
		System.out.println("x: " + aPoint.x + " y: " + aPoint.y);
	}
	
	static void testMethodUsage() {
		Point zero = createPoint(0, 0);
		Point somewhere = createPoint(4, 17);
		if ( ! areEqualPoints(zero, somewhere)) {
			System.out.println("Abstand: " + distanceBetweenPoints(zero, somewhere));
		}
		printPoint(somewhere);
		printPoint(zero);
	}
}

Das ist auch schon alles was es an Syntax gibt. ClassName variableName = new Classname(); erzeugt eine neue Datenstruktur, auf deren Inhalt man mit "." zugreifen kann, wie in createPoint() und arePointsEqual() zu sehen.

Benötigt man eine Datenstruktur nicht mehr, oder will darstellen das "keine Datenstruktur" vorhanden ist, so kann man null als Platzhalter verwenden. So z.B.:

RGB getCurrentColor() {
	if (isCurrentColorTransparent()) {
		System.out.println("Durchsichtige Farben müssen wir erst noch implementieren!");
		return null;
	}
	return currentColor;
}

Java selbst bietet nur diese Funktion für die Datenabstraktion, lässt aber zu das man sie Verschachtelt - das ist aber wieder zum selbst Recherchieren.

Vorsicht vor diesen Fehlern

Bei der Verwendung müsst ihr darauf Achten, das in der Definition der Datenstruktur auf static verzichtet wird. static sorgt dafür das sich alle mit new XXX(); verwendeten Datenstrukturen einen Platz im Speicher teilen und daher alle immer den gleichen Wert haben:

class Point {
	static float x,y;
}

static void testStaticQuirk() {
	Point zero = createPoint(0, 0);
	Point one = createPoint(1, 1);
	if (arePointsEqual(zero, one)) {
		System.out.println("Die Punkte sollten unterschiedlich sein!");
	}
}

Wenn man diesen Effekt erreichen will, dann gut - meist möchte man aber genau das Gegenteil, mehrere Punkte die unterschiedlich sind. Daher Vorsicht vor static.

Man muss auch darauf achten, das man die so erzeugten Datenstrukturen nicht kopieren kann, indem man sie einer neuen Variablen zuweist.

static void testCopying() {
	Point zero = createPoint(0,0);
	Point whatsThis = zero;
	Point copyOfZero = copyPoint(zero);
	
	zero.x = 100;
	
	printPoint(zero);
	printPoint(copyOfZero);
	printPoint(whatsThis);
}

static Point copyPoint(Point aPoint) {
	return createPoint(aPoint.x, aPoint.y);
}

Dieses Beispiel gibt diese drei Zeilen aus:

x: 100 y: 0
x: 0 y: 0
x: 100 y: 0

Also Vorsicht, Datenstrukturen kopiert man indem man ihre Inhalte Kopiert, nicht indem man die Referenzen - man könnte sie auch "Fernbedienungen" nennen - kopiert.

Objektorientierung

Damit besitzt ihr alles notwendige um in Java Objektorientiert zu programmieren, in der Tat haben wir es schon die ganze Zeit gemacht.

Ein Modul "Point" zusammen mit einem Modul "PointModule" bilden zusammen ein Objekt, da sie für den Rest des Programms so verwendet werden können, als wüsste der Rest des Programms nicht das ein Punkt eine x und eine y Koordinate hat - in der Tat, würde sich das ändern, indem man z.B. intern die Polarkoordinaten anstatt der kartesischen speichert, dann könnte der Rest des Programms völlig ohne Änderung funktionieren. Wenn das PointModule zwei methoden besitzt die es erlauben aus einem Point ein x und eine y zu extrahieren - wie auch immer das Errechnet wird.

Danach gibt es nur noch ein paar Syntax-Veränderungen, damit es leichter ist Objekteorientiert zu Programmieren.

Man fasst die Datenstrukturen und die dazugehörigen Methoden in eine "Klasse", also so:

class Point {
	float x,y;

	static Point createPoint(float x, float y) {
		Point aPoint = new Point();
		aPoint.x = x;
		aPoint.y = y;
		return aPoint;
	}
	
	static boolean arePointsEqual(Point aPoint, Point bPoint) {
		return aPoint.x == bPoint.x
			&& aPoint.y == bPoint.y;
	}
	
	static float distanceBetweenPoints(Point aPoint, Point bPoint) {
		return Math.sqrt(Math.pow(ax-bx, 2) 
			+ Math.pow(ay-by, 2));	
	}
}

Dann kann man das static weglassen, damit die Methoden zu den mit new erzeugten Datenobjekten gehören anstatt nur zu dem Modul Point.

Auch die Methoden schreibt man etwas anders. Man schreibt

class Point {
	float x, y;
	
	boolean arePointsEqual(Point bPoint) {
		return this.x == bPoint.x
			&& this.y == bPoint.y;
	}
}

da Java bei jeder Methode die nicht mehr static ist einen neuen ersten Parameter "this" einfügt, also die Methode vom Kompiler so gelesen wird, als stünde dort:

boolean arePointsEqual(Point this, Point bPoint)
	return this.x == bPoint.x
		&& this.y == bPoint.y;
}

Aber natürlich schreibt man das Point this nicht hin.

Aufrufen tut man die Methoden dann so:

Point zero = createPoint(0, 0);
zero.arePointsEqual(createPoint(1, 1));
// oder auch
createPoint(0, 0).arePointsEqual(zero);

Was der Java-Kompiler natürlich in die bekannte Schreibweise

Point zero = createPoint(0, 0);
arePointsEqual(zero, createPoint(1, 1));
// oder auch
arePointsEqual(createPoint(0, 0), zero);

übersetzt. Wieder sieht man das aber natürlich nicht so.

Der nächste Schritt betrifft die Methode createPoint(). Hier ändert sich die Schreibweise zu

Point zero = new Point(0, 0);

und createPoint() schreibt man:

 
Point(float x, float y) {
	this.x = x;
	this.y = y;
}

Das wird natürlich wieder vom Kompiler in die schon bekannte Schreibweise übersetzt.

Zusammengefasst sieht diese Schreibweise so aus (mit ein bisschen anders benannten Methoden, damit die andere Schreibweise besser zu lesen ist:

class Point {
	float x,y;

	Point(float x, float y) {
		this.x = x;
		this.y = y;
	}
	
	boolean isEqualToPoint(Point other) {
		return this.x == other.x
			&& this.y == other.y;
	}
	
	boolean isDifferentFromPoint(Point other) {
		return ! isEqualToPoint(other);
	}
	
	float distanceToPoint(Point other) {
		return Math.sqrt(Math.pow(this.x - other.x, 2) 
			+ Math.pow(this.y - other.y, 2));	
	}
	
	void printPoint() {
		System.out.println("x: " + x + " y: " + y);
	}
	
	static void testUsage() {
		Point zero = new Point(0, 0);
		Point aPoint = new Point(3, 17);
		if (zero.isDifferentFromPoint(aPoint)) {
			System.out.println(zero.distanceToPoint(aPoint));
		}
	}
}

Eine Anmerkung noch: Java bietet noch die Abkürzung an das man this vor einer Variablen oder einer der anderen Methoden in der Klasse Weglassen kann, wenn keine Namens-Verwirrung mit einem Parameter oder einer in der Methode Definierten Variablen passieren kann. Auch hier fügt der Compiler das this vor den Variablen und Methoden wieder ein.

Damit kann ich zwei Begriffe definieren:

Klassse
Die Einheit die Daten und Methoden zusammenfasst. Point ist z.B. eine Klasse
Instanz
Mit new Point(x, y) erzeugt man eine neue Instanz einer Klasse.

Merke: Klassen gibt es immer nur einmal (siehe auch static) während es beliebig viele Instanzen einer Klasse geben kann.

Damit ist alles gesagt was wir in Kurs hier zu Objektorientierter Programmierung zu sagen ist und damit wünschen wir viel Spaß beim Üben!