TU Wien:Einführung in das Programmieren VL (Reiter)/Java Tutorial

Aus VoWi
Zur Navigation springen Zur Suche springen

Datentypen, Operatoren und Objekte[Bearbeiten | Quelltext bearbeiten]

Einfache Datentypen[Bearbeiten | Quelltext bearbeiten]

Zahlen[Bearbeiten | Quelltext bearbeiten]

  • boolean
    • Zum speichern eines Wahrheitswertes (true / false)
    • boolean b = true;
      
  • int
    • Repräsentiert einen Ganzzahlenwert
    • Der Werte Bereich eines Java-Integers liegt zwischen bis (-2,147,483,648 bis +2,147,483,647)
    • int i = 15;
      
  • double
    • Repräsentiert einen Gleitkommawert mit doppelter Genauigkeit
    • double x = 1.14159;
      
    • double x = 2d;
      

weitere, für EPROG nicht benötigte Datentypen[Bearbeiten | Quelltext bearbeiten]

  • Integer
    • byte (von -128 bis +127)
    • short ( bis )
    • long ( bis )
  • Gleitkommazahlen
    • float - einfache Genauigkeit

Zeichen[Bearbeiten | Quelltext bearbeiten]

  • char
    • kann genau ein Zeichen aufnehmen
    • char c = 'a';
      
    • auf diesen Datentyp sind auch Mathematische Operationen anwendbar, da er intern als Zahl gespeichert wird ('9' ist aber nicht 9!)
    • char c = '9' - '0'; // c = 9
      
  • String
    • Zeichenkette
    • String s = "Zeichenkette";
      


Die Objekt-Äquivalente zu primitiven Datentypen[Bearbeiten | Quelltext bearbeiten]

Da man nicht immer primitive Datentypen benutzen kann (zum Beispiel bei Collections, oder allgemein, wenn generische Typen gefragt sind) hat Java eine Reihe an Klassen, die die Äquivalente zu den primitiven Datentypen darstellen. Java unterstützt auch sogenanntes Auto(un)boxing, welches primitive Datentypen in ihren ensprechenden Objekte hin-und-her umwandelt, dies macht es einfach arithmetische Operatoren mit solchen Objekten zu benutzen.

Es gibt folgende Äquivalente (links stehen die primitiven Datentypen und rechts die entsprechenden Klassen):

  • byte - Byte
  • short - Short
  • int - Integer
  • long - Long
  • float - Float
  • double - Double
  • char - Character

(für besonders grosse Zahlen gibt es noch BigInteger und BigDecimal, jedoch sind diese auch anders hand-zu-haben)

Double d = 5d;
d *= 3;  // d = 15

Operatoren[Bearbeiten | Quelltext bearbeiten]

Arithmetische Operatoren[Bearbeiten | Quelltext bearbeiten]

  • Addition: +
    • int a = 5 + 3; // a = 8
      
    • int b = a + 2; // b = 10
      
  • Subtraktion: -
    • int a = 5 - 3; // a = 2
      
    • int b = a - 8; // b = -6
      
  • Multiplikation: *
    • int a = 5 * 3; // a = 15
      
    • int b = a * 2; // b = 30
      
  • Division: /
    • int a = 5 / 2; // a = 2
      
    • int b = a / 2; // b = 1
      
    • Bei der Integerdivision wird mittels abschneiden (truncate) gerundet d.h. auch 29/10==2 !
    • double c = 5d / 2; // c = 2.5
      
    • double d = ((double) a) / 3; // d = 0.66666667
      
  • Modulo: %
    • int a = 63 % 2; // a = 1
      
    • int b = 200 % 3; // b = 2
      

Vergleiche[Bearbeiten | Quelltext bearbeiten]

  • == (ist gleich)
    • Vergleicht 2 Ausdrücke miteinander und gibt einen Wahrheitswert zurück (siehe boolean)
    • boolean a = (5 == 3); // a = false
      
    • a == false // true
      
    • Strings lassen sich auf diese Art aber nicht überprüfen, da sie Objekte sind. Um Objekte zu vergleichen, siehe "Vergleichen von Objekten"
  • != (ist ungleich)
    • boolean a = (5 != 3); // a = true
      
    • a != false // true
      
  • >, <, >=, <=
    • grösser, kleiner, grösser-gleich, kleiner-gleich
    • 5 >= 3 // true
      
    • 8 < 4 // false
      

