Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 5 (Tiger-Release)
Buch: Java ist auch eine Insel
gp Kapitel 2 Sprachbeschreibung
  gp 2.1 Anweisungen und Programme
  gp 2.2 Elemente der Programmiersprache Java
    gp 2.2.1 Textkodierung durch Unicode-Zeichen
    gp 2.2.2 Unicode-Tabellen unter Windows
    gp 2.2.3 Literale
    gp 2.2.4 Bezeichner
    gp 2.2.5 Reservierte Schlüsselwörter
    gp 2.2.6 Token
    gp 2.2.7 Semantik
    gp 2.2.8 Kommentare
    gp 2.2.9 Funktionsaufrufe als Anweisungen
    gp 2.2.10 Die leere Anweisung
    gp 2.2.11 Der Block
  gp 2.3 Datentypen
    gp 2.3.1 Primitive Datentypen
    gp 2.3.2 Wahrheitswerte
    gp 2.3.3 Variablendeklarationen
    gp 2.3.4 Ganzzahlige Datentypen
    gp 2.3.5 Die Fließkommazahlen
    gp 2.3.6 Alphanumerische Zeichen
    gp 2.3.7 Die Typanpassung (das Casting)
    gp 2.3.8 Lokale Variablen, Blöcke und Sichtbarkeit
    gp 2.3.9 Initialisierung von lokalen Variablen
  gp 2.4 Ausdrücke, Operanden und Operatoren
    gp 2.4.1 Zuweisungsoperator
    gp 2.4.2 Verbundoperatoren
    gp 2.4.3 Präfix- oder Postfix-Inkrement und -Dekrement
    gp 2.4.4 Unäres Minus und Plus
    gp 2.4.5 Arithmetische Operatoren
    gp 2.4.6 Die relationalen Operatoren
    gp 2.4.7 Logische Operatoren
    gp 2.4.8 Reihenfolge und Rang der Operatoren in der Auswertungsreihenfolge
    gp 2.4.9 Überladenes Plus für Strings
    gp 2.4.10 Was C(++)-Programmierer vermissen könnten
  gp 2.5 Bedingte Anweisungen oder Fallunterscheidungen
    gp 2.5.1 Die if-Anweisung
    gp 2.5.2 Die Alternative wählen mit einer if/else-Anweisung
    gp 2.5.3 Die switch-Anweisung bietet die Alternative
  gp 2.6 Schleifen
    gp 2.6.1 Die while-Schleife
    gp 2.6.2 Schleifenbedingungen und Vergleiche mit ==
    gp 2.6.3 Die do/while-Schleife
    gp 2.6.4 Die for-Schleife
    gp 2.6.5 Ausbruch planen mit break und Wiedereinstieg mit continue
    gp 2.6.6 break und continue mit Sprungmarken
  gp 2.7 Methoden einer Klasse
    gp 2.7.1 Bestandteil einer Funktion
    gp 2.7.2 Aufruf
    gp 2.7.3 Methoden ohne Parameter
    gp 2.7.4 Statische Funktionen (Klassenmethoden)
    gp 2.7.5 Parameter, Argument und Wertübergabe
    gp 2.7.6 Methoden vorzeitig mit return beenden
    gp 2.7.7 Nicht erreichbarer Quellcode bei Funktionen
    gp 2.7.8 Rückgabewerte
    gp 2.7.9 Methoden überladen
    gp 2.7.10 Vorgegebener Wert für nicht aufgeführte Argumente
    gp 2.7.11 Finale lokale Variablen
    gp 2.7.12 Rekursive Funktionen
    gp 2.7.13 Die Ackermann-Funktion
    gp 2.7.14 Die Türme von Hanoi
  gp 2.8 Dokumentationskommentare
    gp 2.8.1 Ein Dokumentationskommentar setzen
    gp 2.8.2 Mit javadoc eine Dokumentation erstellen
    gp 2.8.3 Generierte Dateien
    gp 2.8.4 Weitere Dokumentationskommentare
    gp 2.8.5 Schalter für das Programm javadoc
    gp 2.8.6 JavaDoc und Doclets
    gp 2.8.7 Bitoperationen
    gp 2.8.8 Vorzeichenlose Bytes in ein Integer und Char konvertieren
    gp 2.8.9 Variablen mit Xor vertauschen
    gp 2.8.10 Die Verschiebeoperatoren
    gp 2.8.11 Setzen, Löschen, Umdrehen und Testen von Bits
    gp 2.8.12 Der Bedingungsoperator
  gp 2.9 Einfache Benutzereingaben


Galileo Computing

2.8 Dokumentationskommentardowntop

