Programmieren lernen mit Kotlin - Christian Kohls - E-Book

Programmieren lernen mit Kotlin E-Book

Christian Kohls

0,0

Beschreibung

Fundierter Einstieg in die objektorientierte Programmierung mit Kotlin
- Zahlreiche Praxisbeispiele, Erklärbilder und anschauliche Alltagsmetaphern
- Durchstarten ohne Vorkenntnisse und eigene Apps entwickeln
- Vermittelt Hintergrundwissen und wie man guten Code gestaltet
- Quellcode und Zusatzmaterial unter plus.hanser-fachbuch.de
- Ihr exklusiver Vorteil: E-Book inside beim Kauf des gedruckten Buches

Steigen Sie ein in die funktionale und objektorientierte Programmierung mit Kotlin. Das Buch richtet sich an Studierende und Quereinsteiger, die erstmalig eine Programmiersprache lernen. Kotlin eignet sich sehr gut als Anfängersprache: Erste Erfolge werden schnell erzielt und der Code ist kurz, präzise, leicht verständlich und robust. Gleichzeitig erlaubt Kotlin die professionelle Entwicklung und die Umsetzung umfangreicher Software-Architekturen.
Das Buch erklärt anschaulich die Grundlagen des Programmierens, z. B. Variablen, Ausdrücke, Kontrollstrukturen und Funktionen. Objektorientierte Konzepte wie Abstraktion, Vererbung, Polymorphie, Kapselung und Komposition werden anhand von praktischen Beispielen eingeführt. In den vertiefenden Abschnitten lernen Sie Android-Apps umzusetzen, Algorithmen und Datenstrukturen selber zu implementieren, z. B. verkettete Listen, und das Entwickeln mit Coroutinen. Anhand eines durchgehenden Beispiels entwickeln Sie ein Simulationsspiel für Android.

Sie lesen das E-Book in den Legimi-Apps auf:

Android
iOS
von Legimi
zertifizierten E-Readern

Seitenzahl: 809

Das E-Book (TTS) können Sie hören im Abo „Legimi Premium” in Legimi-Apps auf:

Android
iOS
Bewertungen
0,0
0
0
0
0
0
Mehr Informationen
Mehr Informationen
Legimi prüft nicht, ob Rezensionen von Nutzern stammen, die den betreffenden Titel tatsächlich gekauft oder gelesen/gehört haben. Wir entfernen aber gefälschte Rezensionen.



Christian KohlsAlexander Dobrynin

Programmieren lernen mit Kotlin

Grundlagen, Objektorientierung und fortgeschrittene Konzepte

2., aktualisierte Ausgabe

Ihr Plus – digitale Zusatzinhalte!

Auf unserem Download-Portal finden Sie zu diesem Titel kostenloses Zusatzmaterial.

Geben Sie auf plus.hanser-fachbuch.de einfach diesen Code ein:

plus-rn34m-tL9pr

Die Autoren:Prof. Dr. Christian Kohls, KölnAlexander Dobrynin, GummersbachKotlin ist ein eingetragenes Warenzeichen der Kotlin Foundation

Alle in diesem Werk enthaltenen Informationen, Verfahren und Darstellungen wurden nach bestem Wissen erstellt und mit Sorgfalt getestet. Dennoch sind Fehler nicht ganz auszuschließen. Aus diesem Grund sind die im vorliegenden Werk enthaltenen Informationen mit keiner Verpflichtung oder Garantie irgendeiner Art verbunden. Autoren und Verlag übernehmen infolgedessen keine Verantwortung und werden keine daraus folgende oder sonstige Haftung übernehmen, die auf irgendeine Art aus der Benutzung dieses Programm-Materials – oder Teilen davon – entsteht. Ebensowenig übernehmen Autoren und Verlag die Gewähr dafür, dass beschriebene Verfahren usw. frei von Schutzrechten Dritter sind. Die Wiedergabe von Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. in diesem Werk berechtigt also auch ohne besondere Kennzeichnung nicht zu der Annahme, dass solche Namen im Sinne der Warenzeichen- und Markenschutz-Gesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften.Die endgültige Entscheidung über die Eignung der Informationen für die vorgesehene Verwendung in einer bestimmten Anwendung liegt in der alleinigen Verantwortung des Nutzers.

Aus Gründen der besseren Lesbarkeit wird auf die gleichzeitige Verwendung der Sprachformen männlich, weiblich und divers (m/w/d) verzichtet. Sämtliche Personenbezeichnungen gelten gleichermaßen für alle Geschlechter.

Bibliografische Information der Deutschen Nationalbibliothek:Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar.

Dieses Werk ist urheberrechtlich geschützt.Alle Rechte, auch die der Übersetzung, des Nachdruckes und der Vervielfältigung des Buches, oder Teilen daraus, vorbehalten. Kein Teil des Werkes darf ohne schriftliche Genehmigung des Verlages in irgendeiner Form (Fotokopie, Mikrofilm oder ein anderes Verfahren), auch nicht für Zwecke der Unterrichtsgestaltung – mit Ausnahme der in den §§ 53, 54 URG genannten Sonderfälle –, reproduziert oder unter Verwendung elektronischer Systeme verarbeitet, vervielfältigt oder verbreitet werden.

© 2023 Carl Hanser Verlag München, http://www.hanser-fachbuch.deLektorat: Sylvia HasselbachCopy editing: Jürgen Dubau, Freiburg/ElbeLayout: le-tex publishing services GmbHCoverkonzept: Marc Müller-Bremer, www.rebranding.de, MünchenTitelmotiv und Umschlagrealisation: Max Kostopoulos

Print-ISBN:        978-3-446-47712-4E-Book-ISBN:   978-3-446-47849-7E-Pub-ISBN:     978-3-446-48005-6

Inhalt

Titelei

Impressum

Inhalt

Vorwort

1 Einführung

1.1 Eine Sprache für viele Plattformen

1.2 Deshalb ist Kotlin so besonders

1.3 Darauf dürfen Sie sich freuen

Teil I: Konzeptioneller Aufbau von Computern und Software

2 Komponenten eines Computers

2.1 Beliebige Daten als binäre Zahlen

2.2 Wie Zahlen in Texte, Bilder und Animationen umgewandelt werden

2.3 Zahlen als ausführbarer Code

3 Zugriff auf den Speicher

3.1 Organisation des Speichers

3.2 Daten im Speicher und Datenverarbeitung im Prozessor

3.3 Heap und Stack

3.4 Programme als Code schreiben statt als Zahlenfolgen

4 Interpreter und Compiler

4.1 Virtuelle Maschinen, Bytecode und Maschinencode

4.2 Kotlin – eine Sprache, viele Plattformen

5 Syntax, Semantik und Pragmatik

5.1 Syntax

5.2 Semantik

5.3 Pragmatik

6 Eingabe – Verarbeitung – Ausgabe

7 Los geht’s

7.1 Integrierte Entwicklungsumgebung

7.2 Projekt anlegen

Teil II: Grundlagen des Programmierens

8 Anweisungen und Ausdrücke

8.1 Ausdrücke

8.1.1 Literale

8.1.2 Operationen

8.1.3 Variablen und Funktionsaufrufe

8.2 Evaluation von Ausdrücken

8.2.1 Evaluieren von Operatoren

8.2.2 Evaluieren von Funktionen

8.2.3 Evaluieren von Variablen

8.3 Zusammenspiel von Werten und Typen

8.3.1 Typprüfungen durch den Compiler

8.3.2 Typen als Bausteine

9 Basis-Datentypen

9.1 Numerics

9.2 Characters und Strings

9.3 Booleans

9.4 Arrays

9.5 Unit

9.6 Any

9.7 Nothing

9.8 Zusammenfassung

10 Variablen

10.1 Deklaration, Zuweisung und Verwendung

10.2 Praxisbeispiel

10.2.1 Relevante Informationen extrahieren

10.2.2 Das Problem im Code lösen

10.2.3 Zusammenfassung

11 Kontrollstrukturen

11.1 Fallunterscheidungen mit if

11.1.1 if-Anweisung

11.1.2 if-Ausdruck

11.2 Pattern-Matching mit when

11.2.1 Interpretieren von Werten

11.2.2 Typüberprüfungen

11.2.3 Überprüfen von Wertebereichen

11.2.4 Abbilden von langen if-else-Blöcken

11.3 Wiederholung von Code mit while-Schleifen

11.3.1 Zählen, wie oft etwas passiert

11.3.2 Gameloop

11.4 Iterieren über Datenstrukturen mit for-Schleifen

11.4.1 Iteration mit Arrays

11.4.2 Iteration mit Ranges

11.4.3 Geht das alles nicht auch mit einer while-Schleife?

11.5 Zusammenfassung

12 Funktionen

12.1 Top-Level- und Member-Functions

12.2 Funktionsaufrufe (Applikation)

12.3 Syntax

12.4 Funktionsdefinition (Deklaration)

