URL dieses Dokuments: http://wiki.macke.it/doku.php/fhwt:skriptjava
Java ist eine Compilersprache, was bedeutet, dass der Quelltext, den ein Programmierer erstellt, von einem speziellen Übersetzungsprogramm - dem Compiler - in ausführbaren Maschinencode übersetzt werden muss. Die Besonderheit bei Java ist dabei, dass der erzeugte Maschinencode nicht plattformspezifisch ist, sondern auf der Java Virtual Machine (JVM) ausführbar ist, die es für so ziemlich jedes Betriebssystem gibt. Daher sind Javaprogramme plattformunabhängig (eine Programmiersprache → viele Plattformen). Der vom Compiler erzeugte Maschinencode heißt Bytecode. Im Vergleich dazu verfolgt Microsofts .NET Framework den Ansatz, mit mehreren Programmiersprachen (C#, Visual Basic usw.) eine Plattform (Windows) zu bedienen (wobei es z.B. mit Mono auch schon eine Implementierung von .NET für Linux gibt).
Der normale Ablauf beim Programmieren ist:
Die Java-VM ist Bestandteil des Java Runtime Environment (JRE), dass auf einem Betriebssystem installiert sein muss, damit man Java-Programme ausführen kann. Im Gegensatz dazu enthält das sog. Java Development Kit (JDK) zusätzliche Programmierwerkzeuge (insb. den Compiler), um selbst Programme zu erstellen.
Ein einfaches Beispielprogramm in Java sieht z.B. so aus:
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World!"); } }
Dieses Programm muss in einer Datei HelloWorld.java
gespeichert und dann mit javac HelloWorld.java
kompiliert
werden. Dazu muss javac.exe
im PATH
liegen. Danach kann das Programm mit java HelloWorld
aufgerufen werden.
Wichtig: Die Datei muss exakt so heißen wie die Klasse, die in ihr definiert wird. Groß- und Kleinschreibung wird unterschieden (Java ist case-sensitive).
Des weiteren ist es sehr wichtig, die Syntax der Programmiersprache exakt einzuhalten. Schon der kleinste Tippfehler kann dazu führen, dass das Programm nicht mehr kompiliert werden kann. Hier ist ein Beispielprogramm, das einige Syntaxfehler enthält:
static void main(String arg[]) { System.out.Println(Die Zahl ist 127 ) System.out.Println(Das doppelte davon ist 2*127) System.out.Println(ENDE)
class
-Definition fehltpublic
vor main()
fehltPrintln
muss println
lauten}
Für Menschen ist es schwierig, an all diese Dinge zu denken. Daher gibt es Programmierwerkzeuge, die einem diese Arbeit abnehmen. Das bekannteste Beispiel im Java-Umfeld ist Eclipse, ein Integrated Development Environment (IDE). Insbesondere bei der Teamarbeit mit Hilfe einer zentralen Quelltextverwaltung wie z.B. Subversion (SVN) ist es sinnvoll, dass alle Teammitglieder die gleichen Einstellungen bzgl. der Quelltextformatierung verwenden, damit es bei Änderungen keine Unterschiede aufgrund von Formatierungsoptionen gibt.
Für bestimmte wichtige Daten sind in Java sog. elementare Datentypen (auch primitive Datentypen genannt) definiert. Die wichtigsten sind:
short
, int
und long
für Ganzzahlenfloat
und double
für Gleitkommazahlenchar
für einzelne Zeichenbool
für WahrheitswerteAls Dezimaltrennzeichen wird nicht ein Komma, sondern ein Punkt verwendet. Bei langen Zahlen vom Datentyp Long ist es sinnvoll, ein "L" als Suffix zu verwenden.
Den Wertebereich der numerischen Datentypen kann man mittels der Konstanten MINVALUE
herausfinden.
und
MAXVALUE
Beispiel:
System.out.println("short kann Werte von " + Short.MIN_VALUE + " bis " + Short.MAX_VALUE + " aufnehmen."); System.out.println("int kann Werte von " + Integer.MIN_VALUE + " bis " + Integer.MAX_VALUE + " aufnehmen."); System.out.println("long kann Werte von " + Long.MIN_VALUE + " bis " + Long.MAX_VALUE + " aufnehmen."); System.out.println("float kann Werte von " + Float.MIN_VALUE + " bis " + Float.MAX_VALUE + " aufnehmen."); System.out.println("double kann Werte von " + Double.MIN_VALUE + " bis " + Double.MAX_VALUE + " aufnehmen.");
Mit dem char '\n' kann ein Zeilenumbruch verursacht werden, mit dem char '\t' ein Tab-Stop.
Datentyp | Byte | Wertebereich |
---|---|---|
Byte | 1 | -128 bis 127 |
Short | 2 | -32.768 bis 32.767 |
Integer | 4 | -2.147.483.648 bis 2.147.483.647 |
Long | 8 | -18.446.744.073.709.551.616 bis 18.446.744.073.709.551.615 |
Um Zahlen aus anderen Zahlsystemen sichtbar zu machen, können die Präfixe "0b" für binäre, "0" für oktale und "0x" für hexadezimale Zahlen verwendet werden. Intern werden alle Zahlen jedoch dezimal gespeichert.
{} curly braces () parenthesis <> angle brackets [] brackets
Der Camel Case ist eine Konvention zur Benennung von Variablen, Methoden und Methodenparametern. Für sie ist kennzeichnend, dass das erste Zeichen eines Bezeichners grundsätzlich klein geschrieben wird. Innerhalb des Bezeichners werden Groß- und Kleinschreibung gemischt verwendet, wobei das erste Zeichen jedes den Bezeichner konstituierenden Wortes groß geschrieben wird.
Beispiele: userName deleteUser createBankAccount logoutAndShutdown
Die wichtigsten Elemente der meisten Programmiersprachen sind die sog. Variablen. Variablen sind benannte Speicherbereiche, die Daten aufnehmen können. Sie werden deklariert (Datentyp und Name werden festlegen) und initialisiert (Wert wird zugewiesen). Der Inhalt einer Variablen kann dann über ihren Namen ausgelesen werden.
Beispiel:
int zahl = 1; System.out.println("zahl hat den Wert: " + zahl);
In Java müssen Variablen initialisiert werden, bevor sie wie im Beispiel verwendet werden können. Dieses Beispiel würde daher einen Fehler erzeugen:
int zahl; System.out.println(zahl);
Die Bezeichner (Namen) der Variablen dürfen beliebig lang sein und Buchstaben, Zahlen und bestimmte Sonderzeichen (z.B. -
, _
, $
) enthalten. Sie dürfen jedoch nicht mit einer Zahl beginnen.
Obwohl es möglich ist, rate ich von der Verwendung von Umlauten und anderen Sonderzeichen wie ß
in Java-Bezeichnern dringend ab. Zum besseren Verständnis sollte man sich auf eine Sprache einigen. Wir arbeiten grundsätzlich mit Englisch.
Laut Konvention werden Variablen-Namen in Java kleingeschrieben und dann in der sog. CamelCase-Notation fortgesetzt: int diesIstEineGrosseZahl;
Bestimmte Zählvariablen haben meist nur einen einzelnen Buchstaben als Namen. Ansonsten sollten unbedingt sprechende (=lange) Namen verwendet werden, da dies die Programme leichter lesbar und verständlicher macht.
Da Java eine statisch typisierte Programmiersprache ist, muss allen Variablen ein Datentyp zugewiesen werden. Dieser Datentyp ist im Nachhinein nicht mehr änderbar. Folgendes Beispiel würde daher zu einem Fehler führen:
int zahl = 1; zahl = "text";
Variablen haben einen sogenannten Gültigkeitsbereich, den Scope. Sie sind nur innerhalb des Blocks verfügbar, in dem
sie definiert wurden, und in allen Blöcken innerhalb dieses Blocks. Ein Block ist ein Codebereich, der mit geschweiften
Klammern umschlossen ist. Man kann in seinem Code beliebige Blöcke definieren und einige Sprachkonstrukte (wie z.B.
class
, if
und for
) benötigen auch explizit einen Block.
Beispiel:
int test = 1; { int test2 = 2; // 1 { int test3 = 3; // 2 } // 3 } // 4
test
und test2
sind verwendbartest
, test2
und test3
sind verwendbartest
und test2
sind verwendbartest
ist verwendbarEs gibt Variablen, die sich während des Programmablaufs verändern können. Im Gegensatz dazu stehen die Konstanten, deren zugewiesener Wert, nicht im Programmablauf verändert werden kann. Dies bewirkt, dass zentrale Werte nicht überschrieben werden können, und die Konstante in anderen Programmteilen widerverwendet werden kann.
Laut Konvention werden Bezeichner von Konstanten in Java in Großbuchstaben und mit Unterstrichen getrennt geschrieben. Damit eine verändere Variable zu einer Konstante wird, ist der Modifizierer final notwendig. Ein Schreibzugriff auf eine Konstante, führt zu einer Fehlermeldung.
Beispiel:
public static final int MWST_SATZ = 19;
Mit Hilfe von Casts kann man Datentypen in andere Datentypen umwandeln.
int number1 = 123; double decimal1 = number1; // impliziter Cast ohne Wertverlust decimal1 = 123.45; int number2 = (int) decimal1; // expliziter Cast mit Wertverlust
Des Weiteren ist das Casten von Character zu Integer und umgekehrt möglich.
int asciiPositionOfA = 'A'; System.out.println(asciiPositionOfA); char characterAtPosition65 = (char) 65; System.out.println(characterAtPosition65);
Um Werte in Strings zu konvertieren, besitzt jeder Datentype die Methode toString()
. Dazu muss man jedoch die
Klassen der Datentypen verwenden. Die elementaren Datentypen wie int
, double
und boolean
besitzen keine Klasse.
Es gibt jedoch für jeden elementaren Datentyp auch eine enstprechende Klasse (Integer
, String
, Double
,
Boolean
usw.).
Integer int1 = 20; String numberStr = int1.toString(); System.out.println(numberStr + 3); // -> 203 String numberToParse = "20.50"; double parsedNumber = Double.parseDouble(numberToParse); System.out.println(parsedNumber + 3); // -> 23.5
Ein Array ist eine Liste mit mehreren Elementen eines bestimmten Datentyps.
Der Datentyp muss angegeben werden sowie auch die Länge, die später nicht mehr geändert werden kann.
Eine Array muss wie folgt angelegt werden, wichtig ist dabei das Schlüsselwort new
:
Datentyp[] Bezeichner = new Datentyp[Länge des Arrays];
Der Vorteil von Arrays ist, dass mehrere Werte einfach verarbeitet werden können (z.B. mit einer for
-Schleife),
wobei Redundanzen im Code (z.B. var1
, var2
, var3
etc.) vermieden werden.
Die sog. Ausdrücke verknüpfen Literale (wie Zahlen oder Zeichenketten), Variablen oder Konstanten mit Operatoren (z.B. +
, *
usw.) und liefern dabei meist einen Wert zurück.
Beispiel:
int sum = x + y + 10;
10
x
, y
=
(Zuweisung), +
(Addition)x + y + 10
(Rückgabewert ist die Summe von x, y und 10)Das gesamte Konstrukt aus dem Beispiel nennt man Anweisung. Alle Anweisungen in Java müssen mit einem Semikolon abgeschlossen werden.
Die grundlegenden Operationen der Mathematik können in Java sehr einfach verwendet werden:
int result = 1 + 5 * 3 - 4 / 2; double result2 = (1 + 5) * (3 - 4) / 2
Man muss jedoch auf den korrekten Datentyp achten, da ansonsten ggfs. Wertverluste auftreten (insb. bei der Division).
double result = 1 / 2; // -> 0 result = 1.0 / 2; // -> 0.5
Eine wichtige Operation ist die Division mit Rest, der Modulo:
int remainder = 10 % 4; // -> 2
Weitere wichtige mathematische Werte und Funktionen - wie das Wurzelziehen, die Potenzrechnung oder die Zahl Pi - sind im Package
Math
definiert:
System.out.println(Math.sqrt(9)); // -> 3 System.out.println(Math.PI); // -> 3.14... System.out.println(Math.pow(2, 10)); // -> 1024
Zusätzlich sind eigene Operatoren für das Inkrementieren und Dekrementieren von Ganzzahlen verfügbar:
int x = 5; System.out.println(x++); // Post-Increment -> x wird erst nach dem Ausgeben erhöht System.out.println(++x); // Pre-Increment -> x wird vor dem Ausgeben erhöht System.out.println(x--); // Post-Decrement -> x wird erst nach dem Ausgeben verringert System.out.println(--x); // Pre-Decrement -> x wird vor dem Ausgeben verringert
Es gibt verschiedene Vergleichsoperatoren für logische Vergleiche:
==
(gleich)<
(kleiner)>
(größer)⇐
(kleiner gleich)>=
(größer gleich)!=
(ungleich)
Das =
kann nicht zum Vergleichen verwendet werden, da es sich hierbei um den Zuweisungsoperator handelt.
System.out.println(3 < 5); // -> true System.out.println(3 == 5); // -> false System.out.println(4 > 8); // -> false
Das Ergebnis eines Vergleichs kann auch in einer Variablen gespeichert werden:
boolean comparison = 3 > 2; System.out.println(comparison); // -> true
Boolesche Werte können mit AND, OR, XOR und NOT kombiniert werden:
System.out.println("true AND false: " + (true && false)); // -> false System.out.println("true OR false: " + (true || false)); // -> true System.out.println("true XOR false: " + (true ^ false)); // -> true System.out.println("NOT false: " + (!false)); // -> true
Ein Algorithmus ist eine Handlungsvorschrift (ein "Rezept") zum Lösen eines Problems. Er umfasst eine endliche Anzahl an Handlungsschritten. Alle Algorithmen können aus drei grundlegenden "Bausteinen" zusammengesetzt werden: Sequenz, Verzweigung und Wiederholung.
Ein Folge von mehreren Anweisungen wird als Sequenz bezeichnet.
int ergebnis = 3 + 5; ergebnis = ergebnis * 8; ergebnis++; System.out.println(ergebnis); // -> 65
Die Verzweigung ist ein grundlegendes Strukturelement für Fallunterscheidungen innerhalb eines Algorithmus. Sie bewirkt die Entscheidung für eine von zwei Möglichkeiten.
if (boolescher Ausdruck) { Anweisung, die ausgeführt wird, wenn der obige Ausdruck ''true'' ist } else { Anweisung, die ausgeführt wird, wenn der obige Ausdruck ''false'' ist }
Es gibt drei grundlegende Arten von Wiederholungen (Schleifen).
Die zählergesteuerte Schleife kann verwendet werden, wenn bekannt ist, wie viele Wiederholungen durchgeführt werden sollen.
Aufbau:
for (Startwert; Abbruchbedingung; Aktion) {}
Abfolge einer for-Schleife:
Beispiel:
for (int i=0; i <= 100; i++) { System.out.println(i); }
Die kopfgesteuerte Schleife sollte verwendet werden, wenn vor dem ersten Durchlauf schon die Bedingung geprüft werden muss.
Aufbau:
while (Abbruchbedingung) {}
Die fußgesteuerte Schleife sollte verwendet werden, wenn mindestens eine Wiederholung durchgeführt werden und die Bedingung erst nach dem Durchlauf geprüft werden soll.
Aufbau:
do { } while (Abbruchbedingung)
Eine Endlosschleife entsteht dann, wenn die Abbruchbedingung nie erfüllt wird und die Schleife daher immer wieder durchlaufen wird.
"Continue" bewirkt, dass die aktuell laufende Wiederholung einer Schleife abgebrochen wird. Es wird zum Ende des Anweisungsblocks gesprungen. Alle nachfolgenden Anweisungen kommen in diesem Schleifendurchlauf nicht mehr zur Ausführung. Die Schleife kann ggf. dennoch weiter durchlaufen werden. Beispiel:
int i = 5; while (i > 0) { i--; if (i == 2) { continue; // -> die 2 wird bei dem Schleifendurchlauf nicht ausgegeben. } System.out.println(i); }
Die Anwendung von break veranlasst, im Gegensatz zu continue nicht nur das Abbrechen der aktuellen Schleifenwiederholung, sondern den Komplettabbruch der Schleife.
In Java werden Funktionen Methoden genannt. Sie kapseln Code und können bei Bedarf von verschiedenen Stellen aus aufgerufen werden (Wiederverwendung). Methodenbezeichner können wie die von Variablen frei gewählt werden. Es gelten die gleichen Namenskonvention wie bei Variablen.
Aufbau: Rückgabetyp Name(Parameter) { Implementierung }
Parameter und innerhalb der Methode definierte Variablen sind nur innerhalb dieser Methode gültig (siehe Scope). Die Kommunikation erfolgt über einen (!) Rückgabewert (Schlüsselwort return
).
Beachte: return
verlässt die Methode, d.h. weiterer Code in der Methode ist unerreichbar (der Compiler merkt das auch).
Der Rückgabetyp ist nicht immer zwingend ein Wert. Er kann auch leer sein, z.B. bei System.out.println()
: Rückgabetyp ist void
= kein Rückgabewert.
Name + Parameter = Signatur der Methode (z.B. ist System.out.println()
mehrfach definiert mit unterschiedlichen Parametern. Welche konkrete Methode ausgeführt wird, entscheiden die Parameter (true
, "fdfhdfh"
, 1
etc.)
Als Rekursion bezeichnet man das Aufrufen einer Methode aus derselben Methode heraus. Beispiele: Fakultätenberechnung (fak(n) = n * fak(n - 1);
), Suchen durch ein Dateisystem.
Achtung: Rekursion ist sehr teuer, da viel Platz auf dem Stack benutzt wird. Es besteht die Gefahr eines StackOverflows, bei dem der Speicherplatz des Stacks nicht mehr ausreicht.
Das Gegenteil ist die Iteration. Man kann alle rekursiven Berechnungen auch iterativ lösen.
Bei der Programmausführung können Fehler auftreten, z.B. wenn durch 0 geteilt wird. Um mit diesen Fehlern umgehen zu können, gibt es die sogenannten Exceptions. In Java werden Exceptions "geworfen", wenn ein Fehler auftritt. Sie können dann im Code "aufgefangen" werden, um sie zu behandeln.
Anweisungen, von denen der Entwickler weiß oder vermutet, dass sie einen Fehler erzeugen könnten, fasst er in try-catch-Blöcke ein.
Das Programm durchläuft zunächst den try
-Block und springt in den catch
-Block, sobald eine Exception auftritt.
Wenn keine Exception auftritt, wird der catch
-Block nicht durchlaufen. Soll in beiden Fällen, also unabhängig vom
Auftreten eines Fehlers, ein weiterer Codeteil durchlaufen werden, wird dieser in den finally
-Block hinter dem
catch
-Block geschrieben.
Es ist dringend davon abzuraten, dass Exceptions durch einen leeren catch
-Block "geschluckt" werden. Vielmehr
sollte im catch
-Block der Fehler behandelt werden oder zumindest eine sinnvolle Fehlermeldung an den Benutzer
ausgegeben werden.
Alle Methoden in Java, die im Laufe ihrer Ausführung eine Exception auslösen könnten, müssen dies explizit kenntlich
machen. Dies geschieht bei der Definition der Methode durch die Verwendung des Schlüsselwortes throws. Dadurch wird
sichergestellt, dass alle Aufrufer dieser Methode immer ein try/catch
nutzen müssen, wenn sie die Methode aufrufen
wollen.
Der Entwickler selbst kann manuell eine Exception erzeugen, indem er im Code mittels throw eine Exception wirft.
Beispiel:
double divide(int a, int b) throws Exception { if (b == 0) throw new Exception("b darf nicht 0 sein."); return (double)a / b; } void test() { double result; try { double = divide(1, 0); } catch (Exception e) { System.out.println("Fehler bei der Berechnung: " + e.getMessage()); result = 0; } }
Ein Objekt ist ein "Ding" der realen Welt, das in der Software abgebildet werden soll. Ein Objekt kann alles sein: ein Auto, ein Vertrag, eine Tür, etc. Ein Objekt hat Funktionen und Eigenschaften. Eine Funktion eines Autos ist z.B. 'Gas geben' und eine Eigenschaft ist 'Farbe'. Die Funktionen heißen in der OO Methoden und die Eigenschaften nennt man Attribute.
Eine Klasse definiert die abstrakten Charakteristika eines Objekts, z.B. dass alle Hunde vier Beine haben und bellen können. Sie dient als Bauplan/Schablone für Objekte.
Ein Objekt ist dann eine konkrete Instanz dieser Klasse, z.B. der Schäferhund mit Namen Rex.
In Java werden Klassen geschrieben. Eine Klasse ist gleichzusetzen mit einem Datentyp. Es könnte also eine Klasse Car
geben, wobei Car dann der Datentyp wäre.
Die Attribute sind Variablen, die direkt unterhalb der Klasse definiert werden (also nicht in einer Methode). Diese Variablen können in allen Methoden der Klasse verwendet werden (siehe Scope).
Methoden werden ebenfalls auf Ebene der Klasse definiert und können damit intern verwendet werden, aber auch von außen aufgerufen werden (wie z.B. s.charAt(1);
, wobei s
dann der Name des Objektes ist).
Beispiel:
class Auto { String farbe; void gibGas() { System.out.println("Brumm brumm"); } }
Ein Objekt wird erstellt, indem der Konstruktor der Klasse aufgerufen wird. Konstruktoren sind besondere Methoden, deren Name mit dem der Klasse übereinstimmen muss und die keinen Rückgabewert haben. Ist kein expliziter Konstruktor angegeben, erzeugt Java automatisch einen Default-Konstruktor ohne Parameter. Wenn man selbst einen Konstruktor erstellt, wird der Default-Konstruktor nicht erzeugt und es kann nur noch mit dem angegebenen Konstruktor gearbeitet werden. Man kann beliebig oft den Konstruktor einer Klasse aufrufen und dabei wird jedes Mal ein völlig neues Objekt erstellt, mit dem dann gearbeitet wird.
Beispiel:
class Auto { String farbe; Auto(String farbe) { this.farbe = farbe; } }
Bei der Objektorientierung soll möglichst alles so implementiert werden, dass einzelne Programmteile gekapselt sind. Dies hat drei Gründe:
Die Sichtbarkeit beschreibt, wer Elemente (Klassen, Variablen und Methoden) nutzen kann. Es gibt drei Sichtbarkeitmodifikatoren:
Selbst | Subklassen | Welt | |
---|---|---|---|
private | x | ||
protected | x | x | |
public | x | x | x |
Bei der Implementierung von Klassen ist darauf zu achten, dass Attribute grundsätzlich als private
zu kennzeichnen sind!
Grund: Andernfalls könnte man Objekten von außen ungültige Werte setzen (z.B. Alter = -1).
Um private
-Variablen auch außerhalb der Klasse benutzen zu können, werden Getter und Setter verwendet:
car.getColor()
car.setColor("green");
Die Vererbung ist eine zentrale Funktion der Objektorientierung und bedeutet, dass Klassen von einer anderen Klasse Methoden und Attribute erben können. Diese Methoden und Attribute werden dadurch wiederverwendbar. Die erbende Klasse heißt Subklasse oder abgeleitete Klasse, die vererbende Klasse heißt Basisklasse.
Das Schlüsselwort extends
gibt die Basisklasse zu einer Subklasse an. Hier erbt die Klasse Circle
von der Klasse
Shape
. Mit dem Schlüsselwort super
hat man die Möglichkeit den Konstruktor der Basisklasse aufzurufen.
public class Circle(int originX, int originY, int radius) extends Shape { super(originX, originY); this.radius = radius; }
Mit der Annotation @Override
werden Methoden gekennzeichnet, die gleichnamige Methoden ihrer Basisklasse überschreiben.
public class A { public void eineMethode() {} } public class B extends A { @Override public void eineMethode() {} }
Durch das Schlüsselwort final
kann eine Methode nicht in einer Subklasse überschrieben werden. Das Schlüsselwort
abstract
definiert eine Methode oder eine ganze Klasse als abstrakt. Abstrakte Klassen können nicht instantiiert
werden. Eine abstrakte Methode definiert lediglich ihre Signatur, und eine Subklasse muss diese Methode dann implementieren.
Die Klasse ist dann nur für den Kopf der Methode zuständig, während die Implementierung an anderer Stelle erfolgt. Durch abstrakte Methoden
wird ausgedrückt, dass die Basisklasse keine Ahnung von der Implementierung hat und dass sich die Unterklassen darum kümmern müssen.
public abstract class MyVehicle { // die Methode getType wird für die Subklassen vorgegeben abstract protected String getType(); } public class MyCar extends MyVehicle { // die Subklasse überschreibt die abstrakte Methode und implementiert etwas Konkretes @Override protected String getType() { return "car"; } }
Die UML (Unified Modeling Language) ist eine grafische Beschreibungssprache, die insb. in der objektorientierten Softwareentwicklung zur Modellierung und Dokumentation eingesetzt wird.
Es gibt in Version 2.0 13 verschiedene Diagrammtypen. Die wichtigsten sind:
Ein Klassendiagramm ist ein Strukturdiagramm zur Modellierung von Klassen, Schnittstellen und deren Beziehungen. Ein Klassendiagramm zeigt also, in welcher Beziehung Klassen zueinander stehen (z.B. Basisklasse und Subklassen). Zudem gibt das Diagramm die Attribute und Methoden der Klassen an.
Beispieldiagramm: http://upload.wikimedia.org/wikipedia/commons/0/03/Klassendiagramm-1.png
In Java gibt es die Möglichkeit, den geschriebenen Code durch einen Testcode testen zu lassen. Dazu wird meist das Framework 'JUnit' verwendet. JUnit ist ein Framework für Unit-Tests und stellt die nötigen Funktionen zum Testen von Code bereit (insb. die sogenannten Assertions). Ein JUnit-Test sieht wie folgt aus:
@Before // wird vor JEDEM Test ausgeführt und sorgt dafür, dass das Testobjekt in seinen Initialzustand versetzt wird public void setup() { sut = new ObjectUnderTest(); } @Test // alle Methoden, die als Test markiert sind, führt JUnit automatisch aus public void test { assertThat(sut.calculate(), is(1)); }
Die zentrale Aufgabe beim Schreiben von Tests ist, sich zu überlegen, welche Fälle getestet werden sollen/müssen.
Beispiel: int add(int z1, int z2)
→ zu testen sind mindestens alle Kombinationen aus [positiven Zahlen, negativen
Zahlen, Null, Integer.MAX, Integer.MIN, MAX+1, MIN-1, +1, -1]
Das ergibt mindestens 81 Kombinationen, damit die Methode erschöpfend getestet ist. Das ist in der Praxis so nicht durchführbar.
Die Menge an Testfällen muss also der Situation angepasst werden. So wird ein Raketeningenieur in einem überlebenswichtigen System sicherlich mehr Tests für die add
-Methode implementieren als Informatikstudenten in einer Übungsaufgabe.
Eine interessante Vorgehensweise beim Testen ist das Test Driven Development: Man schreibt zuerst einen Test und
erst danach den Code, der den Test erfüllt. Dabei kann man sich von Eclipse den zu testenden Code generieren lassen, um
sich Schreibarbeit zu sparen.
Wichtig: Jeder neu geschriebene Test muss am Anfang fehlschlagen, um sicherzugehen, dass der Test nicht immer positiv ist!
In der Programmierung ist es wichtig, in Hinsicht auf zukünftige und noch nicht vorhersehbare Änderungen am Programm, den Code so variabel wie möglich zu erstellen, sodass er sich in Zukunft ohne großen Aufwand abändern lässt. In diesem Zusammenhang führt man häufig das sog. Refactoring durch, eine semantikinvariante Modifikation des Quelltexts. Refactoring ist somit eine manuelle oder automatisierte Strukturverbesserung von Programm-Quelltexten unter Beibehaltung des beobachtbaren Programmverhaltens. Dabei sollen die Lesbarkeit, Verständlichkeit, Wartbarkeit und Erweiterbarkeit verbessert werden, mit dem Ziel, den jeweiligen Aufwand für Fehleranalyse und funktionale Erweiterungen deutlich zu senken. Eine zentrale Voraussetzung für ein "sicheres" Refactoring ohne das Verhalten des Codes zu ändern sind automatische Tests.
Eclipse bietet die Möglichkeit, durch Markieren eines Codeabschnittes diesen automatisch in eine separate Methode auszulagern.
Dies funktioniert durch Markieren des gewünschten Abschnittes mit der rechten Maustaste und Auswahl von Refactor > Extract Method
.
Es öffnet sich ein Fenster, in dem ein Name für die neue Methode eingegeben werden kann.
Die Variablen, die in der Methode Verwendung finden, erkennt Eclipse selbstständig.
Durch bestätigen des Vorgangs implementiert Eclipse im Code selbstständig eine neue Methode.
Eclipse stellt uns im System einen Debugger zur Verfügung. Dies ist ein Tool, mit dem der Code Schritt für Schritt ausgeführt werden kann, um ihn nach Fehlern zu durchsuchen. Um in Eclipse den Debugger nutzen zu können, muss zunächst ein Breakpoint im Code gesetzt werden. Ein Breakpoint ist ein Haltepunkt im Code, bei dessen Erreichen im Programmablauf das Programm angehalten wird, sodass die nächsten Schritte einzeln nachvollzogen werden können. Ein Breakpoint wird in Eclipse gesetzt, indem mit der rechten Maustaste am Seitenrand "Toggle Breakpoint" ausgewählt wird. Um zu sehen was passiert, muss die Perspektive auf "Debug" geändert werden. In dieser Perspektive werden zusätzliche Views bereitgestellt, die z.B. die aktuell verwendeten Variablen und ihren jeweiligen Inhalt anzeigen.
aus \cite{Lau2011b}
System.out.println()
, die Grammatik (z.B. Verschachtelung von Blöcken) und die Syntax (z.B. Semikolons am Zeilenende).aus \cite{Krypczyk2011c}
Math
in Java).