Die Dokumentation von Softwaresystemen ist ein wichtiger, aber oft vernachlässigter Teil der Softwareentwicklung. Im Entwicklungsprozess müssen die Entwickler Zeit in Beschreibungen der einzelnen Komponenten investieren, besonders dann, wenn weitere Entwickler diese Komponenten wieder verwerten. Diese Wiederverwertung wird besonders bei der objektorientierten Programmierung angestrebt. Deshalb müssen die Schnittstellen sorgfältig beschrieben werden. Wichtig bei der Beschreibung sind die Art und die Anzahl der Parameter, die Wirkung der Funktionen und das Laufzeitverhalten. Da das Erstellen einer externen Dokumentation – also einer Beschreibung außerhalb der Quellcodedatei – fehlerträchtig und deshalb nicht gerade motivierend für die Beschreibung ist, werden spezielle Anweisungen in den Java-Quelltext eingeführt. Ein spezielles Programm generiert aus den Formatierungsanweisungen eine Textdatei mit den gewünschten Informationen.


Galileo Computing

2.8.1 Ein Dokumentationskommentar setzen  downtop

In einer besonders ausgezeichneten Kommentarumgebung werden die Beschreibungen, die DokumentationskommentareDoc Comments«) genannt werden, eingesetzt. Die Kommentarumgebung ähnelt dem Blockkommentar.


Beispiel   Ein Dokumentationskommentar

/**
 * Socken sind spezielle Kleidungsstücke.
 */
public class Socke extends Kleidung
{
}

Da ein Dokumentationskommentar /** mit /* beginnt, ist es für den Compiler ein normaler Blockkommentar. Die Kommentare werden oft optisch aufgewertet, indem am Anfang ein Sternchen steht. Dieses wird von JavaDoc ignoriert. Der Dokumentationskommentar beschreibt Methoden- und Klassendefinitionen.


Galileo Computing

2.8.2 Mit javadoc eine Dokumentation erstellen  downtop

Um eine Dokumentation zu erzeugen, wird dem Konsolen-Programm javadoc als Parameter ein Dateiname der zu kommentierenden Klasse übergeben; aus compilierten Dateien können natürlich keine Beschreibungsdateien erstellt werden. Wir starten javadoc im Verzeichnis, in dem auch die Klassen liegen, und erhalten unsere HTML-Dokumente.


Beispiel   Erstelle aus der Socken- und Kleidung-Klasse eine Dokumentation

Listing 2.11   Kleidung.java


/**
 *  Kleidung bildet die Oberklasse für alle Kleidungs-Objekte.
 */
import java.io.*;
public abstract class Kleidung implements Serializable
{
  /**
   *  Jede Kleidung hat eine Farbe.
   */
  protected String farbe;
  /**
   *  Erzeugt neues Kleidungs-Objekt.
   */
  protected Kleidung()
  {
  }
  /**
   *  Erzeugt neues Kleidungs-Objekt,
   *  welches mit einer Farbe initialisiert wird.
   */
  protected Kleidung( String farbe )
  {
    this.farbe = farbe;
  }
  /**
   *  Jede Kleidung hat einen Namen.
   *  Wird von Unterklassen überschrieben.
   */
   abstract String getName();
}

Hinweis   Die Dokumentationskommentare sind so aufgebaut, dass der erste Satz in der Auflistung der Methoden und Attribute erscheint und der Rest in der Detailansicht.

/**
 *   Das ist ein kurzer Satz.
 *   Das ist die ausführliche Beschreibung.
 *   Die ausführliche Beschreibung erscheint
 *   später im Abschnitt "Method Detail".
 *   Der kurze Satz erscheint im Abschnitt "Method Summary".
 */
public void foo() { }

Listing 2.12   Socke.java


/**
 *  Socken sind spezielle Kleidungsstücke.
 */
import java.io.*;
public class Socke extends Kleidung implements Serializable
{
  /**
   *  Erzeugt ein neues Socken-Objekt mit der Farbe Schwarz.
   */
   Socke()
   {
     super( "schwarz" );
   }
   /**
    *  Überschrieben von der Oberklasse.
    */
    public String getName()
    {
      return "Socke";
    }
}

JavaDoc geht durch den Quelltext und parst die Deklarationen und zieht die Dokumentation heraus. Daraus generiert das Tool eine Beschreibung, die in der Regel als HTML-Seite zu uns kommt.


Beispiel   Möchten wir Dokumentationen für das gesamte Verzeichnis erstellen, so geben wir alle Dateien mit der Endung .java an:

$ javadoc *.java

Abbildung
Hier klicken, um das Bild zu Vergrößern

In Eclipse lässt sich eine Dokumentation mit JavaDoc sehr einfach erstellen. Im Menü File, Export ist der Eintrag Javadoc zu wählen und nach ein paar Einstellungen ist die Dokumentation generiert.

HTML-Tags in Dokumentationskommentaren

