29.01.2014 Aufrufe

Primitive Datentypen und Felder (Arrays)

Primitive Datentypen und Felder (Arrays)

Primitive Datentypen und Felder (Arrays)

MEHR ANZEIGEN
WENIGER ANZEIGEN

Erfolgreiche ePaper selbst erstellen

Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.

<strong>Primitive</strong> <strong>Datentypen</strong> <strong>und</strong> <strong>Felder</strong> (<strong>Arrays</strong>)<br />

<strong>Primitive</strong> <strong>Datentypen</strong><br />

Java stellt (genau wie Haskell) primitive <strong>Datentypen</strong> für Boolesche Werte, Zeichen, ganze Zahlen <strong>und</strong> Gleitkommazahlen<br />

zur Verfügung. Der wichtigste Unterschied zu Haskell besteht darin, dass man in Java zwischen<br />

vier verschiedenen Typen für ganze Zahlen <strong>und</strong> zwei verschiedenen Typen für Gleitkommazahlen<br />

wählen kann (<strong>und</strong> darüber hinaus muss man sich daran gewöhnen, dass die Typbezeichnungen mit Kleinbuchstaben<br />

beginnen). Diese verschiedenen Typen unterscheiden sich nach Größe des für sie reservierten<br />

Speicherplatzes <strong>und</strong> folglich auch nach dem Bereich der darstellbaren Zahlen. Die folgende Tabelle zeigt<br />

die vier Typen von ganzen Zahlen mit Vorzeichen (signed integers):<br />

Typbezeichnung Speicherplatz Darstellungsbereich<br />

byte 8 Bit = 1 Byte [−128,127]<br />

short 16 Bit = 2 Byte [−32768,32767]<br />

int 32 Bit = 4 Byte [−2 31 ,2 31 − 1]<br />

long 64 Bit = 8 Byte [−2 63 ,2 63 − 1]<br />

Für Gleitkommazahlen gibt es neben dem bekannten Typ float mit 4 Byte Speicherplatz auch den Typ<br />

double mit 8 Byte Speicherplatz. Dieser wird primär zur Verbesserung der Präzision der Darstellung <strong>und</strong><br />

nur sek<strong>und</strong>är zur Erweiterung des Darstellungsbereichs verwendet.<br />

In der Reihenfolge byte, short, int, long kann der Wert einer Variable eines niederen Typs immer<br />

einer Variable des höheren Typs zugewiesen werden. Gleiches gilt für float <strong>und</strong> double <strong>und</strong> für die Zuweisung<br />

von ganzzahligen Werten auf Gleitkomma–Variable. Da der umgekehrte Weg leicht zu Fehlern<br />

führen kann, ist eine solche Zuweisung nur dann möglich, wenn diese Typumwandlung (type cast) explizit<br />

gefordert wird. Dazu wird, wie das folgende Beispiel zeigt, der Typ, in den umgewandelt werden soll, in<br />

Klammern vor den umzuwandelnden Ausdruck gestellt:<br />

int i = 1000;<br />

short k = (short)i;<br />

Typumwandlungen, bei denen der umzuwandelnde Wert nicht im Darstellungsbereich des neuen Typs liegt,<br />

führen zu Fehlern.<br />

Für die sechs genannten numerischen Typen kann man die arithmetischen Operationen +,-,* <strong>und</strong> / verwenden,<br />

wobei die Operation / bei allen ganzzahligen Typen auch die ganzzahlige Division ausführt. Der<br />

Rest bei der ganzzahligen Division wird mit der Operation % bestimmt. Der Inkrement–Operator ++ <strong>und</strong><br />

der Dekrement–Operator -- sind für alle numerische Typen anwendbar <strong>und</strong> bewirken die Addition bzw.<br />

die Subtraktion von 1. Man kann beide in Präfix–Notation (vor dem Argument) <strong>und</strong> Suffix–Notation (nach<br />

dem Argument) verwenden. Ein Unterschied macht sich dann bemerkbar, wenn der Operator in einer Wertzuweisung<br />

angewendet wird: Bei Präfix–Notation wird erst die Operation ausgeführt <strong>und</strong> dann der Wert<br />

zugewiesen, bei Suffix–Notation erfolgt zuerst die Wertzuweisung <strong>und</strong> dann die Operation. Das folgende<br />

Beispiel verdeutlicht diesen Unterschied:<br />

int i = 20;<br />

ink k = i++; // aktuelle Werte: k=20 <strong>und</strong> i=21<br />

int l = --k; // aktuelle Werte: k=19 <strong>und</strong> l=19


Die Vergleichsoperationen ==,!=,= liefern auf allen numerischen Typen Boolesche Werte. Variable<br />

vom Typ boolean können nur die Werte true <strong>und</strong> false annehmen. Als Operationen auf Booleschen<br />

Werten kann man die Negation ! (einstellig), die Konjunktion && sowie die Disjunktion || verwenden.<br />

Der Typ char verfügt (wie in Haskell) über 16 Bit, mit denen alle Unicode–Zeichen dargestellt werden<br />

