TypeScript für Java-Entwickler - Header

TypeScript für Java-Entwickler

Avatar von Christopher Stock

Java-Entwickler die einen Job als Webentwickler annehmen, werden früher oder später mit der Programmiersprache JavaScript konfrontiert. Obwohl die Namen der beiden Sprachen sehr ähnlich klingen und auch deren Syntax recht ähnlich ist, handelt es sich dabei um zwei grundverschiedene Programmiersprachen.

TypeScript für Java-Entwickler

Für Webentwickler mit Java-Hintergrund stellt TypeScript eine attraktive Alternative für die Entwicklung von JavaScript-Anwendungen dar. Mit dieser Transpilersprache lässt sich JavaScript-Quellcode sehr gut strukturieren und effektiv verwalten.

In meinem letzten Artikel meiner Blogserie über TypeScript stelle ich alle wichtigen Features und Sprachkonstrukte dieser Sprache anhand der entsprechenden Pendants aus der Java-Welt vor und zeige die jeweiligen Gemeinsamkeiten und Unterschiede beider Sprachen auf.
[smartblock id=6354]

1. Kontrollstrukturen

Die Standard-Kontrollstrukturen If-Else, Switch, For und Foreach existieren in TypeScript unverändert – auch deren Syntax ist identisch mit der von Java.

1.1. If-Else Konstrukt

Beim If-Else-Konstrukt gibt es keinerlei Besonderheiten zu beachten; diese Kontrollstruktur funktioniert in beiden Sprachen gleich.

boolean primalCheck    = ( 10 > 5 ); 
boolean secondaryCheck = ( 10 < 5 );

if ( primalCheck )
{
    // passed
}
else if ( secondaryCheck )
{
    // not passed
}
else
{
    // not passed
}

let primalCheck:boolean    = ( 10 > 5 );
let secondaryCheck:boolean = ( 10 < 5 );

if ( primalCheck )
{
    // passed
}
else if ( secondaryCheck )
{
    // not passed
}
else
{
    // not passed
}

1.2. Switch-Anweisung

Unter Java ist die Switch-Anweisung auf Werte der Typen Integer und String sowie auf Enumerations-Konstanten beschränkt. TypeScript ist hier weniger restriktiv und erlaubt den Switch auf Variablen mit Werten beliebiger Typen.

Einen kleinen Unterschied in der Syntax gibt es beim Verwenden von Enumerations-Konstanten: Hier muss unter Java der Klassenname vor der Konstanten ausdrücklich weggelassen werden, wohingegen er in TypeScript angegeben werden muss.

MyDirection direction = this.getDirection();

switch ( direction )
{
    case LEFT:
    {
        this.moveLeft();
        break;
    }

    case RIGHT:
    default:
    {
        this.moveRight();
        break;
    }
}

let direction:MyDirection = this.getDirection();

switch ( direction )
{
    case MyCharacterDirection.LEFT:
    {
        this.moveLeft();
        break;
    }

    case MyCharacterDirection.RIGHT:
    default:
    {
        this.moveRight();
        break;
    }
}

1.3. For-Schleife

Die Syntax der For-Schleife ist ebenfalls identisch in beiden Sprachen. Lediglich bei der Definition der Iterator-Variablen unterscheidet sich deren Syntax.

MyGameObject[] gameObjects = this.getGameObjects();

for ( int i = 0; i < gameObjects.length; ++i )
{
    gameObjects[ i ].render();
}

let gameObjects:Array<MyGameObject> = this.getGameObjects();

for ( let i:number = 0; i < gameObjects.length; ++i )
{
    gameObjects[ i ].render();
}

1.4. Foreach-Schleife

TypeScript bietet eine einfach zu benutzende Foreach-Schleife, mit der komfortabel über alle Elemente einer Collection iteriert werden kann.

Die Syntax divergiert in beiden Sprachen wieder nur geringfügig: Bei TypeScript ist die Besonderheit, dass das Schlüsselwort let für die Iterator-Variable zwar angegeben werden muss, eine explizite Typenangabe dazu allerdings nicht erlaubt ist.

Sofern das Ausgabeformat des Compilers JavaScript ES5 oder niedriger ist, musss die Compileroption downlevelIteration mit dem Wert true angegeben werden damit die Foreach-Schleife verwendet werden kann.

