Kapitel 2 Operatoren
Operatoren sind die grundlegenden Bausteine von Ausdrücken in einer Sprache. Sie sind typgebunden, haben Vorrangregeln und Assoziativität.
Ziel dieses Kapitels ist es, alle Operatoren, ihre Regeln und Seiteneffekte anhand von kurzen Beispielen vorzustellen.
Ausdruck
Kombiniert man Variablen und Literale mit Operatoren, so erhält man – sofern sie gültig sind – einen Ausdruck (Expression). Einen Ausdruck erkennt man daran, dass er auf der rechten Seite einer Zuweisung stehen kann:
var = expression;
Anweisung
Diese Zuweisung ist wiederum eine einzelne Anweisung (Statement) und wird durch ein Semikolon beendet. Anweisungen sind Kommandos, welche von der JVM ausgeführt werden.
Die Operatoren bilden somit die Bausteine für Ausdrücke und Anweisungen. Die meisten Operatoren hat Java in Syntax und Semantik von C/C++ übernommen. Allerdings gibt es auch bei diesen subtile Unterschiede zu C/C++, da Java-Operationen in der Wirkung eindeutig und unabhängig von der Maschine definiert sein müssen.
Pointer vs. Referenz
Java kennt daher auch keine Pointer bzw. Pointer-Arithmetik, das Äquivalent heißt Referenz und ist ein opaker Pointer (siehe Abschnitt 2.3).
2.1 Überblick
In der nachfolgenden Tabelle wird ein Überblick über alle Java-Operatoren gegeben, gruppiert nach Typ und Wirkung.
Vorrang
Stehen mehrere Operationen ohne Klammern in einem Ausdruck, so regelt der Vorrang (V) die Reihenfolge, in der die Operationen ausgeführt werden. Durch entsprechende Klammerung kann natürlich der Vorrang geändert werden.
Assoziativität
Die Assoziativität (A) bestimmt die Richtung, in die der Operator wirkt. Bei binären Operationen wird mit Ausnahme der Zuweisung von links nach rechts (L) gerechnet. Bei unären Operationen steht der zugehörige Operand immer rechts (R) vom Operator, wobei der Inkrement-/Dekrement-Operator auch auf linke Operanden wirkt.
Tabelle 2.1 Überblick über die Operatoren, klassifiziert nach Arten
Art Operator |
Wirkung |
Anmerkung |
A |
B |
Beispiel |
Arithmetisch |
++,-- |
Inkrement
Dekrement |
unär, postfix,
unär, präfix |
L
R |
1
2 |
x++
--i |
|
+,- |
Plus, Minus |
unär, präfix |
R |
2 |
+c, -x |
|
*,/ |
Multiplikation, Division |
|
L |
4 |
7F*i
i/3.1 |
|
% |
Modulo |
auch floating-p. |
L |
4 |
-3.8%1.7 |
|
+,- |
Addition, Subtraktion |
+ wird auch für Strings benutzt |
L |
5 |
i+0xA0FF |
String |
+ |
Aneinanderfügen |
neues S.-Objekt |
L |
5 |
s+"ende" |
Vergleich |
<,<=
>,>= |
Kleiner (-gleich), Größer (-gleich) |
Resultat ist boolean |
L |
7 |
'\u003f'<c 2>='a' |
|
==
!= |
Gleich,
Ungleich |
|
L |
8 |
obj1==obj2 2.0!='a' |
Typvergleich |
instanceof |
nur für Objekte |
L |
9 |
|
Logisch
(Boolean) |
! |
NOT |
unär |
R |
2 |
!false |
|
& |
AND |
|
L |
9 |
a&b |
|
^ |
XOR |
äquivalent zu != |
L |
10 |
a^b |
|
| |
OR |
|
L |
11 |
a|b |
|
&& |
short-circuit AND |
Auswertung des
2. Operanden nur wenn nötig |
L |
12 |
a&&b |
|
|| |
short-circuit OR |
|
L |
13 |
a||b |
Bit-Manipulation |
~ |
Invertieren |
|
R |
2 |
~-1 |
|
<<,>> |
Links/Rechts schieben |
Negativ links:
mit 1 auffüllen |
L |
6 |
-1>>30 |
|
>>> |
Rechts schieben |
mit 0 auffüllen |
L |
6 |
-1>>>30 |
|
& |
AND |
|
L |
9 |
-1&3 |
|
^ |
XOR |
|
L |
10 |
-1^3 |
|
| |
OR |
|
L |
11 |
-1|3 |
Ternär |
? : |
1. Operand true: Ergebnis 2. Operand, false: Ergebnis 3. Operand |
R |
14 |
i!=0?1/i:i |
Zuweisung |
=
+=,-=
*=,/=
%=
&=,|=,^=,<<=
>>=
>>>= |
Zuweisung
in Kombination mit binären arithmetischen Operatoren
in Kombination mit binären
Bit- und Shift-Operatoren |
R |
15 |
i=8
i>>=2
i&=3 |
new-Operator
Bei Anlage von Objekten kann new als Operator angesehen werden, wobei es sich bei
new ClassType ( arguments )
syntaktisch um einen Ausdruck, eine Class Instance Creation Expression handelt, die eine Referenz zu einem neu erschaffenen Objekt der Klasse ClassType liefert. Dieser new-Ausdruck wird ausgeführt, bevor das Objekt in einer Operation verwendet wird (Beispiele siehe Ende Abschnitt 2.1).
Separatoren, Cast, Methoden-Aufruf
Separatoren wie der .Dot-Operator, die eckigen Klammern [] für Array-Elementzugriffe, Methoden-Aufruf (arguments) sowie Typ-Konvertierung (type) mittels Cast sind keine Operatoren, müssen aber ebenfalls in die Vorranghierarchie eingeordnet werden.
Dies führt zu einer erweiterten Vorrang-Tabelle:
Tabelle 2.2 Operatoren und Separatoren, geordnet nach Vorrang
Vorrang
(precedence) |
Operatoren, Separatoren,
Methoden-Aufruf, Cast |
Hinweis |
1 |
new . [] (arguments) ++ -- |
++,-- postfix |
2 |
++ -- + - ~ ! |
unär |
3 |
(type) |
Cast |
4 |
* / % |
|
5 |
+ - |
binär |
6 |
<< >> >>> |
|
7 |
< <= > >= instanceof |
|
8 |
== != |
|
9 |
& |
|
10 |
^ |
|
11 |
| |
|
12 |
&& |
|
13 |
|| |
|
14 |
?: |
|
15 |
= += -= *= /= %= &= |= ^= <<= >>= >>= >>>= |
|
Nachfolgend wird Vorrang und Assoziativität anhand von drei Beispiel-Ausdrücken erläutert:
Tabelle 2.3 Beispiele zu Vorrang und Assoziativität
Initialwert |
Ausdruck |
gleichbedeutend mit |
Ergebnis |
iarr[0]=1 |
++iarr[0] |
++(iarr[0]) |
iarr[0]=2 |
i=1,j=2,
b=false |
i==j==b |
(i==j)==b |
true |
|
-i/j==0==!b |
(((-i)/j)==0)==(!b) |
true |
Im letzten Ausdruck wird zuerst –i und !b berechnet (Priorität 2), dann -i/j (Priorität 4) und dieses Ergebnis mit 0 verglichen (Priorität 8, L-Assoziation, d.h. von links nach rechts). Zum Schluss wird dann das boolsche Ergebnis mit !b verglichen (Priorität 8).
System.out.println(new String("").length());
// :: 0
Methoden-Aufruf sowie new-Operator haben Priorität 1. Bei Auswertung von links nach rechts wird zuerst durch die Class Instance Creation Expression ein leerer String erzeugt, von dem anschließend die Methode length() aufgerufen wird.
2.1.1 Regel für Operanden-Werteberechnung
Regel zur Ermittlung von Operanden-Werten
Es gibt vermeidbare Ausdrücke, in denen eine Variable mehrfach vorkommt und als Seiteneffekt ihren Wert im Ausdruck ändert. Deshalb gilt die folgende Regel:
|
Die Werte der Operanden werden grundsätzlich vor Beginn der Operation von links nach rechts ermittelt, und mit diesen Werten werden dann die Operationen nach ihrer Priorität ausgeführt. |
Zwei einfache Beispiele zu dieser Regel1 :
int[][] a= new int [4][1];
a[3][0]=-1;
int i=1;
System.out.println(i+(i=3)*i); // :: 10 2
System.out.println(a[i][i=0]); // :: -1
Erklärung: In der zweiten Zeile wird – von links nach rechts – für das erste i der Wert 1 festgehalten, für das zweite i der Wert 3, der dann auch für das dritte i gilt. Zu berechnen ist also 1+3*3 nach den o.a. Vorrangregeln.
Mit Hilfe der Regel sind in der letzten Zeile die Indizes 3 und 0, d.h., es wird der Wert von a[3][0] ausgedruckt.
2.2 Arithmetische Operatoren
Generell können arithmetische Operatoren auf alle primitiven Typen angewendet werden, mit Ausnahme des Typs boolean.
Unäre Operatoren
Vor Anwendung der unären Operatoren + und - wird erst eine numeric Promotion (siehe hierzu Abschnitt 1.5.1) durchgeführt. Das unäre Plus führt bei byte, short und char eine Typumwandlung nach int durch.
Im Fall des unären Minus wird der Wert negiert, d.h. einfach das Vorzeichen umgekehrt:
char c= '0';
System.out.println(-c);
// :: -48
c= +c; // C-Fehler
(siehe 2.9)
In der letzten Anweisung bewirkt +c eine Typumwandlung von char nach int, was dann bei der Zuweisung zu einem Fehler führt.
Inkrement-/Dekrement-Operator
Die Operatoren ++ und -- erhöhen bzw. vermindern den Wert der Variablen um Eins:
char c= '0'; int i= 1; double d= 1.;
++c; ++i; d--;
System.out.println(c+","+i+","+d);
// :: 1,2,0.0
Nach der Operation ++c oder c++ hat c dann den Wert (char)(c+1).
Präfix- und Postfix-Form
Bei ++ und -- muss man die Präfix- und Postfix-Formen nur dann unterscheiden, wenn sie Teil eines größeren Ausdrucks sind.
Verwendung der Werte bei Präfix und Postfix
In der Präfix-Form wird zuerst der Operand in-/dekrementiert und dieser Wert dann verwendet. Bei der Postfix-Form wird dagegen mit dem alten Wert gerechnet und dann erst in-/dekrementiert:
int i=1, j, k;
j= ++i; k= i++;
System.out.println(i+","+j+","+k);
// :: 3,2,2
Ausdrücke, in denen eine Variable mehrfach in-/dekrementiert wird, gehören eigentlich in die Kategorie »Logeleien für Zweistein«:
int i= 0;
int[] a= {1,2};
a[i++]= a[--i]+1;
System.out.println(a[0]+","+a[1]); // :: 2,2
Erklärung: Gemäß der Regel in Abschnitt 2.1.1 hat in der 3. Zeile i zuerst den Wert 0. Dann wird durch i++ der Wert zuerst post-inkrementiert, um anschließend durch --i sofort wieder prä-dekrementiert zu werden. Die Anweisung ist also äquivalent zu:
a[0]= a[0]+1; bzw. a[0]++; bzw. ++a[0];
Binäre Operatoren
Die vier Grundrechenarten +, -, * und / sind hinlänglich bekannt und können auch durch numeric Promotion (siehe Abschnitt 1.5.1) für Zeichen verwendet werden.
Für »Integer« sind nur die speziellen Fälle »Division durch Null« (erzeugt eine Ausnahme zur Laufzeit) und »Überschreitungen des Wertebereichs« (ohne Fehler bzw. Warnung) zu beachten.
Modulo
Der Modulo-Operator ist %. Mit k mod m bzw. k%m wird der Divisionsrest bestimmt.
In Java ist dies abweichend von der mathematischen Definition und von C/C++ gelöst, da der Modulo-Operator für alle numerischen Typen definiert ist (d.h. auch für Floating-Point-Typen).
Die mathematische Modulo-Operation ist wie folgt definiert:
Modulo in der Mathematik
Für die ganze Zahl k und die natürliche Zahl m bezeichnet k mod m den (eindeutigen) Rest r aus {0,1,..., m-1}, wobei m die Zahl k - r teilt, kurz m | k-r.
7 mod 4 = 3 , da 4|7-3
-7 mod 4 = 1 , da 4|-7-1
Der Divisionsrest kann nach folgender Formel berechnet werden:
x mod y = x - ëx/yû
*y
Math.floor()
Unter ëx/yû versteht man die größte ganze Zahl, kleiner gleich x/y. Sie ist in Java als Methode Math.floor() verfügbar.
Math.IEEEremainder()
Eine mathematische Implementierung – allerdings implementiert für Double – steht als Methode Math.IEEEremainder() zur Verfügung. Sie entspricht bei ganzzahligen x und y dem Ausdruck:
x % y = (int)(i
- Math.floor(1.*i/j)*j)
Modulo in Java
Die Java-Implementierung ist dagegen:
x % y = (int)(x - (int)(x/y)*y)
In Java wird (int)(a) durch Abschneiden der Nachkommastellen von a implementiert.
Nachfolgend vier einfache Modulo-Operationen:
System.out.println(7%4);
// :: 3
System.out.println(-7%4);
// :: -3
System.out.println(Math.IEEEremainder(-7,4)); //
:: 1.0
System.out.println(-5.7%2.5); // :: -0.7000000000000002
x%0
Wichtig: Hat y den Wert Null, so löst x%y für integrale Werte eine Ausnahme (ArithmeticException) aus, für Floating-Point-Modulo ist das Ergebnis NaN.
2.3 Referenz-Variable
Im Folgenden werden Operationen besprochen, die nicht nur auf primitive Variablen, sondern auch auf Referenz-Variablen wirken.
Eine Variable kann folgenden Typ haben:
|
primitiv |
|
eine Klasse bzw. Interface |
|
ein Array, ein- oder mehrdimensional |
Bei den letzten beiden Typen spricht man von Referenz-Variablen.
Jede Art von Konvertierung zwischen primitiven Typen und Referenz-Typen ist in Java untersagt.
|
Primitive Variable vs. Referenz-Variable
Primitive Variablen enthalten direkt den Wert des zugehörigen Typs. Wie der Name bereits sagt, »zeigt« eine Referenz-Variable dagegen nur auf ein Objekt und damit auf die Felder und deren Werte.
Wichtig: Es ist essenziell, zwischen den Variablen, die Objekte referenzieren, und den Objekten selbst zu unterscheiden.
|
Abbildung 2.1 Variable referenziert Objekt
Beispiel
Die nächsten beiden Anweisungen legen nur zwei Referenzen s und iarr an, aber keine Objekte:
String s;
int[] iarr;
Die Erschaffung der Objekte geschieht erst explizit mit new oder kann bei Strings auch in der Form von Literalen erfolgen (siehe 1.4.6).
iarr= new int[5];
s= new String("1. String-Objekt");
s= "2. String-Objekt";
Die Erschaffung von Objekten ist damit äquivalent zu C++. Damit sind die Gemeinsamkeiten aber auch schon erschöpft. Denn:
In C++ können die zu Referenzen äquivalenten Pointer direkt auf Speicherbereiche gesetzt und mit Pointer-Arithmetik manipuliert werden.
Garbage
Collection:
Entfernen nicht mehr benötigter Objekte durch die JVM
In Java sind die Referenzen opak, d.h. sind von außen nicht einsehbar und bieten keinen direkten Zugriff. Die einzigen Operationen, die indirekt den internen Zeiger manipulieren, sind new und die Zuweisung.
Im Gegensatz zu C++ muss man in Java die Objekte, die nicht mehr referenziert werden, nicht selbst aus dem Speicher entfernen. Andererseits ist es auch gar nicht möglich, denn dies ist alleine Aufgabe der JVM.
Bei dem oben angegebenen Beispiel referenziert s aufgrund der dritten Anweisung das String-Literal "1. String-Objekt" nicht mehr. Damit wird es von der JVM »bei Bedarf entsorgt«.3
2.4 String-Operator
Overloading:
Wiederverwendung des + für Strings
Das Aneinanderfügen (Concatenation) von Strings wird ebenfalls mit Hilfe des Operators + durchgeführt. Das Ergebnis des Aneinanderfügens ist immer ein neues String-Objekt:
"Anfang " + "Ende" Ergebnis-String: "Anfang Ende"
Regeln zu String-Additionen
Ist einer der Operanden kein String, so wird der jeweils andere vor der Operation nach folgender Regel in einen String umgewandelt:
1. |
Für primitive Typen wird die Methode toString() der zugehörigen Wrapper-Klasse4 aufgerufen. |
2. |
Für Referenzen mit Wert null wird der String "null" geliefert, ansonsten wird die Methode toString() des referenzierten Objekts aufgerufen.5 |
3. |
Besteht der Ausdruck aus mehreren Additionen, sind unbedingt die Vorrangregeln zu beachten. |
Beispiele
System.out.println(3+4+"!="+3*2+1);
// :: 7!=61
Zuerst wird 3*2 berechnet, danach werden die restlichen + Operationen von links nach rechts ausgeführt. Die Addition 3+4 ergibt 7. Danach wird 7 nach der o.a. Regel in den String "7" umgewandelt und nacheinander "!=", "6" und "1" angehängt.
Klammert man, erhält man das (vielleicht) erwartete Ergebnis:
System.out.println((3+4)+"=="+(3*2+1)); // ::
7==7
2.5 Vergleichs-Operatoren
Unter den Vergleichs-Operatoren sind vor allem die Gleichheits-Operatoren aufgrund unterschiedlicher Semantik interessant.
Relationale Operatoren
Die relationalen Operatoren <, <=, > und >= können nur mit Zahlen und Zeichen verwendet werden und liefern die logischen Werte true und false. Vor dem Vergleich wird, falls notwendig, eine numeric Promotion durchgeführt.
Vergleiche
mit NaN
Vergleiche mit NaN führen immer zum Ergebnis false :
Double.POSITIVE_INFINITY > Double.NaN
Double.NaN == Double.NaN
Die Ausdrücke liefern somit beide den Wert false.
Gleichheits-Operatoren
Vergleich der Werte bei primitiven Typen
Die Wirkung der Operatoren == und != ist für Ausdrücke vom primitiven Typ und für Referenz-Ausdrücke unterschiedlich. Dies wird in zwei Regeln festgehalten.
Primitive Typen (Wertevergleich):
1. |
Der Operator == liefert für primitive Typen den Wert true genau dann, wenn die Werte der Operanden gleich sind, wobei vorher eventuell eine numeric Promotion durchgeführt wird. |
Der Operator != liefert genau das Gegenteil, d.h. true genau dann, wenn die Werte ungleich sind.6
Vergleich der Identität bei Objekten
Referenzen (Identitätsvergleich):
2. |
Der Operator == liefert für Referenzen den Wert true, wenn beide Referenzen auf dasselbe Objekt zeigen. Werden zwei verschiedene Objekte referenziert, liefert == immer false, egal ob die Objekte im Wert gleich sind. |
Der Operator != liefert dagegen true, wenn die Referenzen nicht auf dasselbe Objekt zeigen.
Zum Referenzvergleich siehe auch das folgende Beispiel zu Strings.
Strings
Häufig werden Strings verglichen, wobei mit der Aussage »Der String s1 ist gleich dem String s2« gemeint ist, dass in s1 und s2 alle Zeichen gleich sind.
Stringvergleich mittels Operator ==
Nach der o.a. zweiten Regel liefert der Vergleich zweier Strings mittels == nur true, wenn die Strings identisch sind. Dies bedeutet, dass zwei Referenzen auf denselben String im Speicher zeigen.
Beispiel
String s1= "gleich", s2="ch";
String s3= new String("gleich"), s4= new String("gleich");
System.out.println(s1=="gleich");
// :: true
System.out.println(s1=="glei"+s2);
// :: false
System.out.println(s1==s3); // ::
false
System.out.println(s3==s4); // ::
false
System.out.println(s1.equals("glei"+s2));
// :: true
System.out.println(s1.equals("glei"+s2));
// :: true
Abbildung 2.2 Drei Strings s1, s3, s4 mit dem Wert »gleich«
Erklärung: Beim ersten Vergleich ist das Ergebnis nur deshalb true, weil im Speicher Literale, die gleich sind, nur einmal vom Compiler angelegt werden. Das heißt, das String-Literal "gleich" existiert nur einmal, wobei es von s1 ebenfalls referenziert wird.
Im zweiten Vergleich wird aus der Variablen s2 und einem Literal ein anderes zweites String-Objekt "gleich" zusammengesetzt. Damit ist das Ergebnis false.
Mittels new werden zur Laufzeit aus dem Literal "gleich" zwei neue String-Objekte erzeugt. Folglich liefern die letzten beiden Vergleiche mit == ebenfalls false.
equals()
vergleicht bei Strings die Werte
Die Methode equals() der Klasse String prüft dagegen korrekt, ob zwei Strings im Wert gleich sind, also in allen Zeichen übereinstimmen.
Arrays
Arrays.equals() vergleicht Arrays
Auch Arrays sind Referenzen. Daher wird durch == kein Elementvergleich durchgeführt.
Jedoch existiert in der Klasse java.util.Arrays die Methode equals(), die unter anderem für Arrays mit primitiven Elementen einen Wertevergleich ermöglicht:
Arrays von primitiven Typen:
int[] iarr= {1,2,3}, jarr={1,2,3};
System.out.println(iarr==jarr); //
:: false
System.out.println(Arrays.equals(iarr,jarr));
// ::
true
Vorsicht: Die Methode Arrays.equals() ist nicht mit der nachfolgenden Object-Methode equals() zu verwechseln. Sie ist statisch und benötigt zwei Array-Argumente.
Arrays von Referenz-Typen:
Sind die Array-Elemente wieder Referenzen auf Objekte (z.B. Strings), ruft die Methode Arrays.equals() für je zwei zu vergleichende Elemente der beiden Arrays die zugehörige Object-Methode equals() auf:
String s1= new String("hallo"),s2= new String("hallo");
String[] sarr1= {s1,s2}, sarr2={s1,s2};
System.out.println(Arrays.equals(sarr1,sarr2));
// ::
true
Abbildung 2.3 Ein Array von int bzw. ein Array von Strings
In der Abbildung 2.3 sind exemplarisch für die beiden Array-Typen ein Array von n int-Werten und ein Array von n String-Werten dargestellt. Beim int-Array sind die Werte direkt in der Variablen gespeichert.
Object-Methoden equals() und toString():
Nicht nur die Klasse String, sondern jede Klasse hat eine Instanz-Methode equals(), die wie toString() von der Klasse Object »geerbt« wird.
Methode equals() muss sinnvoll überschrieben werden
Allerdings liefert das von Object geerbte equals() für eine Klasse nichts anderes als der Operator ==.
Jede Klasse muss daher die Methode equals() für sich sinnvoll implementieren. Da sie ja schon existiert, spricht man dann von Überschreiben (Override). In der Klasse String wurde somit equals() überschrieben.7
2.6 Typvergleich-Operator instanceof
Der linke Operand des Operators instanceof muss eine Referenz sein, der rechte ein Objekttyp, d.h. eine Klasse, Interface oder Array. Der Operator kann also nicht für primitive Typen verwendet werden.
Der Typvergleich wird nach folgender Regel durchgeführt:
Regel für instanceof-Vergleiche
1. |
Sollte der Compiler beim instanceof-Ausdruck feststellen, dass das Ergebnis nur false sein kann, erzeugt er einen Fehler. |
2. |
Ansonsten: Hat die Referenz den Wert null, liefert instanceof den Wert false. |
3. |
Ansonsten: Ist die Instanz, auf die die Referenz zeigt, vom rechts stehenden Typ bzw. kann in ihn implizit konvertiert werden, so liefert instanceof zur Laufzeit true, sonst false. |
Beispiele
String s="abc";
int[] iarr= new int[3], jarr=null;
System.out.println(s instanceof String);
// :: true
System.out.println(iarr instanceof int[]); //
:: true
Unsinnige Vergleiche erzeugen Fehler beim Kompilieren
System.out.println(jarr instanceof int[]); //
:: false
System.out.println(s instanceof int[]); //
C-Fehler
System.out.println(iarr instanceof byte[]); //
C-Fehler
Erklärung: Die beiden letzten Typvergleiche scheitern schon beim Kompilieren, da bereits der Compiler entscheiden kann, dass diese Ausdrücke immer false liefern.
Der Typvergleich ist nützlich für Methoden, die z.B. gewisse Operationen erst durchführen dürfen, wenn sichergestellt ist, ob ein aktuelles Argument wirklich von einem bestimmten Typ ist.8
2.7 Logische Operatoren
Den Namen verdanken die logischen Operatoren der Tatsache, dass ihre Operanden sowie das Ergebnis vom Typ boolean sind.
Es gibt vier logische Operationen: die unäre Verneinung (NOT) und die Operationen und (AND), oder (OR) und exklusiv-oder (XOR).
Die nachstehende Tabelle zeigt die Ergebnisse für alle Operatoren in Abhängigkeit von den Werten der Operanden a und b, wobei && bzw. || eine Sonderform von & bzw. | ist, die weiter unten besprochen wird.
Ergebnisse logischer Operationen
Tabelle 2.4 Ergebnisse der logischen Operationen
a |
b |
a&b bzw. a&&b
(AND) |
a|b bzw. a||b
(OR) |
a^b
(XOR) |
!a
(NOT) |
false |
false |
false |
false |
false |
true |
true |
false |
false |
true |
true |
false |
false |
true |
false |
true |
true |
|
true |
true |
true |
true |
false |
|
Die Operanden a und b können natürlich selbst wieder logische Ausdrücke sein.
Die Berechnung von a op b für die drei Operatoren &, | und ^ läuft wie folgt ab:
Ist der Wert von a false bzw. true, ist der zweite und dritte Schritt für AND bzw. OR überflüssig, da das Ergebnis bereits durch a vorgegeben wird.
Deshalb gibt es jeweils zwei verschiedene Operatoren für AND und OR:
|
Die Operatoren & bzw. | führen den zweiten und dritten Schritt immer aus, d.h. berechnen immer b. |
Short-Circuit-Operatoren && und ||
|
Die Operatoren && bzw. || führen den zweiten und dritten Schritt nur aus, wenn das Ergebnis nicht bereits durch a festliegt. |
Deshalb nennt man && und || auch Short-Circuit-Operatoren.
Short-Circuit-Berechnungen kann man sich bei gewissen Ausdrücken zunutze machen.
Beispiel
int i=0, j=10;
System.out.println(i!=0 & j>0);
// :: false
System.out.println(i!=0 | j>0);
// :: true
System.out.println(!(i!=0) ^ !(j>0));
// :: true
System.out.println(i!=0 && j/i>1);
// Short-Circuit :: false
System.out.println(i!=0 & j/i>1);
// Laufzeit-Fehler
Erklärung: Da die logischen Operatoren mit Ausnahme von NOT im Vorrang weit unten stehen, braucht man meistens nicht zu klammern. Trotzdem sind Klammern alleine wegen der Lesbarkeit sinnvoll.
Logische Ausdrücke sollten nicht unnötig kompliziert sein. Zum Beispiel haben Ausdrücke wie !(i!=0) bzw. !(j>0) nur pädagogische Bedeutung, d.h. (i==0) bzw. (j<=0) ist wohl besser.
Wie zu erwarten, wird im vierten Ausdruck j/i>1 aufgrund von && nicht mehr ausgewertet. Dies verhindert einen unangenehmen Laufzeitfehler (ArithmeticException), der bei einer Integer-Division durch Null ausgelöst wird. Dagegen erzeugt der letzte Ausdruck wie erwartet einen Laufzeitfehler.
2.8 Bitmanipulationen
Identifiziert man false bzw. true mit dem Bit 0 bzw. 1, sind die Ergebnisse der logischen Operationen direkt auf die Bit-Operationen mit Operanden vom integralen Typ übertragbar.
Bit-Operatoren wirken auf jedes Bit
Die Bit-Operatoren & (AND), | (OR), ^ (XOR) und ~ (NOT) müssen auf jedes Bit der Operanden angewendet werden, wogegen die entsprechenden logischen Operatoren nur ein einzelnes Bit manipulieren (zu brauchen).
Zusätzlich existieren noch Schiebe-(shift-)Operationen, die – wie bereits der Name sagt – die Bits in einem integralen Operanden links oder rechts verschieben.
Bit-Operationen sind maschinenunabhängig
Java implementiert natürlich die Bit-Operationen unabhängig vom realen Prozessor in der JVM, d.h., im Gegensatz zu C/C++ führen Bit-Operationen auf allen Maschinen zum gleichen Ergebnis.
2.8.1 Duale bzw. hexadezimale Codierung
Die Auswirkung einer Bit- bzw. Schiebeoperation kann man nur verstehen, wenn die duale Repräsentation von ganzen Zahlen, speziell das 2er-Komplement für negative Zahlen bekannt ist.
Duale Codierung positiver Zahlen
Für die duale Codierung wird eine positive ganze Zahl in die Summe von 2er-Potenzen zerlegt, woraus dann die duale Darstellung sofort abzuleiten ist:
Dezimale Zahl: 43 = 4*101 + 3*100
Duale Zahl: 43 = 32 + 8 + 2 + 1
= 1*25 +0*24 +1*23 +0*22 +1*21 +1*20
= 1010112
Die Zahl 43 – dual codiert – ist also 101011, wobei jedoch je nach integralem Typ mit führenden Nullen auf 8, 16, 32 oder 64 Bits aufgefüllt werden muss.
Da die Darstellung von bis zu 64 Bits nicht sehr attraktiv ist, werden jeweils vier Bit (ein Halbbyte) zu einer hexadezimalen Ziffer 0..9a..f zusammengefasst, was der Darstellung einer Zahl in Potenzen von 16 entspricht. Die 43 als Typ byte codiert ist dann:
43 = 001010112 = 2b16 = 2*161 + 11*160
Nutzt man den Wertebereich nur für positive Zahlen, können in einem Byte die Zahlen von 0..ff bzw. in zwei die Zahlen von 0..ffff codiert werden. Dies entspricht dezimal den Werten 0..255 bzw. 0..65535. Der Typ char ist vorzeichenlos, was z.B. mit einem unären Plus per numeric Promotion getestet werden kann:
System.out.println(+'\uffff'); // :: 65535
Duale Codierung negativer Zahlen
Um negative Zahlen in den dualen Zahlenraum einzubetten, muss der Wertebereich geteilt werden.
1er-Komplement
Eine Möglichkeit ist die, einfach durch Invertieren der Bits die zu einer positiven ganzen Zahl zugehörige negative Zahl zu erzeugen. Dieses so genannte 1er-Komplement wäre dann für -43 vom Typ byte:
43= 00101011 ‡ -43= 11010100
1er-Komplement: Problem Zero Crossing
Da die Addition 43 + -43 = 0 ist, folgen daraus aber unmittelbar zwei duale Darstellungen für die Null vom Typ byte:
0= 00000000 = 11111111
2er -Komplement
Dieses Problem lässt sich dadurch lösen, dass nach der Invertierung noch eine 1 addiert wird. Voilà, das 2er-Komplement:
43= 00101011
-43= 11010101 (= 11010100 + 1)
0= 100000000
Das Ergebnis der Addition führt zwar zu einer Eins im höchstwertigen 9. Bit. Da dies aber außerhalb der erlaubten acht Bits für den Typ byte liegt, wird es einfach abgeschnitten.
Für das 2er-Komplement der Zahlen vom Typ short, int oder long ist die Rechnung nicht anders, wobei dann nur das 17., 33. bzw. 65. Bit abgeschnitten wird.
Höchstwertiges Bit: Vorzeichen-Bit
Für positive Zahlen ist das höchstwertige (linke) Bit 0, für negative Zahlen 1. Das höchstwertige Bit repräsentiert damit das Vorzeichen.
Insbesondere hat -1 in der dualen Codierung alle Bits auf 1 gesetzt.
2.8.2 Regeln für Bit-Operationen
Bit-Operationen gibt es nur für die Typen int und long. Für Bit-Operationen gelten folgende Regeln:
1. |
Operanden vom Typ byte, short oder char werden vor der Operation in den Typ int umgewandelt. |
2. |
Ist einer der Operanden vom Typ long, wird der andere auch in long umgewandelt. |
3. |
Der Typ des Ergebnisses der Operation entspricht dem Operanden, d.h. ist entweder int oder long. |
Die nachfolgende Tabelle aller Bit-Operationen entspricht der Tabelle 2.4, wobei a und b nun Bits repräsentieren:
Tabelle 2.5 Ergebnisse der Bit-Operationen
a |
b |
a&b
(AND) |
a|b
(OR) |
a^b
(XOR) |
~a
(NOT) |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
0 |
0 |
1 |
1 |
0 |
0 |
1 |
0 |
1 |
1 |
|
1 |
1 |
1 |
1 |
0 |
|
2.8.3 Invertierung (Bitwise Complement)
Es werden alle Bits des Operanden invertiert (siehe o.a. Tabelle).
Beispiel
int i=10;
System.out.println(Integer.toBinaryString(i));
// :: 1010
System.out.println(Integer.toHexString(~i)); //
:: fffffff5
System.out.println(~i+","+~~i); //
:: -11,10
toHexString()
toBinaryString()
Erklärung: Mittels der statischen Methode toHexString() bzw. toBinaryString() der Klasse Integer bzw. Long können die dualen bzw. hexadezimalen Codierungen von integralen Operanden in Strings umgewandelt werden, wobei führende Nullen unterdrückt werden (Beispiel siehe 2.8.4).
Die Ausgabe von ~i zeigt die Wirkung des höchstwertigen Bits als Vorzeichen. Die Invertierung einer positiven Zahl ist eine negative und umgekehrt. Insbesondere gilt:
Integer.MAX_VALUE == ~Integer.MIN_VALUE
|
Der Invertierungs-Operator ist invers zu sich selbst, d.h. ~~a == a. |
2.8.4 Bitwise AND, OR, XOR
Die einzelnen Bits der Operanden werden je nach Operator entsprechend verknüpft (siehe Tabelle 2.5).
Beispiel
char c='\u00ff';
short s=527; // 512 + 15 bzw. hex: 200+f
System.out.println(Integer.toHexString(s)); //
:: 20f
System.out.println(Integer.toHexString(s&c));
// :: f
System.out.println(Integer.toHexString(s|c)); //
:: 2ff
System.out.println(Integer.toHexString(s^c)); //
:: 2f0
Erklärung: Die Operanden werden nach der o.a. ersten Regel (siehe 2.8.2) vor den Operationen in den Typ int umgewandelt.
Setzen, Testen, Löschen und Invertieren von Bits
Mit dem zweiten Operanden als Maske können Flag-Bits mit Hilfe des
|
XOR-Operators invertiert werden, |
|
OR-Operators gesetzt werden, |
|
AND-Operators getestet oder gelöscht werden. |
Beispiel
Ist b vom Typ byte, so
invertiert b^m das 5.-8. Bit von b für m= 0xf0 setzt b|m das 6. Bit von b für m= 0x20 testet b&m das 6. Bit von b für m= 0x20 löscht b&m das 6. Bit von b für m=~0x20
2.8.5 Bitwise-Shift-Operationen
Der Left-Shift-Operator << schiebt alle Bits des linken Operanden um die Anzahl von Bits nach links, die der rechte Operand angibt, wobei die (rechten) niederwertigen Bits mit Nullen aufgefüllt werden.
Nach rechts schieben mit oder ohne Vorzeichen
Der Signed-Right-Shift-Operator >> schiebt alle Bits des linken Operanden um die Anzahl von Bits nach rechts, die der rechte Operand angibt, wobei das Vorzeichen (des linken Operanden) erhalten bleibt.
Der Unsigned-Right-Shift-Operator >>> schiebt alle Bits des linken Operanden um die Anzahl von Bits nach rechts, die der rechte Operand angibt, wobei die höchstwertigen Bits mit Nullen aufgefüllt werden.
Wert des Shift-Operanden modulo 32 bzw. 64
|
Da Operanden vom Typ int bzw. long nur um 0..31 bzw. 0..63 Bit nach links bzw. rechts geschoben werden können, werden vom rechten Operanden nur die ersten 5 bzw. 6 Bits berücksichtigt. |
Dies entspricht bei int dem mod 32-Wert des rechten Operanden bzw. bei long dem mod 64 -Wert des rechten Operanden.
In den folgenden Beispielen wird zur Veranschaulichung der Schiebe-Operationen auch die duale Codierung dargestellt.
Beispiele
-3 << 36 Û -3 << 36%32 Û -3 << 4 Û -3 * 24
-3 ‡
|
1111 |
1111 |
1111 |
1111 |
1111 |
1111 |
1111 |
1101 |
-3 << 4 ‡ |
1111 |
1111 |
1111 |
1111 |
1111 |
1111 |
1101 |
0000 |
Multiplikation
mit 2n
Das Schieben um n Stellen nach links entspricht also der Multiplikation mit 2n.
Der Left-Shift-Operator demonstriert recht einfach einen Integer-Überlauf:
1 << 31 Û
-1 << 31 Û
Integer.MIN_VALUE
-2147483648 ‡ |
1000 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
Division durch 2n für positive Zahlen
Beim Right-Shift fallen die niederwertigen Bits weg. Die höchstwertigen Bits werden beim Signed-Right-Shift-Operator für positive Zahlen mit 0, für negative mit 1 aufgefüllt. Damit entspricht das Schieben um n Stellen nach rechts der Integer-Division durch 2n für positive Zahlen.9
Integer.MIN_VALUE >> 31 ==
-1
90 >> 4 Û
90/16 Û
5
-90 >> 4 Û -6 ist nicht gleich 90/4 Û -5
90 ‡
|
0000
|
0000 |
0000 |
0000 |
0000 |
0000 |
0101 |
1010 |
90 >> 4 ‡ |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
0101 |
-90 ‡ |
1111 |
1111 |
1111 |
1111 |
1111 |
1111 |
1010 |
0110 |
-90 >> 4 ‡ |
1111 |
1111 |
1111 |
1111 |
1111 |
1111 |
1111 |
1010 |
Die höchstwertigen Bits werden beim Unsigned-Right-Shift-Operator immer mit 0 aufgefüllt, d.h., der Operand wird nicht als Zahl, sondern als reines Bit-Muster verstanden.
Jede negative Zahl wird somit durch Schieben nach rechts positiv. Insbesondere gilt:
-1>>>1 Û
Integer.MAX_VALUE
2.9 Zuweisungen
Simple
Assignment
Bei der Zuweisung wird der Wert des Ausdrucks der rechten Seite als Wert der Variablen auf der linken Seite gesetzt. Ist der Typ des Ausdrucks verschieden von dem der Variablen, wird eine implizite Typumwandlung versucht. Ist diese nicht möglich, ergibt sich ein Fehler beim Kompilieren. Zuweisungen existieren in zwei Formen:
Compound-Assignment
var op= expression;
Das Symbol op steht für einen binären arithmetischen, Bit-, Shift- oder String-Operator.
Compound-Assignment bedeutet Casting
Die zweite Form ist eine Abkürzung für folgende Zuweisung:
var
= (varType)(var op expression);
wobei var bei op= nur einmal ausgewertet wird.
Die feine Unterscheidung, ob var ein- oder zweimal ausgewertet wird, ist nur für »obskure« Zuweisungen mit gleichzeitigem Seiteneffekt notwendig (siehe hierzu 2.9.1).
Der Cast mittels (varType) zurück in den Typ der Variablen var ist recht angenehm bei arithmetischen Operatoren, da diese ja nur mit den Typen int oder long durchgeführt werden:
char c= 'a';
c= c+2; // C-Fehler, da c+2 vom Typ int
c+=2; // ok!
Zuweisungen haben ein Ergebnis
Die Zuweisung zur Variablen var ist eigentlich ein Seiteneffekt, da die Zuweisung als Ergebnis den Wert des Ausdrucks hat. Dies wird häufig benutzt, um zwei Anweisungen zusammenzufassen.
Verkettung von Zuweisungen
Da die Zuweisung als einziger binärer Operator rechts-assoziativ ist, werden Zuweisungen von rechts nach links ausgeführt:
x=y=z; x=(y=z);
j*=i+=3; j*=(i+=3);
2.9.1 Compound-Assignment und Seiteneffekte
In Verbindung mit mehrfach vorkommenden Variablen wird erkennbar, dass der rechte Operand bei einem Compound-Assignment nur einmal ausgewertet wird.
Beispiel
int[] iArr= {1,2,3}; int i=0;
iArr[i++]+= i; // Wert von i links 0, rechts 1
// also äquivalent zu: iArr[i++]= iArr[0]+i;
// nicht äquivalent zu: iArr[i++]= iArr[i++]+i;
Erklärung: Nach der Regel in 2.1.1 hat i in der letzten auskommentierten Zuweisung von links nach rechts die Werte 0, 1 und 2.
Wirkung der einfachen Zuweisung
Wie bei den Gleichheitsoperatoren ist die Wirkung der Zuweisung für primitive Typen und Referenzen auf Objekte wieder unterschiedlich, da Variablen von primitiven Typen direkt ihre Werte enthalten.
Primitive Typen (Wertkopie):
Zuweisungen
bei primitiven Typen und bei Referenzen
|
Nach der Zuweisung ist der Wert der linken Variable der Wert des rechten Operanden. |
Referenzen (Zeigerkopie):
|
Nach der Zuweisung hat die linke Referenz-Variable den Zeigerwert des rechten Operanden. |
Der Effekt der Zuweisung bei Referenzen ist, dass die linke Variable auf dasselbe Objekt zeigt, das von der rechten Seite referenziert wird.
Beispiel
Anhand von Arrays wird die Wirkung der Zuweisung bei Referenzen sehr deutlich sichtbar:
int[] iArr= {1,2,3}; // Array-Anlage
int[] jArr= iArr; // kopiert
Referenzwert
jArr[2]<<= jArr[1]<<= jArr[0];
// r-shift
System.out.print(iArr[1]+","+iArr[2]); // :: 4,48
Erklärung: Aufgrund der zweiten Zeile zeigen iArr und jArr auf dasselbe int-Array. Die dritte Zeile führt eine Shift-Operation um ein Bit nach rechts auf jArr[1] aus. Dieses Ergebnis shiftet dann jArr[2] wieder nach rechts (um vier Bits).
Auf die nur einmal vorhandenen Array-Elemente kann wahlweise über die Referenz iArr oder jArr zugegriffen werden (siehe Abb. 2.4).
Abbildung 2.4 Resultat der Zuweisung bei iArr= jArr; bzw. s1= s2;
String-Zuweisungen
Strings verhalten sich bei Zuweisungen in der Handhabung – im Gegensatz zu String-Pointern bei C/C++ – unkritisch:
String s1, s2= "nicht änderbar";
s1= s2; // kopiert Referenzwert
Immutable: Strings sind unveränderbar
Beide String-Referenzen s1 und s2 zeigen zwar wie bei C++ nach einer Zuweisung auf nur ein String-Objekt (siehe Abb. 2.4), aber Strings sind unveränderbar, weder über s1 noch s2 lässt sich der String ändern.
Objekte, die sich nach der Anlage nicht mehr verändern lassen, nennt man immutable.
Da es in Java keine String-Operation gibt, die einen String nach der Anlage verändern kann, verhalten sich Strings bei der Zuweisung wie primitive Typen.
Auf Strings ist natürlich auch der Compound-Operator += anwendbar, und er verhält sich wie erwartet:
String s= "big ";
s+= "apple"; // s= "big apple"
2.10 Ternärer Operator
Konditionaler Operator
Der ternäre Operator – auch konditionaler Operator genannt – ist ein Relikt von C/C++. Der Begriff »konditional« ist deshalb treffend, weil das Ergebnis der Operation von einer Bedingung abhängt:
booleanExpression ? trueExpression
: falseExpression
Das Ergebnis des ersten Ausdrucks muss ein Ergebnis vom Typ boolean sein. Ist es true, wird die trueExpression als Ergebnis zurückgeliefert, ansonsten die falseExpression.
Typ-
Kompatibilität
Der Compiler stellt sicher, dass die Typen der trueExpression und falseExpression entweder gleich sind oder implizit in einen gemeinsamen Typ umgewandelt werden können.
Der ternäre Operator ist
short-circuit
Der ternäre Operator ist ein Short-Circuit-Operator, d.h., es wird entweder nur die trueExpression oder nur die falseExpression ausgewertet:
a= i!=0 ? 1/i : Double.POSITIVE_INFINITY;
Sofern i vom Typ int ist, wird bei i=0 die Integer-Division übersprungen. Mittels numeric Promotion ist der gemeinsame Typ des zweiten und dritten Operanden double. Damit muss die Variable a vom Typ double sein, da ansonsten der Compiler einen Fehler erzeugt.
Die nachfolgende Anweisung scheitert am Compiler:
System.out.print(i!=0?1/i:"Div. durch 0"); // C-Fehler
Nachfolgend eine ternäre Variante für eine mathematische Modulo-Operation:
mod= (mod=i%j)<0 &&
j>0? mod+j:mod;
Auch hier wird die Regel aus 2.1.1 genutzt: Der Wert des Modulo mod wird von links nach rechts ermittelt, bevor der ternäre Ausdruck ausgewertet wird. Das heißt, zuerst wird mod auf den Wert von i%j gesetzt und dann – sofern mod negativ und j positiv ist – durch Addition von j angepasst.
2.11 Zusammenfassung
Betrachtet man Tabelle 2.1, sind die Operanden der Java-Operationen überwiegend vom primitiven Typ.
Da die Java-Sprache unabhängig von dem Betriebssystem bzw. der Hardware sein muss, werden die Operationen in Assoziativität, Vorrang und Wirkung eindeutig definiert.
Die Ermittlung der Operandenwerte wird vor der Berechnung der Ausdrücke durchgeführt, und im Gegensatz zu C/C++ gibt es hier zwei Right-Shift-Operatoren.
Sind die Operanden Referenzen, wirken die Operatoren auf den Referenzwerten – den opaken Zeigern – und nicht auf den Objektwerten. Insbesondere ist hierbei die Semantik für Vergleiche mit == bzw. != für Zuweisungen zu beachten.
Operationen mit numerischen Operanden, gleichzeitig mit und ohne Vorzeichen, sind in C/C++ eine häufige Fehlerquelle.
Dies ist wohl ein Grund dafür, dass mit Ausnahme des Typs char die Java-Sprachdesigner nur vorzeichenbehaftete (signed) numerische Typen zulassen.
Leider wurde damit aber für Anwendungsbereiche wie z.B. Grafik bzw. Bildbearbeitung der wichtige Typ Byte (ohne Vorzeichen!) mit den zugehörigen Operationen missachtet.
Hier bietet C/C++ mit unsigned char zwar keine schöne, aber eine bessere Alternative.
2.12 Testfragen
Zu jeder Frage können jeweils eine oder mehrere Antworten bzw. Aussagen richtig sein.
1. |
Welche Aussagen sind zum folgenden Code-Fragment richtig? |
char c= '-';
res= +c;
System.out.println(res);
A: Die Variable res kann vom Typ double sein.
B: Die Variable res kann vom Typ char sein.
C: Ist die Variable res vom Typ int, ist die Ausgabe eine positive Zahl.
D: Der Code führt zu einem Fehler beim Kompilieren.
2. |
Welche Aussagen sind zum folgenden Code-Fragment richtig? |
int i=0, j=1;
System.out.println(++i + j++ +" " +i +" " +j);
A: Die Ausgabe ist: 2 1 1
B: Die Ausgabe ist: 1 1 2
C: Die Ausgabe ist: 2 1 2
D: Der Code führt zu einem Fehler beim Kompilieren.
3. |
Welche Aussagen sind zum folgenden Code-Fragment richtig? |
char c= 1;
System.out.println(-5%3 + c);
A: Die Ausgabe ist: 2
B: Die Ausgabe ist: -2
C: Die Ausgabe ist: -1
D: Der Code führt zu einem Fehler beim Kompilieren.
4. |
Welche Aussagen sind zum folgenden Code-Fragment richtig? |
byte b= 1;
boolean bo1=true, bo2=false;
System.out.println(bo1^!bo2 & false?++b:b--);
A: Die Ausgabe ist: 1
B: Die Ausgabe ist: 2
C: Die Ausgabe ist: 0
D: Der Code führt zu einem Fehler beim Kompilieren.
5. |
Welche Aussagen sind zum folgenden Code-Fragment richtig? |
int i=-1; byte b=-1;
i= i>>>32; b>>>=20;
A: System.out.println(i); Ausgabe: 0
B: System.out.println(i>>>10>>10>>10); Ausgabe: -1
C: System.out.println(i>>>10>>10>>10); Ausgabe: 3
D: System.out.println(b); Ausgabe: -1
E: Die Anweisung b>>>=20; ist gleichbedeutend mit b= b>>>20;
6. |
Welche Ausgaben sind zu den folgenden Deklarationen richtig? |
String s1= new String("100"),s2= new String("100");
String s3= null;
int i=100;
A: System.out.println(s1==s2); Ausgabe: true
B: System.out.println(s1+=i); Ausgabe: 100100
C: System.out.println(s2+=s3); Ausgabe: 100
D: System.out.println(i+=s1); Ausgabe: 200
E: System.out.println(s3!=null & s3.length()>0?">0":"0");
E: Ausgabe: Laufzeit-Fehler (Exception)
7. |
Welche Ausgaben sind zu der folgenden Deklaration richtig? |
int a=-1, b= 8, c= 3;
A: System.out.println(~a==(a^a)); Ausgabe: true
B: System.out.println(~~a==a); Ausgabe: true
C: System.out.println(!a<0 ? a:-a); Ausgabe: 1
D: System.out.println(a>>>1==Integer.MAX_VALUE); Ausgabe: false
E: System.out.println((b&c) % (b|c)); Ausgabe: 0
F: System.out.println((b|c) % (b&c)); Ausgabe: 0
8. |
Welche Ausgaben erzeugt true zu den folgenden Deklarationen? |
double a=0.0, b=1.0;
A: System.out.println(a/a==0.0/0.0);
B: System.out.println(b/a > 0.0/0.0);
C: System.out.println(b/a > Double.MAX_VALUE);
D: System.out.println((int)(b/a)== Integer.MAX_VALUE);
E: System.out.println(b/a == 1/0);
9. |
Welche Anweisungen führen zu einem Fehler beim Kompilieren für die folgenden Deklarationen? |
int[] iarr= null, jarr= {0,1};
String[] s= {"ok"};
A: System.out.println(iarr instanceof int[]);
B: System.out.println(jarr instanceof int[]);
C: System.out.println(jarr instanceof byte[]);
D: System.out.println(s[0] instanceof String);
E: System.out.println(jarr[0] instanceof int);
F: System.out.println(jarr[0] instanceof Integer);
10. |
Welche Aussagen sind zu den folgenden Deklarationen richtig? |
int i= 1;
Integer j= new Integer(1), k= new Integer(1);
A: System.out.println(j); Ausgabe: 1
B: System.out.println(j==k); Ausgabe: true
C: System.out.println(j.equals(k)); Ausgabe: true
D: System.out.println(i==j); Fehler beim Kompilieren
E: System.out.println((i+"").equals(j+""));
E: Fehler beim Kompilieren
1 Weitere Beispiele sind in den Unterabschnitten 2.2.1, in 2.9.1 und 2.10 zu finden.
2 Die Details des Zuweisungs-Operators werden in Abschnitt 2.9 behandelt.
3 Wichtig ist anzumerken, dass das Entfernen nicht mehr referenzierter Objekte nicht vorhersehbar ist. Dies ist JVM-abhängig. Eine Programmierung, die auf garantierter Entfernung basiert, ist fehlerhaft. Garbage Collection wird im Zusammenhang mit finalize() noch näher besprochen (siehe 5.9.1).
4 Zum Beispiel wird für den Typ int die Methode Integer.toString() aufgerufen.
5 Jede Klasse hat eine Methode toString(), da alle Klassen von der Klasse Object
abgeleitet werden, die diese Methode (sehr rudimentär) implementiert.
6 Spezialfall NaN: NaN!=NaN liefert true.
7 Siehe hierzu Kapitel 5, Vererbung.
8 Beispiel: Implementierung der Methode equals()
9 Für positive wie negative Zahlen schneidet Java bei der Integer-Division einfach die Nachkommastellen ab, d.h. entspricht bei negativen Zahlen nicht der mathematischen Definition.
|