home  |  suche  |  kontakt/johner  |  institut  |  hinweise 
studierende  |  tech-docs  |  blog  |  mindmailer 

2 Vertiefung Java

2.1 Assertions

Assertions werden eingesetzt um Vor- und Nachbedingungen prüfen zu können (z.B. dass Preise positiv sein müssen). Das Schlüsselwort dafür heisst assert (gibt es seit Java 1.4). Die Syntax lautet folgendermaßen

assert <BEDINGUNG>:<[BESCHREIBUNG]>;

wobei die Beschreibung optional ist und die Bedingung immer vom Typ boolean sein muss, ansonsten gibt es einen Compilerfehler.

Um Assertions zu aktivieren muss der JVM Parameter ea mit angegeben werden (z.B. java -ea assertions.AssertionTest). Wird ein Programm, das assert-Anweisungen enthält, mit deaktivierten Assertions aufgerufen, verhält es sich, als wären die entsprechenden Zeilen nicht vorhanden.

Wenn Assertions aktiviert sind überprüft das Programm die Bedingung und fährt fort, wenn sie erfüllt ist, andernfalls wird ein AssertionError erzeugt. Wenn die <BESCHREIBUNG> fehlt, hat der AssertionError eine leere Fehlermeldung, ansonsten wird der dort beschriebene Ausdruck an den Konstruktor von AssertionError übergeben und dient als Meldungstext für das Fehlerobjekt.

Auch wenn die selben Tests auch mit einer if-Anweisung gemacht werden könnten, ist es oft empfehlenswert auf Assertions zurückzugreifen:

  • Der Programmcode ist kürzer.
  • Auf den ersten Blick ist zu erkennen, dass es sich um einen Korrektheits-Check handelt und nicht um eine Verzweigung zur Steuerung des Programmablaufs.
  • Assertions lassen sich zur Laufzeit wahlweise an- oder ausschalten. Im Gegensatz zu einer einfachen if-Anweisung verursachen sie praktisch keine Verschlechterung des Laufzeitverhaltens, wenn sie deaktiviert sind.

Jedoch sollten Assertions nur bei der Entwicklung eingesetzt werden und außerdem nie bei public-Methoden oder Kommandozeilenparametern. Diese Überprüfungen sollten immer aktiv sein und nicht abschaltbar sein.

package progstruk.assertions;

import java.util.Scanner;