MyGameObject[] gameObjects = this.getGameObjects();

for ( MyGameObject gameObject : gameObjects )
{
    gameObject.render();
}

let gameObjects:Array<MyGameObject> = this.getGameObjects();

for ( let gameObject of gameObjects )
{
    gameObject.render();
}

2. Klassen, Schnittstellen, Abstraktion und Vererbung

2.1. Klassen und Vererbung

Die Syntax für Klassen ist ebenfalls identisch in beiden Sprachen. Das gilt auch für das Ableiten von Klassen sowie für das Implementieren von Schnittstellen.

class MyPlayer extends MyGameObject implements MyShape, MyCollidable
{
    ...
}

class MyPlayer extends MyGameObject implements MyShape, MyCollidable
{
    ...
}

2.2. Konstruktoren

Bei der Definition des Konstruktors unterscheidet sich die Syntax beider Sprachen. Im Gegensatz zu Java können Konstruktoren in TypeScript nicht überlagert werden.

class MyPlayer extends MyGameObject
{
    public MyPlayer()
    {
        this( "Christopher" );
    }

    public MyPlayer( String name )
    {
        super( name );
    }
}

class MyPlayer extends MyGameObject
{
    public constructor( name:string )
    {
        super( name );
    }
}

2.3. Schnittstellen

Auch Schnittstellen funktionieren genauso wie in Java. Mittels des Schlüsselworts interface eingeleitete Schnittstellen definieren nicht-statische Felder und Methoden, die auf eine Klasse, welche die Schnittstelle implementiert, verfügbar gemacht werden.

Im Gegensatz zu TypeScript können unter Java Schnittstellen auch Konstanten definieren und Felder mit einem Wert vorbelegen. Außerdem müssen unter TypeScript alle durch eine Schnittstelle implementierten Felder explizit überschrieben werden.

Da es sich bei Interfaces um reine Entitäten des TypeScript-Compilers handelt, möchte ich darauf hinweisen, dass diese Information zur Laufzeit nicht mehr verfügbar ist. Die Verwendung des instaceof Operators sollte daher nur auf den Einsatz eigener Klassen beschränkt werden.

interface MyCircle extends MyShape
{
    public static final int SHAPE_ID = 7;

    public int diameter = 0;

    public getDiameter() : number;
}

interface MyCircle extends MyShape
{
    public diameter:number;

    public getDiameter() : number;
}

2.4. Abstrakte Klassen

Abstrakte Klassen existieren auch in TypeScript und stehen der Funktionsweise unter Java in nichts nach. Beide Sprachen leiten abstrakte Klassen mit dem Schlüsselwort abstract ein.

Abstrakte Klassen können auch unter TypeScript nicht direkt instanziiert werden. Daher können sie – neben allen gewöhnlichen statischen und nicht-statischen Methoden – auch abstrakte Methoden definieren, die ebenfalls mit dem Schlüsselwort abstract definiert werden und keinen Methodenkörper besitzen. Dieser wird von konkreten Klassen, die diese abstrakten Klassen erweitern, ausformuliert.

abstract class MyGameObject
{
    protected abstract MyShape getShape();
}

abstract class MyGameObject 
{
    protected abstract getShape() : MyShape;
}

2.5. Finale Klassen und Methoden

Das Schlüsselwort final kennzeichnet in Java eine Klasse, die nicht mehr abgeleitet werden darf. Auch Methoden, die von abgeleiteten Klassen nicht überschrieben werden sollen, können diesen Modifier verwenden.

Leider existiert zu diesem Zeitpunkt unter TypeScript noch kein Äquivalent für das final-Schlüsselwort; Vorschläge dafür befinden sich aber in Bearbeitung. Somit muss auf dieses Feature in TypeScript vorerst verzichtet werden.

final class MyEnemy extends MyGameObject
{
    protected final MyShape getShape()
    {
       return this.shape;
    }
}

class MyEnemy extends MyGameObject 
{
    protected getShape() : MyShape
    {
        return this.shape;
    }
}

3. Statische Typisierung

TypeScript ermöglicht eine statische Typisierung aller Variablen, Parameter und Rückgabewerte. Auf Verletzungen des Typsystems reagiert der TypeScript-Compiler somit analog zum Java-Compiler mit konkreten Fehlern und Warnungen.