In den Kommentaren können HTML-Tags verwendet werden, beispielsweise <b>..</b> und <i>..</i>, um Textattribute zu setzen. Sie werden direkt in die Dokumentation übernommen, müssen also korrekt geschachtelt sein, damit die Ausgabe nicht falsch dargestellt wird. Die Überschriften-Tags <h1>..</h1> und <h2>..</h2> sollten jedoch nicht verwendet werden. JavaDoc verwendet sie zur Gliederung der Ausgabe und weist ihnen Formatvorlagen zu.


Galileo Computing

2.8.3 Generierte Dateien  downtop

Für jede öffentliche Klasse erstellt JavaDoc eine HMTL-Datei. Sind Klassen nicht öffentlich, muss ein Schalter angegeben werden. Die HTML-Dateien werden zusätzlich mit Querverweisen zu den anderen dokumentierten Klassen versehen. Daneben erstellt JavaDoc weitere Dateien:

gp  index-all.html
Die Übersicht aller Klassen, Schnittstellen, Ausnahmen, Methoden und Felder in einem Index
gp  overview-tree.html
Zeigt in einer Baumstruktur die Klassen an, damit die Vererbung deutlich sichtbar ist.
gp  allclasses-frame.html
Anzeige aller dokumentierten Klassen in allen Unterpaketen
gp  deprecated-list.html
Liste der veralteten Methoden und Klassen
gp  serialized-form.html
Listet alle Klassen auf, die Serializable implementieren. Jedes Attribut erscheint mit einer Beschreibung in einem Absatz.
gp  help-doc.html
Eine Kurzbeschreibung von JavaDoc
gp  index.html
JavaDoc erzeugt eine Ansicht mit Frames. Das ist die Hauptdatei, die die rechte und linke Seite referenziert. Die linke Seite ist die Datei allclasses-frame.html. Rechts im Frame wird bei fehlender Paketbeschreibung die erste Klasse angezeigt.
gp  stylesheet.css
Formatvorlage für HTML-Dateien, in der sich Farben und Zeichensätze einstellen lassen, die dann alle HTML-Dateien nutzen.
gp  packages.html
Eine veraltete Datei. Sie verweist auf die neuen Dateien.

Galileo Computing

2.8.4 Weitere Dokumentationskommentare  downtop


Tabelle 2.9   Die wichtigsten Dokumentationskommentare im Überblick

Kommentar Beschreibung
@see Klassenname Verweis auf eine andere Klasse
@see Klassenname oder Methodenname Verweis auf eine andere Methode
@see Ausgeschriebener Klassenname Verweis auf eine voll qualifizierte Klasse
@see Ausgeschriebener Klassenname oder Methodenname Verweis auf eine voll qualifizierte Methode
@version Versionstext Version
@author Autor Schöpfer
@return Rückgabetext Rückgabewert
@param Parametername oder Parametertext Beschreibung der Parameter
@exception Exception-Klassenname oder Exceptiontext Ausnahmen, die ausgelöst werden können
@throws Exception-Klassenname oder
Exceptiontext
Synonym zu oben
{@link Verweis } Einen eingebauten Verweis im Text. Parameter sind wie bei @see.

Beispiele


Beispiele Eine externe Zusatzquelle angeben:

@see <a href="spec.html#section">Java Spec</a>.

Verweis auf eine Funktion, die mit der beschriebenen Funktion verwandt ist:


@see String#equals(Object) equals

Dokumentiere eine Variable. Gib einen Verweis auf eine Methode an:


/**
 * The X-coordinate of the component.
 *

* @see #getLocation()
 */
 int x = 1263732;

Eine veraltete Methode, die auf eine Alternative zeigt:


/**
 * @deprecated  As of JDK 1.1,
 * replaced by {@link #setBounds(int,int,int,int)}
 */


Galileo Computing

2.8.5 Schalter für das Programm javadoc  downtop

Über die umfangreichen Parameter von Javadoc informiert eine HTML-Datei, die dem Java-SDK beigelegt ist.


Beispiel   Für die Java-API-Dokumentation haben die Entwickler eine ganze Reihe von Schaltern eingesetzt. Umgebungsvariablen machen den Aufruf deutlicher.


WINDOWTITLE =Java Platform 1.2 Final API SpecificationDOCTITLE =Java<sup><font size="-2">TM</font></sup> Platform 1.2 Final\
 API SpecificationHEADER =<b>Java Platform 1.2</b><br><font size="-1">Final</font>BOTTOM =<font size="-1"><a href="http://java.sun.com/cgi-bin/\
 bugreport.cgi">Submit a bug or feature</a><br><br>Java is a\
 trademark or registered trademark of Sun Microsystems, Inc. in the\
 US and other countries.<br>Copyright 19931998 Sun Microsystems,\
 Inc. 901 San Antonio Road,<br>Palo Alto, California, 94303, U.S.A.\
 All Rights Reserved.</font>GROUPCORE ="Core Packages" "java.*:com.sun.java.*:org.omg.*"