public class AssertTest {
  public static void main(String args[]) {
    Scanner input = new Scanner(System.in);

    System.out.print("Bitte eine Zahl zwischen 0 und 10 eingeben: ");
    int number = input.nextInt();

    // hier wird mit assert geprüft ob die Zahl >= 0 und <=10 ist
    assert (number >= && number <= 10"falsche Zahl: " + number;

    System.out.println("Sie haben folgende Zahl eingeben " + number);
  
Bitte eine Zahl zwischen 0 und 10 eingeben: -7

 

Bitte eine Zahl zwischen 0 und 10 eingeben: -7

Exception in thread "main" java.lang.AssertionError: falsche Zahl: -7

at progstruk.assertions.AssertTest.main(AssertTest.java:13)

 

 

Diese Fehlermeldung wird ausgegeben, wenn Assertions aktiviert sind und eine Zahl eingegeben wird, die nicht zwischen 0 und 8 eingeben wird.

2.2 Reflection und Annotation

Das Reflection-Modell erlaubt es uns, Klassen und Objekte, die zur Laufzeit von der JVM im Speicher gehalten werden, zu untersuchen und in begrenztem Umfang zu modifizieren. Reflection gehören in die Kategorie Meta-Programming, da sie auf den Klassen und Objekten anderer Programme operieren, es werden sogenannte Metadaten verwendet. Ein Metadatum ist eine Information über eine Information. In Java beschreibt ein Class-Objekt, was Klassen »können«, also welche Konstruktoren und Methoden sie haben, welche Attribute sie besitzen und wie die Erweiterungsbeziehungen sind. Um an die entsprechenden Daten zu gelangen, stehen folgende Methoden auf den Klassen java.lang.reflect.Method und java.lang.Class zur Verfügung. Weil man dabei in die Klasse "hereinschaut" und prüft was darauf definiert ist, spricht man dabei von Introspection.

Methoden auf "Method":

  • getName():String
  • getParameterTypes():Class[]
  • getReturnType():Class

Methoden auf "Class":

  • getConstructors():Constructor[]
  • getMethods():Method[]    //öffentliche und vererbte Methoden
  • getDeclaredMethods():Method[]   //private und öffentliche Methoden der Klasse, keine vererbten Methoden
  • isAbstract():boolean

"Declared" in der Methode getDeclaredMethods() bedeutet welche Methoden sind tatsächlich in der Klasse programmiert, also stehen in der Java-Klasse, egal ob private oder public. Methoden aus der Oberklasse werden hier nicht zurückgeliefert, weil diese sind nicht in der abgefragten Klasse beschrieben.

Hier ein Beispiel wie eine Klasse nur mit dem Klassenname als String instanziert und eine Methode mit Hilfe des Namens aufgerufen wird über Reflections.

Vor- und Nachteile von Reflections

Vorteile Nachteile
Flexibilität - erst zur Laufzeit wird entschieden, welche Klasse/Methode instaziert/aufgerufen wird Performance
compilierter Code kann inspiziert werden (eventl. auch Nachteil) fehleranfällig (keine Kompiliersicherheit, Fehler erst zur Laufzeit)
kann in verteilten Anwendungen zum Einsatz kommen  

Auch bei Annotations geht es um Metadaten. Mit Annotations ist es dem Programmiere möglich Metadaten Klassen, Methoden, Attributen usw. hinzuzufügen. Annotations haben verschiedene Nutzen, z.B.

  • Informationen für den Compiler (Fehlerentdeckung, Warnings ausblenden...)
  • Verarbeitung während der Entwicklungs- und Kompilierzeit, z.B. nutzen Softwaretools Annotations um Code, XML Dateien usw. zu generieren
  • Informationen währen der Laufzeit mit Annotations

Bekannte Annotations sind z.B. @Deprecated (nicht mehr benutzen, abgelehnt) oder @Override (eine Methode der Oberklasse wird überschrieben), diese Annotations werden vor eine Methode gestellt.

Annotations stehen erst mit Java 5.0 zur Verfügung.

Um etwas als Annotations zu deklarieren, wird immer das @-Zeichen vorangestellt.

Folgendes Beispiel zeigt wie man Informationen (Metadaten) in diesem Fall einer Klasse hinzufügt.

package annots;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(value=RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PACKAGE})
public @interface VendorInformation {
  String firmename();
  int anzahlangestellte();
  enum Land{Deutschland, Schweiz};
  Land land();
  //String[] vornamen();
}
//Hier werden den oben definierten Informationen Werte zugewiesen, 
//diese gehören nun zu der Klasse Film  
package annots;

@VendorInformation(
    anzahlangestellte=12,
    land = VendorInformation.Land.Deutschland,
    firmename = "HTWG")
public class Film {
  private String titel;
  private int dauer;
  ...

Die einzelnen Informationsteile dürfen String, primitive, Annotations, Enums, Class und Arrays (aus den entsprechenden Typen) als Rückgabewerte haben.

Man hat die Möglichkeit bei der Definition der Anntotaionstruktur bestimmte Wertigkeitsbereiche mitanzugeben, was wiederrum mit Annotations passiert, man annotiert sozusagen Annotations.

  • @Retention - Sichtbarkeit (SOURCE, CLASS, RUNTIME)
  • @Target - was damit annotiert werden darf (TYPE, METHOD...)

Die Annotations und ihre Informationen kann man jederzeit über die Methode getAnnotations() auf der Klasse java.lang.Class auslesen.

package annots;

import java.lang.annotation.Annotation;

public class ReadAnnotation {
  public static void main(String[] args) {
    Class film = Film.class;
    Annotation[] annotationen = film.getAnnotations();
    System.out.println("Wir haben " + annotationen.length + " Annotationen");
    for (Annotation annotation: annotationen){
      System.out.println("Wir haben die Annotation " + annotation);
    }
  }
}

2.3 JavaDocs

Javadoc ist ein Programm, das aus Java-Quelltexten Dokumentationen im HTML-Format erstellt. Die Dokumentation besteht dabei aus den öffentlichen Klassen-, Interface- und Methodendeklarationen und eventuell zusätzlichen Dokumentationskommentaren.  Ein Dokumentationskommentar beginnt mit /** und endet mit */ und muss im Quelltest immer unmittelbar vor dem zu dokumentierenden Item (Klassendefiniton, Methode...) platziert werden. Dieser Kommentar darf neben normalem Text auch HTML-Tags enthalten. Außerdem erkennt javadoc markierte Absätze, die mit dem @-Zeichen eingeleitet werden. Die Markierung leitet einen neuen Absatz ein und haben einen vordefinierten Wert. Zum Beispiel wird die Markierung @param beim generieren als Parameters: übersetzt.

 

package javadoc;

/**
 * Diese Klasse dient der Dokumentation.
 * Benutzen Sie <b>nicht</b>
 <ul>
 *   <li>Frames, iFrames</li>
 *  <li>Headings h1, h2, ..</li>
 </ul>
 <p>Hingegen können Sie benutzen</p>
 <ol>
 *   <li>Fett a</li>
 *  <li>Listen</li>
 *  <li>Kursiv i</li>
 *  <li>Formatiert code, pre</li>
 *  <li>Tabellen table</li>
 </ol>
 
 @author cjohner
 *
 */
public class StartJavaDoc {
  public static void main(String[] args) {
    
  }
  
  /**
   * Diese Methode berechnet den Jahreszins
   
   @param name der Name des Kunden
   @param betrag der Betrag in Euro
   @param Person der Kreditgeber
   @return der berechnete Jahreszins
   
   @throws Exception
   */
  public int doSth(String name, double betrag, Person personthrows Exception {
    //hier kommt eine Berechnung
    return 0;
  }

}

Bei der Generierung der Dokumentation im HTML-Format wird für jede Klasse eine HTML-Seite generiert, die den gleichen Namen trägt wie die Klasse selbst. In Eclipse kann man sich einfach über File-->Export-->Javadoc die Seiten vom JavaCompiler erzeugen lassen. In der Konsole erfolgt der Aufruf mit dem Befehl javadoc. Für das Beispiel würde die erzeugte Seite so aussehen.

2.4 Logging

Ein Logger bietet dem Programmierer deutlich mehr Möglichkeiten die Ausgaben eines Programms zu variieren. Es kann dort definiert werden, wo die Meldungen ausgegeben werden (Konsole, Schreiben in Datei....), man kann beeinflussen, welche Meldungen tatsächlich ausgegeben werden oder auch die Nachrichten vor der Ausgaben formatieren.

 

Die Filterung, welche Nachrichten ausgegeben werden geschieht über sogenannte LogLevels. Es gibt folgende LogLevel:

  • severe
  • warn
  • info
  • debug
  • fine
  • finer
  • finest

Jeder Meldung, die man dem Logger übergibt, fügt man ein LogLevel hinzu. Das ist entweder möglich direkt über die zum LogLevel zugehörige Methode oder als Übergabeparameter auf der log()-Methode.

         logger.severe("Schhade ein Fehler");
         logger.log(Level.SEVERE, "Schade ein Fehler", e);

Um jetzt zu entscheiden, wieviele Nachrichten man ausgeben will, also wie detailliert sollen meine Informationen sein, wird die Methode setLevel() auf dem Logger genutzt. Als Übergabeparameter werden die static Attribute der Klasse Level genutzt.
logger.setLevel(Level.INFO);

Der Übergabeparameter gibt ab welchem Level der Nachrichten, diese ausgegeben werden. Es werden hier also nicht nur Nachrichten mit dem Level "info" ausgegeben sondern auch alle Nachrichten mit übergeordneteren ("wichtigeren") Leveln.

Bei der Arbeit mit einem Logger ist es unkompliziert Formatierungen der Meldungen anzugeben. Dafür steht die Methode addHandler() zur Verfügung. Dieser Methode wird ein Handler, z.B. ein FileHandler, vom Typ java.util.logging.Handler übergeben. Dieser Handler kann wiederrum einen Filter und Formatter besitzen. Dort kann zum Beispiel das Datumsformat angegeben werden. Es gibt bereits schon fertige Formatter wie z.B. java.util.logging.SimpleFormatter oder java.util.logging.XMLFormatter, die man natürlich durch eine abgeleitete Klasse noch weiter anpassen kann.

package progstruk.logging;

import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;

public class StartLogging {
  private static Logger logger = Logger.getLogger("HTWG");
  public static void main (String[] argsthrows SecurityException, IOException {
  FileHandler fh = new FileHandler("output-htwg.txt");
  //fh.setFormatter(new SimpleFormatter());
  //fh.setFormatter(new XMLFormatter());
  fh.setFormatter(new HtwgFormatter());
  logger.addHandler(fh);
  logger.setLevel(Level.INFO);
  
  int i = 0;
  logger.fine("i ist " + i);
  try {
    ((Object)null).toString();
  catch (Exception e) {
    logger.severe("Schhade ein Fehler");
    logger.log(Level.SEVERE, "Schade ein Fehler", e);
  }
  logger.info("Ich bin fertig");
}
}

2.5 Arbeiten mit Dateien

Einlesen von Dateien

 

  • java.io.Scanner,  parsen von primitiven Typen und Strings mit der Methode next() oder nextInt()

Verarbeitung von eingelesenen Daten

  • java.util.StringTokenizer, ein String kann durch in einzelne Token aufgeteilt werden, durch ein angegebenes Trennzeichen, mit nextToken() weiterlesen

2.6 XML und Dom4J

Dom4j ist eine Programmierschnittstelle für Java für den Zugriff und die Verarbeitung von XML-Dokumenten.

Ein neues Dokument wird mit dem DocumentHelper erzeugt über die statische Methode createDocument(). Auf diesem Dokument kann über addElement() ein neues Wurzelelement hinzugefügt werden. Diese Methode liefert dabei den erzeugten und angehängten Knoten als org.dom4j.Element zurück. Auf diesem Element können wiederrum Kindknoten angehängt werden mit der Methode addElement(). Mit den Methoden addText(String) und addAttribute(String, String) können Werte zwischen den Tags eingetragen werden und dem Element Attribute zugewiesen werden. Bei der Methode addAttribute() wird als erster Übergabeparameter der Name des Attributs und als Zweiter den Wert des Attributs angegeben.

public Document createDocument(){
    Document document = DocumentHelper.createDocument();
    Element wurzel = document.addElement("Freizeit-Semester-3");
    Element student1 = wurzel.addElement("Christopher");
    Element hobby1 = student1.addElement("Hobby");
    hobby1.addText("Lernen-und-Bueffeln");
    hobby1.addAttribute("id""1");
    Element hobby2 = student1.addElement("Hobby");
    hobby2.addText("Programmieren");
    hobby2.addAttribute("id""2");
    
    
    Element stundent2 = wurzel.addElement("Simon");
    
    return document;
  }

Die Klasse org.dom4j.Document stellt nur eine Baumstruktur dar, in der Elemente und eventuell ihre Werte stehen, dies ist noch keine XML-Datei. Mit Hilfe des org.dom4j.io.XMLWriter kann aber nun aus einem Document eine XML-Datei geschrieben werden.

XMLWriter writer;
    try {
      writer = new XMLWriter(new FileWriter("z:\\freizeit.xml"));
      writer.write(document);
      writer.flush();
      writer.close();
    catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

Es besteht die Möglichkeit dem XMLWriter ein OutputFormat zu übergeben. Dabei wird dann die XML-Datei schön formatiert und nicht der Inhalt einfach aneinander gehängt. Dafür muss man dem XMLWriter im Konstruktor ein OutputFormat übergeben, dabei gibt es schon default Formatierungen.

 

OutputFormat format = OutputFormat.createPrettyPrint();        

XMLWriter writer = new XMLWriter(new FileWriter(path), format);

 

Wenn kein leeres Document erzeugt werden soll, sonder ein Document das bereits mit Inhalt aus einem String gefüllt sein soll wird die Methode parseText(String) vom DocumentHelper genutzt. Natürlich muss der String bereits im XML-Format bestehen.

String xmlstring = "<html><body>Hallo</body></html>"

Document htmldocument = DocumentHelper.parseText(xmlstring);

 

Ein Document kann man auch mit Hilfe von XPath Ausdrücken durchsuchen und nach bestimmten Knoten oder Werten suchen.

    List<Node> nodes = freizeit.selectNodes("//Hobby[text()-'Programmieren']");
    for(Node node: nodes){
      System.out.println("Node heißt "+node.getName() "mit Wert "+ node.getText());
    }

2.7 Threads

2.7.1 Nebenläufigkeit

Als Nebenläufigkeit beziechnet man die Fähigkeit eines Systems, zwei oder mehr Vorgänge gleichzeitg oder quasi-gleichzeitig ("parallel") ausführen zu können. Dabei unterscheidet man von Multitasking und Multithreading. Von Multitasking spricht man, wenn auf einem Rechner mehrere Programme (Prozesse) "parallel" ausgeführt werden. Durch Multithreading kann die Nebeläufigkeit innerhalb (!) eines Prozesses realisiert werden, zum Beispiel wartet die GUI auf einen Mausklick. Multithreading erlaubt es, dass das Programm mehrere CPUs nutzt.

Ein wichtiger Unterschied zwischen Threads und Prozessen ist der, dass alle Threads eines Programms sich einen gemeinsamen Adressraum teilen, also auf dieselben Variablen zugreifen, während die Adressräume unterschiedlicher Prozesse streng voneinander getrennt sind.

2.7.2 Realisierung von Multithreading in Java

Threads werden in Java durch die Klasse Thread und das Interfaces Runnable implementiert. In beiden Fällen wird der Thread-Body, also der prallel auszuführende Code, in Form der überlagerten Methode run zur Verfügung gestellt.

package parallel;

public class FastCommand implements Runnable {
  public void run(){
    for (int i = 0; i < 1000; i++) {
      System.out.println("I ist " + i);
    }
  }
}
-------------------------------------------------------------------------------------------
package parallel;

import java.text.SimpleDateFormat;
import java.util.Date;

public class SlowCommand extends Thread{
  public void run() { 
    SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss");
    for(int i = 0; i < 50; i++){
      Date date = new Date();
      System.out.println("Es ist " + sdf.format(date));
    }
  }
}

Nicht immer ist es möglich, eine Klasse, die als Thread laufen soll, von Thread abzuleiten. Dies ist insbesondere dann nicht möglich, wenn die Klasse Bestandteil einer Vererbungshierrachie ist, die eingentlich nichts mit Multithreading zu tun hat. Da Java keine Mehrfachvererbung kennt, kann eine bereits abgeleitete Klasse nicht von einer weiteren Klasse erben. Um trotzdem das Multithreading-Konzept nutzen zu können wird einfach das Interface Runnable implementiert. Dieses Interface besteht nur aus der run-Methode. Tatsächlich muss jede Klasse, deren Instanzen als Thread laufen sollen , das Interface Runnable implementieren (sogar die Klasse Thread selbst).

Um eine nicht von Thread abgeleitete Instanz als Thread laufen zu lassen, sind folgende Schritte zu befolgen.

  1. Zunächst ein Thread-Objekt erzeugen
  2. An den Konstruktor wird das Objekt übergeben das parallel ausgeführt werden soll
  3. Die Methode start des neuen Thread-Objekts aufrufen

Dieses Beispiel zeigt die Vorgehensweise:

package parallel;

public class StartThreads {
  public static void main(String[] args) {
    FastCommand fastCommand = new FastCommand();
    Thread fast = new Thread(fastCommand);
    fast.start();
  }
}

2.7.3 Arbeiten mit Threads

Die Methode run sollte vom Programm niemals direkt aufgerufen werden. Um einen Thread zu starten, ist immer start aufzurufen! Dadurch wird der neue Thread erzeugt und initialisiert und ruft schließlich selbst run auf, um den Anwendungscode auszuführen. Ein direkter Aufruf von run würde dagegen keinen neuen Thread erzeugen, sonder wäre ein normaler Methodenaufruf wie jeder andere und würde direkt aus dem bereits laufenden Thread des Aufrufers erfolgen.

Es gibt 3 Möglichkeiten, dass ein Thread zuende ist

  • das Ende der run()-Methode ist erreicht
  • run() bricht mit Fehlern ab
  • der Thread wird von außen mit interrupt() unterbrochen, allerdings nur wenn innerhalb des Threads eine Überprüfung stattfindet.

Durch Aufruf von interrupt wird ein Flag gesetzt, das eine Unterbrechungsanforderung signalisiert. Durch Aufruf von isInterrupted kann der Thread feststellen, ob das Abbruchflag gesetzt wurde und der Thread beendet werden soll.

package parallel;

public class Interruptable extends Thread {
  
  public void run() {
    while (!isInterrupted() ) {
      
      System.out.println("Bin noch am Kämpfen");
      
      try {
        Thread.sleep(500);
      catch (InterruptedException e) {
        interrupt();
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
  }

}

Mit der while-Schleife wird überprüft, ob der Thread von außen noch nicht abgebrochen wurde.

Die Bildschirmausgabe in diesem Programm ist vermutlich deutlich kürzer, als die Pause nach der Bildschirmausgabe. Deswegen ist es recht wahrscheinlich, dass der Aufruf von interrupt während des Aufrufs von sleep  erfolgt. Ist das der Fall, wird sleep mit einer Interrupted-Exception abgebrochen (auch wenn die geforderte Zeitspanne noch nicht vollständig verstrichen ist). Wichtig ist hier, dass das Abbruchflag von des Exception zurückgesetzt wird (isInterrupted liefert dann wieder false) und der Aufruf von interrupt somit eigentlich verlorengehen würde, wenn er nicht direkt in der catch-Klausel behandelt würde. Wir rufen daher innerhalb der catch-Klausel interrupt erneut auf, um das Flag wieder auf true zu setzen und run die Abbruchanforderung zu signalisieren.

Bei endlos laufenden Threads besteht die Möglichkeit über die Methode setDeamon(true) zu signalisieren, dass beim Beenden der main() der Thread ebenfalls beendet werden soll.

    Thread infinity = new Thread(new InfinityCommand());
    infinity.setDaemon(true);
    infinity.start();

2.7.4 Synchronisierung von Threads

Zur Synchronisierung nebenläufiger Prozesse hat Java das Konzept des Monitors implementiert. Die Klasse Start gibt den beiden Threads einen gemeinsamen  Monitor mit. Über diesen Monitor (vom Typ Object) können sich die beiden Threads mit Hilfe der Methoden wait() und notify() verständigen. Die Methoden wait() und notify() erbt jede Klasse von Object. Beide Methoden müssen in einem Block stehen, der synchornized ist.

Durch synchronized kann entweder eine komplette Methode oder ein Block innerhalb einer Methode geschützt werden. Das folgende Codebeispiel zeigt einen Thread, der einen Teil der run-Methode über das Monitor-Objekt synchonisiert. Das Monitor-Objekt wird ihm im Konstruktor übergeben. Ein anderer Thread, der mit diesem Thread zusammenarbeitet, muss das gleiche Objekt ebenfalls im Konstruktor übergeben bekommen.

package parallel2;

public class BerechnerClient extends Thread {
  private Object monitor;

  public BerechnerClient(Object monitor) {
    this.monitor = monitor;
  }
  
  public void run() {
    
    try {
      synchronized (monitor) {
        System.out.println("Ich, der Client, warte auf den Berechner");
        monitor.wait();
        System.out.println("Endlich, ich, der Client, habe Nachricht bekommen");
      }
    catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
  

}

2.7.5 Synchronisierung von Threads mit Java 5

In Java 5 läuft die Synchronisation nicht mehr über ein normales Object, sondern über die spezielle Klassen java.util.concurrent.locks.Condition und java.util.concurrent.locks.Lock.

  • Condition wird von Lock erzeugt
  • condition.await() ist Pendant zu object.wait()
  • condition.signal() ist Pendant zu object.notify()

signal() und wait() müssen im synchronisierten Block innerhalb des locks stehen. Die Synchronisation erfolgt über den Aufruf der Methode lock.lock() und beim Beenden lock.unlock().

package parallel3;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class BerechnerClient extends Thread {
  private Lock lock;
  private Condition condition;

  public BerechnerClient(Lock lock, Condition condition) {
    this.lock = lock;
    this.condition = condition;
  }

  public void run() {
    
    try {
        lock.lock();
        System.out.println("Ich, der Client, warte auf den Berechner");
        condition.await();
        System.out.println("Endlich, ich, der Client, habe Nachricht bekommen");
        lock.unlock();
      
    catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

}

2.8 Reguläre Ausdrücke

Ab Java 5 gibt es ein neues Package java.util.regex. Mit Hilfe dieses Package können Strings nach bestimmten Pattern ("Textmustern") durchsucht werde und diese auch ersetzen.

Für die Beschreibung von diesen Pattern stehen Zeichenklassen zur Verfügung:

  • \d - Ziffern
  • \D - Nicht-Ziffern
  • \w - Wortzeichen: 0-9, a-z, A-Z
  • \W - Nicht-Wortzeichen
  • \s - Whitespaces: Leerzeichen, Tabulator, Zeilenumbruch...
  • \S - Nicht-Whitespaces
  • \[] - Selbstdefinierte mit beliebigen Zeichen
  • (a|b) - entweder oder
  • . - beliebiges Zeichen

Diese Muster können auch wiederholt werden, dafür gibt es weitere vordefinierten Zeichen:

  • * - Vorstehendes Muster 0 oder mehrmals wiederholen
  • ? - Vorstehendes Muster 0 oder einmal wiederholen
  • + - Vorstehendes Muster 1 oder mehrmals wiederholen

Die Verwendung der Zeichenklassen und die Überprüfung eines String auf bestimmte Textmustern wird im folgenden Beispiel gezeigt.

package reggis;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class StartReggy {
  public static void main(String[] args) {
    String text = new String("labe rlaberHTWG999laber");

    String regex = ".*HTWG\\d+.*";
    boolean stehtdrin = text.matches(regex);
    System.out.println("In unserem Text " + text + " ist >" + regex
        "< enthalten? " + stehtdrin);

    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(text);
    matcher.matches();
  }
}

Der String regex beschreibt ein Textmuster mit Hilfe der oben beschriebenen Zeichenklasse. In diesem Beispiel darf der String mit beliebigen und beliebig vielen Zeichen beginnen, auch kein Zeichen ist möglich. Anschließend muss die Zeichenkette "HTWG" mit mindestens einer Zahl im Anschluss folgen. Der Rest ist optional und es gibt keine Einschränkungen.

Nun gibt es die Möglichkeit den String text auf dieses Textmuster zu untersuchen. Dabei kann einfach die Methode machtes(String) auf der Klasse String aufgerufen werden, diese Methode liefert entweder true oder false. Eine andere Möglichkeit ist es die neuen Java-Klassen java.util.regex.Matcher und java.util.regex.Pattern zu nutzen. Mit der statischen Methode Pattern.compile(String textmuster) kann ein beschriebenes Pattern kompiliert. Diese Methode liefert ein Pattern zurück. Über dieses Objekt kann jetzt einfach die Methode matcher(String) aufgerufen werden. Der Vorteil hier ist, dass das Textmuster nur einmal kompiliert werden muss und bei langen Texten mehrere Textmuster überprüft werden können.

Soll das Ergebnis der Überprüfung als boolean zurückgegeben werden, kann über das Matcher Objekt (wird von der Methode machter(String) zurückgegeben) die Methode machtes() aufgerufen werden.

2.9 PDF Generierung mit iText

Um iText nutzen zu können muss die Library iText-2.0.7.jar erst in das Projekt integriert werden (download on http://www.lowagie.com/iText/download.html). Diese jar ist auch im JFreeChart download im Order libs enthalten.

Einen ersten Einblick, wie iText verwendet wird, zeigt das kommentierte Beispiel.

package pdf;

import java.awt.Color;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;

import com.lowagie.text.Chapter;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Font;
import com.lowagie.text.FontFactory;
import com.lowagie.text.List;
import com.lowagie.text.ListItem;
import com.lowagie.text.PageSize;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Section;
import com.lowagie.text.pdf.PdfWriter;

public class UnserErstesPdf {
  public static void main(String[] args) {
    try {
      // Document wird erstellt mit der Seitengröße und den gewünschten
      // Seitenrändern als Übergabeparamter
      Document document = new Document(PageSize.A4, 50505050);
      // PdfWriter wird generiert, mit dem Document, das geschrieben
      // werden soll und wohin geschrieben werden soll
      PdfWriter writer = PdfWriter.getInstance(document,
          new FileOutputStream("C:\\UnserPdf.pdf"));
      // um Informationen in das Document zu schreiben, muss es erst
      // geöffnet werden
      document.open();

      // Paragraph, Chapter, Section... sind Elemente um den Inhalt zu
      // strukturieren. Mit Hilfe der FontFactory kann ein Font erstellt
      // werden, den jedem Element für die Gestaltung der Schrifart
      // übergeben werden kann.
      // Aufbau des PDF-Dokuments: 
      // - Paragraph beschreibt immer ein Text
      // - Paragraph kann einem Chapter oder einer Section zugeordnet werden
      // - Chapter erzeugt Section
      // - einer Section kann Text(Paragraph) oder z.B. eine List aufnehmen
      
      // Kapitelüberschrift
      Font font1 = FontFactory.getFont(FontFactory.HELVETICA, 18,
          Font.BOLDITALIC, new Color(00255));
      Paragraph titel1 = new Paragraph("Kapitelüberschrift", font1);
      Chapter chapter1 = new Chapter(titel1, 1);

      // Normale Überschriften
      Font font2 = FontFactory.getFont(FontFactory.TIMES, 18, Font.BOLD,
          new Color(25500));
      Paragraph titel2 = new Paragraph("Hauptüberschrift", font2);
      Section section1 = chapter1.addSection(titel2);

      // Normaler Text
      Paragraph normalerText = new Paragraph(
          "Heute ist die 15. Stunde ProgStruct");
      section1.add(normalerText);

      // Liste
      // Verschiedene Konstruktoren möglich, hier:
      // 1.Übergabeparameter: die Auflistung soll nummeriert werden
      // 2.Übergabeparameter: für die Nummerierung sollen keine Buchstaben
      // verwendet werden
      // 3.Übergabeparameter: Wie weit der Text eingerückt werden soll
      List liste = new List(true, false, 10);
      liste.add(new ListItem("Erster Punkt"));
      liste.add(new ListItem("Zweiter Punkt"));
      section1.add(liste);

      document.add(chapter1);
      document.close();

    catch (FileNotFoundException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    catch (DocumentException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

  }
}

2.10 Grafiken mit JFreeChart

Um JFreeChart nutzen zu können müssen erst jfreechart-1.0.8.jar und jcommon-1.0.12.jar in das Projekt eingebunden werden ( download on: http://sourceforge.net/project/showfiles.php?group_id=15494). Mit JFreeChart können alle möglichen Diagramm schnell in Java dargestellt werden. JFreeChart ist auch in Kombination mit iText nützlich, so können Diagramm in einen pdf-Dokument generiert werden.  Ein Beispiel mit JFreeChart ist unter Skripts und Fragen in Stunde 15 zu finden.