Kapitel 8: Motivation
Ein Programm soll sowohl Temperaturen als auch Einkommen verarbeiten, sie beispielsweise grob klassifizieren: sehr hoch, hoch, normal, niedrig, sehr niedrig.
Mit unserem bislang erworbenen Kenntnissen werden Sie es vermutlich wie folgt, oder zumindest ähnlich, formulieren.
1import de.pakad.udemy.StdIn;
2import de.pakad.udemy.StdOut;
3
4/**
5 * Klassifikation von Temperaturen und Einkommen (prozeduraler Stil).
6 */
7public class ClassifyProc {
8
9 /**
10 * Klassifikation von Temperaturen.
11 *
12 * @param temp Temperatur
13 * @return Klassifikation
14 */
15 private static String classifyTemperature(double temp) {
16
17 if (temp < 5.0) return "sehr niedrig";
18 else if (temp < 15.0) return "niedrig";
19 else if (temp < 21.0) return "normal";
20 else if (temp < 30.0) return "hoch";
21 else return "sehr hoch";
22 }
23
24
25 /**
26 * Klassifikation von Einkommen.
27 *
28 * @param temp Einkommen
29 * @return Klassifikation
30 */
31 private static String classifySalary(double sal) {
32
33 if (sal < 1300.0) return "sehr niedrig";
34 else if (sal < 2000.0) return "niedrig";
35 else if (sal < 3500.0) return "normal";
36 else if (sal < 5000.0) return "hoch";
37 else return "sehr hoch";
38 }
39
40
41 /**
42 * Es werden ein Einkommen und eine Temperatur eingelesen und
43 * klassifiziert.
44 *
45 * @param args nicht genutzt
46 */
47 public static void main(String[] args) {
48 double t = StdIn.readlnDouble("Temperatur: ");
49 double s = StdIn.readlnDouble("Einkommen : ");
50
51 StdOut.println("Temperatur: " + classifyTemperature(s));
52 StdOut.println("Einkommen : " + classifySalary(t));
53 }
54}
Listing 8.1: Hanoi.java
So weit, so gut. – Probieren wir das Programm aus:
$ java ClassifyProc
Temperatur: 42
Einkommen : 1200
Temperatur: sehr hoch
Einkommen : sehr niedrig
$ _
Was geschieht, wenn Sie in den Zeilen 49 und 50 die Variablen s und t vertauschen, sodass sie wie folgt lauten?
StdOut.println("Temperatur: " + classifyTemperature(s));
StdOut.println("Einkommen : " + classifySalary(t));
Probieren wir das Programm mit den vorgenommenen Änderungen aus:
$ java ClassifyProc
Temperatur: 42
Einkommen : 1200
Temperatur: sehr hoch
Einkommen : sehr niedrig
$ _
Der Fehler ist, aufgrund der Datenkonstellation, nicht sofort erkennbar.
Testen wir mit anderen Daten, wird der Fehler offensichtlich:
$ java ClassifyProc
Temperatur: 1
Einkommen : 1200
Temperatur: sehr hoch
Einkommen : sehr niedrig
$ _
Bei einer Temperatur von einem Grad Celsius hätten wir ein „sehr niedrig" erwartet. – Wo liegt der Fehler?
Der gemachte Fehler liegt darin, einer Methode zur Klassifizierung von Temperaturen ein Einkommen übergeben zu haben und umgekehrt. Es handelt sich ganz klar um einen semantischen Fehler.
Als streng typisierte Programmiersprache prüft Java sehr genau, ob die Datentypen von Variablen und Methodenparametern kompatibel sind. Kommentare helfen uns, Sinn und Nutzung einer Methode zu verstehen, Konventionen zur aussagekräftigen Benennung von Methoden unterstützen uns, die Lesbarkeit und Verständlichkeit von Programmcodes zu optimieren. Doch nichts und niemand schützt uns vor semantischen Fehlern. Selbst Testfälle können, wenn sie nicht sorgsam konfiguriert sind, Fehler nicht direkt aufzeigen.
Wir bilden sowohl Temperaturen als auch Einkommen auf einem gemeinsamen Datentypen (double) ab. Dadurch ist es Java unmöglich, uns bei der Vermeidung solcher Fehler zu unterstützen.
Wie können wir in Programmen sicherstellen, dass Werte nicht nur korrekt berechnet, sondern auch sinnvoll verwendet werden?
Wir brauchen eine andere Sichtweise auf die Dinge. Und das ist der Punkt, an dem die Objektorientierte Programmierung (kurz: OOP) ansetzt. Wenn wir sowohl für Temperaturen als auch Einkommen eigene Datentypen hätten, könnte die strenge Typprüfung von Java uns unterstützen. Bereits der Java-Compiler könnte dann feststellen, dass Temperaturen und Einkommen keine zueinander kompatiblen Datentypen sind.
Ich möchte Ihnen das anhand des Programm ClassifyOOP.java kurz demonstrieren. Achten Sie für den Moment nicht auf die Syntax. Achten Sie auch nicht darauf, dass ich die erforderlichen Datentypen (Klassen) extrem „schlampig" implementiere. Beachten Sie nur, wie der Java-Compiler reagiert.
1/**
2 * Einfache Modellierung einer Temperatur.
3 */
4public class Temperature {
5
6 private final double temperature;
7
8 /**
9 * Konstruktor für eine Temperatur.
10 *
11 * @param temp Temperatur
12 */
13 public Temperature(double temp) {
14 temperature = temp;
15 }
16
17
18 /**
19 * Liefert die Temperatur.
20 *
21 * @return Temperatur
22 */
23 public double value() {
24 return temperature;
25 }
26}
Listing 8.2: Temperatur.java
1/**
2 * Einfache Modellierung eines Einkommens.
3 */
4public class Salary {
5
6 private final double salary;
7
8 /**
9 * Konstruktor für ein Einkommen.
10 *
11 * @param sal Einkommen
12 */
13 public Salary(double sal) {
14 salary = sal;
15 }
16
17
18 /**
19 * Liefert das Einkommen.
20 *
21 * @return Einkommen
22 */
23 public double value() {
24 return salary;
25 }
26}
Listing 8.3: Salary.java
Sowohl Temperaturen als auch Einkommen haben jetzt jeweils einen eigenen Datentypen.
1import de.pakad.udemy.StdIn;
2import de.pakad.udemy.StdOut;
3
4/**
5 * Klassifikation von Temperaturen und Einkommen (OOP-Stil).
6 */
7public class ClassifyOOP {
8
9 /**
10 * Klassifikation von Temperaturen.
11 *
12 * @param temp Temperatur
13 * @return Klassifikation
14 */
15 private static String classifyTemperature(Temperature temp) {
16 if (temp.value() < 5.0) return "sehr niedrig";
17 else if (temp.value() < 15.0) return "niedrig";
18 else if (temp.value() < 21.0) return "normal";
19 else if (temp.value() < 30.0) return "hoch";
20 else return "sehr hoch";
21 }
22
23
24 /**
25 * Klassifikation von Einkommen.
26 *
27 * @param temp Einkommen
28 * @return Klassifikation
29 */
30 private static String classifySalary(Salary sal) {
31 if (sal.value() < 1300.0) return "sehr niedrig";
32 else if (sal.value() < 2000.0) return "niedrig";
33 else if (sal.value() < 3500.0) return "normal";
34 else if (sal.value() < 5000.0) return "hoch";
35 else return "sehr hoch";
36 }
37
38
39 /**
40 * Es werden ein Einkommen und eine Temperatur eingelesen und
41 * klassifiziert.
42 *
43 * @param args nicht genutzt
44 */
45 public static void main(String[] args) {
46 Temperature t =
47 new Temperature(StdIn.readlnDouble("Temperatur: "));
48 Salary s =
49 new Salary(StdIn.readlnDouble("Einkommen : "));
50
51 StdOut.println("Temperatur: " + classifyTemperature(t));
52 StdOut.println("Einkommen : " + classifySalary(s));
53 }
54}
Listing 8.4: ClassifyOOP.java
Ohne näher darauf einzugehen, das machen wir ab dem nächsten Kapitel, stellen Sie fest, dass es jetzt offenbar zwei Klassen gibt: Temperature und Salary. ClassifyOOP unterscheidet sich nur minimal von ClassifyProc. So wie hier abgedruckt, funktioniert das Programm ClassifyOOP korrekt.
Was geschieht, wenn Sie jetzt in den Zeilen 51 und 52 die Variablen s und t vertauschen?
StdOut.println("Temperatur: " + classifyTemperature(s));
StdOut.println("Einkommen : " + classifySalary(t));
Der Java-Compiler weigert sich, Ihr Programm zu übersetzen. Aus drucktechnischen Gründen habe ich die Ausgabe umgebrochen und gekürzt.
$ javac ClassifyOOP.java
ClassifyOOP.java:51: Fehler: Inkompatible Typen:
Salary kann nicht in Temperature konvertiert werden
StdOut.println("Temperatur: " + classifyTemperature(s));
^
ClassifyOOP.java:52: Fehler: Inkompatible Typen:
Temperature kann nicht in Salary konvertiert werden
StdOut.println("Einkommen : " + classifySalary(t));
^
...
Die strenge Typprüfung erkennt den Fehler bereits bei der (misslungenen) Übersetzung des Programmcodes.
Die Objektorientierte Programmierung kann offenbar helfen, bestimmte Fehler frühzeitig zu erkennen. Das gilt insbesondere für Java. Strenge Typprüfung, von Anfängern oft als Last empfunden, ist ein Segen, den Sie als erfahrene Programmierer zu schätzen wissen. OOP kann aber, ohne Ihre Hoffnungen zu sehr zu dämpfen, nicht zaubern. Sie kann nicht alle potenziellen Fehler erkennen. Sie bietet uns als Programmierern jedoch, neben vielen anderen Aspekten, die wir in den folgenden Kapiteln behandeln, viele Vorteile.