Spring Boot

Was ist Spring Boot?

Mit Spring Boot hat die Spring Community eine echte Vereinfachung bei der Erstellung Spring-basierter Anwendung erreicht. Damit können mit wenigen Programmzeilen Prototypen selbst komplexer Anwendungen erstellt werden, die anschließend zum fertigen Produkt konfiguriert werden können. Der Funktionsumfang von Spring Boot umfasst:

  • Build-Management
    • Ein Spring Boot-Projekt kann mit einer simplen Build-Definition gebaut werden.
  • Projekt-Generator
    • Zur Neuanlage eines Projektes kann die Build-Konfiguration mit einem Online-Dienst der Spring-Community erstellt werden.
  • Convention over Configuration und Autoconfiguration
    • Für Rapid Prototyping sind keine Konfigurationsdateien oder Ähnliches vonnöten. Die Anwendung ist nach der Erstellung sofort lauffähig. Dies gilt auch dann, wenn beispielsweise für eine Web-Anwendung ein Server benötigt wird: Die Autoconfiguration startet einfach einen Tomcat auf Port 8080.
  • Externe Konfiguration
    • Für die ausgelieferte Programmversion muss selbstverständlich eine explizite Konfiguration erfolgen, die Autoconfiguration ist hierfür bestenfalls bedingt geeignet. Dazu bietet Spring Boot einen einfachen Mechanismus über Properties- oder YAML-Dateien. Eine Annotation-basierte Konfiguration ist natürlich auch möglich.
  • Monitoring der Runtime
    • Die in Produktion laufende Anwendung wird prnzipiell wie alle Java-Anwendungen über JMX überwacht. Mit dem Spring Actuator werden zusätzliche Metriken hinzugefügt, die via REST-Schnittstelle abgegriffen werden können.

Die einzelnen Funktionen werden im Folgenden detaillierter beschrieben.

Features

 Build Management

Der Build-Prozess einer Spring Boot-Anwendung wird über Gradle oder über Maven definiert. Die Grundideen sind in beiden Systemen die selben und werden hier an einem Maven-Projekt beschrieben:

Als erstes stellt Spring Boot eine Parent-POM zur Verfügung, in der alle (!) möglichen Spring-Dependencies einer Applikation abstrakt deklariert sind:

Ein konkretes POM eines Projekts definiert als Dependencies die Teile des Frameworks, die benutzt werden sollen durch „Starter“. So wird im folgenden Beispiel ein Projekt mit der Spring Core-Komponente sowie der Test-Umgebung definiert:

 

Projekt Generator Spring Initializr

Mit Hilfe des online verfügbaren Projekt-Generators Spring Initializr wird ein POM für ein Projekt sowie eine simple Rahmenanwendung generiert. Die notwendigen Starter können durch eine einfache Oberfläche hinzugefügt werden. Das fertige Projekt wird als Archiv geladen werden.

Convention over Configuration und Autoconfiguration

Eine Spring Boot-Anwendung wird über eine zentrale Einstiegsklasse definiert, die mit @SpringBootApplication annotiert ist.  Damit sind die folgenden Features automatisch aktiviert:

  • Diese Klasse ist automatisch eine @Configuration
  • ComponentScan für alle Unterpakete, so dass automatisch alle @Components gefunden werden
  • Automatisches Laden der application.properties oder alternativ einer application.yaml zur Anwendungs-Konfiguration. Hierin werden Anwendungs-spezifische Konfigurationen definiert, aber auch die restliche Konfiguration (Logging, Ports, URLs…) werden hier zentral gepflegt.
  • Spring-Profile werden unterstützt. So werden Profile-abhängige Konfigurationen wie application-<profile>.properties automatisch erkannt und gemerged.

 