12.5 Funktionen als Abstraktion

12.6 Scoping

12.7 Rekursive Funktionen

12.7.1 Endlose Rekursion

12.7.2 Terminierende Rekursion

12.7.3 Rekursion vs. Iteration

12.7.4 Von Iteration zur Rekursion

12.8 Shadowing von Variablen

12.9 Inline-Funktionen

12.10 Pure Funktionen und Funktionen mit Seiteneffekt

12.10.1 Das Schlechte an Seiteneffekten

12.10.2 Ohne kommen wir aber auch nicht aus

12.10.3 Was denn nun?

12.11 Die Ideen hinter Funktionaler Programmierung

12.12 Lambdas

12.13 Closures

12.14 Funktionen höherer Ordnung

12.14.1 Funktionen, die Funktionen als Parameter akzeptieren

12.14.2 Funktionen, die Funktionen zurückgeben

12.15 Zusammenfassung

12.16 Das war’s

Teil III: Objektorientierte Programmierung

13 Was sind Objekte?

14 Klassen

14.1 Eigene Klassen definieren

14.2 Konstruktoren

14.2.1 Aufgaben des Konstruktors

14.2.2 Primärer Konstruktor

14.2.3 Parameter im Konstruktor verwenden

14.2.4 Initialisierungsblöcke

14.2.5 Klassen ohne expliziten Konstruktor

14.2.6 Zusätzliche Eigenschaften festlegen

14.2.7 Klassen mit sekundären Konstruktoren

14.2.8 Default Arguments

14.2.9 Named Arguments

14.3 Funktionen und Methoden

14.3.1 Objekte als Parameter

14.3.2 Methoden: Funktionen auf Objekten ausführen

14.3.3 Von Funktionen zu Methoden

14.4 Datenkapselung

14.4.1 Setter und Getter

14.4.2 Berechnete Eigenschaften

14.4.3 Methoden in Eigenschaften umwandeln

14.4.4 Sichtbarkeitsmodifikatoren

14.5 Spezielle Klassen

14.5.1 Daten-Klassen

14.5.2 Enum-Klassen

14.5.3 Singuläre Objekte

14.5.4 Daten-Objekte

14.6 Verschachtelte Klassen

14.6.1 Statische Klassen

14.6.2 Innere Klassen

14.6.3 Lokale innere Klassen

14.6.4 Anonyme innere Objekte

14.7 Inline-Value-Klassen

14.8 Klassen und Objekte sind Abstraktionen

14.9 Zusammenfassung

15 Movie Maker – Ein Simulationsspiel

15.1 Überlegungen zur Klassenstruktur

15.1.1 Eigenschaften und Methoden von Movie

15.1.2 Eigenschaften und Methoden von Director

15.1.3 Eigenschaften und Methoden von Actor

15.1.4 Genre als Enum

15.1.5 Objektstruktur

15.2 Von der Skizze zum Programm

15.2.1 Movie-Maker-Projekt anlegen

15.2.2 Genre implementieren

15.2.3 Actor und Director implementieren

15.2.4 Erfahrungszuwachs bei Fertigstellung eines Films

15.3 Komplexe Objekte zusammensetzen

15.3.1 Skills als eine Einheit zusammenfassen

15.3.2 Begleit-Objekt für Skills

15.3.3 Objektkomposition und Objektaggregation

15.3.4 Zusammensetzung der Klasse Movie

15.3.5 Film produzieren

15.3.6 Ein Objekt für Spieldaten

15.3.7 Code zum Projekt

Teil IV: Vererbung und Polymorphie

16 Vererbung

16.1 Vererbungsbeziehung

16.2 Klassenhierarchien

16.3 Eigenschaften und Methoden vererben

16.4 Zusammenfassung

17 Polymorphie

17.1 Überschreiben von Methoden

17.1.1 Eine Methode unterschiedlich überschreiben

17.1.2 Dynamische Bindung

17.1.3 Überschreiben eigener Methoden

17.1.4 Überladen von Methoden

17.2 Typen und Klassen

17.2.1 Obertypen und Untertypen

17.2.2 Generalisierung und Spezialisierung

17.2.3 Typkompatibilität

17.2.4 Upcast und Downcast

17.2.5 Vorsicht bei der Typinferenz

17.2.6 Smart Casts

18 Abstrakte Klassen und Schnittstellen

18.1 Abstrakte Klassen

18.2 Schnittstellen

18.2.1 Schnittstellen definieren

18.2.2 Schnittstellen implementieren

18.2.3 Schnittstellen für polymorphes Verhalten

18.2.4 Standardverhalten für Interfaces

18.2.5 SAM-Interfaces

18.2.6 Mehrere Interfaces implementieren

18.3 Alles sind Typen

18.4 Zusammenfassung

Teil V: Robustheit

19 Nullfähigkeit

19.1 Nullfähige Typen

19.1.1 Typen nullfähig machen

19.1.2 Optional ist ein eigener Typ

19.2 Sicherer Zugriff auf nullfähige Typen

19.2.1 Überprüfen auf null

19.2.2 Safe Calls

19.2.3 Verkettung von Safe Calls

19.3 Nullfähige Typen auflösen

19.3.1 Überprüfen mit if-else

19.3.2 Der Elvis-Operator rockt

19.3.3 Erzwungenes Auflösen

20 Exceptions

20.1 Sowohl Konzept als auch eine Klasse

20.2 Beispiele für Exceptions

20.2.1 ArrayIndexOutOfBoundsException

20.2.2 ArithmeticException

20.3 Exceptions aus der Java-Bibliothek

20.4 Exceptions auffangen und behandeln

20.4.1 Schreiben in eine Datei

20.4.2 Metapher: Balancieren über ein Drahtseil

20.5 Exceptions werfen

20.6 Exceptions umwandeln

20.6.1 Von Exception zu Optional

20.6.2 Von Optional zu Exception

20.6.3 Exceptions vs. Optionals

20.7 Exceptions weiter werfen

20.8 Sinn und Zweck von Exceptions

21 Movie Maker als Konsolenspiel umsetzen

21.1 Die Gameloop

21.2 Einen neuen Film produzieren

21.3 Statistik anzeigen

22 Entwurfsmuster

22.1 Das Strategiemuster

22.1.1 Im Code verstreute Fallunterscheidungen mit when

22.1.2 Probleme des aktuellen Ansatzes

22.1.3 Unterschiedliche Strategien für die Ausgabe

22.1.4 Nutzen der Strategie

22.2 Das Dekorierermuster

22.2.1 Probleme des gewählten Ansatzes

22.2.2 Dekorierer für Komponenten

22.2.3 Umsetzung des Dekorierers

22.2.4 Nutzen des Dekorierers

22.3 Weitere Entwurfsmuster

23 Debugger

Teil VI: Datensammlungen und Collections

24 Überblick

24.1 Pair und Triple

24.1.1 Verwendung

24.1.2 Syntaktischer Zucker

24.1.3 Destructuring

24.1.4 Einsatzgebiete

24.2 Arrays

24.2.1 Direkter Datenzugriff

24.2.2 Arrays mit null-Referenzen

24.2.3 Arrays mit primitiven Daten

24.2.4 Arrays vs. Listen

24.3 Listen

24.3.1 Unveränderliche Listen

24.3.2 Veränderliche Listen

24.3.3 List und MutableList sind verwandte Schnittstellen

24.4 Sets

24.4.1 Sets verwenden

24.4.2 Mengen-Operationen

24.5 Maps

24.5.1 Maps erzeugen

24.5.2 Arbeiten mit Maps

24.5.3 Maps durchlaufen

25 Funktionen höherer Ordnung für Datensammlungen

25.1 Unterschiedliche Verarbeitung von Listen

25.1.1 Imperative Verarbeitung von Listen

25.1.2 Funktionale Verarbeitung von Listen

25.1.3 Funktionen als kombinierbare Arbeitsanleitungen

25.1.4 Aufbau von Funktionen höherer Ordnung am Beispiel von map

25.2 Hilfreiche Funktionen für Datensammlungen

25.3 Anwendungsbeispiele für Funktionen höherer Ordnung

25.4 Sequenzen

25.4.1 Eager Evaluation – viel zu fleißig

25.4.2 Lazy Evaluation – Daten bei Bedarf verarbeiten

25.4.3 Sequenzen verändern die Reihenfolge

25.4.4 Fleißig oder faul – was ist besser?

26 Invarianz, Kovarianz und Kontravarianz

26.1 Typsicherheit durch Typ-Parameter

26.1.1 Invarianz

26.1.2 Die Grenzen von Invarianz

26.1.3 Kovarianz

26.1.4 Kontravarianz

26.2 Invarianz, Kovarianz und Kontravarianz im Vergleich

27 Listen selbst implementieren

27.1 Was ist eine Liste?

27.1.1 Unterschiedliche Listen als konkrete Formen