GROUPEXT  ="Extension Packages" "javax.*"javadoc -sourcepath /jdk/src/share/classes \
        -d /jdk/build/api                  \
        -use                               \
        -splitIndex                        \
        -windowtitle $(WINDOWTITLE)        \
        -doctitle $(DOCTITLE)              \
        -header $(HEADER)                  \
        -bottom $(BOTTOM)                  \
        -group $(GROUPCORE)                \
        -group $(GROUPEXT)                 \
        -overview overview-core.html       \
        -J-Xmx180m                         \
        java.lang java.lang.reflect        \
        java.util java.io java.net         \
        java.applet                        \

Galileo Computing

2.8.6 JavaDoc und Doclets  downtop

Die Ausgabe von JavaDoc kann den eigenen Bedürfnissen angepasst werden, indem Doclets eingesetzt werden. Ein Doclet ist ein Java-Programm, das auf der Doclet-API aufbaut und die Ausgabedatei schreibt. Das Programm liest dabei wie das bekannte JavaDoc-Tool die Quelldateien ein und erzeugt daraus ein beliebiges Ausgabeformat. Dieses Format kann selbst gewählt und implementiert werden. Wer also neben dem von JavaSoft beigefügten Standard-Doclet für HTML-Dateien Framemaker-Dateien (MIF) oder RTF-Dateien erzeugen möchte, muss nur ein eigenes Doclet programmieren. Daneben dient ein Doclet aber nicht nur der Schnittstellendokumentation. Ein Doclet kann auch dokumentieren, ob es zu jeder Methode auch eine Dokumentation gibt oder ob jeder Parameter und jeder Rückgabewert beschrieben ist.2.8Weitere Operatoren


Galileo Computing

2.8.7 Bitoperationen  downtop

Zahlen und Werte sind im Computer als Sammlung von Bits gegeben. Ein Bit ist ein Informationsträger für die Aussage wahr oder falsch. Bits werden zusammengesetzt zu Folgen wie einem Byte, das aus acht Bits besteht. Die Belegung der Bits ergibt einen Wert. Da jeder der acht Bits unterschiedliche Belegungen annehmen kann, ergeben sich 256 unterschiedliche Zahlen. Ist kein Bit gesetzt, so ist die Zahl 0. Jede Stelle im Byte bekommt dabei eine Wertigkeit zugeordnet.


Beispiel   Die Wertebelegung für die Zahl 19. Sie ist zusammengesetzt aus einer Anzahl von Summanden der Form 2n. Die Zahl 19 berechnet sich aus 16 + 2 + 1. Genau diese Bits sind gesetzt.


Tabelle 2.10   Bitbelegung für die Zahl 19

Bit 7 6 5 4 3 2 1 0
Wertigkeit 2=128 2=64 2=32 2=16 2=8 2=4 2=2 2=1
Belegung für 19 0 0 0 1 0 0 1 1

Mit Bitoperatoren lassen sich Binäroperationen auf Operanden durchführen, um beispielsweise Bits eines Worts zu setzen. Zu den Bitoperationen zählen Verknüpfungen, Schiebeoperationen und das Komplement. Durch die bitweisen Operatoren können die einzelnen Bits abgefragt und manipuliert werden. Als Verknüpfungen bietet Java die folgenden Bitoperatoren an:


Tabelle 2.11   Bitoperatoren in Java

Operator Bezeichnung Funktion
~ Komplement Alle Bits werden invertiert.
| bitweises Oder Bei a|b wird jedes Bit von a und b einzeln Oder-verknüpft.
& bitweises Und Bei a&b wird jedes Bit von a und b einzeln Und-verknüpft.
^ bitweises exklusives Oder (Xor) Bei a^b wird jedes Bit von a und b einzeln Xor–verknüpft.

Betrachten wir allgemein die binäre Verknüpfung a # b. Bei der binären bitweisen Und-Verknüpfung mit & gilt für jedes Bit: Nur wenn beide Operanden a und b 1 sind, dann ist auch das Ergebnis 1. Bei der Oder-Verknüpfung mit | muss nur einer der Operanden 1 sein, damit das Ergebnis 1 ist. Bei einem exklusiven Oder (Xor) ist das Ergebnis 1, wenn nur genau einer der Operanden 1 ist. Sind beide gemeinsam 0 oder 1, ist das Ergebnis 0. Dies entspricht einer binären Addition oder Subtraktion. Fassen wir das Ergebnis noch einmal in einer Tabelle zusammen:


Tabelle 2.12   Bitoperatoren in einer Wahrheitstafel

Bit 1 Bit 2 ~Bit 1 Bit 1 & Bit 2 Bit 1 | Bit 2 Bit 1 ^ Bit 2
0 0 1 0 0 0
0 1 1 0 1 1
1 0 0 0 1 1
1 1 0 1 1 0


Galileo Computing

2.8.8 Vorzeichenlose Bytes in ein Integer und Char konvertieren  downtop