Zusätzlich wird eine Autokonfiguration angeboten, die einiges an Hintergrund-„Magie“ durchführt. Dies sei an einem konkreten Beispiel demonstriert:

  • Die Anwendung benötigt eine Datenbank und deklariert somit in seinem POM den Starter für JDBC.
  • Zusätzlich wird ein Datenbanktreiber angegeben.
  • Jetzt startet die Autokonfiguration und erzeugt automatisch eine DataSource. Dazu wird untersucht, ob der angegebene  Datenbank-Treiber eine Embedded-Konfiguration ermöglicht. Hier sind aktuell H2, Derby und die HSQLDB unterstützt.

Diese Autokonfiguration kann aber beispielsweise auch einen kompletten Jetty-Server konfigurieren und starten. Dazu ist nur der Web-Starter zu inkludieren.

Externe Konfiguration

Die eben besprochene Autoconfiguration ist für Prototyping sehr praktisch. Für reale Anwendungen muss diese jedoch durch explizite Konfiguration ersetzt werden, also beispielsweise den Port des Jetty-Servers oder eine DataSource. Dazu bietet Spring Boot

  • die application.properties
    • spring.datasource.user=Hugo
  • Aufruf-Parameter der Anwendung
    • -Dspring.datasource.user=Hugo
  • Falls zumindest eine DataSource definiert ist wird die Autokonfiguration nicht durchgeführt.

Monitoring

Der Spring-Actuator stellt umfangreiche Informationen via JMX oder über eine REST-Schnittstelle zur Verfügung. Darin werden nicht nur die aktuellen Metriken des Java-Prozesses angezeigt sondern auch beispielsweise das Geflecht der Spring-Beans.

Das Hinzufügen des Actuators erfolgt wie bei Spring Boot üblich durch das Hinzufügen des Starters zum POM. Welche Endpoints aktiviert werden wird in der application.properties definiert.

Ebenso einfach können die Metriken über Jolokia abgegriffen werden.

Fazit

Spring Boot wird zwar als Ergänzung zum Spring-Framework angeboten, ist nach meiner Auffassung aber so überzeugend, dass selbst simple Projekte damit realisiert werden sollten. Selbst wenn „nur“ der Build-Prozess mit Parent Startern benutzt wird, ist der Mehrwert den Aufwand der Einarbeitung in jedem Falle wert. Dies beschreibt ein anderer Artikel zu Spring Boot, darin wird ein RESTful Web Service mit Datenbank-Anbindung programmiert. Die Datenbank-Anbindung mit Spring Data ist Thema eines weiteren Artikels.


Seminar zum Thema

Weiterlesen

Einwickeln von Exceptions

Über das Einwickeln von Exceptions

Das Leben wäre schöner, gäbe es nur RuntimeExceptions – leider aber gibt’s auch Exceptions.

Eine übliche Lösung dieses Problems besteht bekanntlich darin, checked Exceptions in RuntimeExceptions einzuwickeln – und statt einer Exception dann diese RuntimeException zu werfen.

JPA z.B. wickelt alle SQLExceptions in JPA-Exceptions ein – und letztere sind RuntimeExceptions. Und auch das Springframework verwendet dieselbe Technik.

Dieses Einwickeln ist aber immer nervtötend. Betrachten wird folgendes Code-Fragment:

 

(Man beachte, dass die run-Methode der Thread-Klasse keine checked-Exception werfen darf – wir müssen also einwickeln.)

Das Einwickeln erfordert immer sechs Zeilen.

Für die folgende Diskussion verwenden wir zwei Demo-Methoden, die jeweils eine checked Exception werfen können – eine Methode foo, die nichts zurückliefert, und eine bar-Methode, die ein int–Resultat liefert:

Wir benutzen diese Methoden in einer Runnable– und einer Supplier-Implementierung. Bekanntlich darf weder die run-Methode von Runnable noch die get-Methode von Supplier eine checked-Exception werfen – wir wickeln diese Exception also in eine RuntimeException ein:

Nervtötend…

Wir entwickeln eine kleine Utilitiy-Klasse:

Die Klasse definiert zunächst „Exception“-verträgliche Versionen der beiden Standard-Interfaces Runnable und Supplier: die Interfaces XRunnable und XSupplier.