27.1.2 Eine Schnittstelle für alle möglichen Listen

27.1.3 Typ-Parameter selbst definieren (Generics)

27.1.4 Verschiedene Implementierungen derselben Schnittstelle

27.2 Implementierung der SimpleList durch Delegation

27.3 Implementierung der SimpleList mit Arrays

27.3.1 Datenstruktur

27.3.2 Direkte Abbildung der Listen-Operationen auf ein Array

27.3.3 Listen-Operationen mit aufwendiger Laufzeit bei Arrays

28 Verkettete Listen

28.1 Basisstruktur der verketteten Liste

28.2 Implementierung der verketteten Liste

28.3 Umsetzung der Funktionen

28.3.1 Einfügen am Anfang einer verketteten Liste

28.3.2 Zugriff auf das erste Element der verketteten Liste

28.3.3 Zugriff auf das letzte Element der verketteten Liste

28.3.4 Allgemeines Schema zum Durchlaufen einer verketteten Liste

28.3.5 Elemente der verketteten Liste zählen

28.3.6 Zugriff auf das n-te Element

28.3.7 Die verbleibenden Methoden implementieren

28.4 Über alle Listenelemente iterieren

28.4.1 Die Schnittstelle Iterable

28.4.2 Iterator implementieren

28.4.3 Iterator verwenden

28.4.4 Interne Iteration

29 Testen und Optimieren

29.1 Korrektheit von Programmen

29.2 Testfälle in JUnit schreiben

29.2.1 Assertions

29.2.2 Implementierung der Liste testen

29.3 Teste zuerst

29.4 Klasseninvariante

29.4.1 Alternative Implementierung von size() für die verkettete Liste

29.4.2 Gewährleistung eines gültigen Zustands

30 Optimierung und Laufzeiteffizienz

30.1 Laufzeit empirisch ermitteln

30.2 Laufzeit theoretisch einschätzen

30.3 Die O-Notation

30.4 Praktische Beispiele für die O-Notation

31 Unveränderliche verkettete Liste

31.1 Datenstruktur für die unveränderliche Liste

31.1.1 Fallunterscheidung durch dynamische Bindung

31.1.2 Explizite Fallunterscheidung innerhalb der Funktion

31.1.3 Neue Listen erzeugen statt Liste verändern

31.1.4 Hilfsfunktionen über Companion-Objekt bereitstellen

31.2 Rekursive Implementierungen

31.2.1 map und fold als rekursive Implementierung

31.2.2 forEach und Endrekursion

Teil VII: Android

32 Android Studio

32.1 Erstellen eines Projekts

32.2 Aufbau von Android Studio

32.3 Funktionsweise einer Android-App

32.3.1 MainActivity

32.3.2 Context

32.3.3 Manifest und Gradle-Skripte

32.4 Projektstruktur einer Android-App

32.5 Theming

32.6 Preview

33 Jetpack Compose

33.1 Deklarative UI-Entwicklung

33.2 Composable-Functions

33.3 Layout

33.4 State-Management

33.4.1 MutableState

33.4.2 Die remember-Funktion

33.4.3 State-Hoisting

33.5 Modifier

33.6 App-Architektur

33.6.1 UI-Layer

33.6.2 Data-Layer

33.6.3 Unidirectional-Data-Flow

33.6.4 Lokaler State

33.6.5 Observable-Types

33.7 Composition und Recomposition

33.7.1 Composition-Phase

33.7.2 Layout-Phase

33.7.3 Drawing-Phase

33.8 Persistenz

34 Entwicklung der Movie-Maker-App

34.1 Setup

34.2 ViewModel und DataStore

34.3 Start-Screen

34.3.1 Scaffold

34.3.2 Budget-Screen

34.3.3 Top-Bar

34.4 Produce-Movie-Screen

34.4.1 TitleTextfield

34.4.2 Actor-Pager

34.4.3 Budget-Slider

34.4.4 State-Hoisting

34.4.5 Produce-Movie-Button

34.5 Movie-Produced-Screen

34.6 Movie-Production-Error-Screen

34.7 Navigation

Teil VIII: Nebenläufigkeit

35 Grundlagen

35.1 Threads

35.1.1 Nicht-determinierter Ablauf

35.1.2 Schwergewichtige Threads

35.2 Koroutinen (Coroutines)

35.2.1 Koroutine vs. Subroutine

35.2.2 Coroutines vs. Threads

35.3 Zusammenfassung der Konzepte

36 Coroutines verwenden

36.1 Nebenläufige Begrüßung

36.1.1 Koroutine im Global Scope starten

36.1.2 Mehrere Koroutinen nebenläufig starten

36.1.3 Künstliche Wartezeit einbauen mit sleep

36.1.4 Informationen über den aktuellen Thread

36.2 Blockieren und Unterbrechen

36.2.1 Mehrere Koroutinen innerhalb von runBlocking starten

36.2.2 Zusammenspiel von Threads

36.3 Arbeit auf Threads verteilen

36.4 Jobs

36.5 Nebenläufigkeit auf dem main-Thread

36.5.1 Zusammenspiel von blockierenden und unterbrechenden Abschnitten

36.5.2 Abwechselnde Ausführung

36.6 Strukturierte Nebenläufigkeit mit Coroutine Scopes

36.7 runBlocking fürmain

36.8 Suspending Functions

36.8.1 Unterbrechen und Fortsetzen – Behind the scenes

36.8.2 Eigene Suspending Functions schreiben

36.8.3 Async

36.8.4 Strukturierte Nebenläufigkeit mit Async

36.8.5 Auslagern langläufiger Berechnungen

36.9 Dispatcher

36.9.1 Dispatcher festlegen

36.9.2 Wichtige Dispatcher für Android

37 Wettlaufbedingungen

37.1 Beispiel: Bankkonto

37.1.1 Auftreten einer Wettlaufbedingung

37.1.2 Unplanbare Wechsel zwischen Threads

37.2 Vermeidung von Wettlaufbedingungen

37.2.1 Threadsichere Datenstrukturen

37.2.2 Thread-Confinement

37.2.3 Kritische Abschnitte

38 Deadlocks

39 Aktoren

40 Da geht noch mehr

40.1 Infix-Notation

40.2 Operatoren überladen

40.3 Scope-Funktionen

40.3.1 apply-Funktion

40.3.2 let-Funktion

40.3.3 also-Funktion

40.3.4 Unterschiede der Scope-Funktionen

40.3.5 with-Funktion

40.4 Extension Functions

40.5 Weitere Informationsquellen

Vorwort

Kotlin ist inzwischen als Programmiersprache etabliert. Der Großteil aller professionellen Apps im Google-Play-Store ist in Kotlin entwickelt, und Studien von Google zeigen, dass Kotlin-Code robuster läuft. Zudem ist die Entwicklung im Vergleich zu Java sehr viel produktiver, sodass Kotlin schon aus ökonomischer Sicht viele Vorteile bietet. Vor allem aber: Kotlin macht Spaß und führt zu eleganterem Code.

Mit diesem Buch können Sie ohne Vorkenntnisse in die Programmierung einsteigen. Dabei werden Sie verschiedene Ansätze kennenlernen und praktisch anwenden. Nach der Lektüre des Buches können Sie kleinere Softwareprojekte entwickeln, also zum Beispiel eigene Ideen umsetzen, Aufgaben und Problemstellungen verstehen und lösen sowie Softwarespezifikationen in lauffähige Programme überführen. Sie können einfache Algorithmen selbst entwickeln und Standardalgorithmen und Datenstrukturen umsetzen. Sie können Apps für Android-Systeme entwickeln oder Programme für Server und Desktop-Rechner schreiben.

Die Welt des Programmcodes ist unsichtbar. Wir haben festgestellt, dass einige Konzepte besonders schwer zu begreifen sind und dass oft falsche Vorstellungen existieren. Es wurde daher großer Wert darauf gelegt, möglichst viele Konzepte mit Metaphern, praktischen Anwendungsbeispielen und Bildern zu veranschaulichen. Dabei bauen wir auf unseren langjährigen Erfahrungen in der Programmierausbildung auf. Am Ende des Buches können Sie Apps mit einer grafischen Benutzerschnittstelle entwickeln und aus unsichtbarem Code eine visuell ansprechende App entwickeln.

Dieses Buch richtet sich vor allem an Einsteiger und Anfänger. Es werden keine Vorkenntnisse vorausgesetzt. Gleichzeitig denken wir, dass auch fortgeschrittene Entwickler und Umsteiger von anderen Programmiersprachen von diesem Buch profitieren werden.