Logische Verknüpfungen[Bearbeiten | Quelltext bearbeiten]

  • && (UND-Verknüpfung)
    • gibt true zurück, wenn beide Eingangs-Ausdrücke true liefern, ansonsten false.
    • true  && true  // true
      
    • true  && false // false
      
    • false && true  // false
      
    • false && false // false
      
  • || (ODER-Verknüpfung)
    • gibt true zurück, wenn einer der beiden Eingangs-Ausdrücke true liefert, ansonsten false.
    • true  || true  // true
      
    • true  || false // true
      
    • false || true  // true
      
    • false || false // false
      
  • ^ (XOR-Verknüpfung)
    • gibt true zurück, wenn einer der beiden Eingangs-Ausdrücke true und der andere false liefert.
    • true  ^  true  // false
      
    • true  ^  false // true
      
    • false ^  true  // true
      
    • false ^  false // false
      
  • ! (Negation)
    • Kehrt einen Wahrheitswert um
    • !true  // false
      
    • !false // true
      

Logische Verknüpfungen werden einem noch öfter unterkommen (auch in der Mathematik unter "Elementare Aussagenlogik" und Mengenlehre). Sie können auch mittels Wahrheitstabellen dargestellt werden, jedoch ist dies für den Anfang nicht nötig und wird auch noch genügend in anderen Fächern wie Mathematik und Grundzüge der Informatik behandelt.

Bitweise Operatoren[Bearbeiten | Quelltext bearbeiten]

Bitweise Operatoren wendet man auf Ganzzahlen an. Bitweise Operatoren haben die Selbe Funktion wie Logische Operatoren, nur werden sie Bitweise (in der Binärdarstellung von rechts nach links) auf eine Zahl angewandt.

  • &
    •  10 & 3 // 2, weil 10 = 0b1010 und 3 = 0b11: 1 && 0 = 0, 1 && 1 = 1, 0 && 0 = 0, 1 && 0 = 0 -> 0b0010 = 2
      
  • |
    •  10 | 3 // 11, 1 || 0 = 1, 1 || 1 = 1, 0 || 0 = 0, 1 || 0 = 1 -> 0b1011 = 11
      
  • ^
    •  10 ^ 3 // 11, 1 ^ 0 = 1, 1 ^ 1 = 0, 0 ^ 0 = 0, 1 ^ 0 = 1 -> 0b1001 = 9
      
  • ~
    • Dazu gebe ich kein Beispiel an, da es von der Anzahl der Bitstellen abhängt, die das Zahlenformat aufnehmen kann.

Objekte[Bearbeiten | Quelltext bearbeiten]

Die Object-Klasse ist die Wurzel aller in Java existierender Klasse, und bietet grundlegende Methoden wie zum Beispiel equals, clone und toString. Mittels Klassen kann man zum Beispiel eigene Datentypen erstellen, wenn man zum Beispiel einen Datentyp Human möchte, der einen Menschen repräsentiert sähe das wiefolgt aus:

public class Human
{
  int height;
  long eyeColor, hairColor;
  String firstName, secondName, surName, nick;
  Human[] relations;
}

Die Klasse beinhaltet jetzt nur sehr wenige Eigenschaften eines Menschen, aber ich denke es ist das Prinzip klar.

toString[Bearbeiten | Quelltext bearbeiten]

Die toString Methode wird aufgerufen wenn man einen String aus dem Objekt erzeugen möchte.

System.out.println(obj);

Wenn man keine eigene toString-Methode definiert gibt es nur einen nicht sehr aussagekräftigen Referenzwert auf das Objekt aus, jedoch kann man die Methode überschreiben, im Falle des Menschen sähe das so aus:

public class Human
{
  int height;
  long eyeColor, hairColor;
  String firstName, secondName, surName, nick;
  Human[] relations;

  public String toString()
  {
    return "Height: " + height + "\nEye-Color: " + eyeColor; // and so on
  }
}

Wenn man die toString-Methode nicht überschreibt wird die Standard-toString-Methode von Object aufgerufen, die nur eine kryptische Addresse zurückgibt.

Vergleichen von Objekten[Bearbeiten | Quelltext bearbeiten]

Um Objekte, wie zB Strings zu vergleichen muss man sich der .equals-Methode bedienen, weil ein simpler Vergleich mit ==, die Referenzwerte vergleicht, und die nur gleich sind, wenn es auch wirklich die selbe Instanz des Objektes ist.

"abc".equals("def") // false
String a = "cde", b = "cde";
a.equals(b) // true

Um überprüfen zu können, ob ein Objekt grösser oder kleiner als ein anderes ist, muss die Klasse das Comparable interface implementieren, dann sind Vergleiche über .compareTo(Object) möglich. Das Ergebnis von .compareTo ist ein Integer-Wert <0 wenn das erste Objekt kleiner ist, 0 wenn es gleich ist und >0 wenn es grösser ist.