Liegt ein Byte als Datentyp vor, so kann es zwar automatisch zu einem int angepasst werden, aber die automatische Typkonvertierung erfüllt nicht immer den gewünschten Zweck.


byte b1 =       100;
byte b2 = (byte)200;

System.out.println( b1 );                  // 100
System.out.println( b2 );                  // –56

Beim zweiten Byte ist eine Typanpassung nötig, da 0x90 größer als 0x7f ist und daher den Zahlenbereich eines Byte –128 bis 127 überschreitet. Dennoch kann ein Byte das gegebene Bitmuster annehmen und repräsentiert dann die negative Zahl –56. Wird diese ausgegeben, findet bei println(int) eine automatische Typanpassung auf ein int statt und die negative Zahl als byte wird zur negativen int-Zahl. Das bedeutet, dass das Vorzeichen übernommen wird. In einigen Fällen ist es jedoch wünschenswert, ein byte vorzeichenlos zu behandeln. Bei der Ausgabe soll dann ein Datenwert zwischen 0 und 255 herauskommen. Um das zu erreichen, schneiden wir mit der Und-Verknüpfung – alle anderen Bits ausgenommen – die unteren acht heraus. Das reicht schon. Die Lösung zeigt die Funktion byteToInt():


static int byteToInt( byte b )
{
  return b & 0xff;
}

Die Schreibweise (int)b ist nicht nötig, da die automatische Typanpassung das Byte b in einer arithmetischen Operation automatisch auf ein int konvertiert.

Mit einer ähnlichen Arbeitsweise können wir auch die Frage lösen, wie sich ein Byte, dessen Integerwert im Minusbereich liegt, in ein char konvertieren lässt. Der erste Ansatz über eine Typumwandlung (char)byte ist falsch, und auf der Ausgabe dürfte nur ein rechteckiges Kästchen oder ein Fragezeichen erscheinen:


byte b = (byte) ’ß’;
System.out.println( (char) b );    // Ausgabe ist  ?

Das Dilemma ist wieder die fehlerhafte Vorzeichenanpassung. Bei der Benutzung des Bytes wird es zuerst in ein int konvertiert. Das »ß« wird dann zu –33. Im nächsten Schritt wird diese –33 dann zu einem char umgesetzt. Das ergibt 65503, was einen Unicode-Bereich trifft, der zurzeit kein Zeichen definiert. Es wird wohl auch noch etwas dauern, bis die ersten Außerirdischen uns neue Zeichensätze schenken. Gelöst wird der Fall wie oben, in dem von b nur die unteren acht Bits betrachtet werden. Das geschieht wieder durch ein Ausblenden über den Und-Operator. Damit ergibt sich korrekt:


char c = (char) (b & 0x00ff);

Galileo Computing

2.8.9 Variablen mit Xor vertauschedowntop

Eine besonders trickreiche Idee für das Vertauschen von Variableninhalten arbeitet mit der Xor-Funktion und benötigt keine temporäre Zwischenvariable. Die Zeilen zum Vertauschen von x und y lauten wie folgt:


int x = 12,
    y = 49;

x ^= y;  // x = x ^ y
y ^= x;  // y = y ^ x
x ^= y;  // x = x ^ y

System.out.println( x + " " + y );  // Ausgabe ist: 49 12

Der Trick funktioniert, da wir mit Xor etwas »rein- und rausrechnen« können. Zuerst rechnen wir in der ersten Zeile das y in das x. Wenn wir anschließend die Zuweisung an das y machen, dann ist das der letzte schreibende Zugriff auf y, also muss hier schon das vertauschte Ergebnis stehen. Das stimmt auch, denn expandieren wir die zweite Zeile, steht dort »y wird zugewiesen an y ^ x« und dies ist y ^ (x ^ y ). Der letzte Ausdruck verkürzt sich zu y = x, da aus der Definition der Xor-Funktion für einen Wert a hervorgeht a ^ a = 0. Die Zuweisung hätten wir zwar gleich so schreiben können, aber dann wäre der Wert von y verloren gegangen. Der steckt aber noch in x aus der ersten Zuweisung. Betrachten wir daher die letzte Zeile x ^ y. y hat den Startwert von x, doch in x steckt ein Xor-y. Daher ergibt x ^ y den Wert x ^ x ^ y, und der verkürzt sich zu y. Demnach haben wir den Inhalt der Variablen vertauscht. Im Übrigen können wir für die drei Xor-Zeilen alternativ schreiben:


y ^= x ^= y;   // Auswertung automatisch y ^= (x ^= y)
x ^= y;

Da liegt es doch nahe, die Ausdrücke weiter abzukürzen zu x ^= y ^= x ^= y. Doch leider ist das falsch (es kommt für x immer null heraus). Den motivierten Lesern bleibt dies als Denksportaufgabe.


Galileo Computing

2.8.10 Die Verschiebeoperatoren  downtop