Hilfestellung bei der Umsetzung von Kotlin-Programmen bietet inzwischen auch der Online-Dienst ChatGPT. Sie können ChatGPT bitten, Algorithmen zu schreiben, Code zu überprüfen, Fehler zu finden und Codeabschnitte zu erklären. Das funktioniert oft sehr gut, aber nicht selten erfindet ChatGPT Lösungen, die zwar richtig aussehen, aber leider falsch sind. Daher raten wir zu einem vorsichtigen Umgang mit diesem Werkzeug. Zur Unterstützung beim Lernen ist ChatGPT sicherlich geeignet. Einzelne Codeabschnitte oder Konzepte können Sie sich von diesem Chatbot ausführlich erklären lassen. Bei einfachen Algorithmen funktioniert dies gut. Bei komplexeren Programmen kommt ChatGPT aber noch durcheinander, liefert unvollständige und eben auch falsche Lösungen.

In dieser überarbeiteten Auflage haben wir einige neue Sprachkonzepte aufgenommen und vor allem das Kapitel zur Android-App-Entwicklung vollständig überarbeitet. In der ersten Auflage wurden die Layouts noch mit XML-Dateien beschrieben. In der nun vorliegenden Auflage geschieht die Entwicklung vollständig mit Jetpack Compose. Dieses Framework hat sich inzwischen für die Entwicklung von Android-Apps durchgesetzt.

Alle Codebeispiele und zusätzliche Übungsaufgaben finden Sie im Download-Portal von Hanser-Plus: Geben Sie auf

plus.hanser-fachbuch.de

diesen Zugangscode ein:

plus-rn34m-tL9pr

Unserem Ko-Autor der ersten Auflage, Florian Leonhard, möchten wir besonders danken für die gemeinsame Entwicklung und Umsetzung des Buchkonzepts. Für den fachlichen Austausch möchten wir uns bei unseren Teamkollegen an der TH Köln bedanken. Insbesondere bei David Petersen, der wesentliche Inspirationen zu diesem Buch beigetragen hat. Für intensives Feedback und fachlichen Austausch danken wir Anja Bertels, Dominik Deimel und Dennis Dubbert. Kotlin macht Spaß und mit euch zusammen besonders viel.

Und nun wünschen wir auch Ihnen viel Spaß beim Coden und Entwickeln!

Christian Kohls, Alexander Dobrynin

Im Juli 2023

1Einführung

Kotlin ist eine Programmiersprache, die verschiedene Ansätze verfolgt. Sie erlaubt objektorientierte und funktionale Programmierung. Das Entwickeln mit objektorientierten Programmiersprachen gehört heute zu den Grundanforderungen aller Informatikerinnen und Informatiker. Es gibt Ihnen die Möglichkeit, eigene Projektideen umzusetzen, betriebliche Prozesse zu modellieren und nützliche Werkzeuge für Anwender zu entwickeln. Die objektorientierte Herangehensweise schärft zudem Ihre analytischen und ganzheitlichen Denkfähigkeiten. Denn Sie müssen Sachverhalte differenziert betrachten, Theorien und Modelle entwickeln, Prozesse analysieren und optimieren, Probleme ganzheitlich betrachten und abwägen, Probleme in Teilprobleme zerlegen, die (Anwendungs-) Story verstehen und erzählen können. Zudem erlernen Sie ein systematisches Vorgehen für Problemlöseaufgaben und damit kreative Denkweisen. Objektorientierte Denker sind Weltversteher und Weltveränderer!

Weltversteher, weil sie die wahrgenommene Welt der Situation entsprechend und der Aufgabe angemessen in Klassen und Objekte, Zustandsbeschreibungen und Verhaltensweisen, Rollen und Beziehungen abbilden können. Weltveränderer, weil gute (und leider auch schlechte) Software unmittelbaren Einfluss auf den Lebensalltag der Anwender hat. Denken Sie nur daran, was mit Software alles möglich ist. Und bald werden Sie die nächste großartige App entwickeln! Während Objekte unsere Welt besonders gut abbilden, ist der Vorteil von Funktionen, dass sie Rechenregeln und Transformationen mit mathematischer Präzision festlegen können. Funktionale Programmieransätze bauen darauf auf und unterstützen uns dabei, Funktionalitäten miteinander zu verknüpfen, zu testen und wiederzuverwenden. Kotlin vereint diese und andere Konzepte zu einer modernen Sprache.

Kotlin ist eine noch recht junge Programmiersprache, die sich jedoch rasant durchsetzt. Fast alle kommerziell erfolgreichen Android-Apps werden derzeit in Kotlin entwickelt. Und das, obwohl Kotlin der Öffentlichkeit erst im Jahr 2011 erstmals vorgestellt wurde. Eine stabile Version gibt es seit 2016. Inzwischen hat Kotlin die Version 1.9 erreicht. Diese Version wurde am 6. Juli 2023 veröffentlicht, also kurz vor Redaktionsschluss dieses Buches. Wir haben die Neuerungen bereits in diesem Buch berücksichtigt und weisen an einzelnen Stellen darauf hin, wenn eine bestimmte Kotlin-Version benötigt wird. Das nächste größere Release wird Kotlin 2.0 sein.

Wir haben alle Kotlin-Entwicklerkonferenzen (KotlinConf) der letzten Jahre besucht und sind von der großen und aktiven Entwicklercommunity sehr begeistert. Neue Sprachfeatures werden mit der Community diskutiert, und Google bevorzugt Kotlin nicht nur als Entwicklungssprache für die Android-Plattform, sondern stellt selbst große Teile des eigenen Codes auf Kotlin um. Viele bekannte Firmen setzen auf Kotlin und teilen auf der KotlinConf ihre Erfahrungen. Damit ist Kotlin im Gegensatz zu vielen anderen neuen Sprachen keine „akademische Sprache“, die nur für die Hochschullehre interessant ist. Ganz im

Gegenteil: Immer mehr Firmen, Entwickler und Open-Source-Projekte wechseln zu Kotlin, da sich mit dieser Sprache Lösungen schneller und sicherer entwickeln lassen. Mit Jetpack Compose steht zudem ein sehr guter und moderner Ansatz für die Entwicklung grafischer Benutzeroberflächen bereit, mit dem sich nicht nur mobile Apps sehr schnell umsetzen lassen, sondern auch Desktop-Anwendungen für Ihren Mac oder PC.

Kotlin wurde von der Firma JetBrains entwickelt und steht unter einer Open Source-Lizenz. JetBrains ist ein führender Anbieter für integrierte Entwicklungsumgebungen wie z. B. IntelliJ. Bei JetBrains hat man irgendwann festgestellt, dass die Entwicklung mit der Programmiersprache Java an vielen Stellen zu aufwendig ist. Kotlin wurde basierend auf dem Erfahrungswissen, was effiziente Softwareentwickler wirklich benötigen, konzipiert. Kotlin vereint dabei viele erprobte Konzepte aus den letzten Jahrzehnten. Kotlin setzt konsequent auf Mechanismen, die sich bewährt haben und die in der Programmierpraxis zu einer erhöhten Produktivität führen – und dadurch letztlich mehr Freude bereitet. Ja, Kotlin macht Spaß!

1.1Eine Sprache für viele Plattformen

Mit Kotlin können Sie für viele verschiedene Plattformen entwickeln.

Android: Kotlin ist die primäre Entwicklungssprache für die Android-Plattform von Google. Damit hat sie eine hohe Relevanz, da sich Apps für Android mit Kotlin schneller, besser und leichter entwickeln lassen.

JVM: Programme, die in Kotlin geschrieben werden, können für die Java-Plattform kompiliert werden. Das heißt: Die Kotlin-Programme werden in Java-Bytecode übersetzt und laufen dann auf jeder Java Virtuellen Maschine (JVM). Diese JVMs gibt es für verschiedene Betriebssysteme. Ihre Programme können also auf verschiedenen Rechnersystemen ausgeführt werden.

JavaScript: Kotlin kann aber auch nach JavaScript übersetzt werden. Das heißt: Sie können die Programmierung von Webanwendungen auch mit Kotlin durchführen. Dies gilt sowohl für die Programmierung des Clients (also JavaScript, das im Browser ausgeführt wird) als auch des Servers (also JavaScript, das auf dem Server ausgeführt wird).

Kotlin Native: Darüber hinaus gibt es mit Kotlin Native den Ansatz, ein Kotlin-Programm direkt für eine bestimmte Rechnerarchitektur zu kompilieren. Es wird also nicht Bytecode für eine virtuelle Maschine wie die JVM erzeugt, sondern echter Maschinencode für die jeweilige Plattform (z. B. iOS, Mac oder Linux).

Kotlin Multiplattform: Die Multiplattform-Entwicklung zielt darauf ab, dass Sie Ihre Programme einmal schreiben und dann auf verschiedenen Plattformen wie z. B. Android, iOS, Desktop-Systeme und im Web laufen lassen können. Dabei wird die Anwendungslogik geteilt und für alle Systeme verwendet. Sie legen z. B. nur einmal fest, wie Daten verarbeitet werden sollen. Die Gestaltung der Benutzeroberfläche kann dann die nativen Bedienelemente der verschiedenen Plattformen berücksichtigen. Mit Jetpack Compose, das wir ebenfalls in diesem Buch behandeln, ist es sogar möglich, die gesamte Benutzeroberfläche plattformübergreifend zu gestalten. Diese Technologie befindet sich allerdings aktuell noch zum Teil in der Alpha-Version (Stand Juli 2023).