können. Werte vom Typ char können ohne explizite Typumwandlung auf Variable der Typen int, long,<br />

float, double zugewiesen werden, für die Gegenrichtung ist eine explizite Typumwandlung erforderlich.<br />

Zu jedem primiten Datentyp ist eine sogenannte Wrapper–Klasse definiert. Mit zwei Ausnahmen (int,<br />

char) tragen diese Klassen jeweils den gleichen Namen, aber mit Großbuchstaben am Anfang:<br />

Byte, Short, Integer, Long, Float, Double, Boolean, Character<br />

Wie ein kurzer Blick in daie Systembeschreibung API (Application Programming Interface, zu finden unter<br />

-> http://java.sun.com/j2se/1.5.0/docs/api/) verrät, stellen die Wrapper–Klassen eine Reihe nützlicher Funktionen<br />

zur Verfügung. Darüber hinaus bieten sie aber auch die Möglichkeit, Zahlen oder Zeichen wie ein<br />

Objekt (-> nächste Themen) zu behandeln.<br />

Bei der Deklaration einer Variablen eines primitiven Typs wird (bei der Programmausführung) ein ensprechend<br />

großer Abschnitt im Speicher reserviert, der mit dem Namen der Variablen assoziiert ist. Wenn mit der<br />

Deklaration noch keine Wertzuweisung erfolgt, wird der Speicherplatz mit einem sogenannten Default–Wert<br />

belegt, nämlich 0 für alle ganzzahligen Typen, 0.0 für Gleitkommatypen, false für boolean <strong>und</strong> das durch<br />

16 Nullen codierte Zeichen NUL für den Typ char. Bei einer Zuweisung der Form x = ausdruck; wird der<br />

Wert von ausdruck auf den Speicherplatz von x kopiert. Variable eines primitiven Typs haben also immer<br />

einen Wert <strong>und</strong> können deshalb auch als Werttypen bezeichnet werden. Im Gegensatz dazu sind alle anderen<br />

<strong>Datentypen</strong> in Java sogenannte Referenztypen, d.h. ihr Name ist nicht mit einem konkreten Objekt dieses<br />

Typs, sondern mit einer Referenz (Verweis) assoziiert, die auf solch einen Objekt oder aber auf null (ein<br />

symbolischer Ausdruck für NICHTS) verweist. Mit einer Zuweisung wird in einem solchen Fall nicht das<br />

Objekt kopiert, sondern nur die Referenz auf dieses Objekt. Referenzen sind mehr als nur ein einfacher Zeiger,<br />

aber man kann sich eine Referenz gut als einen Zeiger auf einen bestimmten Speicherinhalt vorstellen.<br />

Das Prinzip kommt bereits bei einem Datentyp zum Tragen, der eine Zwitterstellung zwischen primitiven<br />

<strong>Datentypen</strong> <strong>und</strong> Objekten einnimmt, dem sogenannten Feld (Array).<br />

<strong>Felder</strong><br />

Ein Feld oder Array repräsentiert einen Folge von Daten gleichen Typs <strong>und</strong> belegt dabei einen zusammenhängenden<br />

Speicherabschnitt. Die Daten in einem Feld der Länge n sind von 0 bis n−1 nummeriert. Es<br />

gibt zwei Möglichkeiten, ein Array zu deklarieren, in der Vorlesung bevorzugen wir die Varainte<br />

typename [ ] arrayname;<br />

aber alternativ kann auch<br />

typename arrayname [ ];<br />

verwendet werden. Die Leerzeichen zwischen dem Namen <strong>und</strong> der öffnenden Klammer bzw. zwischen den<br />

Klammern wurden nur zur besseren Lesbarkeit gesetzt, man kann auf beide verzichten. Mit einer solchen<br />

Deklaration wird eine Referenz angelegt, die auf null, also auf nichts verweist. Wie bei primitiven <strong>Datentypen</strong><br />

kann man die Deklaration auch mit einer Zuweisung verbinden. Dazu muss das zugewiesene Array


entweder schon deklariert sein, oder es muss im Speicher angelegt werden. Auch für das Neuanlegen gibt<br />

es zwei Möglichkeiten, nämlich nur die Feldlänge anzugeben (<strong>und</strong> damit alle Speicherzellen mit Default–<br />

Werten zu füllen) oder alle Daten, die im Array gespeichert werden sollen, direkt aufzulisten (womit die<br />

Feldlänge implizit festgelegt wird). Das folgende Beispiel demonstriert diese Varainten:<br />

int[] a1;<br />

// a1 ist (Referenz auf) null<br />

int[] a2 = new int[4]; /* a2 ist (Referenz auf) ein int-Array der Laenge 4, in<br />

dem alle Eintraege den Default-Wert 0 haben */<br />

int[] a3 = {1,2,3}; // a3 ist (Referenz auf) ein int-Array der Laenge 3<br />

int[] a4 = a2; // a4 ist (Referenz auf) auf gleiches Array wie a2<br />

int[] a5 = a1; // a5 ist (Referenz auf) null<br />

Wie man sieht, wird bei einer Zuweisung nur die Referenz übertragen, es erfolgt keine Kopie des eigentlichen<br />

Feldes im Speicher.<br />

Auf den i–ten Eintrag eines <strong>Arrays</strong> a kann man mit a[i] zugreifen, die Länge steht als Eigenschaft a.length<br />

zur Verfügung. Wir illustrieren das an einer Fortsetzung des obigen Beispiels:<br />

int i = a3[1]; // i hat den Wert 2, denn die Nummerierung beginnt mit 0<br />

a2[3] = 5;<br />

// eine 0 wird mit 5 ueberschrieben<br />

int j = a4[3]<br />

/* j hat den Wert 5 weil a2 <strong>und</strong> a4 auf das gleiche Array<br />

verweisen */<br />

Die letzte Zeile macht noch einmal deutlich, dass nach Zuweisung von Array–Variablen (wie in unserem<br />

Beispiel a4=a2;) jede Änderung an dem durch die eine Variable referenzierten Objekt auch für die andere<br />

Variable wirksam ist. Das ist ein f<strong>und</strong>amentaler Unterschied zu Variablen für primitiven <strong>Datentypen</strong>:<br />

int n1 = 3; //<br />

int n2 = n1; // beide haben den Wert 3<br />

n1 = 5; // n1 hat den Wert 5, aber n2 hat immer noch den Wert 3<br />

Bei der Verwendung des Operators == auf Variable eines nichtprimitiven Typs muss man beachten, dass<br />

die Referenzen auf Gleichheit getestet werden <strong>und</strong> es nicht darauf ankommt, ob die referenzierten Objekte<br />

gleich sind oder nicht. Auch diesen Effekt kann man an einem einfachen Beispiel demonstrieren:<br />

int[] A = {2,3,4} // ein erstes Array mit Eintraegen 1,2,3 wir angelegt<br />

int[] B = A; // B ist Referenz auf das gleiche Array<br />

int[] C = {2,3,4} // ein zweites Array mit Eintraegen 1,2,3 wir angelegt<br />

boolean c = (A == C); /* c ist false, denn die Referenzen verweisen auf zwei<br />

verschiedene Speicherabschnitte */<br />

boolean b = (A == B); // b ist true, beide Referenzen verweisen auf erstes Array<br />

In der folgenden Grafik ist dargestellt, wie die Ausführung der ersten drei Zeilen des Codes im Speicher<br />

realisiert wird.


Code<br />

Variable<br />

Speicher<br />

int[ ] A = {2,3,4};<br />

A<br />

Referenz<br />

2 3 4<br />

A<br />

Referenz<br />

int[ ] B = A;<br />

2 3 4<br />

B<br />

Referenz<br />

A<br />

Referenz<br />

2 3 4<br />

int[ ] C = {2,3,4};<br />

B<br />

C<br />

Referenz<br />

Referenz<br />

2 3 4


Um eine wirkliche Kopie eines <strong>Arrays</strong> zu erzeugen, verwendet man die Funtion clone(). Aus Gründen, die<br />

erst später klar werden, muss aber zusätzlich noch eine Typumwandlung erfolgen:<br />

int[] A = {1,2,3} // ein erstes Array mit Eintraegen 1,2,3 wird angelegt<br />

int[] B = (int[]) A.clone(); /* ein zweites Array mit Eintraegen 1,2,3 wird<br />

als Kopie des ersten <strong>Arrays</strong> angelegt */<br />

boolean b = (A == B); /* b ist false, Referenzen sind verschieden<br />

Durch die Verwendung von mehreren Klammerpaaren können höherdimensionale <strong>Arrays</strong>, mit anderen Worten<br />

<strong>Felder</strong> von <strong>Felder</strong>n, angelegt werden. Das folgende Beispiel zeigt wieder die verschiedenen Möglichkeiten<br />

auf, solche <strong>Arrays</strong> zu deklarieren <strong>und</strong> zu definieren.<br />

int[][] A;<br />

// Referenz auf null<br />

int[][] B = new int[3][]; /* Referenz auf Feld der Laenge 3, dessen<br />

Eintraege jeweils Referenzen auf null sind */<br />

int[][] C = new int[3][2]; /* Referenz auf Feld der Laenge 3, dessen<br />

Eintraege jeweils Referenzen auf int-<strong>Felder</strong><br />

der L\"ange 2 sind */<br />

int[][] D = new int[][2]; // Fehler<br />

int[][] E = {{1,2}{2,2,5}{4}}; // gueltig trotz verschiedener Laengen<br />

Bei der Verwendung der Methode clone() ist wieder volle Aufmerksamkeit geboten. Entwerfen Sie für das<br />

folgende Beispiel ein grafischen Schema nach obigem Vorbild, um sich die in den Kommentaren genannten<br />

Fakten klar zu machen.<br />

int[][] data = {{1,2,3}{4,5}};<br />

int[][] copy = (int[][]) data.clone();<br />

copy[0][0] = 100; // data[0][0] hat auch den Wert 100<br />

copy[1] = new int[] {7,8,9}; // data[1] hat sich nicht geaendert

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!