Vergleiche bei eigenen Objekten[Bearbeiten | Quelltext bearbeiten]

Wenn man selbst eine Objekt-Klasse schreibt und sie vergleichen möchte muss man die Methode .equals implementieren, die könnte wiefolgt aussehen:

public boolean equals(Object o)
{
	if(this.getClass().equals(o.getClass()))
	{
		OwnType ot = (OwnType) o;
		return ot.varField.equals(this.varField)
	}
	return false;
}

compareTo analog nach implementieren des Comparable interfaces.

Arrays[Bearbeiten | Quelltext bearbeiten]

Arrays dienen der Speicherung von mehreren Werten in einer Variable. Es kann zum Beispiel sehr aufwändig sein, wenn man 10 Werte einliest und dann jeden in einer eigenen Variable ablegt (siehe folgendes Beispiel). statt:

int a = 3, b = 5, c = 7, d = 8, e = 12, f = 21, g = 92, h = 120, i = -5, j = -12;

könnte man einfach einen Array machen:

int[] arr = new int[] {3, 5, 7, 8, 12, 21, 92, 120, -5, -12};

Um auf die einzelnen Variablenwerte zuzugreifen schreibt man die gewünschte Werte-Position in eckige Klammern. Gezählt wird ab Position 0, das heisßt wenn man das 3. Element auslesen möchte, macht man folgendes:

int[2] // 7};

Werte lassen sich auf die selbe Art auch setzen.

int[2] // 7
int[2] = 3;
int[2] // 3};

Man kann natürlich auch vorerst einen leeren Array erstellen, und ihn erst später mit Daten füllen:

int[] arr = new int[10];
arr[0] = 3;
arr[1] = 5;
...

Wenn man vorheriges Beispiel fortsetzt, und von den Werten die Summe und den Mittelwert berechnen möchte, lässt sich dies ganz einfach mit einer for-each-Schleife (siehe Schleifen - for-each) durchführen:

int[] arr = new int[] {3, 5, 7, 8, 12, 21, 92, 120, -5, -12};
int sum = 0;
for(int i : arr)
{
    sum += i;
}
double average = ((double) sum) / arr.length; // Mittelwert als double

wenn man das selbe mit den 10 verschiedenen Variablen machen würde, würden sich nicht nur etliche Fehler einschleichen, es wäre auch im Code ziemlich unbequem handzuhaben. Weiters ist es nicht erweiterbar, weil dem Array könnte man jetzt einfach 5 Elemente anhängen, und die Schleife würde wieder aufs richtige Resultat kommen, bei den Variablen müsse man wieder ein wenig herumprogrammieren.

Man könnte zum Beispiel auch einen String-Array nehmen, und von jedem Element die Zeichenlänge aufsummieren oder alle 'a's in einem String-Array zählen. Dies lasse ich allerdings als Übungsaufgabe übrig.


Collections[Bearbeiten | Quelltext bearbeiten]

Collections sind Datentypen, die eine gewisse Anzahl an Objekten speichern können (ähnlich eines Arrays). "Collection" steht also wie die die 1:1 Übersetzung nahelegt für einen "Haufen" von Daten. Wie diese Daten angeordnet werden, wird durch die jeweilgen Implementation bestimmt (Die Klasse "Collection" selbst ist nur ein Interface und kann nicht instanziert werden). Je nach Einsatzgebiet wählt man die passende Collection (je nachdem ob der Schwerpunkt auf Geschwindkeit/Effizienz: bei einfügen/auslesen, suchen, löschen, erweitern,einfachheit der Bedienung etc. gebraucht wird).

Es werden hier einige wichtige Collections vorgestellt:

  • List
    • ArrayList - oft verwendet wenn man ein Array-artigen Datentyp braucht - ein dymanischer Array mit einigen Eigenschaften einer List (zB. add). Intern ist es einfach ein Array.
    • Vector - ähnlich einer ArrayList eine dynamisch wachsender Datentyp.
    • LinkedList - Die klassischer Ausformung einer verketteten Liste (kann auch als Queue oder Deque verwendet werden)
  • Set - Eine Collection die keine Duplikate speichert
    • HashSet - schnelles Einfügen und Suchen - Einträge sind aber intern unsortier.
    • TreeSet - Ein Sortiertes Set
  • Stack - eigentlich ein Untertyp von Vector mit aber einem ganz speziellen Verhalten: man hat nur 2 Methoden (push ,pop) - ähnlich einem Stapel wird das Element zurückgeliefert, dass als letzes auf den "Stack" draufgelegt worden ist. (LIFO: Least in First Out)
  • Queue - wie eine "Schlange" an der Kassa: es kommt der dran, der am längsten in der Queue wartet (FIFO: First in First Out). Praktisch das Gegenteil eines Stacks.
  • Map - Eine Map hat immer einen Key und einen Value, in manchen Programmiersprachen auch als associative-Arrays zu finden. Damit kann man andere Werte als Schlüssel (Key) verwenden.
    • HashMap