1.2Deshalb ist Kotlin so besonders

Hier noch einmal die wichtigsten Gründe, die für Kotlin sprechen:

Einfacher Einstieg: Kotlin lässt sich schneller lernen. Es ist verständlicher, und die Sprachelemente sind prägnanter, also ausdrucksstärker. Sie können sich besser auf die wesentlichen Konzepte fokussieren, da unnötiger „Boilerplate Code“ entfällt. Das ist Code, der eigentlich nicht nötig ist, z. B. weil er keine neuen Sachverhalte ausdrückt. Ein Beispiel sind die vielen setter- und getter-Methoden, die man in anderen Programmiersprachen wie zum Beispiel Java schreiben muss.

Eleganter: Viele Programme lassen sich mit Kotlin eleganter schreiben. Mehr noch: Man muss die Programme eleganter schreiben. Sie lernen also gleich den richtigen Stil und können diesen auch in anderen Sprachen einsetzen.

Effiziente Entwicklung: Vieles lässt sich in Kotlin kürzer und effizienter ausdrücken. Das spart nicht nur Zeit beim Schreiben des Quellcodes. Durch kurze, übersichtliche Programme lassen sich Fehler besser vermeiden, und Sie behalten besser den Überblick.

Effiziente Ausführung: Kotlin stellt viele optimierte Algorithmen für immer wieder anfallende Aufgaben zur Verfügung. Das Verarbeiten oder Sortieren von Listen ist in Kotlin sehr gut gelöst und leicht nutzbar.

Erprobte Konzepte: Kotlin baut auf erprobten Konzepten auf. Die Sprache enthält konzeptionell nichts Neues, dafür (fast) alles Gute aus den letzten 50 Jahren Softwareentwicklung. Die Sprache ist praktisch und effizient, bietet mehr Sicherheit und vereint objektorientierte und funktionale Programmierkonzepte.

Erklärt sich selbst: Der Quellcode ist lesbarer für Menschen. Sie können den Code von anderen Entwicklern (und womöglich Ihren eigenen Code selbst nach ein paar Wochen) viel besser verstehen.

Error-less: Viele Fehler, die man in anderen Sprachen leicht machen kann, werden in Kotlin gar nicht erst zugelassen oder zumindest leichter vermieden. So gibt es in Kotlin beispielsweise keine sogenannten Null-Fehler mehr.

Erfrischend: Viele Entwickler berichten, dass das Entwickeln mit Kotlin wieder mehr Spaß macht! Und tatsächlich bekommt man beim Entwickeln mit Kotlin leuchtende Augen. Und manchmal auch tränende Augen – vor Freude über Kotlin und Ärger darüber, dass früher so vieles anstrengender war.

1.3Darauf dürfen Sie sich freuen

Aufbau des Computers: Programme laufen auf dem Prozessor eines Computers. Dabei verändern sie Daten im Speicher. Um zu verstehen, wie aus dem Quellcode, den Sie schreiben, lauffähige Programme auf dem Rechner werden, schauen wir uns zunächst den grundlegenden Aufbau von Computern an. Dabei werden wir sehen, wie aus lauter Nullen und Einsen bunte Bilder werden können. Da wir Daten im Speicher liegen haben, werden wir uns auch mit der Organisation des Speichers beschäftigen.

Basics: Zur Steuerung von Programmen benötigen wir ein paar grundlegende Zutaten. Dazu gehören Ausdrücke und Anweisungen, um dem Rechner zu sagen, was er ausführen soll. Mit Variablen können wir Daten speichern und später wieder darauf zugreifen. Mit Kontrollstrukturen können wir steuern, wie ein Programm abläuft. Somit können wir auf Benutzereingaben und verschiedene Zustände reagieren. Mit Funktionen werden wir wiederverwendbare Codebausteine definieren, aus denen sich komplexe Programme zusammensetzen lassen.

Objekte und Klassen: Wir werden Grundlagen über Objekte und Abstraktion als Klassen kennenlernen. Wie können wir ermitteln, welche Eigenschaften von Objekten relevant sind? Wie abstrahiere ich von konkreten Objekten in der Welt auf eine allgemeine Klasse? Klassen beschreiben die Struktur, die Eigenschaften und die Verhaltensweisen von allen Objekten, die zu einer Klasse gehören. Man spricht von Objektinstanzen (oder einfache Instanzen), wenn man sich auf die Exemplare einer Klasse bezieht. Das Erzeugen eines neuen Objekts erfolgt über Konstruktoren. Wir werden uns ansehen, wie Konstruktoren die erste Version eines Objekts bauen. Zudem werden wir Objekte miteinander in Beziehung setzen. Zum Beispiel werden wir einen Film aus Budget, Schauspieler und Regisseur zusammensetzen, um dies in einem Spiel zu verwenden.

Vererbung und Polymorphie Dies ist ein weiteres zentrales Thema der objektorientierten Programmierung. Klassen stehen in einer Vererbungshierarchie zueinander. Klassen können andere Klassen erweitern, wobei die Eigenschaften und Methoden einer Oberklasse an die Unterklasse vererbt werden. Wenn die übergeordnete Klasse Tasse die Eigenschaft „Volumen“ und die Methode „trinken“ besitzt, dann wird auch die untergeordnete Klasse Kaffeetasse diese Eigenschaften haben, sie kann aber um spezifische Eigenschaften (z. B. „Kaffeesorte“) und Methoden (z. B. „istNochHeiss“) ergänzt werden. Dabei kann das Verhalten von Methoden auch durch Überschreiben geändert werden (z. B. weil man aus einer Thermotasse anders trinkt). Eng verbunden mit der Definition von Klassen und Schnittstellen sind Typkonzepte und Polymorphie. Polymorphie bedeutet Vielgestaltigkeit. Wenn Sie an „Fahrzeug“ denken, dann kann dieses Fahrzeug sehr viele unterschiedliche Gestalten haben: Auto, Fahrrad, Kutsche. Dennoch können Sie allgemeine Eigenschaften und Methoden nutzen, wenn diese Gestalten auf einer abstrakteren Ebene vom gleichen Typ sind. So lässt sich für alle Fahrzeuge sagen: „fahre los“. Egal, ob es sich um eine Kutsche oder ein Fahrrad handelt. Die Umsetzung wird aber ganz unterschiedlich aussehen.

Robustheit: Damit wir auch ordentliche Programme schreiben können, werden wir uns mit verschiedenen Konzepten zur Robustheit von Code beschäftigen. Wie können Sie auf Fehler und Ausnahmesituationen reagieren? Was passiert, wenn eine Datei gelesen werden soll, die gar nicht existiert? Was passiert, wenn ein Server nicht erreichbar ist? Ausnahmezustand! Wir werden lernen, wie wir eine Operation versuchen können und das Scheitern schon einplanen und auffangen. Das funktioniert bestens für Situationen, wo Sie selbst keinen Einfluss darauf haben, ob etwas wie gewünscht funktioniert. Dann können Sie nämlich darauf reagieren. Denn das Auffangen ist viel komplizierter als die Fehlerkorrektur. Und vor allem wollen Sie bei der Entwicklung ja auch, dass Fehler erkannt und somit beseitigt werden können. Wir werden uns die Vor- und Nachteile für verschiedene Software-Designoptionen anschauen. Aus diesen Überlegungen heraus haben sich über die Jahre hinweg Entwurfsmuster entwickelt, die gute Lösungen für bestimmte Aufgaben darstellen.

Datensammlungen: Wir schauen uns verschiedene Möglichkeiten an, um Datensammlungen (z. B. Listen, Verzeichnisse, Paare, Mengen) abzubilden und Operationen darauf auszuführen. Zudem werden wir selbst Datenstrukturen entwickeln, um Objekte zu speichern. Dies wird unter anderem am Beispiel einer verketteten Liste illustriert.

UI Design und App-Entwicklung: Die Android-Plattform stellt spezielle Bibliotheken zur Verfügung, um grafische Oberflächen zu gestalten und die Funktionen von Android-Geräten zu nutzen. Wir werden eine grafische Oberfläche bauen und Code definieren, um auf Eingabeevents (Klick auf einen Button) zu reagieren.

Nebenläufigkeit: Häufig müssen mehrere Dinge gleichzeitig ausgeführt werden. Bei Spielen sollen zum Beispiel gleichzeitig mehrere Figuren bewegt werden. Auf Ihrem Smartphone sollen gleichzeitig mehrere Bilder aus dem Internet heruntergeladen werden, während Sie weiterhin mit dem Programm interagieren. Für diese Nebenläufigkeit führt Kotlin das Konzept der Coroutines ein. Wir werden uns anschauen, wie sich nebenläufige Programme gestalten lassen und auf welche Stolpersteine geachtet werden muss.