Unter Java gibt es drei Verschiebeoperatoren (engl. shift-operator). Sie bewegen die Bits eines Datenworts in eine Richtung. Obwohl es nur zwei Richtungen rechts und links gibt, muss noch der Fall betrachtet werden, ob das Vorzeichen beim Links-Shift beachtet wird oder nicht.

i << n

Der Operand i wird unter Berücksichtigung des Vorzeichens n-mal nach links geschoben (mit 2 multipliziert). Der rechts frei werdende Bitplatz wird mit 0 aufgefüllt. Das Vorzeichen ändert sich jedoch, sobald eine 1 von der Position MSB – 1 nach MSB geschoben wird. MSB steht hier für Most Significant Bit, also das Bit mit der höchsten Wertigkeit in der binären Darstellung. Erschreckenderweise ergibt 1 << 32 = 1 und nicht 0, wie zu erwarten wäre.

i >> n

Da es in Java nur vorzeichenbehaftete Datentypen gibt, kommt es unter Berücksichtigung des Vorzeichens beim normalen Rechts-Shift zu einer vorzeichenrichtigen Ganzzahldivision durch 2. Dabei bleibt das linke Vorzeichen-Bit unberührt. Je nachdem, ob das Vorzeichen-Bit gesetzt ist oder nicht, wird eine 1 oder eine 0 von links eingeschoben.


Hinweis   Die herausgeschobenen Bits sind für immer verloren!

System.out.println( 65535 >> 8 );  // 255
System.out.println( 255 << 8);     // 65280
Es ist 65535 = 0xFFFF und nach der Rechtsverschiebung 65535 >> 8 ergibt sich 0x00FF = 255. Schieben wir nun wieder nach links, also 0x00FF << 8, dann ist das Ergebnis 0xFF00 = 65280.

i >>> n

Der Operator >>> verschiebt eine Variable (Bitmuster) bitweise um n Schritte nach rechts, ohne das Vorzeichen der Variablen zu berücksichtigen (vorzeichenloser Rechts-Shift). So werden auf der linken Seite (MSB) nur Nullen eingeschoben; das Vorzeichen wird mitgeschoben. Bei einer positiven Zahl hat dies keinerlei Auswirkungen, und das Verhalten ist wie beim >>-Operator.


Beispiel   Verschiebung einer positiven und negativen Zahl

Listing 2.13   ShiftRightDemo.java


class ShiftRightDemo
{
  public static void main( String args[] )
  {
    int i = 64;
    int j = i >>> 1;

    System.out.println( " 64 >>> 1 = " + j );

    i =64;
    j = i >>> 1;

    System.out.println( "-64 >>> 1 = " + j );
  }
}

Die Ausgabe ist für den negativen Operanden besonders spannend:


64 >>> 1 = 32
-64 >>> 1 = 2147483616

Ein <<<-Operator macht allerdings keinen Sinn, da beim Links-Shiften sowieso nur Nullen eingefügt werden.

Beim Shift nach links oder rechts steht vor dem binären Operator im rechten Operanden die Anzahl Bits, die verschoben werden. Bei einem int machen hier natürlich nur Werte bis 32 Sinn und für ein long Werte bis 64 Bit. Mit anderen Worten: Nur die ersten 5 beziehungsweise 6 Bit sind nötig.


Beispiel   Mit den Verschiebe-Operatoren ist es auch möglich, die einzelnen Bytes eines größeren Datentyps zu extrahieren. Ein int besteht etwa aus 8 Bytes. Wollten wie die 4 Bytes eines ints in einem byte-Feld repräsentiert haben, so könnten wir Folgendes schreiben:

byte b[] = { (byte)(v >>> 24) & 0xFF, (byte)(v >>> 16) & 0xFF,
             (byte)(v >>>  8) & 0xFF, (byte)(v       ) & 0xFF);

Division und Multiplikation mit Verschiebeoperatoren

Wird ein Wert um eine Stelle nach links geschoben, so kommt in das niedrigste Bit (das Bit ganz rechts) eine Null hinein und alle Bits schieben sich eine Stelle weiter. Das Resultat ist, dass die Zahl mit 2 multipliziert wird. Natürlich müssen wir mit einem Verlust von Informationen rechnen, wenn das höchste Bit gesetzt ist, denn dann wird es herausgeschoben, aber das Problem haben wir auch schon bei der normalen Multiplikation. Es gibt in Java keinen Operator, der die Bits rollt, der also die an einer Stelle herausfallenden wieder an der anderen Seite einfügt.

Wenn ein einmaliger Shift nach links mit 2 multipliziert, so würde eine Verschiebung um zwei Stellen nach links eine Multiplikation mit 4 bewirken. Allgemein gilt: Bei einem Shift von i nach links ergibt sich eine Multiplikation mit 2i. Wir können dies dazu nutzen, beliebige Multiplikationen durch Verschiebung nachzubilden.