Hinweis: für Collections muss man das package "java.util.*" importieren (am Anfang ein "import java.util.*;")

Bei Collections muss der Typ ein Objekt sein, also kein Primitive wie int, jedoch gibt es dementsprechende Objekt-Boxen für primitives:

  • int - Integer
  • char - Character
  • String - bleibt, da der String kein primitive ist!

Der Typ wird nach der Collection mit <TYP> gekennzeichnet. (auch unter generischen Typen bekannt) Falls man zur Laufzeit nicht sicher sagen kann welchen Typ man braucht kann man mit "Collection<Object>" initialisieren, muss jedoch dann immer auf den entsprechenden Typen casten (umwandeln), damit man die Methoden der speziellen Klasse nutzen will. Dies ist aber NICHT empfohlen und zeugt fast immer von schlechten Programmierstil! (u.a. weil Down-Casts immer in OOP Sprachen immer schlecht und unsicher sind!) In so einem Fall eher ein Array verwenden.

Nun zu einem Beispiel mit einer ArrayList:

import java.util.*;

public class ArrayListTest
{
  public static void main(String[] args)
  {
    ArrayList<String> strList = new ArrayList<String>();
    ArrayList<Integer> intList = new ArrayList<Integer>();
    
    strList.add("Hallo");
    strList.add("Welt");
    
    intList.add(5);
    intList.add(18);

    System.out.println(strList.get(0) + " " + strList.get(1)); // Hallo Welt
    System.out.println(intList.get(1) - intList.get(0)); // 13
  }
}

Andere Collections analog dazu. (bei der HashMap initialisieren mit: HashMap<KEY,VAL> d.h. eine HashMap, die als Key Characters und als Values Strings halten soll, wird so initialisiert:

HashMap<Character,String> hmap = HashMap<Character,String>()

)

Das praktische an Collection ist, dass diese meist Methoden haben um eine Collection in eine andere umzuwandeln zb. Hashmap in ein ArrayList (mit der Hashmap Methode "values").

Weitere Details lassen sich in der Java Dokumentation "The Collection Framework" finden: [1]

Kontrollstrukturen[Bearbeiten | Quelltext bearbeiten]

Bedingungen[Bearbeiten | Quelltext bearbeiten]

if-Bedingung[Bearbeiten | Quelltext bearbeiten]

Die if-Bedingung ist eines der wichtigsten Elemente der Programmiersprachen. Sie dient zur Überprüfung von Ausdrücken. Am besten lernt man ja noch immer durch Beispiele:

int a = 3;
if(a == 3)
{
    System.out.println("a ist gleich 3");
}
else
{
    System.out.println("a ist nicht gleich 3");
}

Wenn man dies Ausführt (natürlich in der passenden Klasse) erhält man wie zu erwarten "a ist gleich 3" (ausser man ändert die Variable a)

ein if-Konstrukt kann auch ohne else auftreten zB:

int a = 3;
if(a == 3)
{
    System.out.println("a ist gleich 3");
}

bzw auch mit einer oder mehreren "else if"-bedingung

int a = 3;
if(a == 3)
{
    System.out.println("a ist gleich 3");
}
else if(a == 4)
{
    System.out.println("a ist gleich 4");
}
else if(a > 5)
{
    System.out.println("a ist grösser 5");
}
else
{
    System.out.println("a erfüllt keine der obigen Bedingungen (ist entweder = 5 oder <3)");
}

inline if-Bedingung[Bearbeiten | Quelltext bearbeiten]

Im Allgemeinen kann man alles, was sich mit einer inline-if-Bedingung machen lässt auch so umsetzen, jedoch spart man sich ab und zu ein paar Zeilen an Code.

Eine inline-if-Bedingung ist im Grunde nichts anderes als eine If-else-Bedingung in einer Zeile notiert. Dieses Konstrukt sieht wiefolgt aus:

( Bedingung ? Ergebnis_wenn_true : Ergebnis_wenn_false )
int a = 3;
System.out.println(( a == 3 ? "a ist gleich 3" : "a ist nicht gleich 3" ));

switch[Bearbeiten | Quelltext bearbeiten]

Eine switch-Bedingung ist wie mehrere if-else if-else-Konstruke, jedoch spart man sich wieder ein bisschen an Code und es ist auch besser lesbar.

int a = 3;
switch(a)
{
   case 3:
         // a = 3
      break;
   case 4:
         // a = 4
      break;
   case 5:
         // a = 5
      break;
   default:
         // andernfalls...
}

Ein Nachteil ist das man auf diese Art keine Objekte (wie zB Strings) Vergleichen kann. Es lassen sich auch die "break;" anweisungen entfernen, dann führt er auch den code des nächsten mit aus, darauf möchte ich jetzt jedoch nicht eingehen. (vielleicht hat ja wer anderer Lust?)

Schleifen[Bearbeiten | Quelltext bearbeiten]

while-Schleife[Bearbeiten | Quelltext bearbeiten]

Eine while-Schleife führt etwas so lange aus, bis die Bedingung nicht mehr erfüllt wird. Auf Deutsch würde man sagen "Solange XY erfüllt ist, mache ..." (XY ist in dem Fall die Bedingung)

while(x > 3) // while(XY) // XY wurde durch die Beispielbedingung "x > 3" ersetzt
{
...
}

Mit der Bedingung "x > 3" wird die Schleife solange ausgeführt bis x nicht mehr grösser als 3 ist. Wenn die Bedingung schon Anfangs nicht erfüllt ist wird die ganze Schleife übersprungen.

do-while (Schwanzgesteuert)[Bearbeiten | Quelltext bearbeiten]

Die do-while-Schleife kann insofern sinnvoll sein, als dass man etwas mindestens einmal ausführen möchte und erst dann die Bedinung überprüft werden soll.

do
{
...
} while(x > 3);

In dem Beispiel wird der Code mindestens einmal ausgeführt, und dann sooft, bis x nicht mehr grösser als 3 ist.

Beispiel[Bearbeiten | Quelltext bearbeiten]

Da do-while-Schleifen in der Praxis kaum verwendet werden, hier ein Beispiel wofür sie sich zum Beispiel gut eignen:

Dieser Code liest solange eine Zeile von der Konsole ein und verarbeitet sie, bis "parseCommand" >false< liefert

do
{
	String cmd = System.console().readLine();
} while(parseCommand(cmd));
  • parseCommand
    
    ist eine Funktion um den von der Konsole eingelesenen Befehl zu verarbeiten, er gibt einen Boolean-Wert zurück. Falls der Rückgabewert false ist, wird das Programm beendet.
  • System.console().readLine()
    
    ist der Befehel um eine Zeile von der Konsole einzulesen.

for-Schleife[Bearbeiten | Quelltext bearbeiten]

Eine for-Schleife ist zum hoch/ab-zählen gedacht. Auf Deutsch würde man sagen "Zähle von x bis y in n-Schritten und mache ...", dass in Java so aussehen würde:

for(int i = x; x <= y; i += n)
{
...
}

Die Variable i wird in dem Fall auch Zählervariable genannt. Schreibweise: for(initialisierung; ausführungsbedingung; fortsetzungsanweisung)

Eine for-Schleife liese sich auch durch eine while-Schleife darstellen, man bekommt aber mehr code (äqivalent zur for-Schleife oben)

int i = x;
while(i <= y)
{
...
i += n;
}

Um ein Array Element für Element durchzugehen kann man folgenden Code verwenden:

int[] array = new int[] {1, 2, 3, 4, 5};
for(int i = 0; i < array.length; i++)
{
System.out.println(array[i]);
}

gibt folgendes aus:

1
2
3
4
5

for-each[Bearbeiten | Quelltext bearbeiten]

Die for-each-Schleife stellt eine Sonderform der for-Schleife dar und wird dazu verwendet alle Elemente eines Arrays oder einer Collection durchzugehen:

int[] array = new int[] {1, 2, 3, 4, 5};
for(int i : array)
{
System.out.println(i);
}

gibt folgendes aus:

1
2
3
4
5

Der Unterschied zur obigen Variante ist, das es auch für Collections möglich ist, jedoch kommt man an die Positionen der Werte im Array nicht so einfach heran. (Falls zweiteres nötig sein sollte, bitte bei der normalen for-Schleife bleiben)

Schreibweise: for(variable : array)

das Beispiel von oben liese sich natürlich auch mittels einer normalen for-Schleife durchführen, nur ist es bei weitem nicht so elegant. Äquivalenter Code:

int array[] = new int[] {1, 2, 3, 4, 5};
for(int i = 0; i < array.length; i++)
{
System.out.println(array[i]);
}

Endlosschleifen[Bearbeiten | Quelltext bearbeiten]

Endlosschleifen sind normalerweise nicht empfohlen zu verwenden, aber wenn man nicht darum herumkommt gibt es folgende einfache Möglichkeiten:

while(true)
{
...
}


for(;;)
{
...
}

Jedoch sollte man nicht vergessen, die Endlosschleife mit einer

break;

abzubrechen, da ansonst das Programm endlos läuft. (und es sich dann nur über [CTRL]+[C] beenden lässt)

Exceptions[Bearbeiten | Quelltext bearbeiten]

Exceptions dienen zur Fehlererkennung und Fehlerbehandlung von Klassen. Ich denke es hat jetzt wenig Sinn den Vorteil von Exceptions im Vergleich zu anderen Programmiersprache wie C zu nennen, da dies ja ein Beginners-Tutorial sein soll.

Exceptions sind Klassen, die die "Exception"-Klasse, oder eine ihrer Kinderklassen, wie RuntimeException etc., erweitern. Eine RuntimeException wird zum Beispiel ausgelöst, wenn man eine Division durch 0 durchführen möchte (im genaueren ist es eine "ArithmeticException"). Eine "normale" Exception in dem Sinne wäre zum Beispiel die IOException, die von der File-Klasse geworfen wird, wenn ein Input-/Output-Error (Eingabe-/Ausgabe-Fehler) passiert.

Exceptions, die nicht die RuntimeException-Klasse erweitern müssen per try-catch gefangen werden, ansonst bekommt man einen Compiler-Fehler. Im übrigen sollen RuntimeExceptions nicht gefangen werden, sondern es soll über Abfragen auf Fehler überprüft werden. Das heisst, wenn man zum Beispiel einen Scanner einliest nicht folgendes machen:

Scanner sc = new Scanner(Sytem.in);
try
{
    while(true)
    {
        System.out.println(sc.nextInt());
    }
}
catch(Exception e)
{
}
System.out.println("Alle Werte eingelesen");

sondern bei folgendem bleiben:

Scanner sc = new Scanner(Sytem.in);
while(sc.hasNextInt())
{
    System.out.println(sc.nextInt());
}
System.out.println("Alle Werte eingelesen");

Deklaration und werfen von Exceptions[Bearbeiten | Quelltext bearbeiten]

Im weiteren Text werde ich immer, wenn nicht anders genannt, von normalen Exceptions (und nicht von RuntimeExceptions) reden. Um eine Exception weren zu können, muss man sie zuerst im Methodenkopf per throws deklarieren, das sieht wiefolgt aus:

public void throwsException throws IOException
{
    throw new IOException("Just for fun Exception...");
}

In dem Beispiel sieht man auch gleich, wie man eine neue Exception wirft, nämlich über "throws".

Fangen von Exceptions[Bearbeiten | Quelltext bearbeiten]

Normale Exceptions müssen, wie schon gesagt, immer gefangen werden, ansonst bekommt man mit dem Compiler Schwierigkeiten. Fangen von Exceptions mit dem try-catch-Konstrukt:

try
{
    throws new IOException("J4F Exception");   // so hätte dies keinen Sinn, bitte durch die Methode, die die Exception wirft ersetzten ;)
}
catch(IOException e)
{
    System.out.println(e);
}
catch(SecurityException e)
{
    System.out.println(e);    // wird bei diesem Beispiel code nie erreicht, soll aber zeigen, dass mehrere catch-Blöcke möglich sind
}

Beim try-catch-Block gibt es auch noch ein "finally" das immer aufgerufen wird. (Dann eben ein try-catch-finally oder nur ein try-finally-Block)

Auf den try-catch-finally-Block möchte ich nicht im Detail eingehen, jedoch möchte ich noch eine Besonderheit des try-finally-Blocks zeigen. Welchen Wert würde folgende Funktion zurückliefern?

try
{
    return 5;
}
finally
{
    return 10;
}

Um es aufzulösen, die Funktion liefert 10 zurück, das heisst mit dem finally-Block lässt sich Code auch nach einem return ausführen! (Man kann zB im try ein "return a;" schreiben und danach dann die variable a nochmal verändern)

Eigene Exceptions[Bearbeiten | Quelltext bearbeiten]

Eigene Exceptions lassen sich leicht durch erweitern der Exception-Klasse schreiben, und sehen eigentlich immer wiefolgt aus:

public class MyException extends Exception
{
    public MyException()
    {
        super();
    }

    public MyException(String message)
    {
        super(message);
    }

    public MyException(String message, Throwable cause)
    {
        super(message, cause);
    }

    public MyException(Throwable cause)
    {
        super(cause);
    }
}

Über den Konstruktor mit dem cause-Parameter lassen sich Exceptions "durchwerfen", dh wenn von der File Klasse eine IOException geworfen wird, kann man sie mittels try-catch fangen und über throws mit einer eigenen Exception durchwerfen. (Exceptions erweitern immer die Throwable-Klasse)

OOP (Objektorientierte Programmierung)[Bearbeiten | Quelltext bearbeiten]

Klassen (class)[Bearbeiten | Quelltext bearbeiten]

Klassen sind das wichtigste Element einer Objektorientierten Programmiersprache wie Java. Klassen bestehen aus der Klassensignatur und dem Klassenkörper. Die Klassensignatur gibt den Namen, die Sichtbarkeit, die Vererbungen/Interfaces der Klasse an und zeigt an, dass es sich um eine Klasse handelt. Eine Klassensignatur für die "TestClass" sieht so aus:

public class TestClass

In dem Fall implementiert die Klasse keine Interfaces und erweitert in dem Sinn auch keine Klasse (ausser der Object-Klasse, die Basis aller Klassen in Java ist). Die Sichtbarkeit ist auf "public" gesetzt, das heisst sie ist öffentlich sichtbar (es kann jede andere Klasse auf diese Klasse zugreifen). Im Klassenkörper befinden sich die zur Verfügung gestellten Methoden und Variablen der Klasse. Variablen in Klassen sollten auf "private" gesetzt werden und über Getter-/Setter-Methoden zugreifbar gemacht werden. (Ein Beispiel unter "Beispiel für Getter und Setter")

Instanzieren von Klassen[Bearbeiten | Quelltext bearbeiten]

Um eine Klasse verwenden zu können muss man sie zuerst instanzieren, das heisst ein neues Klassen-Objekt erstellen. Die Instanzierung erfolgt über das Keyword "new". (oder über Factory-Methoden, auf die ich jetzt nicht eingehen möchte)

Beispiel zum Instanzieren einer Klasse:

TestClass tc = new TestClass();

Um ein Praxisbezogneres Beispiel zu geben:

Double value = new Value(5d);

Da Java jedoch Auto(un)boxing unterstützt, funktioniert auch folgendes:

Double value = 5d;

Beispiel für Getter und Setter[Bearbeiten | Quelltext bearbeiten]

Für das Verstehen des folgenden Beispiels sollte man darauf hinweisen, dass double und Double nicht das selbe ist. double ist der Primitive Datentyp und Double das Objekt-Äquivalent. In der Funktionalität ist in Java kein Unterschied, da es Auto(un)boxing unterstützt, jedoch kann Double den Wert null. (null entspricht nicht 0, sondern bedeutet, das das Feld noch keinen Wert besitzt)

Ein Beispiel, bei dem die Getter-/Setter-Methoden auf den ersten Blick eventuell nicht sinnvoll erscheinen wäre folgendes:

public class GetterSetterSample
{
    private Double value;
    public GetterSetterSample()
    {
        this.value = 0d;
    }

    public GetterSetterSample(double value)
    {
        this.value = value;
    }

    public void setValue(double v)
    {
        this.value = v;
    }

    public Double getValue()
    {
        return this.value;
    }

    public Double getValueMul5()
    {
        return this.value * 5d;
    }
}

Der Variablenzugriff erfolgt so:

GetterSetterSample gse = new GetterSetterSample();
gse.setValue(5d);
gse.getValue();    // 5
gse.getValueMul5()    // 25

da man es auch durch folgendes ersetzen könnte:

public class GetterSetterSample
{
    public Double value;
    public GetterSetterSample()
    {
        this.value = 0d;
    }

    public GetterSetterSample(double value)
    {
        this.value = value;
    }

    public Double getValueMul5()
    {
        return this.value * 5d;
    }
}

In dem Fall würde der Variablenzugriff wiefolgt erfolgen:

GetterSetterSample gse = new GetterSetterSample();
gse.value = 5d;
gse.value;    // 5
gse.getValueMul5()    // 25

Soweit wäre auch alles gut, jedoch ergibt sich ein Problem, wenn der User versucht value auf null zu setzen, und dies ist nur bei der zweiten Möglich (weil der Setter der 1. Klasse einen double-Typ verlangt, der ein primitiver Datentyp ist und somit kein null als Parameter übernimmt) Man könnte bei der 2. Version also Folgendes ausführen, das zu einer Exception führt:

GetterSetterSample gse = new GetterSetterSample();
gse.value = null;
gse.value;    // null
gse.getValueMul5()    // Exception

Modifier von Methoden und Variablen[Bearbeiten | Quelltext bearbeiten]

Es gibt grundsätzlich folgende modifier von Methoden und Variablen:

  • Sichtbarkeitsbezogene Modifier
    • private - Zugriff nur für die Klasse selbst
    • protected - Zugriff auch für erbende Klassen
    • (default) - Zugriff nur innerhalb des selben Packages (auf Packages möchte ich an dieser Stelle nicht weiter darauf eingehen)
    • public - Öffentlicher Zugriff - alle Klassen können darauf zugreifen
  • Andere
    • final - Variable kann nur ein mal zugewiesen werden (zum Beispiel für Konstanten zu verwenden)
    • static - Zugriff ohne Instanziierung, wenn möglich vermeiden durch Objektorientierung - Manchmal nötig für primitive Datentypen oder Arrays

Es gibt noch weitere Modifier, wie volatile, auf die ich aber nicht eingehen möchte da sie nicht zu Einführung ins Programmieren gehören.

public class TestClass
{
    private int x = 3;
    public static final double PI = 3.14159;
    float u = 1.32;    // default modifier

    private int returnX()    // private: nur von dieser Klasse aus zugreifbar
    {
        return x;
    }

    protected double returnXmulPI()    // protected: auch für erbende Klassen zugreifbar
    {
        return x * TestClass.PI;
    }

    public static void outputName(String name)    // static: ohne Instanziierung ausführbar, wenn möglich vermeiden, da Java objekt-orientiert ist
    {
        output("Name: " + name);
    }

    private static void output(String txt)    // static, private: Bedeutung siehe oben
    {
        System.out.println(txt);
    }
}


Überladen von Methoden (overloading)[Bearbeiten | Quelltext bearbeiten]

Um beim obigen Beispiel (der 1. Klasse) zu bleiben könnte man setValue überladen, das heisst verschiedene Parameter unterstützten:

public class GetterSetterSample
{
    // ... code from above

    public void setValue(double v)
    {
        this.value = new Double(v);
    }

    public void setValue(int v)
    {
        this.value = new Double((double) v);
    }

    public void setValue(String v)
    {
        this.value = new Double(v);    // alternative: this.value = Double.valueOf(v);
    }

    // ... code from above

}

Schnittstellen (interface)[Bearbeiten | Quelltext bearbeiten]

Eine Schnittstelle legt fest, welche Methoden eine Klasse implementieren muss. Zum Beispiel könnte es ein Interface "ElectronicDevice" geben, das ein Elektrogerät darstellt. Elektroeräte müssen über einen Stromanschluss und einen Ein-Aus-Knopf verfügen. In einem Interface kann man hier zB einstecken, ausstecken, einschalten, ausschalten und überprüfen ob es ein-oder-ausgeschalten ist verlangen.

public interface ElectronicDevice
{
    public void plugIn();
    public void plugOut();

    public void turnOn();
    public void turnOff();

    public boolean isTurnedOn();
}

Eine Klasse, die dieses Interface implementiert muss dann die gegebenen Methoden unterstützen.

public class Radio implements ElectronicDevice
{
    private boolean turnedOn = false;

    public void plugIn()
    {
        // Plug in
    }

    public void plugOut()
    {
        // Plug out
    }

    public void turnOn()
    {
        turnedOn = true;    // turn on
    }

    public void turnOff()
    {
        turnedOn = false;    // turn off
    }

    public boolean isTurnedOn()
    {
        return this.turnedOn;
    }

    public double searchStation()
    {
        return 0d;    // search radiostation and return frequency
    }
}

Vererbung[Bearbeiten | Quelltext bearbeiten]

Eine Klasse kann immer nur eine andere Klasse erweitern (extends <SuperClass>), aber beliebig viele Interfaces implementieren (implements <Interface1>[, <Interface2>, <Interface3> ...])

Prinzipien der OOP[Bearbeiten | Quelltext bearbeiten]

Weitere Kapitel[Bearbeiten | Quelltext bearbeiten]

Rekursion[Bearbeiten | Quelltext bearbeiten]

bei der Rekursion verweise ich vorübergehend noch auf Wikipedia [[2]]

--Anwesender 16:31, 11. Jul. 2009 (CEST)