Grundlagen der Programmierung –Teil1 Einheit 1I - 16. Okt. 2009 GDP DDr. Karl D. Fritscher basierend auf der Vorlesung “Grundlagen der Programmierung” von DI Dr. Bernhard Pfeifer Einfache Datentypen • • Java kennt acht elementare Datentypen, die gemäß Sprachspezifikation als primitive Datentypen bezeichnet werden. Daneben gibt es die Möglichkeit, Arrays zu definieren (die eingeschränkte Objekttypen sind), und als objektorientierte Sprache erlaubt Java natürlich die Definition von Objekttypen. Gleitkommatypen import java.io.*; public class Sample { public static void main (String[] argument) throws IOException { System.out.println (1/10); Ergebnis ?? } } • Wenn wir das Programm übersetzen und ausführen, so würden wir erwarten, dass als Ergebnis 0.1 am Bildschirm ausgegeben wird. • Zu unserer Überraschung wird jedoch 0 ausgegeben. Das ist aber vermeintlich falsch! • Aber Achtung! Wir haben im obigen Programm mit Ganzzahlen gearbeitet. Der Divisionsoperator ist in Java jedoch so definiert, dass die Division zweier ganzer Zahlen wiederum eine ganze Zahl ist. Und somit stimmt die Division natürlich: 1 / 10 = 0 mit 1 Rest Gleitkommatypen • Java kennt die beiden Fließkommatypen float (einfache Genauigkeit) und double (doppelte Genauigkeit). Die Länge beträgt 4 Byte für float und 8 Byte für double. • Fließkommaliterale werden immer in Dezimalnotation aufgeschrieben. Sie bestehen aus einem Vorkommateil, einem Dezimalpunkt, einem Nachkommateil, einem Exponenten und einem Suffix. Um ein Fließkommaliteral von einem integralen Literal unterscheiden zu können, muss mindestens der Dezimalpunkt, der Exponent oder der Suffix vorhanden sein. Entweder der Vorkomma- oder der Nachkommateil darf ausgelassen werden, aber nicht beide. • Vorkommateil und Exponent können wahlweise durch das Vorzeichen + oder eingeleitet werden. Der Exponent, der durch ein e oder E eingeleitet wird, ist optional. Auch der Suffix kann weggelassen werden, wenn durch die anderen Merkmale klar ist, dass es sich um eine Fließkommazahl handelt. Der Suffix kann entweder f oder F sein, um anzuzeigen, dass es sich um ein float handelt, oder d oder D, um ein double anzuzeigen. Fehlt er, so ist das Literal (unabhängig von seiner Größe) vom Typ double. Gültige Beispiele: ????? 47 Gleitkommatypen • Neben diesen numerischen Literalen gibt es noch einige symbolische Literale in den Klassen Float und Double des Pakets java.lang. NaN entsteht beispielsweise bei der Division durch 0, POSITIVE_INFINITY bzw. NEGATIVE_INFINITY sind Zahlen, die größer bzw. kleiner als der darstellbare Bereich sind. MAX_VALUE und MIN_VALUE repräsentieren die größte bzw. kleinste darstellbare Zahl. Gleitkommatypen import java.io.*; public class Sample { public static void IOException { System.out.println } } Ergebnis ?? main (String[] (1d/10.); argument) throws Datentyp char • Will man mit einzelnen Zeichen arbeiten, so steht der Datentyp char zur Verfügung (2 Byte, UTF -16) • Literalkonstanten werden dabei unter ein einfaches Hochkomma gestellt • Daten von diesem Datentyp werden intern mit 16 Bit dargestellt (unicode) import java.io.*; public class Buchstabe { public static void IOException { main (String[] argument) char ersterBuchstabe = 'A'; System.out.println("Der erste Buchstabe des Alphabets ist ein"); System.out.println(ersterBuchstabe); } } throws Datentyp char • char wird innerhalb der Java Virtual Machine als int betrachtet: import java.io.*; public class Buchstabe { public static void IOException { main (String[] argument) throws char ersterBuchstabe = 'A'; char zweiterBuchstabe = 'B'; System.out.println(„Die Addition von erstem und zweitem Buchtaben ergibt"); System.out.println(ersterBuchstabe+zweiterBuchstabe); } } Ergebnis ?? Wahrheitswerte • Beim Programmieren steht man des öfteren vor dem Problem, dass man Werte miteinander vergleichen muss. Das Ergebnis des Vergleichs liefert dann WAHR oder • • FALSCH. In Java existieren die Wahrheitswerte true und false Die Auswertung von logischen Ausdrücken liefert als Ergebnis Werte des Typs boolean Siehe auch Folie „Logische Operatoren“ Implizite/Explizite Typumwandlung • Es kann vorkommen, dass man einen gewissen Datentyp für eine Operation benötigt, jedoch einen anderen vorliegen hat . Will man z.B.: eine 64 Bit Zahl mit einer 32 Bit Zahl addieren, so steht man vor dem Problem, dass die Addition nur für denselben Typ definiert ist. In einem solchen fall benötigt man also eine Typumwandlung, damit die Operation überhaupt durchgeführt werden kann. • Bei der oben beschriebenen Problemstellung muss man gar nichts tun. Denn der Compiler erkennt, dass die eine Zahl einen Zahlenbereich hat, der den der anderen Zahl umschliesst. Aus diesem Grund wird einfach die int Zahl in eine long Zahl umgewandet, und die Addition kann ausgeführt werden. • Diese (automatische) Vorgehensweise nennt man implizite Typkonvertierung (implicite typecast) • Implizite Typkonvertierungen treten immer dann auf, wenn ein kleinerer Zahlenbereich in einen größeren Zahlenbereich abgebildet wird. byte short int long float double Implizite/Explizite Typumwandlung • Eine automatische Konvertierung wird vom Compiler in folgenden Fällen vorgenommen Bei einer Zuweisung, wenn der Typ der Variablen und des zugewiesenen Ausdrucks nicht identisch ist. Bei der Auswertung eines arithmetischen Ausdrucks, wenn Operanden unterschiedlich typisiert sind. int a; float b,c; c=a+b; Beim Aufruf einer Methode, falls die Typen der aktuellen Parameter nicht mit denen der formalen Parameter übereinstimmen. public double doSomething (double a, double b) { .... } float x,y,z; Z=doSomething(x,y); 54 Implizite/Explizite Typumwandlung • Vorsicht bei Konvertierung in char bzw. von char in byte, short oder int !! Konvertierung von short in char: Class Short2Char { public static void main (String args[]) Da die Breite von short und { char gleich ist bleibt das Bitmuster erhalten. Da char char posChar, negChar; aber nur für pos. Ganzzahlige Short pos Short = 1; Werte definiert ist, geht jedoch Short negShort = -1; die Bedeutung des posChar = (char) posShort; Vorzeichens verloren neg Char = (char )negShort; System.out.println(„positiver Short“ + posShort + „ist als char „ +(int) posChar); System.out.println(„negativer Short“ + negShort + „ist als char „ +(int) negChar); } Ausgabe: Positiver Short 1 ist als Char 1 } Negativer Short -1 ist als Char 65535 54 Implizite/Explizite Typumwandlung • Will man beispielsweise eine Variable vom typ double in eine Variable vom Typ int konvertieren, so kann man das nicht so ohne weiteres tun, denn es geht ja durch diese einschränkende Konvertierung Information verloren. • Daher nimmt der Compiler an, dass es sich um einen Fehler handelt und gibt eine Fehlermeldung aus: Incompatible type for declaration Explicit cast needed to convert from double to int • Der Grund für dieses Verhalten liegt darin begründet, dass es sich bei einer solchen Vorgangsweise häufig um einen Programmierfehler handelt. Um jedoch eine Konvertierung vornehmen zu können, muss man den Compiler dazu zwingen, es zu tun. Dies nennt man explizite Typkonvertierung (explicit typecast): int result • = (int) Math.Pi; Eine Umwandlung von boolean in einen anderen Datentyp ist nicht möglich! 55 Definite Assignment • • In Java gibt es ein Konzept, das sich Definite Assignment nennt. Gemeint ist damit die Tatsache, dass jede lokale Variable vor ihrer ersten Verwendung definitiv initialisiert sein muss. Dazu muss im Quelltext eine Datenflussanalyse durchgeführt werden, die jeden möglichen Ausführungspfad von der Deklaration einer Variablen bis zu ihrer Verwendung ermittelt und sicherstellt, dass kein Weg existiert, der eine Initialisierung auslassen würde. PROBLEM? Wrapper-Klassen/Objekte Wrapper-Objekte nehmen einen einfachen Datentyp in einem Objekt auf. Damit erfüllen sie zwei wichtige Aufgaben: • Die Datenstrukturen, die in Java Verwendung finden, können nur Objekte aufnehmen (zB java.util.Vector). So stellt sich das Problem, wie primitive Datentypen zu diesen Containern hinzugefügt werden können. Die Klassenbibliothek bietet daher für jeden primitiven Datentyp eine entsprechende Wrapper-Klasse (auch »Ummantelungsklasse« oder »Envelope Class« genannt) an. Exemplare dieser Klassen kapseln je einen Wert des zugehörigen primitiven Typs. • Zusätzlich zu dieser Eigenschaft bieten die Wrapper-Klassen Funktionen zum Zugriff auf den Wert und einige Umwandlungsfunktionen. Bsp: int i=10; Integer ii = new Integer( i ); String s = ii.toString(); String s = "10"; int i = Integer.parseInt( s ); float f = Float.parseFloat( s ); Es existieren Wrapper-Klassen zu allen primitiven Datentypen und zusätzlich eine Klasse für void. Ausdrücke und Operatoren • Obwohl wir Ausdrücke schon in unseren ersten kleinen Programmen kennengelernt haben, wollen wir diese jetzt noch einmal etwas genauer unter die Lupe nehmen. Merke: Ein Ausdruck ist die kleinste ausführbare Einheit eines Programms. Ein Ausdruck besteht immer aus mindestens einem Operator und einem oder mehreren Operanden, auf die der Operator angewendet wird. Nach den Typen der Operatoren unterscheidet man zwischen arithmetischen / nummerischen, relationalen , logischen , (bitweise) Zuweisungs , und sonstigen Operatoren (oft als Methode implementiert zB instanceof) Neben dem Typ ist auch die Stelligkeit eines Operators von zentraler Bedeutung (siehe auch Vorlesungsunterlagen vom 9. 10. 2009) Arithmetische Operatoren • • • • Es existiert die Addition, Subtraktion, Multiplikation, Division und der Restwertoperator Zusätzlich gibt es den einstelligen Operator für ein positives oder negatives Vorzeichen Arithmetische Operatoren erwarten numerische Operanden und liefern einen numerischen Rückgabewert. Haben die Operanden unterschiedliche Datentypen, so wird ein automatischer Type-Cast durchgeführt. + + * / % ++ -- positives Vorzeichen negatives Vorzeichen Summe Differenz Multiplikation Quotient Restwert Prä/Postinkement Prä/Postdekrement +n gleichbedeutend mit n -n kehrt das Vorzeichen um a+b ergibt Summe von a,b a-b a*b a/b a modulo b a=a+1 a=a-1 Relationale Operatoren • Relationale Operatoren dienen dazu, Ausdrücke miteinander zu vergleichen und in Abhängigkeit davon einen logischen Rückgabewert zu produzieren == Gleich != Ungleich a!=b => true wenn a ungleich b < Kleiner a<b => true wenn a kleiner b <= Kleiner gleich > >= Größer Größer gleich a==b => true wenn a=b a<=b => true wenn a kleiner oder gleich b a>b a>=b 59 Logische Operatoren • • Logische Operatoren dienen dazu, boolsche Werte miteinander zu verknüpfen. Im Gegensatz zu relationalen Operatoren, die durch Vergleiche einen boolschen Wert produzieren, werden logische Operatoren zur Weiterverarbeitung von Wahrheitswerten verwendet. Es gibt die Grundoperationen UND, ODER und NICHT. UND und ODER werden in zwei Varianten zur Verfügung gestellt: • Short-Circuit-Evaluation es wird ein logischer Ausdruck (weiter rechts) nur dann ausgewertet, wenn er für das Ergebnis noch von Bedeutung ist • keine Short-Circuit-Evaluation ! logisches NICHT !a => false, wenn a true und vice versa && UND mit SCE a&&b => true wenn a und b true sonst false || ODER mit SCE a||b => true, wenn mind. einer der Ausdrücke true ist, sonst false & UND ohne SCE | ODER ohne SCE ^ Exklusiv-Oder a^b => true, wenn beide Ausdrücke einen unterschiedlichen Wert haben Bitweise Operatoren • Mit diesen Operatoren kann auf die Binärdarstellung von numerischen Operanden zugegriffen werden. • Ein numerischer Datentyp wird dabei binär (dual) repräsentiert, und es können die einzelnen Bits direkt manipuliert werden. ~ Einerkomplement ~a entsteht aus a, indem alle Bits von a invertiert werden & bitweises UND a&b ergibt den Wert, der entsteht, wenn die korrespondierenden Bits von a und b miteinander UND verknüpft werden | bitweises ODER ^ bitweises XOR >> rechtsschieben mit Vorzeichen >>> rechtsschieben ohne Vorzeichen << linksschieben a|b ergibt den Wert, der entsteht, wenn die korrespondierenden Bits von a und b miteinander ODER verknüpft werden a>>b schiebt um b bits in a nach rechts Bitweise Operatoren Beispiele: 14 & 1 14 | 1 1110 & 0001 = 0000 1110 | 0001 = 1111 = 15 8 >>3 00000000 00000000 00000000 00001000 00000000 00000000 00000000 00000001 -7 >> 3 11111111 11111111 11111111 11111001 11111111 11111111 11111111 11111111 -7 >>> 3 11111111 11111111 11111111 11111001 00011111 11111111 11111111 11111111 = abschneidende Ganzahldivision durch 8 (=23) Zuweisungsoperatoren = += -= *= /= %= &= |= ^= <<= >>= Einfache Zuweisung Additionszuweisung a+=3; entspricht a=a+3; Subtraktionszuweisung Multiplikationszuweisung Divisionszuweisung Modulozuweisunng UND Zuweisung a&=b weist a den Wert zu von a&b ODER Zuweisung XOR Zuweisung Linksschiebungszuweisung Rechtsschiebungszuweisung Sonstige Operatoren • Fragezeichenoperator • Type-Cast Operator Bsp: • int c = (int) 4.2; InstanceOf – Operator Bsp: BMW instanceof Auto 63 63 Priorität von Operatoren • Wie bereits erwähnt sind Operatoren sind in Java mit so genannten Prioritäten versehen, wodurch gewisse Reihenfolgen bei der Auswertung (=operator ranking) eingehalten werden • Darüber hinaus gibt es folgende Regeln zur Auswertung eines Ausdrucks in JAVA: 1. Als erstes werden Teilausdrücke in Klammern ausgewertet. 2. Danach werden Ausdrücke mit unären Operatoren ausgewertet 3. Abschließend werden Teilausdrücke mit mehrstelligen Operatoren ausgewertet 4. Bei Operatoren mit gleicher Priorität (siehe auch Tabelle) wird die Reihenfolge der Auswertung anhand Ihrer Assoziativität bestimmt Bsp: a – b + c ……. Es wird zuerst die Operation a-b durchgeführt bevor die Addition mit c erfolgt -> linksassoziativ (beachte Reihenfolge der Auswertung kann durch Setzung von Klammern beeinflusst werden: a-(b+c) (siehe auch Regel 1)) • In Java sind Zuweisungsoperatoren, der Bedingungsoperator und unäre Operatoren rechtsassoziativ. Alle anderen Operatoren sind linksassoziativ. Priorität von Operatoren • Wie bereits erwähnt sind Operatoren sind in Java mit so genannten Prioritäten versehen, wodurch gewisse Reihenfolgen bei der Auswertung eingehalten werden (Punkt vor Strich) Operatoren für Objekte • Auch wenn wir noch nicht genau wissen, wie man ein Objekt erzeugt, bzw. was ein Objekt ist, so wollen wir uns trotzdem der Vollständigkeit halber anschauen, welche Operatoren für Objekte zur Verfügung stehen. • String Verkettung • • • Der + Operator kann nicht nur mit numerischen Operanden verwendet werden, sondern auch für eine Konkatenation von Strings - also Zeichenketten. Dabei gilt es folgende Regel zu beachten: Ist ein Operand vom Typ String, so werden alle weiteren als Typ String betrachtet und der + Operator wird als Konkatenator verwendet. In Java ist die Konvertierung in einen String von nahezu jeden Typen definiert. Bei primitiven Datentypen wird diese Konvertierung direkt vom Compiler, und bei Referenzdatentypen durch die Methode (Funktion) toString() ausgeführt. System.out.println (“3+4=“+3+4); System.out.println (“3+4=“+(3+4)); Referenztypen-Dynamische Variablen • Referenztypen sind neben den primitiven Datentypen die zweite wichtige Klasse von Datentypen in Java. • Objekte sind Referenztypen • Objekte werden in Java als dynamische Variablen angelegt auf dem „Heap“ abgelegt HEAP ?? Speicherbereiche in JAVA In Java unterschiedet man 3 verschiedene Speicherbereiche Stack Heap Method Area Stack (=Stapel): Informationen werden hier temporär abgelegt Im Stack werden zB lokale Variablen einer Methode gespeichert Auf Information, die zuletzt abgelegt wurden, kann als erstes wieder zugegriffen werden („last in – first out“ = LIFO stack) Heap (=Halde): stellt Speicherplatz für dynamische Variablen zur Verfügung Größe des Heaps ist beschränkt Garbage Collector gibt Speicherplatz der nicht mehr referenziert wird frei und ordnet Speicherbereich neu Method Area: Speicherbereich für Klassenvariablen Plus gesamter Programmcode der Klasse Referenztypen-Dynamische Variablen • Speichermanagement Während primitive Datentypen einfach deklariert werden, und dann verwendet werden können, reicht dies bei Referenztypen nicht mehr aus. Sie müssen mithilfe des new-Operators erzeugt werden (Ausnahme Arrays und Strings) StringBuffer myStringBuffer = new StringBuffer Auto mercedes = new Auto(150,rot); Klasse / Typ Var.-Name Operator Konstruktor Parameter (Spez. Methode zur Initialisierung eines Objekts) Was passiert im Speicher ?? (); Speicherbereiche in JAVA Stack Stack Nicht definierte Referenz Auto bmw; bmw Heap bmw = new Auto(150,rot); bmw Heap Auto-Objekt Definierte Referenz: Das im Heap erzeugte Objekt wird referenziert. Referenztypen-Dynamische Variablen • Referenztypen können prinzipiell genauso benutzt werden wie primitive Typen. Da sie jedoch lediglich einen Verweis (Alias) darstellen, ist die Semantik einiger Operatoren anders als bei primitiven Typen Die Zuweisung einer Referenz kopiert lediglich den Verweis auf das betreffende Objekt. Das Objekt selbst wird nicht kopiert. Nach einer Zuweisung zweier Referenztypen „zeigen“ diese also auf ein und das selbe Objekt. Sollen Referenztypen kopiert werden, so ist ein Aufruf der Methode clone()erforderlich Der Gleichheitstest zweier Referenzen testet, ob beide Verweise gleich sind, d.h. auf dasselbe Objekt „zeigen“. Soll auf inhaltliche Gleichheit getestet werden, so kann die equals() Methode verwendet werden. Referenztypen • Wie wir bereits gehört haben verfügt Java über ein automatisches Speichermanagement. Dadurch muss man sich nicht um die Speicherbereinigung kümmern.Wird ein Objekt nicht mehr benötigt, oder kann es nicht mehr erreicht werden, so wird es vom Garbage Collector entfernt. Um trotz des Verzichts auf die expliziten Destruktoren (=Objektvernichter) doch noch bestimmte Anweisungen direkt vor der Entfernung von Objekten durchführen zu können, besitzt jede Klasse die Methode finalize(): Ähnlich wie der implizite Konstruktor (Objekterzeuger und Initialisierer) muss diese Methode nicht extra implementiert werden. Es kann aber auch ein expliziter Finalizer geschrieben werden. Die Methode finalize() eines Objektes wird automatisch aufgerufen, bevor es vom Garbage Collector entfernt wird. Arrays • Wie bereits erwähnt sind Arrays in Java im Grunde genommen Objekte. Obwohl dies bei der Benutzung kaum auffällt hat dies folgende Konsequenzen: • Array Variablen sind Referenzen • Arrays besitzen Instanz-Variablen und Methoden • Arrays werden erst zur Laufzeit erzeugt • Dennoch bleiben Arrays immer eine Reihung von Elementen eines festen Grundtyps. • Arrays sind semidynamisch - d.h. ihre Größe wird zur Laufzeit festgelegt, kann jedoch im Nachhinein nicht mehr geändert werden. Arrays • Ein Array wird in Java wie folgt deklariert Deklaration der Array-Variablen Erzeugen eines Arrays und Zuweisung an die Array-Variable • Die Deklaration entspricht dabei syntaktisch der einer einfachen Variable, mit dem Unterschied, dass am Typnamen eckige Klammern angehängt werden int[] a; double [] b; • Die eckigen Klammern können theoretisch auch hinter dem Variablennamen geschrieben werden, aber es sollte jedoch vermieden werden. 71 Zugriff auf Arrayelemente • Bei der Erzeugung eines Arrays mit n Elementen werden die Elemente von 0 bis n-1 durchnummeriert Erzeugung eines int-Arrays mit 3 Elementen a[] = new int[3]; oder Int[] a = {1, 5, 3}; • Der Zugriff auf die Elemente erfolgt dabei über den numerischen Index, der nach der Arrayvariable in eckigen Klammern angegeben wird values ai 20 15 2 44 77 12 2 9 0 1 index i 0 1 2 3 4 5 6 7 8 9 public class Test { public static void main (String[] args) { int[] values = new int[10]; values[0] values[1] = = 20; 15; System.out.println } } (“Erstes Element: “ + values[0]); Mehrdimensionale Arrays • Mehrdimensionale Arrays werden erzeugt, indem zwei oder mehr Paare eckiger Klammern bei der Deklaration angegeben werden. • Mehrdimensionale Arrays werden als Arrays von Arrays angelegt. • Die Initialisierung erfolgt analog zu eindimensionalen Arrays. Beispiel: int [][] twodim = new int[2][3]; 74 Zeichenketten -Strings • Mehrere Zeichen des Datentyps char können zu einer Zeichenkette zusammengefasst werden. Zeichenketten werden dabei als Strings bezeichnet. • Strings werden in Java allerdings nicht als Werte eines einfachen Datentyps gewertet, sondern stellen Objekte dar (siehe auch link) • Strings werden unter einem doppelten Hochkomma eingeschlossen • Für die Erzeugung eines String Objekts gibt es 2 Möglichkeiten: 1. Mit dem new Operator: String name = new String(„Klaus“); 2. Implizite Erzeugung bei Verwendung einer konstanten Zeichenkette: String name = „Klaus“; • Implizit erzeugte String Objekte können wiederverwendet werden: .class Datei kleiner Interpreter kann Klassenübergreifend optimieren String name = „Klaus“; String gleicherName = Klaus“; name Heap String Klaus gleicherName Übungsfragen 1. Was versteht man unter Typumwandlung? 2. Wie kann es bei einer Typumwandlung zu unerwünschten Veränderungen im Wert kommen? 3. Was versteht man unter expliziter bzw. impliziter Typumwandlung? Nennen sie jeweils ein Beispiel. 4. Was sind Operatoren? In welche Klassen können Operationen eingeteilt werden? 5. Was versteht man unter der Priorität eines Operator s? In diesem Zusammenhang fällt auch manchmal der Begriff „operator ranking“ . Was versteht man darunter ?