Sie definiert dann zwei statische wrapException-Methoden. Der ersten Methode wird ein XRunnable übergeben, der zweiten ein XSupplier. Die erste Methode ruft die run-Methode des XRunnables auf und wickelt eine Exception, die von dieser Methode geworfen werden kann, in eine RuntimeException ein. Die zweite Methode ruft die get-Methode des XSuppliers auf und liefert deren Resultat als eigenes Resultat zurück (und wickelt ein…)

Eine Anwendung dieser Methode kann zunächst einen statischen Import definieren:

Die Thread-Anwendung, die oben vorgestellt wurde, kann nun wesentlich konziser formuliert werden:

Dasselbe gilt auch für das Runnable– und Supplier-Beispiel:

Bevor wir unsere Überlegungen weiterführen, ist zunächst eine kleine Refaktorierung angesagt. Wir können die erste wrapException-Methode auf die zweite wrapException-Methode abbilden:

Exceptions sollten auch in spezifische RuntimeExceptions eingewickelt werden können. Wir erweitern unsere TryCatch-Klasse um zwei weitere wrapException-Methoden, denen zusätzlich zu dem XRunnable resp. dem XSupplier jeweils eine Function übergeben wird – ein exceptionWrapper, welchem ein Throwable übergeben wird und welcher dann eine RuntimeException liefern muss:

Angenommen nun, die Anwendung definiert folgende RuntimeException-Klasse:

Dann können die beiden neuen wrapException-Klassen wie folgt genutzt werden:

Auch hier ist ein wenig Refaktorierung angesagt. Wir können eine der beiden alten wrapException-Methode auf die neue abbilden:

Hier noch einmal der komplette Quellcode von TryCatch:

 

Seminare zum Thema:

Clean Code – Professionelle Codeerstellung und Wartung

Java 8 / JDK8 Update

Java Erweiterungen II – Vertiefung

Weiterlesen

Java 9 und Jigsaw

Neue Tools in Java 9

Java 9 Modul-Konzept

Die wichtigste Neuerung von Java 9 ist zweifellos das neue Modul-Konzept – auch als „jigsaw“ bezeichnet (JEPs 200, 201, 220, 260, 261, 281).

Das Modulkonzept verfolgt die Ziele „reliable configuration“ und „strong encapsultion“. Was ist damit gemeint?

Im „alten“ Java werden Klassen Pakete zugeordnet. Nur die als „public“ gekennzeichneten Klassen eines Pakets können von Klassen anderer Pakete genutzt werden. Nicht-öffentliche Klassen können nur in anderen Klassen desselben Pakets verwendet werden.

Das Paket war bislang die höchste Organisations-Einheit, die Java kannte. Natürlich konnten class-Dateien zu jar-Dateien zusammengefasst werden – eine jar-Datei war bislang aber eben nichts anderes als ein gezipptes Verzeichnis mit class-Dateien. Eine jar-Datei hatte also kein dem Compiler oder der VM bekannte Java-Beschreibung. Das „Interface“ einer jar war identisch mit ihrem Inhalt.

Daraus ergaben sich Probleme:

  • Angenommen, eine Anwendung enthält Klassen der Pakete jj.mod.pub und jj.mod.pri. In jj.mod.pub existiert die Klasse Foo, in jj.mod.pri existiert die Klasse Bar – eine Helper-Klasse, die von Foo genutzt wird. Bar muss nun public sein – ansonsten könnte sie von Foo nicht genutzt werden. Damit ist Bar natürlich auch für alle anderen Klassen nutzbar – obwohl sie nur als Helper-Klasse für Foo gedacht war. Wobei es völlig unerheblich ist, wie die class-Dateien zu jars zusammengefasst werden.
  • Klassen, die ein und demselben Paket zugeordnet waren, konnten auf verschiedene jar-Dateien aufgeteilt werden. Das trug nicht unbedingt zur Übersichtlichkeit bei.
  • Die Klassen, die zur Compilationszeit und zur Laufzeit herangezogen wurden, wurden über den CLASSPATH ermittelt. Enthielt der CLASSPATH zwei gleichnamige Klassen, so wurde die erste dieser Klassen herangezogen (die Änderung der Reihenfolge der Elemente des CLASSPATH konnte also eine sehr überraschende Wirkung haben).
  • Einer jar-Datei konnte man nicht ansehen, von welchen anderen jars sie abhängig war. Im Grunde war es immer Glücksache, alle benötigten jar-Dateien aufgefunden und bereitgestellt zu haben. Dieses Problem kann zwar durch die Benutzung von Build-Werkzeugen wie Apache Maven oder auch dem OSGi-Framework gelöst werden, aber eben nicht im Rahmen des Java-Standards.