TEIL IKonzeptioneller Aufbau von Computern und Software

Um ein besseres Verständnis zu erlangen, was es eigentlich heißt, Software zu entwickeln und lauffähige Programme zu schreiben, müssen wir uns ein wenig mit der Hardware beschäftigen – dem Computer. Computer sind heute allgegenwärtig. Damit sind nicht nur die großen Kisten unterm Schreibtisch oder die mobilen Laptops gemeint. Auch Ihr Smartphone ist ein Computer! Und zwar ein Computer, der sehr viel leistungsfähiger ist als die großen Geräte, die noch vor ein paar Jahren unter dem Schreibtisch standen. Darüber hinaus erhalten kleine Computer jedoch auch Einzug in immer mehr Alltagsgegenstände und Maschinen: von der Zahnbürste über die Waschmaschine, über den Fahrkartenautomat hin zum Autopiloten in Bahnen oder Flugzeugen und natürlich der Bordcomputer in unseren Autos. Viele Waschmaschinen nutzen inzwischen dieselben Chips, die einst in unseren ersten Heimcomputern eingebaut wurden.

Doch was ist eigentlich ein Computer und woraus setzt er sich zusammen? Der Begriff Computer leitet sich aus dem englischen Verb compute ab, welches mit rechnen übersetzt werden kann. Daher wird im deutschsprachigen Raum der Computer auch häufig als Rechner bezeichnet. Tatsächlich macht ein Computer nichts anderes: Er rechnet und rechnet und rechnet und rechnet.

Das klingt vielleicht etwas merkwürdig, wenn wir an die uns vertrauten Anwendungen denken: Was hat die Kurznachricht bei WhatsApp oder Twitter mit Berechnungen zu tun? Wo bitte schön sind die Berechnungen bei grafikintensiven Spielen wie SimCity, Angry Birds oder Fortnite? Die Antwort lautet: ziemlich versteckt, aber dafür überall. Und daher wollen wir uns in diesem Kapitel ein kleines bisschen auf die Suche nach diesen Berechnungen und Zahlen machen. Denn wenn wir verstehen, was unsere Software und Hardware im Innersten zusammenhält, dann bekommen wir auch ein besseres Verständnis für die Funktionsweise der Programme, die wir selbst entwickeln. Und wir werden verstehen, was eigentlich ein „Programm“ ist!

2Komponenten eines Computers
3Zugriff auf den Speicher

Beim Ausführen einer Operation ist es in der Regel wichtig, auf bereits existierende Daten im Speicher zuzugreifen und das Ergebnis auch wieder zurück in den Speicher zu schreiben. Der Speicher besteht im Prinzip aus einer sehr großen Anzahl von Speicherzellen. Jede einzelne Speicherzelle kann, wie bereits erwähnt, nur zwischen den Werten 0 und 1 unterscheiden. Erst durch den Zusammenschluss mehrerer Speicherzellen ergeben sich interessante Einheiten von Zahlenwerten, die dann wiederum auf unterschiedliche Weise interpretiert werden können. Damit auf einen solchen Zusammenschluss von Speicherzellen (z. B. 8 Bits als 1 Byte) zugegriffen werden kann, müssen diese Einheiten adressiert werden. Daher spricht man auch von Speicheradressen. Eine Speicheradresse besteht dabei nicht etwa aus „Blumenstraße 99“, sondern einfach aus einer nüchternen Zahl. Diese Adresszahl sagt z. B.: Verwende die Daten aus den Speicheradressen 390 und 506. Schreibe dann das Ergebnis in die Speicheradresse 7002.

Man kann sich dies so vorstellen, als würde man eine Reihe von Türen über die vielen Speicherzellen legen. Jede Tür hat eine eigene Adresse. Öffnet man die Tür an der Stelle 390, dann können dort andere Bitfolgen stehen als hinter der Tür 506.

Wie die Bitfolgen hinter den Türen interpretiert werden sollen, hängt von der Instruktion ab, die mit diesen Daten arbeiten wird. Dieselbe Bitfolge kann als Zahl, Farbwert, Buchstabe oder sogar als weitere Instruktion interpretiert werden.

Daher legt man bei der Verwendung der Daten, also quasi beim Öffnen der Türen über eine Adresse, auch fest, was mit den Daten geschehen soll. Dies geschieht über die Festlegung eines Datentyps. Wenn eine Bitfolge als Zeichen interpretiert wird, dann ist dies ein anderer Datentyp als bei der Interpretation als ganze Zahl. Der Typ legt dabei nicht nur fest, wie die Nullen und Einsen zu interpretieren sind, sondern auch, welche Arten der Veränderungen erlaubt sind. Anders gesagt: Der Typ bestimmt die Interpretation der Datenwerte und die Anzahl der erlaubten Operationen. Welche Typen uns von Kotlin angeboten werden, werden wir in Kapitel 9, „Basis-Datentypen“, kennenlernen. Wie wir eigene Typen festlegen, werden wir in Teil III, „Objektorientierte Programmierung“, noch sehen.

Übrigens:

Die unterschiedliche Interpretation der Zahlen als Werte in einem Programm oder als Instruktionen für ein Programm ist ein mächtiges Werkzeug. Es ermöglicht uns, im selben Speicher sowohl Daten als auch Programme zu speichern. Gleichzeitig ist es die Angriffsfläche, die leider auch Viren nutzen. Denn wenn es gelingt, die Zahlen im Speicher zu verändern, die als Instruktionen interpretiert werden, dann können Instruktionsfolgen eingeschleust werden, die schadhaft sind. So kann eine harmlos aussehende Operation, bei der ein Rechenergebnis in eine Speicherzelle geschrieben wird, in Wirklichkeit dazu führen, dass diese Zahl als Instruktion interpretiert wird, die schädliche Konsequenzen hat.

3.1Organisation des Speichers

Es kann durchaus sein, dass für bestimmte Datentypen etwas mehr Speicherplatz benötigt wird. Nehmen wir an, dass für eine Person der Vor- und Nachname sowie das Alter gespeichert werden sollen. Dann lässt sich hierfür ein etwas größeres Bitfeld reservieren. Dies entspricht in unserer Metapher einer größeren Tür. Auch diese Tür hat eine Adresse, über die dann aber strukturiert auf Unterelemente zugegriffen werden kann.

Der Zugriff auf alle Türen im Hauptspeicher geschieht gleich schnell. Der Prozessor muss nur die Adresse wissen. Danach hat er einen direkten Zugriff auf die Bits hinter dieser Tür. Dieser Hauptspeicher heißt daher auch RAM (Random Access Memory). Der Zugriff kann auf beliebige Werte geschehen.

Neben dem Zusammenschluss mehrerer Einzeldaten hinter einer etwas größeren Tür ist es auch üblich, dass mehrere Daten desselben Typs zu einem Datenfeld (einem Array) zusammengefasst werden. Dies entspricht im Prinzip einer Häuserfront mit einer fixen Anzahl von Türen, hinter denen Daten desselben Typs gespeichert werden. Für eine Zeichenkette wird z. B. ein Feld mit Buchstaben benötigt. Damit diese zusammengehörenden Elemente nicht wild verstreut sind und man nach dem jeweils nächsten Buchstaben suchen muss, speichert man diese hintereinander. Dadurch lässt sich auch sehr schnell auf die einzelnen Türen des Feldes zugreifen: Wenn Sie wissen, dass Laura in der Straße 23 hinter der 4. Tür wohnt, dann kommen Sie schnell dort hin. Wenn Laura aber etwas flexibler ist und mal hier, mal dort wohnt, dann müssen Sie vielleicht bei mehreren Türen nachschauen, um Laura zu finden. Arrays ermöglichen also einen schnellen Zugriff, sind jedoch etwas weniger flexibel.

3.2Daten im Speicher und Datenverarbeitung im Prozessor

Während der Zugriff gezielt über Speicheradressen erfolgt, müssen diese noch zum Prozessor gelangen, um dort verarbeitet zu werden. Die Daten müssen also für die Verarbeitung ausgelesen und das Ergebnis später wieder an eine Speicheradresse geschrieben werden. Für die Verarbeitung müssen die Bits daher in den Prozessor transportiert werden. Diese Aufgabe übernimmt ein Datenbus, der die Bits in den Prozessor kopiert. Der Prozessor hat eigene Speicherfelder, mit denen er sehr, sehr schnell operieren kann. Dies geht aber nur so schnell, weil die Anzahl begrenzt ist. Stellen Sie sich vor, Sie haben einen Aktenschrank, bestehend aus Ordnern, in denen Daten stehen, z. B. Bilanzdaten. Dieser Schrank ist wie Ihr RAM. Sie können schnell auf einen der Ordner zugreifen und eine bestimmte Seite aufschlagen. Egal welchen Ordner Sie nehmen, die Zugriffszeit ist (ungefähr) gleich lang. Aber doch recht langsam, um überhaupt auf die Daten zuzugreifen. Wenn Sie dann den Ordner geöffnet haben, dann können Sie die gerade relevanten Daten auf ein Blatt Papier kopieren. Die Daten, die jetzt auf dem Papier stehen, können Sie selbst (und Sie sind jetzt der Prozessor, der Berechnungen ausführt) sehr viel schneller verarbeiten als die Daten aus den Ordnern. Das geht aber nur, weil auf dem Blatt Papier nur wenige Daten stehen, und Sie direkt damit arbeiten können. Und wenn Sie ein Ergebnis berechnet haben und gerade nicht mehr brauchen, dann archivieren Sie es – für die spätere Verwendung – wieder in einem der Ordner in Ihrem Aktenschrank.