In TypeScript wird der Typ der Variable mit einem Doppelpunkt eingeleitet und hinter deren Namen angegeben.

String name = "Christopher";

let name:string = "Christopher";

3.1. Typisierung lokaler Variablen

Lokale Variablen können mit dem Schlüsselwort let oder var eingeleitet werden. Hierbei ist die Variante mit let zu bevorzugen, da die Variable in diesem Fall nur innerhalb des aktuellen Scopes verwendet werden kann, beispielsweise nur innerhalb eines foreach-Körpers. Somit steht diese Variante dem Standardverhalten lokaler Variablen aus Java in nichts nach.

Wie in JavaScript auch ist es in TypeScript guter Stil, lokale Variablen die sich nicht verändern mit dem Schlüsselwort const anstatt let einzuleiten.

MyGameObject[] gameObjects = this.getGameObjects();

for ( int i = 0; i < gameObjects.length; ++i )
{
    gameObjects[ i ].render();
}

// i is not available here!

let gameObjects:Array<MyGameObject> = this.getGameObjects();

for ( let i:number = 0; i < gameObjects.length; ++i )
{
    gameObjects[ i ].render();
}

// i is not available here!

3.2. Typisierung von Klassenvariablen

Sowohl statische als auch nicht-statische Klassenvariablen können unter TypeScript typisiert werden. Die Syntax für die Typisierung ist hier die selbe wie für lokale Variablen.

class MyLevel
{
    public  static  MyLevel                 currentLevel    = null;

    public          int                     width           = 0;
    public          int                     height          = 0;

    public          Vector<MyGameObject>    gameObjects     = null;

    ...
}

class MyLevel
{
    public  static  currentLevel    :MyLevel                = null;
    
    public          width           :number                 = 0.0;
    public          height          :number                 = 0.0;

    public          gameObjects     :Array<MyGameObject>    = null;

    ...
}

3.3. Typisierung von Rückgabewerten

Für jede Funktion sowie für statische und nicht-statische Klassenmethoden kann unter TypeScript der Rückgabewert explizit angegeben werden. Dieser wird auch hier durch einen Doppelpunkt eingeleitet und ist zwischen der Signatur und dem Körper der Funktion zu definieren.

TypeScript unterstützt wie Java das Schlüsselwort void, mit dem ausgedrückt werden kann, dass eine Methode über keinen Rückgabewert verfügt.

public void setName( String name )
{
    this.name = name;
}

public setName( name:string ) : void
{
    this.name = name;
}

3.4. Casting

Das Casten von Variablen ist in TypeScript mittels spitzer Klammern möglich und funktioniert genauso wie in Java. Hiermit kann beispielsweise ein Objekt einer Superklasse in ein Objekt einer abgeleiteten Klasse gecastet werden.

MyGameObject gameObject = this.getCurrentGameObject();

if ( gameObject instanceof MyPlayer )
{
    MyPlayer player = (MyPlayer)gameObject;

    player.jump();
}

let gameObject:my.MyGameObject = this.getCurrentGameObject();

if ( gameObject instanceof my.MyPlayer )
{
    let player:MyPlayer = <MyPlayer>gameObject;

    player.jump();
}

4. Visibility Modifier

Auch unter TypeScript existieren die drei Visibility-Modifier public, protected und private. Die Verwendung ist identisch mit denen aus der Java-Welt, wobei nur beim protected-Modifier ein Unterschied existiert: Da es unter TypeScript keine Pakete gibt, gewährt dieser Modifier lediglich abgeleiteten Klassen Zugriff.

Diese Visibility Information liegt im kompilierten JavaScript-Code nicht mehr vor. Daher existieren auch unter TypeScript Code-Konventionen, die private oder protected Member einer Klasse mit einem Unterstrich zu präfixen.

abstract class MyGameObject
{
    private String name = null;

    protected MyGameObject( String name )
    {
        this.name = name;
    }

    public String getName()
    {
        return this.name;
    }
}

abstract class MyGameObject
{
    private name:string = null;

    protected MyGameObject( name:string )
    {
        this.name = name;
    }

    public getName() : String
    {
        return this.name;
    }
}

5. Static Modifier

Auch in TypeScript können Methoden und Felder einer Klasse sowohl statisch als auch nicht-statisch definiert werden. Das Verhalten steht dem von Java in nichts nach.