Solche und ähnliche Probleme können vermieden werden, wenn das neue Modul-Konzept verwendet wird.

Ein Modul ist jar-Datei, die in ihrem Wurzelverzeichnis eine module-info.class enthält. Diese Datei wird vom Compiler aufgrund einer module-Info.java-Datei. Diese Datei enthält die Selbstbeschreibung des Moduls. Zu dieser Beschreibung gehören u.a. zwei wichtige Elemente: welche Pakete des Moduls sind von anderen Modulen nutzbar? – und von welchen anderen Modulen ist das Modul abhängig?

Wir greifen auf das obige Szenario zurück. Wir bauen ein Modul mit den Klassen Foo und Bar:

Diese beiden Klasse sollen ein Modul namens jj.mod bilden. Wir schreiben folgende module-info.java:

Das Modul hat einen eigenständigen Java-Namen:  jj.mod. Solche Modul-Namen werden nach demselben Schema gebildet wie Paket-Namen. Der Modul-Name, die Namen der Pakete, die das Modul zusammenfasst, sind technisch völlig unabhängig voneinander – dennoch sollte natürlich ein gewisser Zusammenhang erkennbar sein.

Eine Modul jj.appl, welches dieses Modul nutzt, wird folgende Beschreibungsdatei enthalten:

Die Klassen des jj.appl-Module können nun die Klassen aller Pakete des Moduls jj.mod nutzen, welche von diesem Modul exportiert werden – hier also nur die Klasse Foo:

Module können zudem transitiv exportiert werden; und sie können ihre Pakete nur einer begrenzten Menge anderer Module zur Nutzung zur Verfügung stellen.

Mit diesem neuen Modul-Konzept wird der Modul-Pfad eingeführt, der den CLASSPATH ersetzen kann. Wird statt des CLASSPATH der Modul-Pfad verwendet, wird zudem verhindert, dass Pakete auf mehrere Module verteilt werden können.

Natürlich muss gewährleistet sein, dass auch „alte“ jar-Dateien weiterhin genutzt werden können: sofern eine jar-Datei keine module-info.class enthält, exportiert sie implizit all ihre Pakete – und kann auf alle anderen jar-Dateien (auch auf modulare) zugreifen.

Und umgekehrt kann auch eine alte VM die neuen modularen jars nutzen – alte VMs ignorieren eine Datei namens module-info.class („illegaler“ Klassenname wegen des Bindestrichs).

Auch die rt.jar ist übrigens in mehrere Module zerlegt worden (eine Anwendung muss nur diejenigen Java-SE-Module an Bord haben, die sie tatsächlich benötigt).

Insbesondere bei der Erstellung großer Programmsysteme und bei der Erstellung von Bibliotheken ist das neue Modul-Konzept natürlich von grundlegender Bedeutung.

Publisher-Subscriber

Java 9 führt ein Publisher-Subscriber-Framework ein, welches der Reactive Streams Specification entspricht (java.utilconcurrent.Flow). Reactive Streams sind „asynchronous streams of data with non-blocking back pressure“. Ein reactive stream lässt sich grob als eine Kombination des Iterator- und des Observer-Patterns. Ein Iterator folgt dem Pull-Modell; ein Oberser folgt dem Push-Modell. Beim Flow-API fordert zunächst der Subscriber eine bestimmte Anzahl von zu verarbeitenden Objekten an (pull); dann stellt der Publisher diese Objekte dem Supplier in der von ihm gewünschten Anzahl zu (push). Das von Java 9 eingeführte Framework spezifiziert allerdings ausschließlich Interfaces – Implementierungen dieser Interfaces werden bereitgestellt z.B. von RxJava. (JEP 266)

