Java-Grundlagen Teil 2 – Typen in Java verstehen

Primitives, Wrapper-Klassen, Autoboxing und die ==-Falle bei Strings: Was man über Javas Typsystem von Anfang an wissen sollte – mit Übungsaufgaben zum Selbermachen.

Java

Java-Grundlagen Teil 2 – Typen in Java verstehen

Marcel Strahl5 Min. Lesezeit

Java-Grundlagen Teil 2 – Typen in Java verstehen

Im ersten Teil ging es darum, warum Java zu Unrecht als verstaubt gilt.

Jetzt geht es richtig los. Und zwar da, wo jede Sprache anfängt: bei den Typen.

Java ist statisch typisiert – jeder Wert hat einen Typ, und der Compiler prüft ihn, bevor das Programm läuft. Das fühlt sich am Anfang streng an. In Wahrheit ist es ein Sicherheitsnetz: Eine ganze Klasse an Bugs kann gar nicht erst entstehen.


Was ist überhaupt ein Typ?

Ein Typ sagt dem Compiler, was ein Wert ist und was man damit machen darf.

42 ist eine Zahl. Damit kann man rechnen. "42" ist Text, der nur zufällig aussieht wie eine Zahl.

In Java steht der Typ direkt vor der Variablen – man kann ihn also immer ablesen:

int zahl = 42;
String text = "42";
double preis = 4.2;
boolean aktiv = true;

Und zum Ausprobieren gibt es ein unterschätztes Werkzeug, das bei jedem JDK dabei ist: jshell. Einfach im Terminal jshell eingeben, Ausdruck reintippen, Ergebnis samt Typ sehen. Beim Lernen ist das dein var_dump().

jshell> 42
$1 ==> 42
 
jshell> "42"
$2 ==> "42"

Die primitiven Typen

Java hat acht primitive Typen. Vier davon braucht man ständig:

int – Ganzzahlen

int alter = 34;
int temperatur = -5;
int jahr = 2026;

Ganze Zahlen von etwa -2,1 Milliarden bis +2,1 Milliarden. Reicht das nicht, nimmt man long.

double – Kommazahlen

double preis = 19.99;
double pi = 3.14159;

Wichtig: Der Dezimaltrenner ist ein Punkt, kein Komma – auch wenn wir im Deutschen „Kommazahl“ sagen.

Doubles haben außerdem eine fiese Falle eingebaut – dazu mehr im Zusatz am Ende des Artikels.

boolean – Wahr oder Falsch

boolean istAngemeldet = true;
boolean hatBezahlt = false;

Zwei mögliche Werte: true oder false. Das Rückgrat jeder if-Abfrage.

Anders als in PHP oder JavaScript gibt es kein „truthy“ und „falsy“: if (1) kompiliert in Java schlicht nicht. Ein if will einen boolean. Punkt.

char – ein einzelnes Zeichen

char note = 'A';
char umlaut = 'ä';

Einfache Anführungszeichen, genau ein Zeichen. Intern ist ein char übrigens eine Zahl – 'A' ist 65. Das wird in den Aufgaben noch interessant.

Die übrigen vier (byte, short, long, float) merkt man sich für später – gebraucht werden sie deutlich seltener.


String – Text ist kein Primitive

Jetzt wird es wichtig. String ist kein primitiver Typ, sondern eine Klasse:

String name = "Marcel";
String begruessung = "Hallo " + name; // "Hallo Marcel"

Man erkennt es schon an der Großschreibung. Und dieser Unterschied ist nicht kosmetisch – er führt direkt zur bekanntesten Java-Anfängerfalle:

String a = "Hallo";
String b = new String("Hallo");
 
System.out.println(a == b);      // false (!)
System.out.println(a.equals(b)); // true

== vergleicht bei Objekten die Referenz – also ob es sich um dasselbe Objekt im Speicher handelt. equals() vergleicht den Inhalt.

Deshalb die Regel ab Tag 1: Strings immer mit equals() vergleichen, nie mit ==.

Dass == bei String-Literalen manchmal trotzdem true liefert (Stichwort String-Pool), macht die Falle nur gemeiner – der Bug zeigt sich dann erst später mit echten Daten.


Wrapper-Klassen und Autoboxing

Für jeden primitiven Typ gibt es eine Klassen-Version: intInteger, doubleDouble, booleanBoolean.

Gebraucht werden die, sobald Collections ins Spiel kommen – eine List<int> gibt es nicht, nur eine List<Integer>:

List<Integer> zahlen = List.of(1, 2, 3);