Während das Speichern und Verändern auf dem Blatt Papier sehr schnell geht (ähnlich wie bei den Speicherregistern eines Prozessors), dauert der Zugriff auf die Daten im Ordner etwas länger (ähnlich wie beim Hauptspeicher des Computers). Allerdings ist auch der Hauptspeicher nicht beliebig groß. In Ihren Aktenschrank passt nur eine begrenzte Anzahl von Ordnern. Auch wenn moderne Aktenschränke immer größer werden (wir meinen natürlich: moderne Computer immer mehr RAM haben), so gibt es doch Grenzen, wenn wir sehr, sehr viele Daten speichern möchten. Aus diesem Grund gibt es neben dem Hauptspeicher noch verschiedene Arten des Massenspeichers. Dieser ist im Vergleich zum RAM relativ langsam. Beispiele für Massenspeicher sind Festplatten, USB-Sticks oder auch das Speichern in der Cloud. Umin der Analogie der Aktenordner zu bleiben: Der Massenspeicher ist das große Archiv im Keller. Dort passt viel, viel mehr hinein als in einen Aktenschrank. Aber der Zugriff auf die dortigen Ordner dauert auch nochmals etwas länger. Und wenn Sie einen Cloud-Dienst nutzen, dann verlagern Sie das Speichern der Daten (oder Akten) an einen entfernten Ort. Das kann durchaus sinnvoll sein, da dort noch viel, viel mehr Speicher zur Verfügung steht. Sie können ja auch Aktenordner in externen Speicherhäusern lagern. Dabei dauern An- und Abtransport noch einmal etwas länger. Und Sie sind auf eine gute Transportinfrastruktur angewiesen – eine schnelle Internetverbindung, die hoch verfügbar sein sollte. Und Sie müssen dem Betreiber des Speicherhauses trauen, dass die Daten vor fremden Zugriffen geschützt werden – ähnlich wie Sie einem Cloud-Anbieter trauen müssen, dass Ihre Daten nicht gehackt oder gar der Cloud-Anbieter selbst in die Daten schaut.

3.3Heap und Stack

Der Hauptspeicher mit seinen Bits und Bytes lässt sich, wie wir bereits am Beispiel unterschiedlich großer Türen und Arrays gesehen haben, unterschiedlich organisieren. Eine weitere wesentliche Unterscheidung ist die Organisation als Heap und als Stack. Heap (englisch für „Halde“) bedeutet, dass Daten an beliebigen, bislang ungenutzten Positionen gespeichert werden können. Das bedeutet etwa: Sie richten eine neue Tür in der gewünschten Größe dort ein, wo noch Platz ist. Das ist einerseits praktisch, da Sie an beliebiger Stelle Daten speichern können. Gleichzeitig führt dies zu einer Fragmentierung des Speichers, sodass gelegentlich aufgeräumt werden muss (was Zeit kostet). Zudem müssen Sie genau organisieren, wie Sie die Tür wiederfinden, denn sie kann sich ja überall befinden. Beim Stack (englisch für „Stapel“) wird dagegen ein Speicherbereich fester Größe reserviert, und Daten können nacheinander darauf abgelegt und wieder heruntergenommen werden. Dabei kann, wie bei einem Stapel, jeweils nur auf das oberste Element zugegriffen werden. Stapel helfen Ihnen dabei, die Zugriffsreihenfolge auf bestimmte Daten effizient zu organisieren. Wir werden später sehen, dass dies beim Aufruf von Funktionen, also dem Abarbeiten wiederkehrender Anweisungsfolgen, wichtig ist, damit die Arbeit wieder an der richtigen Stelle fortgesetzt wird. Stellen Sie sich vor, dass Sie gerade auf einem Blatt Papier eine komplizierte Berechnung anstellen. An einer bestimmten Stelle benötigen Sie eine Nebenrechnung. Für diese Nebenrechnung verwenden Sie ein weiteres Blatt Papier. Dies entspricht einem neuen Speicherblock auf dem Stack (oft als Stack-Frame bezeichnet). Ihr zusätzliches Blatt Papier für die Nebenrechnung liegt wie auf einem Stapel über Ihrer eigentlichen Berechnung. Alle Zahlen und Werte, die Sie auf diesem zusätzlichen Blatt Papier aufschreiben oder verändern, haben keine Auswirkungen auf das darunter liegende Blatt Papier. Wenn Sie mit Ihrer Nebenrechnung fertig sind, entfernen Sie das zusätzliche Blatt Papier wieder und machen genau an derselben Stelle auf dem darunter liegenden Blatt Papier weiter.

3.4Programme als Code schreiben statt als Zahlenfolgen

Wir haben also gesehen, dass der Speicher, der nur aus Nullen und Einsen besteht, auf unterschiedliche Weise organisiert wird. Zudem können die Nullen und Einsen unterschiedlich interpretiert werden. Die Zahlen können unterschiedliche Dinge repräsentieren: Datenwerte und Instruktionen für den Prozessor. Darüber hinaus können diese Zahlen auch Adressen für einen Speicherbereich darstellen. Über diese Adressen lassen sich andere Speicherbereiche referenzieren oder festlegen, wo im Speicher die nächste Instruktion für die Ausführung durch den Prozessor liegt. Dieses ganze Zahlenwirrwarr erscheint aber recht kompliziert, wenn wir doch eigentlich unsere Welt abbilden wollen, indem wir Geschäftsprozesse modellieren, Spiele schreiben, Videos oder Bilder verarbeiten, Texte schreiben oder Nachrichten verwalten und versenden möchten.

Und genau aus diesem Grunde gibt es höhere Programmiersprachen wie Kotlin, mit denen wir Programme in einer Sprache festlegen können, die sowohl für uns verständlich als auch für den Computer interpretierbar ist. Dabei abstrahieren wir von den konkreten Instruktionen, die wir dem Prozessor geben müssen, auf für uns bedeutungsvolle Konzepte, wie z. B. der Ausgabe eines Textes auf dem Bildschirm. Kotlin ermöglicht z. B. die Ausgabe des Worts „Hallo“ auf dem Bildschirm mit folgender Zeile Code:

println("Hallo")

Der Prozessor kann damit aber nicht direkt etwas anfangen. Wir haben lange darüber diskutiert, dass ein Prozessor nur Instruktionen ausführen kann, die als Zahlen im Speicher liegen. Es muss also einen Weg geben, um diese Zeile Code in solche Zahlen zu übersetzen. Genau dies ist die Aufgabe von Interpretern und Compilern.

4Interpreter und Compiler