abstract class MyGameObject
{
    private static MyGameObject[] allGameObjects = new MyGameObject[] {};

    public static MyGameObject[] getAllGameObjects()
    {
        return MyGameObject.allGameObjects;
    }
}

abstract class MyGameObject
{
    private static allGameObjects:Array<MyGameObject&gt = [];

    public static getAllGameObjects() : Array<MyGameObject&gt
    {
        return MyGameObject.allGameObjects;
    }
}

6. Konstanten

Lokale Konstanten können in TypeScript mit dem Schlüsselwort const eingeleitet werden. Für Konstanten die innerhalb eines Klassenkörpers definiert werden, muss das Schlüsselwort readonly verwendet werden.

public      static  final   int     CANVAS_WIDTH        = 800;
public      static  final   int     CANVAS_HEIGHT       = 600;

public      static  readonly    CANVAS_WIDTH        :number     = 800;
public      static  readonly    CANVAS_HEIGHT       :number     = 600;

7. Enumerations

Enumerations stellen unter Java eine sehr mächtige und pragmatische Möglichkeit zur Strukturierung unseres Quellcodes dar, da sie die volle Funktionalität einer Klasse – inklusive Feldern, Methoden und Konstruktoren bieten – und dabei die Anzahl sowie Beschaffenheit der Instanzen exakt durch die Definition der Enumerations-Konstanten festlegen.

Auch unter TypeScript stellen Enumerations eine typisierte Alternative zur Verwendung von primitiven Datentypen dar. Leider sind sie hier aber nicht so umfangreich wie in Java.

Unter TypeScript werden Enumerations-Konstanten standardmäßig durch einen Integer-Wert repräsentiert, können aber auch mit anderen Werten, wie beispielsweise Strings, belegt werden. Die Definition von Feldern, Methoden oder Konstruktoren ist hier aber nicht möglich.

Enumerations können auch mit dem Schlüsselwort const eingeleitet werden. In diesem Fall werden im generierten JavaScript-Code alle Konstanten vom Compiler inline mit dem entsprechenden Wert ersetzt und die Existenz der Enumeration selbst aufgelöst.

enum MyDirection
{
    LEFT,
    RIGHT
    ;
}

enum MyImage
{
    PLAYER_WALK_LEFT(  "player/walk_left.png"  ),
    PLAYER_WALK_RIGHT( "player/walk_right.png" ),
    ;

    private String path = null;

    private MyImage( String path )
    {
        this.path = path;
    }

    public String getPath()
    {
        return this.path;
    }
}

enum MyDirection
{
    LEFT,
    RIGHT
    ;
}

enum MyImage
{
    PLAYER_WALK_LEFT  = "player/walk_left.png",
    PLAYER_WALK_RIGHT = "player/walk_right.png",
}

Weitere Teile der Serie

· TypeScript als JavaScript-Alternative
· Der große TypeScript-Workshop
· TypeScript-Tooling mit npm und Webpack

8. Generics

Generics existieren auch unter TypeScript und stehen dem aus Java gewohnten Umfang in nichts nach. Es handelt sich dabei um parametrisierbare Typen, die bei der Instanziierung der Klasse dynamisch gesetzt werden können. Als einfaches Beispiel kann ein Array angesehen werden, das nur Elemente eines festgelegten Datentyps beinhalten darf.

class MyCollection<K>
{
    private Vector<K> storage = null;

    public MyCollection()
    {
        this.storage = new Vector<K>();
    }

    public void addEntry( K k )
    {
        this.storage.add( k );
    }
}

MyCollection<String> myNames = new MyCollection<String>();
myNames.addEntry( "Christopher" );

class MyCollection<K>
{
    private storage:Array<K> = null;

    public constructor()
    {
        this.storage = new Array<K>();
    }

    public addEntry( k:K ) : void
    {
        this.storage.push( k );
    }
}

let myNames:MyCollection<string> = new MyCollection<string>();
myNames.addEntry( "Christopher" );

9. Modularisierung

Das Definieren von Namensräumen ist in Java von Anfang an nativ implementiert. Mittels den Anweisungen package und import kann jede Kompilationseinheit einen weltweit eindeutigen Paketnamen definieren und zudem genau spezifizieren, welche Klassen außerhalb des eigenen Pakets innerhalb der Einheit benötigt werden.