Java wandelt automatisch zwischen int und Integer um. Das nennt sich Autoboxing – bequem, aber mit einer Falle:

Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true
 
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false (!)

Kein Witz. Java cached Integer-Objekte von -128 bis 127 – darüber sind es verschiedene Objekte, und == vergleicht wieder Referenzen.

Die Lektion ist dieselbe wie bei Strings: Objekte vergleicht man mit equals(). == ist für Primitives.


null – Nichts

null bedeutet: Hier ist bewusst kein Wert.

String mittlererName = null; // hat halt nicht jeder

Wichtig: Nur Objekttypen können null sein. Ein int kann nie null sein – ein Integer schon.

Und hier wohnt Javas berühmtester Fehler: die NullPointerException. Wer auf null eine Methode aufruft, bekommt sie:

String name = null;
name.length(); // NullPointerException

Die moderne Antwort darauf heißt Optional – dazu kommen wir in einem späteren Teil. Fürs Erste reicht die Regel: Immer überlegen, ob ein Wert null sein kann, bevor man Methoden darauf aufruft.


var – Typinferenz seit Java 10

Seit Java 10 darf der Compiler den Typ lokaler Variablen selbst herleiten:

var alter = 34;          // int
var name = "Marcel";     // String
var preise = new ArrayList<Double>();

Wichtig: Das ist keine dynamische Typisierung. Der Typ steht zur Compile-Zeit fest, er wird nur nicht hingeschrieben. var alter = 34; und dann alter = "vierunddreißig"; – Compilerfehler.

Mein Rat: var nutzen, wenn der Typ rechts offensichtlich ist. Sonst ausschreiben.


Typen in Methoden

In Java sind Parameter- und Rückgabetypen keine Option, sondern Pflicht – und genau das macht Methodensignaturen so lesbar:

double berechnePreisMitSteuer(double nettoPreis, double steuersatz) {
    return nettoPreis * (1 + steuersatz);
}
 
String findeBenutzername(int id) {
    return id == 1 ? "marcel" : null;
}

Was man dafür bekommt:

  • Der Code dokumentiert sich selbst
  • Die IDE kann einem wirklich helfen
  • Falsche Aufrufe scheitern beim Kompilieren, nicht beim Kunden

In PHP muss man sich declare(strict_types=1) erst angewöhnen. In Java ist strict der einzige Modus.


Aufgaben

Genug Theorie.

Am meisten hängen bleibt, wenn man es selbst tippt. jshell öffnen – oder für den Anfang reicht auch ein Online-Playground wie OneCompiler.

Aufgabe 1: Typen raten

Was gibt Java hier jeweils aus? Erst überlegen, dann in jshell ausführen.

System.out.println(10 / 3);
System.out.println(10 / 3.0);
System.out.println("5" + 3);
System.out.println('A' + 1);
System.out.println(1 == 1.0);

Aufgabe 2: Steckbrief

Lege ein record Profil(String name, int alter, List<String> hobbys, String beruf) an. Erzeuge eine Instanz und gib einen Satz aus wie: Marcel ist 34 und hat 3 Hobbys.

Tipp: hobbys().size() zählt die Einträge, String.formatted() oder + baut den Satz.

Aufgabe 3: Die ==-Falle erleben

Erzeuge zwei Strings mit gleichem Inhalt – einen als Literal, einen mit new String(...). Vergleiche beide mit == und mit equals(). Was kommt jeweils raus – und warum?

Aufgabe 4: Der Geld-Klassiker

Ein Kunde kauft drei Artikel zu je 0.10 €. Prüfe mit ==, ob die Summe 0.30 ergibt. Falls nicht: Rechne dasselbe noch einmal in Cent als int und prüfe erneut.

Aufgabe 5: Typ-Detektiv

Schreibe eine Methode String beschreibeTyp(Object wert), die für einen beliebigen Wert einen Satz zurückgibt, z. B. "Das ist ein Integer mit dem Wert 42".

Tipp: wert.getClass().getSimpleName() liefert den Typnamen. Bonusfrage: Warum kommt bei beschreibeTyp(42) „Integer“ raus und nicht „int“?


Lösungsvorschläge

Erst selbst probieren. Ehrlich. Dann vergleichen.

Lösung zu Aufgabe 1
System.out.println(10 / 3);    // 3 – int / int bleibt int, der Rest wird abgeschnitten
System.out.println(10 / 3.0);  // 3.3333333333333335 – sobald ein double dabei ist, wird double gerechnet
System.out.println("5" + 3);   // "53" – String + Zahl wird zur String-Verkettung
System.out.println('A' + 1);   // 66 – char ist intern eine Zahl, 'A' ist 65
System.out.println(1 == 1.0);  // true – der int wird für den Vergleich zum double erweitert