jshell

Java 9 enthält nun eine jshell, ein interaktives Tool zur Auswertung von Deklarationen, Expressions und Anweisungen. Das Tool implementiert das REPL-Muster (Read-Eval-Print-Loop). Zwei kleine Beispiele:

jshell> Math.sqrt(2)

$1 ==> 1.4142135623730951

 

jshell> {     

   …> int a = 25;     

   …> int b = 15;     

   …> while (a != b) {     

   …> if (a > b) a -= b; else b -= a;     

   …> }     

   …> System.out.println(a);     

   …> }

5

Man kann nun also Java-Fragmente ausprobieren, ohne zunächst den Code komplett übersetzen und zu seiner Ausführung eine VM starten zu müssen (JEP 222).

Collection-Factory-Methods

Java 9 führt einige neue Factory-Methoden ein, mittels derer auf bequeme Weise immutable Collections erzeugt werden können (List.of, Set.of, Map.of, Map.ofEntries).

Erweiterungen der Klasse Process

Die Klasse Process erlaubt nun eine wesentlich erweiterte Kontrolle und Monitoring nativer Prozesse (diese Möglichkeiten waren bislang recht begrenzt). Neu sind in diesem Kontext u.a. die Interfaces ProcessHandle und ProcessHandle.Info.

Stream-API

Das Stream-API wurde erweitert. Neu sind hier insbesondere die Methoden dropWhile und takeWhile.

StackWalker

Informationen über den aktuellen Stack mussten bislang mit der Throwable-Methode getStackTrace abgefragt werden. Diese Methode liefert den kompletten Stack zurück. Mittels der in Java-9 eingeführten Klasse StackWalker können solche Informationen nun lazy ermittelt werden. Somit kann man z.B. erhebliche Kosten sparen, sofern man nur an den obersten Elementen des Stacks interessiert ist (JEP 259)

HTTP/2

Java 9 führt ein neues HTTP client API ein, das HTTP/2 und WebSocket implementiert und ersetzt somit das alte HttpURLConnection-API. Das neue API wird als Incubator-Modul ausgeliefert (es ist also noch offiziell Bestandteil der SE) (JEP 110)

Multi-release jars

Das jar-Dateiformat erlaubt nun die Koexistenz mehrerer Java-Release-spezifischer Versionen von class-Dateien („multi-realease jar-files“). Dieses neue Feature verlangte natürlich auch die Erweiterung des jar-Tools (JEP 238)

GC

Als Garbage-Collector wird nun standardmäßig der G1 verwendet (dieser Collector eignet sich für große Heaps und mimimiert die Häufigkeit von Pausen). Er ersetzt somit den bislang verwendeten Parallel GC (JEP 248).

Compact Strings

Der Speicherverbrauch eines Programms wird maßgeblich von der Speicherung von Strings bestimmt. Strings wurden bislang mittels Arrays von chars implementiert – und ein char umfasst bekanntlich 2 Byte. In den meisten Fällen reicht aber ein einziges Byte zur Speicherung eines Zeichens aus. In Java 9 werden Strings nun – sofern möglich – platzsparender gespeichert („compact strings“) (JEP 254).

Parser-API für JavaScript

Der Java-Script-Interpreter Nashorn wurde um ein Parser-API erweitert. Das API erlaubt die Visitor-basierte Inspektion eines JavaScript-AST-Baumes (JEP 236)

Diamond-Operator

Bislang war die Benutzung des „Diamond-Operators“ beschränkt: bei der Definition von anonymen Klassen konnte er nicht verwendet werden. Diese Beschränkung ist nun aufgehoben worden (auch Kleinigkeiten sind wichtig…).

Für wen hat sich das Warten gelohnt? Für alle!