Unter TypeScript funktioniert die Modularisierung leider nicht so unkompliziert. Best Practice ist hier, auf die Verwendung der Schlüsselwörter namespace und module zu verzichten und stattdessen einfach alle zu einem Modul gehörenden Dateien innerhalb einer einzelnen Datei zu exportieren. Diese einzelne Exportdatei kann dann als eigener Namensraum in eine Kompilationseinheit importiert werden.

// src/de/christopherstock/mygame/MyLWJGLKeys.java

package de.christopherstock.mygame;

import de.christopherstock.mygame.io.*;
import java.awt.geom.*;
import org.lwjgl.input.*;

public class MyLWJGLKeys extends Keyboard
{
    public static final void checkKeys()
    {
        boolean keyHoldStrafeLeft = Keyboard.isKeyDown( Keyboard.KEY_A );

        ...
    }
}

// src/my.ts

export * from './game/MyGame';
export * from './game/MyLevel';
...
// src/game/MyGame.ts

import * as Matter from 'matter-js';
import * as my     from '../my';

export class MyGame
{
    public init()
    {
        my.MyDebug.init.log( "Initing game engine" );

        let engine:Matter.Engine = Matter.Engine.create();

        ...
    }
}

10. Exception-Handling

In Java können in einem Try-Catch-Konstrukt mehrere Catch-Blöcke definiert werden, sodass das Auftreten verschiedener Exceptions unterschiedlich behandelt werden kann. In TypeScript hingegen kann lediglich ein einzelner Catch-Block definiert werden, bei dem die hier definierte Variable zudem nicht typisiert werden darf. Trotzdem ist es möglich, auf unterschiedliche Subklassen der Error-Klasse unterschiedlich zu reagieren.

Zudem unterstützt TypeScript das Schlüsselwort finally, mit dem eine Codesektion definiert werden kann, die in jedem Fall im Anschluss an die Ausführung des Catch-Blocks durchlaufen wird, selbst wenn der Catch-Block mit einer weiteren Exception aussteigt.

try
{
    throw new RuntimeException( "Intentional RuntimeException" );
}
catch ( RuntimeException re )
{
    // passed
}
catch ( Exception e )
{
    // not passed
}
finally
{
    // always passed after catch block
}

try
{
    throw RangeError( "Intentional RangeError" );
}
catch ( e )
{
    // passed

    if ( e.name == "RangeError" )
    {
        // passed
    }
}
finally
{
    // always passed after catch block
}

Fazit

Anhand der gezeigten Codebeispiele ist deutlich erkennbar, dass die beiden Sprachen Java und TypeScript sehr viele Gemeinsamkeiten haben. TypeScript bietet hervorragende Möglichkeiten zur Erstellung und Verwaltung von gut strukturiertem Quellcode, dessen Codequalität der von Java-Anwendungen in nichts nachsteht.

Für Java-Entwickler die sich mit der Sprache JavaScript auseinandersetzen stellt TypeScript daher eine attraktive Alternative für den Umstieg zu JavaScript dar.

Ich würde mich sehr freuen, wenn ich mit meinem Artikel einen schnellen Einstieg oder Umstieg in die Programmiersprache TypeScript geben konnte. In diesem Beispielprojekt kommen die meisten der hier gezeigten TypeScript-Techniken zur Anwendung.

Wie immer bin ich für Feedback unter christopher.stock@mayflower.de erreichbar und dankbar.

Avatar von Christopher Stock

Kommentare

4 Antworten zu „TypeScript für Java-Entwickler“

  1. TypeScript für Java-Entwickler https://t.co/e3t8DRAvTy via @mayflowergmbh

  2. TypeScript für Java-Entwickler https://t.co/bTZL2wl9Dt via @mayflowergmbh

  3. #TypeScript für #Java-Entwickler – unser @jenetic1980 erklärt die Unterschiede der beiden Sprachen: https://t.co/8h4AFrEdKA

  4. Die Unterschiede zwischen #Java und #TypeScript im Überblick, erklärt von unserem @jenetic1980: https://t.co/8h4AFrEdKA

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert


Für das Handling unseres Newsletters nutzen wir den Dienst HubSpot. Mehr Informationen, insbesondere auch zu Deinem Widerrufsrecht, kannst Du jederzeit unserer Datenschutzerklärung entnehmen.