![]() |
|
|||||
Interfaces: Rollen, die Objekte spielen können Ein Interface hat damit den Charakter einer Rolle, die ein reales Objekt beherrscht oder nicht. Somit sind Interfaces abstrakt, man kann von ihnen keine Objekte erzeugen. Eine Klasse kann beliebig viele Interfaces implementieren. Jedes Objekt kann dann z.B. beim Methoden-Aufruf anstelle eines Interface-Parameters stehen, sofern die zugehörige Klasse das Interface implementiert hat. Damit werden schwerwiegende Defizite der Vererbung unter Beibehaltung der Hauptvorteile wie Substitution behoben. Methoden und DatenKeine globalen Funktionen und Daten Für C/C++-Konvertiten ist die Tatsache ungewohnt, dass es keine globalen Funktionen und Daten außerhalb von Klassen gibt. Der auszuführende Code ist immer in den Methoden (Methods) der Klassen enthalten. Aufruf oder Zugriff mit Hilfe des Da Methoden und Daten an Klassen oder deren Objekte gebunden sind, erfolgt der Aufruf einer Klassen-/Objekt-Methode oder der Zugriff auf Daten von außen über den zugehörigen Klassen- bzw. Objektnamen, separiert durch den Punkt (dot-operator)7 vom Methoden-/Datennamen: Klasse.methode() bzw. objekt.methode() Klasse.feld bzw. objekt.feld Die Klammern müssen bei Methoden immer angegeben werden, auch wenn sie keine Parameter haben. Damit ist ein Methoden-Aufruf leicht von einem Datenzugriff zu unterscheiden. ArraysWie bei C/C++ werden Arrays als geordnete Kollektion von Elementen mit gleichem Typ durch eckige Klammern deklariert, wobei auch der Zugriff auf ein Element über den Index in eckigen Klammern erfolgt. Damit sind allerdings die Gemeinsamkeiten mit C/C++ auch schon erschöpft. Denn bereits die bevorzugte Art, in Java Arrays zu deklarieren, stellt sich folgendermaßen dar:
byte[] bArr; String[] sArr; Da die Angabe der Größe bei der Deklaration nicht erlaubt ist, kann die Array-Variable ein Array beliebiger Länge referenzieren. Diese Art der Deklaration kann auch für die Rückgabe von Arrays aus einer Methode verwendet werden (was C/C++ ohnehin nicht erlaubt). Anlage eines Arrays mittels new Mit der o.a. Deklaration ist noch kein Array angelegt. Dies geschieht normalerweise mittels eines new-Ausdrucks und kann direkt bei der Deklaration oder auch zu einem späteren Zeitpunkt erfolgen: byte[] bArr= new byte[10]; Die Variable bArr referenziert nun ein Array mit zehn Elementen vom Typ byte (Näheres zu byte: siehe 1.3). Für kleine Arrays gibt es eine sehr praktische Kurzform, die neben der (impliziten) Anlage des Arrays bereits den einzelnen Array-Elementen Werte zuweist: int[] iArr= {1,2,3}; int[] iArr= new int[3]; iArr[0]=1; iArr[1]=2; iArr[2]=3; 1.1.2 Java-Applikation
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Modularisierung des Codes mit Hilfe des Package-Konzepts |
| Ein hierarchisches Namenssystem, das es ermöglicht, Namenskollisionen global zu vermeiden |
Die Konvention beinhaltet eine Anleitung zur Abbildung der Package- und Klassennamen in die Verzeichnisstrukturen und Dateien unterschiedlicher Betriebssysteme. Damit müssen natürlich die Namen von Packages gültige Verzeichnisnamen bei allen Betriebssystemen sein.
Package: Kollektion zusammengehöriger Klassen/Interfaces
Nach dem Vorbild von Modula werden alle Klassen in Java-Packages gekapselt. Ein Package besteht aus einer Kollektion von:
| Klassen |
| Interfaces (Schnittstellen) |
| Subpackages |
Mindestens besteht es aus einem dieser Elemente. Ein Package kann also durchaus nur eine »Hülle« für Subpackages sein, d.h. selbst keine Klassen oder Interfaces enthalten.
Ein Package bildet für die enthaltenen Mitglieder einen so genannten Namensraum (namespace).
Innerhalb des Namensraums müssen die Namen der Mitglieder eindeutig sein, außerhalb können sie immer über den vorangestellten Package-Namen identifiziert werden.
Damit wird das Problem der Eindeutigkeit auf die Stufe der Packages verlagert, d.h., für eine Internet-Anwendung muss beispielsweise gewährleistet sein, dass Package-Namen eindeutig sind.
Konvention für eindeutige Package-Namen
Sun hat sich für die Packages aus dem eigenen Haus die Namen beginnend mit java, javax oder sun reserviert.
Der Rest der Welt generiert eindeutige Bezeichnungen anhand der eigenen global eindeutigen Internet-Domain-Namen. Nach Java-Konvention kehrt man sie dazu um und schreibt alles durchgängig klein, also z.B.:
| Domain Name | Package-Name startet mit |
Spezieller Package-Name |
| MeinName.de | de.meinname | de.meinname.meinpaket |
| OurCompany.com | com.ourcompany | com.ourcompany.project1.dbms |
| IBM.com | com.ibm | com.ibm.sf.samples.addressbook |
Hierarchie mittels Subpackages
Die letzten beiden Beispiele zeigen hierarchisch angeordnete Packages, wobei ein Subpackage-Name vom übergeordneten Package-Namen durch einen Punkt getrennt wird.
Alle Package-Namen des SanFrancisco Business-Projekts von IBM beginnen z.B. mit com.ibm.sf, wobei Beispiele in einer Gruppe von Subpackages enthalten sind, die mit com.ibm.sf.samples beginnen.
simple vs. fully qualified name
Innerhalb eines Packages müssen die so genannten einfachen Namen (simple names) von Subpackages, Klassen und Interfaces eindeutig sein. Es können also keine zwei Mitglieder eines Packages den gleichen Namen tragen.
Beim vollen Namen (fully qualified name) wird der einfache Name um den vorangestellten vollen Package-Namen ergänzt.
Innerhalb des Subpackages java.awt gibt es z.B. ein Subpackage mit einfachem Namen image bzw. vollem Namen java.awt.image.
Damit ist u.a. ausgeschlossen, dass in java.awt ein weiteres Mitglied, z.B. eine Klasse mit Namen image existiert, wobei allerdings eine Klasse Image erlaubt ist, da Java zwischen Groß- und Kleinbuchstaben unterscheidet.
No-Name/unnamed/Default-Package
Obwohl »gute Sitte«, muss ein Package-Name nicht unbedingt angegeben werden. Fehlt die Angabe, so gehören alle angegebenen Klassen bzw. Interfaces zum default bzw. unnamed Package.
| Da weder die Anzahl der unnamed-Packages noch ihre Abbildung in ein reales Dateisystem festgelegt ist, sollten nur kleine Beispiele bzw. temporärer Code in einem default Package getestet werden. |
Für rein lokale Anwendungen kann die erwähnte Namenskonvention sicherlich ignoriert werden, für kommerzielle Anwendungen ist sie sehr hilfreich.10
Java-Code – genauer eine Übersetzungseinheit (Compilation-Unit) – besteht aus folgenden drei Elementen, die exakt in dieser Reihenfolge aufeinander folgen müssen:
Definieren und Importieren von Packages
| einer Package-Deklaration (optional) |
| Import-Anweisungen von anderen Packages (optional) |
| Klassen bzw. Interface-Definitionen (mindestens eine) |
Im folgenden Beispiel wird zuerst ein Package deklariert, gefolgt von zwei Importanweisungen (Erklärung siehe 1.2.3) und einer Klasse:
package com.company.samples.test; import java.awt.*; import java.math.BigInteger;
// Anschließend Klassen und Interfaces class Test1 { ... }
Durch das vorangestellte Schlüsselwort package wird das Package deklariert. Fehlt die Zeile, handelt es sich um das unnamed Package.
Klassen und Interfaces (nicht Subpackages!) im selben Package können sich gegenseitig über den einfachen Namen referenzieren. Darüber hinaus kann auch auf die fundamentalen Klassen und Interfaces von java.lang immer über den einfachen Namen zugegriffen werden.11
Um auf Klassen und Interfaces in anderen Packages zugreifen zu können, müssen diese dort public erklärt sein:
public class Test1 { ... }
Für den Zugriff auf public erklärte Klassen anderer Packages muss per Default der volle Name verwendet werden.
Import: Vereinfachter Zugriff auf Klassen anderer Packages
Da dies auf Dauer mühselig ist, kann mittels der Importanweisungen auf alle (oder nur eine) Klasse(n) bzw. Interface(s) des angegebenen Packages auch über den einfachen Namen zugegriffen werden.
Im Beispiel von 1.2.2 werden mit Hilfe des Schlüsselwortes import und des Metasymbols * 12 alle Klassen und Interfaces aus java.awt importiert, gefolgt vom Import genau einer Klasse BigInteger aus java.math.
Führt der Import allerdings zu Namenskollisionen, da in zwei Packages die einfachen Namen gleich sind, muss der Konflikt durch Angabe des vollen Namens beseitigt werden.13
Die Abbildung des Package-Konzepts in ein Verzeichnis- und Dateisystem ist natürlich vom Betriebssystem abhängig. In Java gibt es hierzu die folgenden Regeln, die aber nicht unmittelbar zum Sprachkern zählen und somit durchaus Ausnahmen zulassen:
Regeln zu Packages,
compilation unit, Klassen und Dateisystem
| 1. | Eine Übersetzungseinheit kann beliebig viele Klassen und Interfaces enthalten, wovon jedoch höchstens eine public erklärt werden kann. |
| 2. | Eine Übersetzungseinheit wird in einer Datei mit der Extension .java abgespeichert. |
| 3. | Der Name der Datei muss den Namen der Klasse bzw. des Interfaces haben, die public erklärt wird. Gibt es keine, kann der Name frei gewählt werden. |
| 4. | Nach dem Kompilieren der Übersetzungseinheit wird der Byte-Code jeder Klasse bzw. jedes Interfaces in eine separate Datei mit Namen der Klasse bzw. des Interfaces und der Extension .class abgespeichert. |
| 5. | Die Package-Hierarchie wird in eine entsprechende Verzeichnishierarchie aufgelöst, d.h., die .java- bzw. .class-Dateien befinden sich in Unterverzeichnissen, die den Package-Namen abbilden.14 |
Nur die vierte Regel muss von allen Java-Entwicklungsumgebungen eingehalten werden. Die anderen gelten für Entwicklungsumgebungen, die dateibasierend sind und zur Speicherung des Programmcodes keine Datenbank verwenden.15
Entsprechend der fünften Regel liegt im Beispiel 1.2.2 die Datei Test1.class des Packages com.company.samples.test bei Windows im Unterverzeichnis com\company\samples\test bzw. bei Unix im Unterverzeichnis com/company/samples/test.
Diese Regel besagt nicht, dass die .java-Dateien im selben Verzeichnis wie die zugehörigen .class-Dateien liegen müssen. Denn die Unterverzeichnisse können für .java- und .class-Dateien verschiedene Ausgangsverzeichnisse (Root-Verzeichnisse) haben. 16
Wäre die Klasse Test1 public erklärt, müsste die Übersetzungseinheit, die den Java-Code enthält, nach der dritten Regel in einer Datei Test1.java abgespeichert sein.
Dem Java-Interpreter, z.B. java, braucht nicht angegeben zu werden, wo sich die Klassen der Plattform (J2SE) bzw. der Extentions befinden.
Für alle anderen Klassen, die zur Applikation gehören, sind folgende Regeln zu beachten. Der Interpreter sucht
Regeln zur
Ausführung von Applikationen
| 1. | per default innerhalb des aktuellen Verzeichnisses die Klassen des unnamed Packages. |
| 2. | per default unterhalb des aktuellen Verzeichnisses in einem Unterverzeichnis, das dem Package-Namen entspricht, nach den Klassen des Packages. |
| 3. | unterhalb der Verzeichnisse, die entweder mittels der Umgebungsvariable CLASSPATH (des Betriebssystems) oder alternativ der Option –classpath (des Interpreters) gesetzt werden. |
Die flexibelste Methode ist wohl, mittels –classpath beim Aufruf die Unterverzeichnisse anzugeben. Die Angaben in CLASSPATH werden damit überschrieben bzw. ersetzt.
Aufruf einer
Applikation unter Windows
Werden z.B. nur Klassen aus dem Package j2buch.kap1 benötigt, wobei die Klasse j2buch.kap1.Test1 ausgeführt werden soll, dann kann die Klasse Test1 nach der zweiten Regel mittels
C:\JB\classes> java j2buch.kap1.Test1
aufgerufen werden. Dies setzt voraus, dass sich Test1 im Unterverzeichnis C:\JB\classes\j2buch\kap1 befindet.
Nach der dritten Regel kann Test1 auch mittels
java –classpath C:\JB\classes j2buch.kap1.Test1
aus jedem beliebigen Verzeichnis aufgerufen werden.
Eine weitere Möglichkeit ist die, alle benötigten Klassen in eine JAR-Datei test.jar zu packen, die Test1 als Main-Class-Attribut enthält, und diese mittels der Option –jar auszuführen, z.B.:
java –jar C:\JB\test.jar
Abbruch der normalen Programmausführung durch Exceptions
Fehler, die nicht durch den Compiler abgefangen werden können, d.h. erst bei der Ausführung in der JVM auftreten, werden durch »Auslösen einer Ausnahme« (throwing an exception) von der JVM signalisiert.
Diese Ausnahme kann dann entweder im Programm durch einen entsprechenden Exception-Handler behandelt, d.h. abgefangen werden, oder die JVM bricht die Programmausführung mit einer detaillierten Fehlermeldung (Art/Ort der Exception) ab.
Acht so genannte primitive Datentypen sind Bestandteil der Sprache, werden also vom Compiler direkt erkannt. Hierzu zählen ein logischer (boolean) Typ, ein Zeichentyp sowie sechs numerische Typen.
Daneben gibt es noch Referenz-, Klassen- und Array-Typen.
Zeichen und ganze Zahlen werden unter dem Begriff integraler Typ, float und double unter Floating-point-Typ zusammengefasst.
Die primitiven Datentypen sind in der nachfolgenden Tabelle kurz zusammengestellt:
| Type | Wertebereich | Default-Wert | Bit-Größe | Anmerkung |
| boolean | true, false | false | 1 | |
| char | \u0000 .. \uFFFF | \u0000 | 16 | unsigned |
| byte | -27 .. 27 -1 | 0 | 8 | |
| short | -215 .. 215 -1 | 0 | 16 | |
| int | -231 .. 231 -1 | 0 | 32 | |
| long | -263 .. 263 -1 | 0 | 64 | |
| float | Float.MIN_VALUE .. Float.MAX_VALUE, Float.NaN, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY |
0.0 | 32 | Darstellbare Werte: ±1.402e-45 .. ±3.402e38 |
| double | Double.MIN_VALUE .. Double.MAX_VALUE, Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY |
0.0 | 64 | Darstellbare Werte: ±4.94e-324 .. ±1.79e308 |
Gegenüber C/C++ gibt es einen eigenen boolean-Typ, der eine Umwandlung von/nach Integer nicht zulässt, d.h., 0 oder 1 werden als Ersatz für false und true nicht akzeptiert (siehe 1.5).
Alle numerischen Typen sind signed, d.h. erlauben Vorzeichen und haben eine fest definierte Größe, unabhängig von der Maschine bzw. dem Betriebssystem.
Nachfolgend die wichtigsten Regeln zu Zahlen-Operationen:
Over- bzw. Underflow bei Integer-Werten
| 1. | Bei Integer-Arithmetik stellt weder der Compiler noch die JVM sicher, dass der Wertebereich ausreicht. Ist das Ergebnis außerhalb des Wertebereichs, ist es schlichtweg falsch. |
| 2. | Nur eine Division durch Null bzw. Modulo Null erzeugt eine ArithmeticException (Ausnahme siehe 1.2.5). |
Wertüberschreitung bei Floating-Point-Typen
| 3. | Floating-Point-Typen kennen die Werte »nicht definiert«, d.h. NaN (Not-a-Number) sowie ±Unendlich, d.h. NEGATIVE_INFINITY bzw. POSITIVE_INFINITY, die Überschreitungen des Wertebereichs abfangen. |
| 4. | Eine Operation mit einem »nicht definierten« Wert ergibt kein Ergebnis im normalen Wertebereich. |
| 5. | Eine Operation mit NaN führt immer zum Ergebnis NaN. |
| Berechnung von | Ergebnis | Anmerkung |
| -0.0 / 0.0 | NaN | Undefiniert, d.h. NaN |
| 1.0 / -0.0 | NEGATIVE_INFINITY | es gibt eine negative Null |
| (0.0 / 0.0) * (1.0 / 0.0) | NaN | NaN bleibt immer bestehen |
| 0.0 == -0.0 | true | -0.0 ist gleich 0.0 17 |
| -0.0/0.0 == -0.0/0.0 | false | NaN ist mit nichts gleich18 |
Aufbauend auf einem für Java erlaubten Zeichensatz – dem Unicode – besteht Java-Source-Code aus folgenden atomaren Elementen:
Bestandteile eines
Java-Programms
| Whitespaces (Zwischenräume) |
| Comments (Kommentare) |
| Identifier (Bezeichner bzw. Namen) |
| Separators (Trennzeichen) |
| Literals (Literale) |
| Keywords (Schlüsselworte) |
| Operators (Operatoren) |
Java verwendet als erlaubten Zeichensatz Unicode, womit es sich bereits von C/C++ in seinen Grundlagen unterscheidet.
Eine Sprache für Internet-Applikationen muss neben dem Standardzeichensatz für Englisch auch andere Zeichensätze wie Arabisch, Katakana, Griechisch etc. darstellen können.
Da Unicode-Zeichen 16-Bit codiert sind, können prinzipiell 65.536 (=216 ) Zeichen dargestellt werden. Die Unicode-Tabelle ist in Bereiche für verschiedene Sprachen eingeteilt.
Kompatibel zu
ASCII, ISO-Latin-1
Im Tabellenbereich von 0...127 bzw. 0...255 ist Unicode identisch mit dem ASCII- bzw. ISO-Latin-1-Zeichensatz. Für diese Zeichen kann man also das oberste der beiden Bytes ignorieren.19
Da man selten eine Tastatur mit 65.536 Tasten zur Eingabe benutzt, gibt es eine spezielle Escape-Sequenz für Unicode-Zeichen (siehe 1.4.6).
Java ist frei formatierbar
(free-form)
Die Anzahl Leerstellen, Tabulatoren oder Zeilenumbrüche (Linefeed bzw. Carriage-Return) zwischen Token (Symbolen) können beliebig gewählt werden, sie werden ohnehin bei der lexikalischen Analyse durch den Compiler entsorgt.
Es gibt drei Arten von Kommentaren, die von C++ adaptiert wurden:
| einzeilige: |
i=i+1; // oder i++; oder ++i; oder i+=1;
| mehrzeilige: |
/* Arrays können im C/C++ oder im Java-Stil deklariert werden */ int iarr[]; byte[] barr;
| mehrzeilige Kommentare, die mit /** starten. Hieraus kann mit Hilfe von javadoc eine HTML-Dokumentation generiert werden. javadoc versteht einfache HTML-Formatierungen sowie mit @ beginnende Tags, d.h. Hinweise auf Text mit festgelegter Bedeutung: |
/** money factory class – a Pattern @author BG @version 0.9 <U>code name</U>: <B>W98 Green Banana</B> */
Ein Identifier ist ein vom Programmierer wählbarer Name für Variablen, Marken, Methoden und Klassen mit folgenden Restriktionen:
| Das erste Zeichen muss ein (Unicode) Buchstabe, ein Unterstreichungsstrich _ oder ein Währungssymbol $, £ bzw. ¥ sein. |
| Ab dem zweiten Zeichen sind noch zusätzlich Ziffern erlaubt. |
| Schlüsselwörter (siehe Tabelle 1.5) sind nicht erlaubt. |
Nachfolgend einige zulässige (erste Zeile) bzw. unzulässige Identifier:
_i__ £4 äß _123 Õre
2i ab/1 B-1 ab! a%b #a goto
Java verwendet neun Zeichen als Trennzeichen mit besonderer Bedeutung, wobei der häufigste Separator wohl das Semikolon ist, das Anweisungen abschließt. Die restlichen sind verschiedene Arten von Klammern, Komma und Punkt:
; , . ( ) { } [ ]
Im Gegensatz zu einem Identifier repräsentiert ein Literal einen konstanten Wert eines bestimmten Typs. Literale können in Ausdrücken, Zuweisungen und als Argumente beim Methoden-Aufruf verwendet werden.
Es gibt Literale zu den primitiven Typen, zur Klasse String, das null-Literal sowie noch Literale der Klasse Class, die im Folgenden kurz vorgestellt werden:
Die einzigen Literale vom Typ boolean sind true und false.
boolean ok = false;
Zeichen-Literale müssen immer in Hochkommata eingeschlossen werden.
Hexadezimale Eingabe von Zeichen
Für Zeichen, die nicht mittels Tastatur eingegeben werden können, wird die Unicode-Tabellenposition in Form von (maximal) vier hexadezimalen Ziffern 0..F (Basis 16) mit dem Präfix \u eingegeben.20 0 Die Hex-Ziffern können groß A..F oder klein a..f geschrieben werden.
Für die Eingabe von speziellen Zeichen gibt es in Java wie C/C++ die nachfolgenden Escape-Codes:
| Escape-Code | Zeichen |
'\'' |
Hochkomma (single quote) |
'\"' |
Anführungszeichen (double quote) |
'\\' |
Schrägstrich nach hinten (backslash) |
'\b' |
Schritt zurück (backspace) |
'\t' |
Tabulator (tab) |
'\n' |
Zeilenschaltung (linefeed/newline) |
'\r' |
Wagenrücklauf ( carriage return) |
'\f' |
Seitenschaltung (form feed) |
Mit Hilfe des Escape-Codes lassen sich Unicode-Zeichen eingeben:
char c1='\u00c4'; System.out.print(c1=='Ä'); // :: true
Die als integrale Literale bezeichneten ganzen Zahlen vom Typ int oder long können dezimal, hexadezimal oder sogar oktal eingegeben werden, wobei die hexadezimale Eingabe mit 0x oder 0X und die oktale mit 0 beginnen muss.
Alle Literale sind per default vom Typ int. Wird an das Literal (das Suffix) l oder L angehängt, ist es vom Typ long.
int i= 0x10+10+010; // 16+10+8 System.out.print(i+" "+0x10L); // :: 34 16
Ein Floating-Point-Literal vom Typ float oder double ist eine Zahl mit Dezimalpunkt und/oder einem angehängten Exponent und/oder einem Suffix f oder F für float bzw. d oder D für double. Fehlt das Suffix, ist das Literal per default vom Typ double.
Der Exponent beginnt mit E oder e, gefolgt von einem optionalen Vorzeichen und dem Exponenten (möglicher Wertebereich siehe Tabelle 1.2). Damit sind alle nachfolgenden Zahlen gültige Floating-Point-Literale:
0. .0 +1f -6e+1 7.1E-1F
Dabei ist die letzte Zahl gleich 0.71 und vom Typ float .
Strings sind Zeichensequenzen und werden immer in Anführungszeichen (double quotes) gesetzt. Innerhalb des Strings können die einzelnen Unicode-Zeichen (wie bei Zeichen-Literalen) per Escape-Code oder hexadezimal eingegeben werden.
System.out.print("\"\u00c4\tÖ\tÜ\"\nNeue Zeile");
gibt (unter Windows) zwei Zeilen auf der Konsole aus:21
"Ä Ö Ü" Neue Zeile
String-Literale sind keine Werte vom primitiven Typ, sondern sind eine kurze elegante Art, Objekte der Klasse String anzulegen.
Somit können auf String-Literale alle Instanz-Methoden der Klasse String angewendet werden, wie z.B. die Methode length(), die die Länge des Strings zurückgibt:
System.out.print("".length()); // :: 0
null: Kein Objekt referenziert
Neben den Variablen vom primitiven Typ gibt es noch Referenzvariablen, die auf Objekte von Klassen zeigen. Um anzuzeigen, dass eine Referenzvariable auf kein Objekt zeigt, wird das Literal null (ein Schlüsselwort) verwendet.
String s= null; // s zeigt auf kein String-Objekt
System.out.println(s); // :: null System.out.println(s.length()); // Exception
Die letzte Code-Zeile führt nicht zu einem Compilerfehler, sondern bei Ablauf des Programms zu einer NullPointerException, da von einem nicht existierenden Objekt keine Länge abgefragt werden kann.
Class-Literale
enthalten Klassen-Informationen
Die Klasse Class enthält Objekte zu allen Datentypen (inkl. sich selbst). Durch Anhängen von .class hinter einem beliebigen Typ kann man ein zu diesem Typ zugehöriges Literal vom Typ Class schaffen.
Class intTyp= int.class;
Dies kann u.a. zur Untersuchung von Klassen verwendet werden. Das nachfolgende Code-Fragment gibt alle Methoden der Klasse String aus (die Klasse Method ist aus Package java.lang.reflect zu importieren):
Class stringType= String.class;
Method[] methodArr= stringType.getMethods();
for (int i=0; i<methodArr.length; i++) System.out.println(methodArr[i]);
Keyword:
Identifier mit fester Bedeutung
Schlüsselwörter sind vordefinierte Identifier mit einer speziellen Bedeutung. Sie dürfen deshalb auch nicht als normale Identifier verwendet werden.
Es gibt zurzeit 59 Schlüsselwörter, die anhand ihrer Funktion in Kategorien eingeteilt werden können. Zum Beispiel gibt es für Bedingungen die Schlüsselworte if, else und switch und eine Gruppe von elf Schlüsselwörtern, die zwar reserviert (®) sind, aber nicht benutzt werden.
| abstract | const ® | float | int | protected | throw |
| boolean | continue | for | interface | public | throws |
| break | default | future ® | long | rest ® | transient |
| byte | do | generic ® | native | return | true |
| byvalue ® | double | goto ® | new | short | try |
| case | else | if | null | static | var ® |
| cast ® | extends | implements | operator ® | super | void |
| catch | false | import | outer ® | switch | volatile |
| char | final | inner ® | package | synchronized | while |
| class | finally | instanceof | private | this |
Operatoren sind spezielle Symbole für Operationen auf Operanden. Je nach Anzahl der Operanden, auf die der Operator angewendet wird, unterscheidet man unäre und binäre Operatoren sowie einen ternären Operator.
Java kennt 37 Operatoren, die anhand ihrer Funktion klassifiziert werden, wie z.B. arithmetische oder logische Operatoren. Operatoren werden in Kapitel 2 behandelt.
Numerische Typen können bei Bedarf ineinander konvertiert werden. Selbst der Typ char ist im Prinzip eine nicht negative Zahl zwischen 0..216 -1 und kann daher in eine ganze Zahl umgewandelt werden.
Keine Konvertierung von/nach boolean
Im Gegensatz zu C/C++ gibt es keine Konvertierung eines primitiven Typs von/nach boolean.
Konvertierung: widening vs.
narrowing
Es gibt zwei Arten der Konvertierung:
| die widening Conversion, die Konvertierung in einen Typ mit einem größeren Wertebereich (siehe Tabelle 1.2) |
| die narrowing Conversion, die Konvertierung in einen Typ mit einem kleineren Wertebereich |
Eine widening Conversion wird vom Compiler automatisch, d.h. implizit durchgeführt. Nachfolgend wichtige Regeln zur dieser Art der Konvertierung:
Initialisierung bzw. Zuweisung bei byte, short und char
| 1. | Literale vom Typ char oder int können Variablen vom Typ byte, short oder char zugewiesen werden, sofern sie im erlaubten Wertebereich liegen (wenn nicht, führt dies zu einem Fehler bei der Kompilierung, kurz C-Fehler). |
| 2. | Ansonsten kann eine implizite Konvertierung nur in Richtung der Pfeile (siehe Abb. 1.2) erfolgen. |
| 3. | Eine automatische Konvertierung findet – sofern notwendig – bei der Übergabe von aktuellen Argumenten an Methoden und bei Zuweisungen statt. |
Eventueller Verlust an Genauigkeit
| 4. | Bei Konvertierung einer int oder long nach Floating-Point kann nicht der Erhalt der Genauigkeit garantiert werden. |
|
Nach der zweiten Regel kann also der Typ byte vom Compiler z.B. in ein long konvertiert werden, aber nicht umgekehrt.
Die Typen byte und short können nicht automatisch in char sowie char nicht in byte und short umgewandelt werden.
byte b= 'a'; // siehe Ausnahme: wie b= 97;
char c1= 97; // siehe Ausnahme: wie c= 'a';
char c2= b; // C-Fehler: kein widening conversion
int i= 1L; // C-Fehler: kein widening conversion
b= i; // C-Fehler: kein widening conversion
b= 128; // C-Fehler: siehe Ausnahme
Wird nach der vierten Regel z.B. ein int (32 Bit) in eine float (32 Bit) umgewandelt, so bedeutet dies evtl. einen Verlust an Genauigkeit, da float, bedingt durch den zusätzlichen Exponenten, eine maximale Genauigkeit von nur sieben Dezimalziffern hat:
int i=1234567891; float f= i; System.out.println(f); // :: 1.23456794E9
Operationen mit unterschiedlichen Typen
Nehmen zwei unterschiedliche primitive Typen (siehe Abb. 1.2) an einer Operation teil, werden sie vom Compiler vor der Operation auf einen gemeinsamen Typ konvertiert.
Dies nennt man auch numeric Promotion und läuft bildlich nach folgender Regel ab:
| Bei der Konvertierung wird der Typ gewählt, der am weitesten rechts in der Abbildung 1.1 steht, wobei man mit int beginnen muss. |
Regel für die
implizite numerische Umwandlung
Übersetzt in die vier »normalen« Regeln heißt das:
| 1. | Ist einer der Operanden vom Typ double, wird der andere in ein double konvertiert. |
| 2. | Ansonsten: Ist einer der Operanden vom Typ float, wird der andere in ein float konvertiert. |
| 3. | Ansonsten: Ist einer der Operanden vom Typ long, wird der andere in ein long konvertiert. |
| 4. | Ansonsten: Beide Operanden werden in ein int konvertiert. |
Bei dem nachfolgenden Code-Fragment führen i*i bzw. i*i*1. zu einem falschen Ergebnis.23 Die letzte Berechnung ist nach der ersten Numeric-Promotion-Regel allerdings wieder richtig:
int i= 1000000;
Die Reihenfolge der Operationen entscheidet
System.out.println(i*i); // :: -727379968
System.out.println(i*i*1.); // :: -7.27379968E8
System.out.println(1.*i*i); // :: 1.0E12
Eine den Wertebereich einengende numerische Konvertierung ist nicht implizit, sondern nur explizit möglich.
Dazu stellt man einfach den gewünschten Typ in Klammern vor den zu konvertierenden Wert (Cast).
Cast: Explizites Konvertieren ohne Gewähr
Es ist allein die Aufgabe des Programmierers sicherzustellen, dass die Daten beim Cast nicht falsch werden, da z.B. im integralen Bereich die obersten Bits einfach abgeschnitten werden.
int i= 0xFFFF; // i hat den Wert 65535
short s= (short) i; // s hat den Wert –1
byte b1= 0, b2= 1, b3;
b3= b1+b2; // C-Fehler 24
b3= (byte)(b1+b2); // die 2. Klammer ist notwendig
float f= 0.0; // C-Fehler, richtig: f= 0.0F
Ein Floating-Point-Typ wird in einen integralen Typ durch Abschneiden der Stellen hinter dem Dezimalpunkt umgewandelt:
System.out.println((int)10.9999); // :: 10
Java kennt zwei Arten von Variablen:
Member bzw. Felder vs. lokale Variable
| Member-Variablen, d.h. Variablen, die als Mitglieder bzw. Felder (fields) einer Klasse deklariert werden, |
Statische vs. Instanz-Variable
| lokale Variablen25 (auch automatische Variable genannt), die lokal in einer Methode deklariert sind. |
Bei Feldern unterscheidet man zwischen
| statischen Variablen, auch Klassen-Variablen genannt (static bzw. Class Variable), die es unabhängig von den Objekten – den Instanzen von Klassen – nur einmal pro Klasse gibt. Sie erkennt man an dem vorangestellten Schlüsselwort static. |
| Instanz-Variablen (Instance Variable), die es für jedes Objekt gibt. |
Im Folgenden wird der Begriff Feld im Deutschen für Member-Variablen verwendet.
Für die Initialisierung gelten folgende Regeln:
Ablauf und Werte der Initialisierung
| 1. | Felder sowie die Elemente eines Arrays werden immer automatisch initialisiert, logische Variablen mit false, primitive mit 0 und Referenz-Variablen mit null. |
| 2. | Die Initialisierung der statischen Variablen findet beim Laden der Klasse statt, bei Instanz-Variablen diyrekt nach der Erschaffung des Objekts26 (d.h. vor der Ausführung des Codes im Konstruktor). |
| 3. | Lokale Variablen werden nicht automatisch initialisiert. |
| 4. | Die Verwendung einer nicht initialisierten lokalen Variable führt zu einer Fehlermeldung des Compilers. |
Die nachfolgende Klasse InitTest enthält eine Klassen- und eine Instanz-Variable sowie eine Objekt-Methode.
In der Methode foo() wird ein int-Array und eine nicht initialisierte lokale Variable i deklariert, die bei Verwendung zu einem Fehler beim Kompilieren führt.
class InitTest { static double d; // class field String s; // instance field
void foo() { // Nonsens Name int i; // lokale Variable int[] iarr= new int[2]; // lokales int-Array System.out.println(iarr[0]); // :: 0 System.out.println(d+" "+s); // :: 0.0 null System.out.println(i); // C-Fehler: 4. Regel } }
Jeder Programmierer entwickelt über kurz oder lang seinen eigenen Stil beim Codieren. Solange er isoliert arbeitet: kein Problem. Kritisch wird es erst, wenn er im Team arbeitet, sein Code von anderen verstanden und später modifiziert werden muss.
Hier kann Software-Engineering viel von älteren Ingenieurwissenschaften lernen, die Normen und Konventionen zur Conditio sine qua non für Ingenieure machen.
Deshalb soll das Kapitel mit einigen einfachen, aber wichtigen Namenskonventionen beendet werden, die das Leben in einer Java-Gemeinschaft sicherlich angenehmer gestalten:
Konventionen
bei der Wahl der Namen
| Für die Ausgabe ist Unicode eine »Offenbarung«. Dies bedeutet aber nicht, dass man nun Identifier in Deutsch, Spanisch oder Arabisch schreiben soll. Der ASCII-Zeichensatz ist immer die bessere Wahl, zumal für Klassen, die gleichzeitig auch Dateinamen sind.27 |
| Besteht ein Identifier aus mehr als einem Wort, so beginnt jedes »innere« Wort mit einem Großbuchstaben (Ausnahme: Package). |
Der gesamte Name wird in Kleinbuchstaben geschrieben und beginnt – sofern Eindeutigkeit erforderlich ist – mit dem umgekehrten Internet-Domain-Namen (siehe 1.2.1).
Eine Klasse beginnt immer mit einem Großbuchstaben. Klassennamen enthalten in der Regel Substantive.28
MeineErsteKlasse XMLUtility
Ein Interface unterliegt aufgrund seiner Ähnlichkeit zu einer Klasse der gleichen Konvention. Damit lässt sich ein Interface allerdings aufgrund des Namens nur selten von einer Klasse unterscheiden.29 9
Dies stört nicht unbedingt, sofern man z.B. mit UML-Klassendiagrammen (siehe Kapitel 4, Modellierung und UML) arbeitet.
Alternativ kann man nach MS-MFC30 -Konvention jeden Namen mit dem Präfix I versehen.
IPerson IProduct IUnknown
Eine Konstante (eine static final deklarierte Variable) vom primitiven Typ wird durchgängig groß geschrieben, wobei Wörter durch einen Unterstreichungsstrich getrennt werden:31
PI DEM_EURO_EXCHANGE_RATE
Eine Methode, Variable oder ein Parameter beginnt immer mit einem Kleinbuchstaben.
Eine Methode sollte in der Regel mit einem Verb beginnen.
Der Name eines Parameters oder einer lokalen Variablen kann durchaus nur aus einem Buchstaben bestehen, sofern die Bedeutung klar ist.
isEmpty() setNewName(String s) firstName
Konventionen sind nur ein erster Schritt. Ein weiterer ist die Suche nach einem passenden Namen für eine Methode. 32
| Findet man keinen besseren Methodennamen als setzeDaten(), liefereEierWolleMilchFleisch() oder fubar(), sollte man sein Klassen-Design überdenken. Vielleicht gibt es ja doch bessere und für den Benutzer einsichtigere Methoden. |
Das Wort »Java« steht für eine Technologie, die auf drei Säulen ruht, Sprache, Plattform und JVM. Klassen und Interfaces werden in Packages organisiert, die über mehrere Übersetzungseinheiten verteilt sein können.
Klassen kapseln Daten und Code und können mit Hilfe der Methode main() ausführbar gemacht werden. Die JVM lädt Klassen erst bei Bedarf und reagiert auf Laufzeitfehler mit Exceptions.
Unicode, Schlüsselwörter sowie acht primitive Datentypen inklusive ihrer Literale bilden das Fundament der Sprache. Numerische Datentypen können implizit oder explizit ineinander konvertiert werden.
Obwohl es auch zu Strings Literale gibt, zählen sie nicht zu den primitiven Datentypen, sondern sind Instanzen bzw. Objekte der Klasse String.
Felder einer Klasse sowie Array-Elemente werden automatisch initialisiert, nicht initialisierte lokale Variablen werden vom Compiler erkannt.
Zu Java gibt es akzeptierte Namenskonventionen, die man durchaus verletzen darf, sofern man Viren programmiert.
Zu jeder Frage können jeweils ein oder mehrere Antworten bzw. Aussagen richtig sein.
| 1. | Welche Aussagen sind zu der ausführbaren Klasse Startable richtig? |
D: Startable kann in einer Compilation Unit mit Namen Test.java enthalten sein.
| 2. | Welche main()-Methode kann als Startmethode für eine Applikation verwendet werden? |
| 3. | Welche Kommentare sind korrekt gesetzt, sodass der Code fehlerfrei kompiliert wird? |
| 4. | Welche Identifier sind gültig? |
| 5. | Welche Aussagen sind zu folgendem Code-Fragment richtig? |
char c= '1'; res= 2*c; System.out.println(res);
E: Die zweite Codezeile ist fehlerhaft, egal von welchem Typ res ist.
| 6. | Welche Aussagen sind zur Klasse InitTest richtig? |
class InitTest { static boolean ok; String s; void test() { int i; //System.out.println(i); } }
C: Bei Entfernen der Kommentarsymbole // wird InitTest nicht kompiliert.
| 7. | Welche Aussagen sind richtig? |
B: Die Anweisung char c= 65000; wird ohne Fehler kompiliert.
C: Die Anweisung char c= '/u0100'; wird ohne Fehler kompiliert.
| 8. | Was sind gültige Literale? |
| 9. | Welche Aussagen sind zu folgendem Code-Fragment richtig? |
int i= (int) (1./0.); System.out.println(i);
B: Bei der Ausführung wird eine Exception aufgrund der Division durch Null generiert.
| 10. | Welche Aussagen sind zu folgendem Code-Fragment richtig? |
String s= "1"; int i= (int) s; System.out.println(i);
B: Bei der Ausführung wird eine Exception aufgrund des unerlaubten Casts in der zweiten Zeile generiert.
1 Dies ist unweigerlich mit Kompromissen verbunden. Deshalb wird Java-Quereinsteigern empfohlen, parallel eine Einführung mit möglichst vielen Beispielen zu lesen (siehe Literaturhinweise).
2 API: Applications Programming Interface
3 deprecated: missbilligt, d.h. ersetzt und verbessert durch Neueres.
4 JIT: Just In Time Compilation, Hotspot Technology etc.
5 J2EE (Enterprise Edition), J2ME (Micro Edition), konfigurierbare virtuelle Maschine, KVM etc.
6 Für die korrekte Zusammenstellung und Distribution von Klassen werden deshalb Deployment-Werkzeuge mit den Entwicklungsumgebungen angeboten.
7 Der dot-operator ist an sich kein Operator, sondern ein Separator (siehe 1.4.5).
8 Sehr gebräuchlich ist die Bezeichnung Java App für eine Applikation oder ein Applet.
9 Allerdings nur, wenn StartableClass.class mittels classpath(-Option) auch gefunden wird (siehe auch 1.2.5) und die JVM diese Art von Humor versteht.
10 Womit auch public deklarierte Klassen bzw. Interfaces ausgeschlossen sind, da diese ja gerade zur Benutzung in anderen Packages vorgesehen sind (ohne Package-Name ist die Verwendung von public-Klassen vom aktuellen Verzeichnis abhängig).
11 Dies bedeutet den impliziten Import von java.lang.
12 Bedeutung von *: Sequenz von null oder mehr Zeichen, speziell hier aller Klassennamen.
13 Punkte im Package-Namen stellen Verzeichnistrenner des jeweiligen Betriebssystems dar.
14 Punkte im Package-Namen stellen Verzeichnistrenner des jeweiligen Betriebssystems dar.
15 Die fünfte Regel ist selbst für dateibasierende Entwicklungsumgebungen recht vage.
16 Bei JBuilder 4 liegen Source- und Class-Dateien in unterschiedlichen Verzeichnissen.
17 Mit dem Operator == wird auf Gleichheit hin geprüft.
18 Auch nicht mit sich selbst. Der Test auf Double.NaN erfolgt z.B. mittels:
Double.isNaN(0./0.).
20 Alternativ gibt es noch eine oktale Eingabe von \000 bis \377 für die
ersten 256 Zeichen.
21 Eine mittels \n hart kodierte »neue Zeile« ist betriebssystemabhängig.
Besser ist println().
22 Sie werden hier nur der Vollständigkeit halber erwähnt. Zum Verständnis des
Kapitels sind sie nicht notwendig.
23 Gemäß Regel zur Integer-Arithmetik (siehe 1.3) wird die Überschreitung des Wertebereichs bei int und long nicht erkannt. Die Operationen werden von links nach rechts ausgeführt (siehe Kapitel 2, Operatoren).
24 Nach der vierten Regel wird mit byte, char und short nicht gerechnet, sondern mit int. Das Ergebnis ist also vom Typ int und muss explizit per Cast in Typ byte oder char umgewandelt werden (siehe zu arithmetischen Operationen auch Abschnitt 2.2).
25 Auch automatische Variable genannt
26 Dvor der Ausführung des Codes im Konstruktor
27 Wen das nicht überzeugt, der sollte einfach Excel-Tabellen mit (deutschem) VBA-Code für europäische Dependancen erstellen.
28 Zusätzlich sollte das Zeichen $ nicht im Klassennamen verwendet werden, da er zur Separierung von Namen innerer Klassen verwendet wird.
29 Außer wenn der Name Rollencharakter hat, wie z.B. Cloneable.
30 MFC: Microsoft Foundation Class (Library).
31 Bei konstanten Referenz-Variablen hängt die Konvention von der Semantik ab: Ist das Objekt, auf das die Referenz zeigt, auch konstant, d.h. immutable, so gilt auch hier die Großschreibregel.
32 Zu foo() bzw. fubar() siehe auch www.netmeg.net/jargon/terms/f/foo.html.
| << zurück |
| |||||
| |||||
| |||||
| |||||
| |||||
| |||||
| |||||
| |||||
Copyright © Galileo Press GmbH 2001 - 2002
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken und speichern. 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.
Die Veröffentlichung der Inhalte oder Teilen davon bedarf der ausdrücklichen schriftlichen Genehmigung von Galileo Press. Falls Sie Interesse daran haben sollten, die Inhalte auf Ihrer Website oder einer CD anzubieten, melden Sie sich bitte bei: stefan.krumbiegel@galileo-press.de