Was sich Entwickler jetzt noch wünschen? Das Modul-Konzept wird von einigen als zu statisch kritisiert. Kritisiert wird zuweilen auch, dass Module nicht geschachtelt werden können.

 

Seminare zu Java 9:

Neuerungen Java 9 und Jigsaw

Weiterlesen

Git in Java-Seminaren – Teil 1: Kurzeinführung in Git

In dieser Reihe möchte ich das Versionsverwaltungssystem Git vorstellen, das auch für Java-Seminare der Integrata benutzt wird, um den Teilnehmern eine komfortable Arbeitsumgebung zu definieren. Weiterhin werden die Musterbeispiele für verschiedene Seminare über öffentliche Git-Server bereitgestellt.

Dieser erste Teil liefert eine Kurzeinführung in das elementare Arbeiten mit Git.

Teil 2 stellt das Eclipse-PlugIn für Git vor, da Eclipse die Standard-Entwicklungsumgebung bei der Integrata ist.

Teil 3 schließlich zeigt, wie Git in Java-Seminaren benutzt werden kann.

Eine Kurzeinführung in Git

Ein Versionsverwaltungssystem ermöglicht es, verschiedene Stände eines Dateibestands eindeutig und reproduzierbar abzulegen. Solche Systeme sind innerhalb eines Software-Projekts unverzichtbar, um Quellcodes und  Konfigurationsdateien abzulegen. Git ist ein so genanntes Verteiltes Source Code Management System. Das bedeutet, dass auch ohne Server-Anbindung auf jedem Rechner mit Git-Installation ein vollständiges Versionsverwaltungssystem, ein so genanntes Git-Repository, zur Verfügung steht. Ein Repository besteht aus einer internen Dateiablage (das Verzeichnis .git), das enthaltende Verzeichnis ist der Arbeitsbereich des Git-Projekts.

„Vollständig“ bedeutet, dass der Anwender unter anderem die folgenden Befehle benutzen kann, die typisch für das Arbeiten mit einem Versionsverwaltungssystem sind:

  • commit
    • Mehrere Änderungen an einem Bestand werden mit einer Beschreibung versehen und im Versionsverwaltungssystem abgelegt. Dabei schreitet der aktive Branch mit jedem commit einen Schritt voran.
    • Bei Git müssen Dateien mit Hilfe des Befehls add erst einmal für eine Änderung vorgemerkt werden.
  • branch
    • Erzeugt einen neuen Branch, auf dem dann unabhängig weiterentwickelt werden kann. Branches können beliebig benannt werden und in beliebiger Anzahl existieren.
  • checkout <branch-name>
    • Die Dateien eines Branches werden in den Arbeitsbereich kopiert.
  • merge <branch-to-merge>
    • Änderungen eines Branches werden mit einem anderen Branch zusammengeführt. Dies kann natürlich zu Konflikten führen, diehändisch bereinigt werden müssen.
  • status
    • Zeigt den aktuellen Status des Repositories an, also beispielsweise geänderte oder neu hinzugefügte Dateien.

Git Server

Die Benutzung eines gemeinsamen Servers ist auch bei Git sinnvoll möglich. Im Internet sind Git-Server nach Anmeldung frei verfügbar, in denen dann beliebig viele Repositories angelegt werden können. Bekannt sind hier GitHub und GitLab.

Ist auf einem Git-Server ein Repository eingerichtet, wird kann dieses bei vorhandener Internet-Verbindung als lokales Repository „gecloned“ werden.

Zur Verwendung eines Remote Repositories werden zusätzliche Befehle benötigt:

  • clone <directory or url>
    • Das entfernte Repository wird komplett in das aktuelle lokale Verzeichnis übertragen.
  • pull
    • Stehen auf dem entfernten Repository neue Versionen zur Verfügung, so werden diese in das lokale Verzeichnis übernommen.
    • Hierbei wird effektiv der lokal vorhandene Branch mit dem korrespondierenden entfernten Branch gemerged. Dabei können natürlich wiederum Konflikte auftreten, die im lokalen Repository gelöst werden müssen.
  • push
    • Lokale Änderungen werden in das entfernte Repository übertragen. Das entfernte Repository wird aber diese Änderungen nur akzeptieren können, wenn keine Konflikte vorhanden sind.
    • So müssen im normalen Workflow in Git vor einempusherst einmal der potenziell geänderte Stand gepulled werden. Dann werden die Stände lokal konsolidiert und anschließend erst hochgepushed.

