IT-Übungen

Java: Was gibt der Code aus?

Jede Lösung wurde mit OpenJDK 21 ausgeführt und geprüft.

Lesestand

  1. String-Verkettung ★☆☆ Strings · Operatoren

    Was gibt dieses Programm aus?

    public class Main {
        public static void main(String[] args) {
            System.out.println(1 + 2 + "3" + 4 + 5);
        }
    }
    Lösung anzeigen

    Ausgabe:

    3345

    Auswertung von links nach rechts: 1 + 2 = 3 (Zahl), dann 3 + "3" = "33" (String). Ab da ist alles String-Verkettung: "334", dann "3345".

  2. Ganzzahldivision ★☆☆ Arithmetik

    Was gibt dieses Programm aus?

    public class Main {
        public static void main(String[] args) {
            System.out.println(7 / 2);
            System.out.println(7 / 2.0);
            System.out.println(-7 / 2);
            System.out.println(-7 % 3);
        }
    }
    Lösung anzeigen

    Ausgabe:

    3
    3.5
    -3
    -1

    int / int schneidet die Nachkommastellen ab, und zwar Richtung 0 (also -3, nicht -4). Sobald ein Operand double ist, wird mit Komma gerechnet. Der Rest hat in Java immer das Vorzeichen des Dividenden: -7 % 3 = -1.

  3. Switch ohne break ★☆☆ Kontrollfluss

    Was gibt dieses Programm aus?

    public class Main {
        public static void main(String[] args) {
            int x = 2;
            switch (x) {
                case 1: System.out.print("1");
                case 2: System.out.print("2");
                case 3: System.out.print("3");
                default: System.out.print("d");
            }
        }
    }
    Lösung anzeigen

    Ausgabe:

    23d

    Ohne break "fällt" die Ausführung in alle folgenden Fälle durch (Fall-through). Einstieg bei case 2, dann weiter durch case 3 und default.

  4. Gleitkomma ★☆☆ Arithmetik · double

    Was gibt dieses Programm aus?

    public class Main {
        public static void main(String[] args) {
            System.out.println(0.1 + 0.2 == 0.3);
            System.out.println(0.1 + 0.2);
        }
    }
    Lösung anzeigen

    Ausgabe:

    false
    0.30000000000000004

    0.1 und 0.2 sind im Binärsystem nicht exakt darstellbar. Die Summe weicht minimal von 0.3 ab. Für Geld und exakte Vergleiche: BigDecimal oder Vergleich mit Toleranz.

  5. Stream-Summe ★☆☆ Streams

    Was gibt dieses Programm aus?

    import java.util.stream.IntStream;
    
    public class Main {
        public static void main(String[] args) {
            int sum = IntStream.rangeClosed(1, 10)
                    .filter(x -> x % 2 == 1)
                    .map(x -> x * x)
                    .sum();
            System.out.println(sum);
        }
    }
    Lösung anzeigen

    Ausgabe:

    165

    rangeClosed schließt die 10 ein. Ungerade Zahlen: 1, 3, 5, 7, 9. Quadrate: 1 + 9 + 25 + 49 + 81 = 165. (385 wäre die Summe aller Quadrate 1–10, 220 die der geraden.)

  6. char plus int ★★☆ Datentypen · Strings

    Was gibt dieses Programm aus?

    public class Main {
        public static void main(String[] args) {
            System.out.println('a' + 1);
            System.out.println("" + 'a' + 1);
            char c = 'a';
            c += 1;
            System.out.println(c);
        }
    }
    Lösung anzeigen

    Ausgabe:

    98
    a1
    b

    'a' + 1 ist eine int-Addition ('a' = 97). Mit "" vorne wird zur String-Verkettung. c += 1 enthält einen impliziten Cast zurück auf char, deshalb kommt 'b' heraus (c = c + 1 würde dagegen nicht kompilieren).

  7. Prä- und Post-Inkrement ★★☆ Operatoren

    Was gibt dieses Programm aus?

    public class Main {
        public static void main(String[] args) {
            int i = 5;
            int j = i++ + ++i;
            System.out.println(i + " " + j);
        }
    }
    Lösung anzeigen

    Ausgabe:

    7 12

    i++ liefert 5 und erhöht i auf 6. ++i erhöht dann auf 7 und liefert 7. j = 5 + 7 = 12, i ist am Ende 7.

  8. Kurzschluss-Auswertung ★★☆ Operatoren · Logik

    Was gibt dieses Programm aus?

    public class Main {
        static int calls = 0;
    
        static boolean check(boolean v) {
            calls++;
            return v;
        }
    
        public static void main(String[] args) {
            if (check(false) && check(true)) { }
            System.out.println(calls);
            if (check(false) & check(true)) { }
            System.out.println(calls);
        }
    }
    Lösung anzeigen

    Ausgabe:

    1
    3

    && wertet die rechte Seite nicht mehr aus, wenn links schon false steht (1 Aufruf). Das einfache & wertet immer beide Seiten aus (2 weitere Aufrufe, also 3).

  9. Integer-Überlauf ★★☆ Datentypen · Arithmetik

    Was gibt dieses Programm aus?

    public class Main {
        public static void main(String[] args) {
            System.out.println(Integer.MAX_VALUE + 1);
            System.out.println(Math.abs(Integer.MIN_VALUE));
            System.out.println((byte) 200);
        }
    }
    Lösung anzeigen

    Ausgabe:

    -2147483648
    -2147483648
    -56

    int läuft still über: MAX_VALUE + 1 springt auf MIN_VALUE. Math.abs(MIN_VALUE) ist nicht darstellbar und bleibt negativ – ein klassischer Bug. (byte) 200 schneidet auf 8 Bit: 200 - 256 = -56. Sichere Alternative: Math.addExact wirft bei Überlauf eine Exception.

  10. Rekursion ★★☆ Rekursion

    Was gibt dieses Programm aus?

    public class Main {
        static int f(int n) {
            if (n == 0) return 0;
            return n % 2 + 10 * f(n / 2);
        }
    
        public static void main(String[] args) {
            System.out.println(f(13));
        }
    }
    Lösung anzeigen

    Ausgabe:

    1101

    Die Funktion wandelt n in seine Binärdarstellung um und schreibt sie als Dezimalzahl: 13 = 8 + 4 + 1 = 1101. Die niedrigste Stelle (n % 2) landet ganz rechts, weil das rekursive Ergebnis mit 10 multipliziert wird.

  11. Labels mit break und continue ★★☆ Kontrollfluss

    Was gibt dieses Programm aus?

    public class Main {
        public static void main(String[] args) {
            int count = 0;
            outer:
            for (int i = 0; i < 3; i++) {
                for (int j = 0; j < 3; j++) {
                    if (j == 2) continue outer;
                    if (i == 2) break outer;
                    count++;
                }
            }
            System.out.println(count);
        }
    }
    Lösung anzeigen

    Ausgabe:

    4

    Bei i = 0 und i = 1 zählen jeweils j = 0 und j = 1 (je 2), bei j = 2 geht es direkt zum nächsten i. Bei i = 2 bricht schon j = 0 die äußere Schleife ab. Ergebnis 2 + 2 = 4.

  12. list.remove: Index oder Objekt? ★★☆ Collections · Overloading

    Was gibt dieses Programm aus?

    import java.util.ArrayList;
    import java.util.List;
    
    public class Main {
        public static void main(String[] args) {
            List<Integer> list = new ArrayList<>(List.of(1, 2, 3));
            list.remove(1);
            list.remove(Integer.valueOf(1));
            System.out.println(list);
        }
    }
    Lösung anzeigen

    Ausgabe:

    [3]

    remove(1) mit int-Literal ruft remove(int index) auf und entfernt das Element an Position 1, also die 2. remove(Integer.valueOf(1)) ruft remove(Object) auf und entfernt den Wert 1. Übrig bleibt [3].

  13. Integer-Cache ★★★ Autoboxing · Gleichheit

    Was gibt dieses Programm aus?

    public class Main {
        public static void main(String[] args) {
            Integer a = 127, b = 127;
            Integer c = 128, d = 128;
            System.out.println(a == b);
            System.out.println(c == d);
            System.out.println(c.equals(d));
        }
    }
    Lösung anzeigen

    Ausgabe:

    true
    false
    true

    Autoboxing nutzt Integer.valueOf, das Werte von -128 bis 127 zwischenspeichert. Darum zeigen a und b auf dasselbe Objekt, c und d auf zwei verschiedene. == vergleicht Referenzen, equals den Wert. Lehre: Wrapper-Objekte immer mit equals vergleichen.

  14. String-Pool ★★★ Strings · Gleichheit

    Was gibt dieses Programm aus?

    public class Main {
        public static void main(String[] args) {
            String s1 = "Java";
            String s2 = "Java";
            String s3 = new String("Java");
            System.out.println(s1 == s2);
            System.out.println(s1 == s3);
            System.out.println(s1 == s3.intern());
        }
    }
    Lösung anzeigen

    Ausgabe:

    true
    false
    true

    Literale landen im String-Pool, s1 und s2 sind dasselbe Objekt. new String erzeugt immer ein neues Objekt. intern() liefert die Pool-Instanz zurück, also wieder dieselbe wie s1.

  15. return im finally ★★★ Exceptions · Kontrollfluss

    Was gibt dieses Programm aus?

    public class Main {
        static int test() {
            try {
                return 1;
            } finally {
                return 2;
            }
        }
    
        public static void main(String[] args) {
            System.out.println(test());
        }
    }
    Lösung anzeigen

    Ausgabe:

    2

    finally läuft immer, auch nach einem return im try. Ein return im finally überschreibt den ursprünglichen Rückgabewert (und würde sogar eine Exception verschlucken). Kompiliert mit Warnung, gilt als schlechter Stil.

  16. Overloading mit null ★★★ Overloading

    Was gibt dieses Programm aus?

    public class Main {
        static void m(Object o) { System.out.println("Object"); }
        static void m(String s) { System.out.println("String"); }
    
        public static void main(String[] args) {
            m(null);
            Object x = "Hallo";
            m(x);
        }
    }
    Lösung anzeigen

    Ausgabe:

    String
    Object

    Beim Overloading entscheidet der Compiler zur Übersetzungszeit anhand des statischen Typs. null passt zu beiden, gewählt wird die spezifischere Methode (String). x hat den statischen Typ Object, also wird m(Object) gewählt, auch wenn zur Laufzeit ein String drinsteckt.

  17. Feld-Verdeckung vs. Überschreiben ★★★ Vererbung · Polymorphie

    Was gibt dieses Programm aus?

    public class Main {
        public static void main(String[] args) {
            A a = new B();
            System.out.println(a.name + a.getName());
        }
    }
    
    class A {
        String name = "A";
        String getName() { return name; }
    }
    
    class B extends A {
        String name = "B";
        @Override
        String getName() { return name; }
    }
    Lösung anzeigen

    Ausgabe:

    AB

    Felder sind nicht polymorph: a.name richtet sich nach dem statischen Typ A. Methoden werden dagegen zur Laufzeit dynamisch gebunden: getName() aus B liefert B.name.

  18. Statische Methoden verdecken ★★★ Vererbung · static

    Was gibt dieses Programm aus?

    public class Main {
        public static void main(String[] args) {
            Parent p = new Child();
            System.out.println(p.hello() + " " + p.who());
        }
    }
    
    class Parent {
        static String hello() { return "Parent"; }
        String who() { return "Parent"; }
    }
    
    class Child extends Parent {
        static String hello() { return "Child"; }
        String who() { return "Child"; }
    }
    Lösung anzeigen

    Ausgabe:

    Parent Child

    Statische Methoden werden nicht überschrieben, sondern nur verdeckt (hiding). Der Aufruf über eine Referenz vom Typ Parent ruft Parent.hello(). who() ist eine Instanzmethode und wird dynamisch gebunden.

  19. HashSet ohne hashCode ★★★ Collections · equals/hashCode

    Was gibt dieses Programm aus?

    import java.util.HashSet;
    import java.util.Set;
    
    public class Main {
        public static void main(String[] args) {
            Set<Point> set = new HashSet<>();
            set.add(new Point(1, 2));
            set.add(new Point(1, 2));
            System.out.println(set.size());
            System.out.println(new Point(1, 2).equals(new Point(1, 2)));
        }
    }
    
    class Point {
        int x, y;
        Point(int x, int y) { this.x = x; this.y = y; }
    
        @Override
        public boolean equals(Object o) {
            if (!(o instanceof Point)) return false;
            Point p = (Point) o;
            return x == p.x && y == p.y;
        }
    }
    Lösung anzeigen

    Ausgabe:

    2
    true

    equals ist überschrieben, hashCode nicht. HashSet sucht zuerst über hashCode den Bucket, und zwei verschiedene Objekte haben (praktisch immer) verschiedene Standard-Hashcodes. equals wird dann gar nicht erst gefragt. Regel: equals und hashCode immer gemeinsam überschreiben (oder record verwenden).

  20. Ternärer Operator und Unboxing ★★★ Autoboxing · Exceptions

    Was gibt dieses Programm aus?

    public class Main {
        public static void main(String[] args) {
            Integer i = null;
            try {
                Integer r = true ? i : 0;
                System.out.println(r);
            } catch (Exception e) {
                System.out.println(e.getClass().getSimpleName());
            }
        }
    }
    Lösung anzeigen

    Ausgabe:

    NullPointerException

    Weil ein Zweig int ist (0), wird der Ausdruck als int typisiert und i wird automatisch ausgepackt (i.intValue()). Auf null knallt das, obwohl das Ergebnis wieder einem Integer zugewiesen wird. Ein sehr realer Bug in Produktionscode.