Vor allem 10 / 3 erwischt jeden: Java rundet nicht, es schneidet ab.

Lösung zu Aufgabe 2
import java.util.List;
 
record Profil(String name, int alter, List<String> hobbys, String beruf) {}
 
var profil = new Profil("Marcel", 34, List.of("Programmieren", "Lesen", "Gaming"), "Software-Entwickler");
 
System.out.println(
    "%s ist %d und hat %d Hobbys.".formatted(profil.name(), profil.alter(), profil.hobbys().size())
);
Lösung zu Aufgabe 3
String a = "Hallo";
String b = new String("Hallo");
 
System.out.println(a == b);      // false – zwei verschiedene Objekte im Speicher
System.out.println(a.equals(b)); // true  – gleicher Inhalt
 
String c = "Hallo";
System.out.println(a == c);      // true – beide Literale zeigen auf denselben Eintrag im String-Pool

Genau wegen des letzten Falls ist == bei Strings so tückisch: Es funktioniert manchmal. equals() funktioniert immer.

Lösung zu Aufgabe 4
double summe = 0.10 + 0.10 + 0.10;
System.out.println(summe == 0.30); // false – Float-Rundungsfehler
System.out.println(summe);         // 0.30000000000000004
 
int summeInCent = 10 + 10 + 10;
System.out.println(summeInCent == 30); // true – deshalb Geld immer in Cent als int (oder BigDecimal)
Lösung zu Aufgabe 5
String beschreibeTyp(Object wert) {
    return "Das ist ein %s mit dem Wert %s".formatted(wert.getClass().getSimpleName(), wert);
}
 
System.out.println(beschreibeTyp(42));      // Das ist ein Integer mit dem Wert 42
System.out.println(beschreibeTyp("42"));    // Das ist ein String mit dem Wert 42
System.out.println(beschreibeTyp(true));    // Das ist ein Boolean mit dem Wert true
System.out.println(beschreibeTyp(4.2));     // Das ist ein Double mit dem Wert 4.2

Zur Bonusfrage: Ein int passt nicht in einen Object-Parameter – Java boxt ihn deshalb automatisch in ein Integer. Genau das Autoboxing aus dem Artikel.


Zusatz: Die Double-Falle

Und jetzt kommt eine Falle, in die wirklich jeder einmal reinläuft:

System.out.println(0.1 + 0.2 == 0.3); // false
System.out.println(0.1 + 0.2);        // 0.30000000000000004

Ja, wirklich. false.

Doubles sind intern binär gespeichert und dadurch nicht beliebig genau. Genau deshalb rechnet man in echten Projekten Geldbeträge niemals mit double, sondern in Cent als int/long – oder mit BigDecimal, wenn es um krumme Sätze wie Zinsen geht.

Das ist keine Theorie. Das ist die Sorte Bug, die irgendwann in einer Rechnung landet.


Wie geht es weiter?

Das waren die Bausteine, aus denen jedes Java-Programm besteht.

Im nächsten Teil geht es um Kontrollstrukturen – if, switch Expressions, Schleifen. Also darum, wie ein Programm Entscheidungen trifft.

  • 5 Min. LesezeitPHP

    PHP-Grundlagen Teil 2 – Typen in PHP verstehen

    Welche Datentypen es in PHP gibt, wofür man sie wirklich braucht und warum strict_types von Anfang an gesetzt gehört – mit Übungsaufgaben zum Selbermachen.

    • #PHP
    • #Grundlagen
    • #Tutorial
    • #Software-Entwicklung
    • #Webentwicklung
    • #Backend
  • 5 Min. LesezeitC#

    C#-Grundlagen Teil 2 – Typen in C# verstehen

    Value Types, Reference Types, Nullable Reference Types und warum decimal für Geld gemacht ist: die Grundlagen des C#-Typsystems – mit Übungsaufgaben zum Selbermachen.

    • #C#
    • #Grundlagen
    • #Tutorial
    • #Software-Entwicklung
    • #Backend
  • 5 Min. LesezeitKotlin

    Kotlin-Grundlagen Teil 2 – Typen in Kotlin verstehen

    val vs. var, Typinferenz, Null-Safety und warum Kotlin nichts implizit umwandelt: die Grundlagen des Kotlin-Typsystems – mit Übungsaufgaben zum Selbermachen.

    • #Kotlin
    • #Grundlagen
    • #Tutorial
    • #Software-Entwicklung
    • #Backend