Ein Beispiel

  • Auf GitLab.com steht ein simples Projekt mit einer einzigen Datei namens README.md zur Verfügung. Dieses wird in auf einem lokalen Rechner gecloned
    • git clone https://github.com/JavacreamTraining/simple.git
  • Die Datei kann über einen Texteditor geändert und gespeichert werden.
  • git status zeigt die Änderung an.
  • git commit -a -m "my commit message" stellt die Änderungen unter Versionskontrolle.
  • git status zeigt die Konsistenz zwischen Workspace und Repository an, es gibt keine nicht committeten Stände.
  • git push wird hier nicht funktionieren, das Repository ist auf dem Server aus nahe liegenden Gründen geschützt.

Im nächsten Teil wird das Arbeiten mit Git durch die Verwendung des Eclipse-Plugins vereinfacht werden.


Seminar zum Thema

Weiterlesen

Swift und Java

Java und Apple Swift

Apple Swift – Eine moderne Programmiersprache

Mit der Programmiersprache Swift hat Apple ein modernes Werkzeug für die Anwendungsentwicklung für Mac und iOS bereit gestellt. Diese Sprache löst das von vielen Entwicklern als etwas gewöhnungsbedürftig und sperrig betrachtete Objective C ab.  Mit der aktuellen Version 3 steht eine erste vollständige und unabhängige Implementierung zur Verfügung. Die anstehende Version 4 wird weitere Features einführen, jedoch keine Änderungen an der Core-Sprache mehr vornehmen.

Swift wurde von Apple als Open Source-Initiative freigegeben und steht damit auch für andere Plattformen zur Verfügung, z.B. für Ubuntu. Eine durchaus beachtenswerte Abkehr von der bisherigen Abschottung der Apple-Produkte. Allerdings gilt dies natürlich nur für den Kern der Sprache, die App-Programmierung muss aus Lizenz-Gründen weiterhin unter Apple Hardware und Betriebssystem erfolgen. Diese Einschränkung ist für diesen Artikel allerdings nicht relevant, da ich mich hier bewusst auf den Vergleich der Programmiersprachen konzentrieren möchte; für einen (durchaus interessanten!) Vergleich von Google Android  und Apple Swift gibt es andere Artikel.

Swift vereinigt etablierte Konzepte aus Sprachen wie Java, C#, JavaScript und natürlich auch Objective C und ergänzt diese durch eigene Ideen. Damit finden Programmierer einesteils recht viel Bekanntes, aber eben auch unerwartete und damit überraschende Konstrukte.  Hier möchte ich auf die Unterschiede zu Java eingehen, die aus meiner Erfahrung heraus relevant sind.

Swift und Java – Welche Konzepte sind interessant?

Für Java-Entwickler ist Swift auch deshalb interessant, weil einige Konzepte durchaus auch in einer zukünftigen Java-Version Einzug halten könnten. Weiterhin gibt es doch eine Reihe von Besonderheiten, die den Einstieg in Swift erschweren könnten.

Konsequente Type Inference

Swift ist wie Java streng und statisch typisiert. Damit prüft der Swift-Compiler, dass einer einmal deklarierten Variable kein Wert eines anderen Datentyps zugewiesen werden kann.

Allerdings ist eine Typ-Angabe bei der Deklaration optional; der Typ kann meistens aus dem Typen der Zuweisung bestimmt werden:

 

Optionals

Sehr gut gelöst ist meiner Meinung nach der Umgang mit Null-Werten, in Swift nilgenannt. Einer Variablen muss ein Nicht-Null-Wert zugewiesen werden, es sei denn, sie wird explizit als Optional-Typ deklariert:

Optionals sind in Swift eigene Typen, so dass der Compiler bei Zuweisungen die normale Typ-Prüfung durchführt und Fehler erkennt:

Optionale Typen müssen vor ihrer weiteren Verwendung „ausgepackt“=unwrapped werden. Dafür gibt es in Swift zwei Möglichkeiten:

  • Explizites Unwrap mit dem !-Operator
  • Sicheres Unwrap über die if -let -Konstruktion:

Obwohl diese Sequenzen für einen Java-Entwickler erst einmal merkwürdig aussehen liegt der Vorteil auf der Hand: Bereits der Compiler erkennt  Zuweisungen von Null-Werten, so dass der Entwickler auf null-Prüfungen oder das Fangen einer NullPointerException verzichten kann.

Und dann haben wir noch das „Optional Chaining“ beim Zugriff auf Eigenschaften eines Objekts:

Falls message2 nil sein sollte wird die hasPrefix-Methode nicht ausgeführt.

Klassen und Strukturen in Swift

Swift kennt zwei unterschiedliche Definitionen eines benutzerdefinierten Datentyps: Klassen und Strukturen.

Während Instanzen von Klassen wie in Java stets über Referenzen angesprochen werden, werden Strukturen immer als Werte angesehen:

Swift - Der Unterschied zwischen Strukturen und Klassen
Der Unterschied zwischen Strukturen und Klassen

Ganz besonders verwirrend für Java-Entwickler ist, dass alle Collections-Implementierungen in Swift als Strukturen realisiert wurden!

Weitere Besonderheiten sind eher unter „syntactic sugar“ zu sehen:

  • Einfache Definition von Properties durch get– und set-Blöcke
  • Bei Strukturen werden die Konstruktoren automatisch angelegt

Extensions

Alle Klassen und Strukturen sind in Swift „Open for Extension“. Das bedeutet, dass auch ohne Vererbung vorhandene Klassen jederzeit erweitert werden können. Nur zur Klarstellung: Dies gilt selbstverständlich auch für die Klassen der Standard-Bibliothek!

Zur Definition wird das Schlüsselwort extension benutzt. Im Folgenden eine (zugegebenermaßen triviale) Erweiterung der Person-Klasse sowie ein neuer Konstruktor für String:

 

Extensions können eine Klasse nicht mit neuen Attributen ausstatten. get– und set-Blöcke können jedoch verwendet werden.

Verschiedenes

  • Neben Klassen und Strukturen unterstützt Swift auch Enums und Tuples
  • Als Zeichen für Namen von Variablen oder Funktionen können beliebige Unicode-Zeichen benutzt werden.
  • Funktionsparameter haben eine  internen und gegebenenfalls einen öffentlichen Namen, der beim Aufruf mitgegeben werden muss:

    Sollen die Parameter nicht benannt sein wird der Unterstrich als öffentlicher Name benutzt:
  • Die Swift-Installation enthält eine REPL, so dass Sequenzen und einfache Anwendungen auch ohne Installation einer Entwicklungsumgebung ausgeführt werden können.
  • Schnittstellen heißen in Swift protocol, nicht interface.
  • Mit der String Interpolation werden Variablen bzw. Ausdrücke direkt in Zeichenketten ausgewertet:

  • Operatoren können jederzeit geändert oder für eigene Datentypen definiert werden

  • Auch neue Operatoren sind einfach zu deklarieren. So wird im folgenden Beispiel das Unicode-Zeichen für das Herz-Symbol als Operator zwischen zwei Personen definiert:

 

Die Beispiele

Die im Artikel beschriebenen Code-Fragmente stehen über meinen GitHub-Account zur freien Verfügung und können gerne für eigene Experimente benutzt werden. Die Sourcen gibt es hier.

Die Beispiele benötigen eine Swift-Installation, ich habe hierzu die Ubuntu-Installation benutzt.


Seminar zum Thema

Weiterlesen