Interpreter führen Anweisungsfolgen wie z. B. die println-Funktion direkt aus, indem sie bei der Abarbeitung des Programms die Bedeutung des Textes interpretieren und dann entsprechende Berechnungen durchführen. Ein Interpreter ist also selbst ein Programm, das ausgeführt werden kann. Der Interpreter liest dann ein anderes Programm ein, das als Quelltext vorliegt. Quelltext ist dabei der Programmcode, der in einer bestimmten Programmiersprache verfasst wurde, also z. B. in Kotlin. Dieser Quelltext kann in einer Datei vorliegen, die eingelesen wird, oder auch direkt in einer Entwicklungsumgebung eingetippt werden, wie dies z. B. in der Online-Umgebung von Kotlin geschieht (https://play.kotlinlang.org).

Der Interpreter verarbeitet dann diesen Text. Damit der Interpreter weiß, wo er mit der Arbeit beginnen kann, gibt es Einstiegspunkte, die festlegen, wo begonnen werden soll. Bei einigen Programmiersprachen ist dies die erste Zeile, die in einer Datei steht. Bei Kotlin ist dagegen der Einstiegspunkt eine Funktion, die den Namen main() tragen muss. Mit Funktionen werden wir uns noch sehr intensiv beschäftigen. Für den Moment können wir uns eine Funktion einfach als einen ausführbaren Codeschnipsel vorstellen. Und die main-Funktion ist der Codeschnipsel, der beim Programmstart ausgeführt werden soll:

fun main() { println("Hallo") }

Beim Ausführen wird also mit der main-Funktion begonnen. In dieser Funktion können dann einzelne Anweisungen und Ausdrücke stehen, die nacheinander ausgeführt werden. Ein Interpreter stellt in diesem Beispiel fest: „Aha, ich soll erst einmal etwas auf dem Bildschirm ausgeben. Dies wird durch println() festgelegt. Was ausgegeben wird, ist in diesem Fall durch einen Parameter festgelegt, nämlich "Hallo".“

Wenn der Interpreter auf eine Anweisung stößt, die er nicht versteht, dann bricht er ab und meldet eine Fehlermeldung. Ein Interpreter arbeitet einen Quellcode also quasi on the fly ab, also während des Programmablaufs. Das ist im Prinzip wie ein Simultanübersetzer, der die Aussagen aus einer Sprache (Sprache des Quelltexts) live in eine andere Sprache (Sprache der Maschine) übersetzt.

Ein Compiler hat dagegen eine andere Arbeitsweise. Auch er übersetzt einen Quelltext in Code, der von der Maschine ausgeführt werden kann. Allerdings übersetzt der Compiler das Programm vollständig in Maschinencode, bevor es ausgeführt werden kann. Somit kann der Compiler auch schon Fehler melden, bevor überhaupt die Programmausführung startet. Ein Compiler ist also kein Simultanübersetzer, sondern ein Programm, das einen Quelltext vollständig in einen Maschinencode übersetzt.

Definition Compiler

Ein Compiler ist ein Programm, welches ein Programm in Form von Quelltext entgegennimmt und ein anderes Programm erzeugt. Bei diesem Prozess werden eine Menge Überprüfungen und Optimierungen durchführt. Wenn das Programm keine Syntax- und Typfehler hat, kommt am Ende ein Programm heraus, welches der Computer ausführen kann.

In beiden Fällen – beim Interpreter wie beim Compiler – findet aber eine Übersetzung statt. Wenn Sie in der Experimentierumgebung von Kotlin eine Zeile Code eingeben und ausführen lassen, dann wird im Hintergrund übrigens der gesamte (wenn auch sehr kurze) Quellcode kompiliert (d. h. in Maschinencode) übersetzt. Weil dies so schnell geht und auch auf bereits eingegebene Werte zugegriffen werden kann, scheint der Ablauf wie bei einem Interpreter zu sein. Der Unterschied zwischen einem Interpreter und einem Compiler verschwindet also fast, wenn es sich um die Übersetzung sehr kurzer Quelltexte handelt. Das ist ähnlich wie der Unterschied zwischen einem Simultanübersetzer und einem Übersetzer, der einen Text vollständig übersetzt. Wenn der vollständige Text sehr kurz ist, dann gibt es (gefühlt) keinen Unterschied mehr.

4.1Virtuelle Maschinen, Bytecode und Maschinencode

Bei Kotlin wird der Quellcode grundsätzlich kompiliert. Allerdings wird der Quellcode nicht direkt in Maschinencode übersetzt, der unmittelbar von einem Prozessor ausgeführt werden kann. Denn es gibt viele verschiedene Rechnerarchitekturen und somit auch verschiedene Prozessoren, die zum Einsatz kommen. Und während es bei Buchstaben standardisierte Codes gibt, die festlegen, welcher Buchstabe durch welche Zahl repräsentiert wird, gibt es bei den Prozessoren verschiedener Hersteller unterschiedliche Formen, wie Instruktionen als Zahlen codiert werden. Das liegt unter anderem daran, dass die Prozessoren unterschiedliche Befehlssätze haben, um z. B. komplexere Operationen in einem Schritt auszuführen. Es gibt hier konkurrierende Systeme, die sich durch eine unterschiedliche bzw. unterschiedlich schnelle Verarbeitung der Daten an die Spitze setzen möchten.

Um von diesen Unterschieden verschiedener Prozessortypen zu abstrahieren, kommen oft virtuelle Maschinen zum Einsatz. Eine virtuelle Maschine besteht ähnlich wie eine reale Computer-Maschine vor allem aus einem Prozessor zum Verarbeiten von Instruktionen und einer Speicherverwaltung. Bei einer virtuellen Maschine ist dieser Prozessor jedoch keine Hardware-Komponente, sondern selbst ein Stück Software, also ein Programm. Dieser virtuelle Prozessor löst das Problem, dass unterschiedliche Hardware-Prozessoren mit unterschiedlichen Befehlssätzen ausgestattet sind. Die virtuelle Maschine stellt nämlich einen einheitlichen Befehlssatz zur Verfügung. Die virtuelle Maschine kann dann für verschiedene Hardware-Systeme umgesetzt werden. Damit wird erreicht, dass auf verschiedenen Hardware-Plattformen derselbe Code ausgeführt wird. Dieser Code ist im Prinzip ein Zwischencode, der als Bytecode bezeichnet wird. Für den Compiler bedeutet dies, dass nicht zwischen verschiedenen Hardware-Plattformen unterschieden werden muss, sondern für eine virtuelle Maschine übersetzt werden kann. Zudem übernimmt die virtuelle Maschine die Speicherverwaltung und sorgt dafür, dass keine Speicherbereiche (im realen Hardware-RAM) belegt werden, die außerhalb der virtuellen Maschine liegen. Eine sehr weit verbreitete virtuelle Maschine ist die Java Virtual Machine (JVM). Diese Architektur wurde ursprünglich für die Programmiersprache Java entwickelt. Es gibt verschiedene Umsetzungen von dieser virtuellen Maschine – einerseits von verschiedenen Herstellern, andererseits für verschiedene Hardware-Plattformen. So gibt es eine JVM für Mac-Systeme, für Windows-Systeme, für Unix-Systeme, aber auch für Android-Smartphones. Die JVM ist nichts anderes als eine Laufzeitumgebung, die Programmcode ausführen kann. Dieser Programmcode verwendet den Befehlssatz der JVM und wird als Bytecode bezeichnet.

Ein Compiler kann nun den Quelltext einer von Menschen gut lesbaren Sprache (wie Kotlin oder Java) in Bytecode für die JVM übersetzen. Die JVM interpretiert oder übersetzt diesen Bytecode dann in spezifische Instruktionen für die jeweilige Hardware-Plattform – egal welcher Prozessor dort zum Einsatz kommt. Damit ist es theoretisch möglich, denselben Quellcode nur ein einziges Mal zu übersetzen und dann über die JVM auf verschiedenen Hardware-Plattformen laufen zu lassen. Allerdings ist es so, dass einzelne Bibliotheken – also zur Verfügung gestellte fertige Funktionalitäten – nur für bestimmte Plattformen verfügbar sind. Das liegt daran, dass z. B. einerseits sowohl für Mac und Windows jeweils grafische Benutzeroberflächen mit Fenstern als auch die Ein- und Ausgabe über eine Konsole zur Verfügung stehen. Hier gibt es auch gemeinsame Bibliotheken (z. B. Java FX), mit denen kompilierte Programme entwickelt werden können, die 1:1 auf beiden Plattformen laufen. Andererseits gibt es aber etwa für Android-Systeme spezifische Bibliotheken, die eine spezifische Gestaltung der Benutzeroberfläche berücksichtigen und auch auf für Smartphones spezifische Funktionalitäten Zugriff gewähren, etwa die Bewegungssensoren oder das Starten eines Telefonanrufs.

4.2Kotlin – eine Sprache, viele Plattformen

Kotlin ist als moderne Programmiersprache so ausgelegt, dass sie auf möglichst vielen Plattformen läuft. Es gibt daher verschiedene Compiler für die unterschiedlichen Zielplattformen. Mit anderen Worten: Der in Kotlin geschriebene Quelltext kann sowohl für die Java Virtuelle Maschine als auch für andere Systeme übersetzt werden. Insbesondere ist eine Übersetzung in JavaScript-Code sowie auch das Kompilieren für native Hardware-Plattformen möglich.

Der häufigste Fall ist derzeit noch die Kompilierung von Kotlin-Programmen für die Java Virtuelle Maschine. Darauf werden wir uns auch in diesem Buch konzentrieren. Dabei ist es keine Einschränkung, dass die JVM ursprünglich einmal für die Programmiersprache Java entwickelt wurde. Inzwischen gibt es längst mehrere andere Sprachen, die auch für die JVM übersetzt werden können. Es ist sogar ein Vorteil, dass Kotlin für die JVM übersetzt werden kann. Zum einen übernehmen wir damit die Plattformunabhängigkeit (mit den oben genannten Einschränkungen), die auch für Java galt. Zum anderen klappt das Zusammenspiel mit existierenden Java-Programmen (die Interoperabilität der Programmiersprachen Kotlin und Java) reibungslos. Man kann Kotlin vollständig nutzen, ohne Java zu kennen. Wenn Sie jedoch Java-Kenntnisse oder bereits Code in Java entwickelt haben, dann können Sie dieses Wissen bzw. diesen Code problemlos weiter verwenden.