Beispiel   Multiplikation mit 10 durch Verschiebung der Bits

10 * n == n << 3 + n << 1
Das funktioniert, da 10*n = (8+2)*n = 8*n + 2*n gilt.

Diese Umsetzung ist nicht immer einfach, und es gibt tatsächlich kein Verfahren, welches eine optimale Umsetzung liefert. Doch arbeiteten viele Prozessoren auf diese Weise intern die Multiplikation ab, und ein Compiler nutzt dies gern zur Optimierung der Laufzeit. Eine Verschiebeoperation ist bei vielen Prozessoren schneller als eine Multiplikation. Doch ist hier Obacht zu geben, denn eine lange Folge von Verschiebungen ist nicht schneller, sondern langsamer als eine direkte Multiplikation.

Neben der Addition kommt selbstverständlich auch die Subtraktion infrage. Ersetzen wir im oberen Beispiel das Plus durch ein Minus, so bekämen wir eine Multiplikation mit 6. Natürlich müssen wir auf die Überläufe der Zwischenergebnisse bei großen Zahlen achten. Diese würde es bei einer echten Multiplikation nicht geben.

Was wir am Beispiel der Verschiebung nach links gezeigt haben, funktioniert genauso mit einem Shift nach rechts. Jetzt wird bei einmaliger Verschiebung durch 2 dividiert. Das Ergebnis ist natürlich immer nur ganzzahlig, da die Verschiebeoperatoren nur auf int und long definiert sind; so ist 7 >> 2 = 3. Bei negativen Zahlen sieht das etwas anders aus, denn hier ist das Ergebnis –7 >> 2 = –4. Würden wir die die Division ganz klassisch mit / 2 realisieren ist das Ergebnis 7 / 2 = 3 und –7 / 2 = –3. Bei einer gedachten Optimierung ist auf diese Sonderheit bei negativen Zahlen zu achten.

Die Bitoperatoren in Assembler verglichen mit <<<, << und >>

Auch in Assembler gibt es zwei Gruppen von Schiebeoperatoren: die arithmetischen Schiebebefehle (SAL und SAR), die das Vorzeichen des Operanden beachten, und die logischen Schiebebefehle (SHL und SHR), die den Operanden ohne Beachtung eines etwaigen Vorzeichens schieben. Die Befehle SAL und SHL haben die gleiche Wirkung. So ist >>> der Bitoperator, in dem das Vorzeichen nicht beachtet wird, wie SHR in Assembler. Es gibt in Java auch keinen Bitoperator <<<, da – wie in Assembler – SAL = SHL gilt (<<< würde die gleiche Wirkung haben wie <<).

Bits rotieren

Java hat zwar Operatoren zum Verschieben von Bits, aber nicht zum Rotieren. Beim Rotieren werden Bits um eine bestimmte Stelle verschoben, die herausfallenden Bits kommen aber auf der anderen Seite wieder rein.

Eine Funktion ist leicht geschrieben: Der Trick dabei ist, die herausfallenden Bits vorher zu extrahieren und auf der anderen Seite wieder einzusetzen.


public static int rotateLeft( int v, int n )
{
  return (v << n) | (v >>> (32n));
}

public static int rotateRight( int v, int n )
{
  return (v >>> n) | (v << (32n));
}

Die Funktionen rotieren jeweils n Bits nach links oder rechts. Da der Datentyp int ist, ist die Verschiebung n in dem Wertebereich von 0 bis 31 sinnvoll.


Galileo Computing

2.8.11 Setzen, Löschen, Umdrehen und Testen von Bits  downtop

Die Bitoperatoren lassen sich zusammen mit den Shift-Operatoren gut dazu verwenden, ein Bit zu setzen respektive herauszufinden, ob ein Bit gesetzt ist. Betrachten wir folgende Funktionen, die ein bestimmtes Bit setzen, abfragen, invertieren und löschen:


Beispiel   Anwendung aller Bitoperatoren

int setBit( int n, int pos )
{
  return n | (1 << pos);
}
int clearBit( int n, int pos )
{
  return n & ~(1 << pos);
}

int flipBit( int n, int pos )
{
  return n ^ (1 << pos);
}
boolean testBit( int n, int pos )
{
  int mask = 1 << pos;

  return (n & mask) == mask;
  // alternativ: return (n & 1<<pos) != 0;
}


Galileo Computing

2.8.12 Der Bedingungsoperatotoptop

In Java gibt es ebenso wie in C(++) einen Operator, der drei Operanden benutzt. Dies ist der Bedingungsoperator, der auch Konditionaloperator, ternärer Operator beziehungsweise trinärer Operator genannt wird. Er erlaubt es, den Wert eines Ausdrucks von einer Bedingung abhängig zu machen, ohne dass dazu eine if-Anweisung verwendet werden muss. Die Operanden sind durch ? beziehungsweise : voneinander getrennt:


ConditionalOrExpression ? Expression : ConditionalExpression

Der erste Ausdruck muss vom Typ boolean sein und bestimmt, ob das Ergebnis Expression oder ConditionalExpression ist. Der Bedingungsoperator kann eingesetzt werden, wenn der zweite und dritte Operand ein numerischer Typ, boolescher Typ oder Referenztyp ist. Der Aufruf von Methoden, die demnach void zurückgeben, ist nicht gestattet.

Eine Anwendung für den trinären Operator ist oft eine Zuweisung an eine Variable:


Variable = Bedingung ? Ausdruck1 : Ausdruck2;

Der Wert der Variablen wird jetzt in Abhängigkeit von der Bedingung gesetzt. Ist sie erfüllt, dann erhält die Variable den Wert des ersten Ausdrucks, andernfalls wird der Wert des zweiten Ausdrucks zugewiesen.


Beispiel   So etwa für ein Maximum:

max = ( a > b ) ? a : b;

Dies entspricht beim herkömmlichen Einsatz für if/else:


if ( a > b )
  max = a;
else
  max = b;

Mit dem Rückgabewert können wir alles Mögliche machen, etwa ihn direkt ausgeben. Das wäre mit if/else nur mit temporären Variablen möglich.


System.out.println( ( a > b ) ? a : b );

Beispiele

Der Bedingungsoperator findet sich häufig in kleinen Funktionen. Dazu einige Beispiele:


Beispiel   Um das Maximum oder Minimum zweier Ganzzahlen zurückzugeben, definieren wir die kompakte Funktion:

static int min( int a, int b ) { return a < b ? a : b; }
static int max( int a, int b ) { return a > b ? a : b; }


Beispiel   Der Absolutwert einer Zahl wird zurückgegeben durch

x >= 0 ? x : -x


Beispiel   Der Groß-/Kleinbuchstabe im ASCII-Alphabet soll zurückgegeben werden. Dabei ist c vom Typ char.

// Konvertierung in Großbuchstaben

c = (char)(Character.isLowerCase(c) ? (c-a+A) : c);

// Konvertierung in Kleinbuchstaben

c = (char)(Character.isUpperCase(c) ? (c-A+a) : c);

Da diese Umwandlung nur für die 26 ASCII-Buchstaben funktioniert, ist es besser, Bibliotheksmethoden für alle Unicode-Zeichen zu verwenden. Dazu dienen die Funktionen toUpperCase() und toLowerCase().



Beispiel   Es soll eine Zahl n, die zwischen 0 und 15 liegt, zur Hexadezimalzahl konvertiert werden.

(char)( (n < 10) ? (0+ n) : (a’ – 10 + n ) )

Einige Fallen

Die Anwendung des trinären Operators führt schnell zu schlecht lesbaren Programmen und sollte daher vorsichtig eingesetzt werden. In C(++) führt die unbeabsichtigte Mehrfachauswertung in Makros zu schwer auffindbaren Fehlern. Gut, dass uns das in Java nicht passieren kann. Durch ausreichende Klammerung muss sichergestellt werden, dass die Ausdrücke auch in der beabsichtigten Reihenfolge ausgewertet werden. Im Gegensatz zu den meisten Operatoren ist der Bedingungsoperator rechtsassoziativ. (Die Zuweisung ist ebenfalls rechtsassoziativ.) Die Anweisung


b1 ? a1 : b2 ? a2 : a3

ist demnach gleichbedeutend mit


b1 ? a1 : ( b2 ? a2 : a3 )

Beispiel   Wollen wir eine Funktion schreiben, die für eine Zahl n abhängig vom Vorzeichen –1, 0 oder 1 liefert, lösen wir das Problem mit geschachteltem trinären Operator.

int sign( int n )
{
  return (n < 0) ? –1 : (n > 0) ? 1 : 0;
}

Der Bedingungsoperator ist kein lvalue

Der trinäre Operator liefert als Ergebnis einen Ausdruck zurück, der auf der rechten Seite einer Zuweisung verwendet werden kann. Da er rechts vorkommt, nennt er sich auch rvalue. Er lässt sich nicht derart auf der linken Seite einer Zuweisung einsetzen, dass er eine Variable auswählt, der ein Wert zugewiesen wird.


Beispiel   Folgende Anwendung des trinären Operators ist in Java nicht möglich:34

((richtung>=0) ? hoch : runter) = true;






1   Die Idee ist nicht neu. In den Achtzigerjahren verwendete Donald E. Knuth das WEB-System zur Dokumentation von TeX. Das Programm wurde mit den Hilfsprogrammen weave und tangle in ein Pascal-Programm und eine TeX-Datei umgewandelt.

2   In C(++) kann dies durch *((Bedingung) ? &a : &b) = Ausdruck; über Pointer gelöst werden.





Copyright © Galileo Press GmbH 2004
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de