Article

Java-Kotlin-TypeScript cheat sheet

A simple cheat sheet with Java, Kotlin and TypeScript code samples.

Intermediate
Florian Beaufumé
Florian Beaufumé LinkedIn X GitHub
Published 25 Nov 2024 - 30 min read
Java-Kotlin-TypeScript cheat sheet

Table of contents

Introduction

When I switch from Java, my main programming language, to Kotlin or TypeScript, I sometimes hesitate on a language feature or syntax. That's why I wrote a short and private cheat sheet. Later I initiated a more complete article out of it, partly to motivate myself to dive deeper and improve on the topic. I hope you will find it useful.

This sheet currently targets the main stable features of Java 21, Kotlin 2.0 and TypeScript 5. Also note that I focus on features that are common to at least two languages. Future updates may cover additional features.

If you found a typo or an error, feel free to contact me on X, BlueSky, or by email.

Comments

Single line comment.

// A single line comment
// A single line comment
// A single line comment

Multi-line comment.

/* A multi-line
comment */
/* A multi-line
comment */
/* A multi-line
comment */

Documentation comment.

/**
* Increment an integer by one.
*
* @param i the integer to increment
* @return the incremented value
*/

int increment(int i) {
return i + 1;
}
/**
* Increment an integer by one.
*
* @param i the integer to increment
* @return the incremented value
*/

fun increment(i: Int) = i + 1
/**
* Increment a number by one.
*
* @param n - The number to increment
* @returns The incremented value
*/

function increment(n: number): number {
return n + 1;
}

Basic types

Numeric types.

// See note #1
byte // 1 byte
short // 2 bytes
int // 4 bytes
long // 8 bytes
float // 4 bytes
double // 8 bytes
// See note #2
Byte UByte // 1 byte
Short UShort// 2 bytes
Int UInt // 4 bytes
Long ULong // 8 bytes
Float // 4 bytes
Double // 8 bytes
number

Boolean type.

boolean
Boolean
boolean

String types.

String
char
String
Char
string

Special types.

Object
Any
any
void
Unit
void

Notes:

  1. These are primitive types, Java also support Object types such as Byte, Short, Integer, etc.
  2. The U prefix is used for the unsigned variant.

Console logging

System.out.println("Hello");
println("Hello")
console.log('Hello'); // #1
console.warn('Some warning'); // #2
console.assert(qty < 50, 'Too large');
console.table(arrayOrObject);

Notes:

  1. TypeScript string can be defined using single or double quotes, i.e. 'Hello' or "Hello".
  2. The console supports additional methods such as debug, error, etc.

String interpolation

// Not supported, use concatenation
// or MessageFormat, printf, etc.
"Hello " + name
"Hello $name"
"Hello ${user.name}"
`Hello ${name}`

Variables

Declaration of a constant (that may be mutable or not).

final int i = 1;
final var i = 1;
val i: Int = 1
val i = 1
const i: number = 1;
const i = 1;

Definition of a variable.

int i = 1;
var i = 1;
var i: Int = 1
var i = 1
let i: number = 1;
let i = 1;
var i: number = 1; // #1
var i = 1; // #1

Notes:

  1. let is generally preferred over var, they have similar behavior but different scopes.

Arrays

Create an array of integers.

int[] a = {1, 2};
int a[] = {1, 2}; // #1
a: Array<Int> = arrayOf(1, 2)
a: IntArray = intArrayOf(1, 2) // #2
a: number[] = [1, 2];
a: Array<number> = [1, 2];

Notes:

  1. This C-style syntax is supported but not recommended.
  2. Preferred for arrays of basic types.

Collections

Create an immutable list of integers.

List<Integer> l = List.of(1, 2);
List<Integer> l = Arrays.asList(1, 2);
l: List<Int> = listOf(1, 2)
a: ReadonlyArray<number> = [1, 2];
a: readonly number[] = [1, 2];

Create a mutable list of integers.

List<Integer> l = new ArrayList<>(); //#1
List<Integer> l =
new ArrayList<>(immutableList);
l: MutableList<Int> = mutableListOf(1, 2)
a: number[] = [1, 2];
a: Array<number> = [1, 2];

Create an immutable map.

Map<String, Integer> m =
Map.of("a", 1, "b", 2);
m: Map<String, Int> =
mapOf("a" to 1, "b" to 2)
m: ReadonlyMap<string, number> =
mutableMap;

Create a mutable map.

Map<String, Integer> m =
new HashMap<>(); // #2
Map<String, Integer> m =
new HashMap<>(immutableMap);
m: MutableMap<String, Int> =
mutableMapOf("a" to 1, "b" to 2)
m: Map<string, number> =
new Map<string, number>([
['a', 1], ['b', 2]
]);

From a list (in Java and Kotlin) or array (in TypeScript) of strings, extract the three shortest lengths of strings containing a given letter.

List<Integer> lengthList = stringList
.stream()
.filter(s -> s.contains("e"))
.map(String::length)
.sorted()
.limit(3)
.toList();
val lengthList = stringList
.filter { it.contains("e") }
.map { it.length }
.sorted()
.take(3)
const lengthArray = stringArray
.filter(s => s.includes("e"))
.map(s => s.length)
.sort((a, b) => a - b)
.slice(0, 3);

Notes:

  1. Java supports additional list types: LinkedList is better for insertions/removals, CopyOnWriteArrayList is thread safe, etc.
  2. Java supports additional map types: ConcurrentHashMap is thread safe, etc.

Type alias

// Not supported
typealias Strings = Array<string>
type Strings = string[];

Destructuring

Object destructuring.

// Supported in limited cases,
// such as instanceof or switch for
// records, see dedicated sections
var (name, age) = user // #1
let {name, age} = user;
let {name: otherName} = user; // #2

Array or list destructuring.

// Not supported
var (a, b) = listOf(1,2)
let [a, b] = [1, 2];

Notes:

  1. This requires component operator methods or data class.
  2. This destructures the name property to a variable named otherName.

Functions

A simple function.

int add(int a, int b) {
return a + b;
}
fun add(a: Int, b: Int): Int {
return a + b
}

fun add(a: Int, b: Int): Int = a + b // #1
// Function as a statement
function add(a: number, b: number): number{
return a + b;
}

// Function as an expression
const add = function(
a: number,
b: number): number {
return a + b;
};

// Arrow function
const add = (a: number, b: number): number
=> a + b;

Function with default argument.

// Not supported, use overloading instead

void foo(int a) {
foo(a, 42);
}

void foo(int a, int b) { }
fun foo(a: Int, b: Int = 42) { }
function foo(a: number, b: number = 42) { }

Notes:

  1. Preferred for single-expression functions.

Generics

Generic method.

public <U, V> void foo(U u, V v) { }

foo("hello", 42);
fun <U, V> foo(u: U, v: V) { }

foo("hello", 42)
function foo<U, V>(u: U, v: V) { }

foo('hello', 42);

Generic class.

class Box<T> {
private T value;

public Box(T value) {
this.value = value;
}

public T unbox() {
return value;
}
}

Box<String> box = new Box<>("hello");
String value = box.unbox();
class Box<T>(val value: T) {
fun unbox() = value
}

val box = Box("hello")
val value = box.unbox()
class Box<T> {
constructor(private value: T) { }

unbox(): T {
return this.value;
}
}

const box = new Box<string>('hello');
const value = box.unbox();

Varargs, rest and spread

Declare a method with variable arguments.

void foo(String... names) { }
fun foo(vararg names: String) { }
function foo(...names: string[]) { }

Call a method with variable arguments.

foo(myStringArray);
foo(*myStringArray)
foo(...myStringArray);

Classes

Define a basic class.

class User {
private String name;
private int age;

public User(String name, int age) {
this.name = name;
this.age = age;
}

// Getters and setters for name and age

public String getDescription() {
return name + ", " + age;
}
}
class User(
var name: String,
var age: Int) {

fun getDescription() = "$name, $age"
}
class User {
constructor(
public name: string,
public age: number) {
}

getDescription(): string {
return `${this.name}, ${this.age}`;
}
}

Inherit from another class.

class Employee extends User {
private String company;

public Employee(
String name,
int age,
String company) {
super(name, age);
this.company = company;
}

// Getter and setter for company
}
class Employee(
name: String,
age: Int,
var company: String
) : User(name, age) // #1
class Employee extends User {
constructor(
name: string,
age: number,
public company: string) {
super(name, age);
}
}

Declare a static attribute (in Java and TypeScript) or a companion object (in Kotlin).

class User {
public static int headCount = 0; // #2

{
headCount++; // #3
}
}

System.out.println(User.headCount);
class User {
companion object {
var headCount = 0
}

init {
headCount++ // #4
}
}

println(User.headCount)
class User {
static headCount: number = 0;

constructor() {
User.headCount++;
}
}

console.log(User.headCount);

Instantiate the class.

new User("Alice", 42);
User("Alice", 42)
new User("Alice", 42);

Notes:

  1. Kotlin classes are final by default, this means that the base class must additionally be declared open, i.e. open class User ....
  2. Private visibility with getter and setter is usually recommended. Proper synchronization may be needed.
  3. This instance initializer block syntax is lesser known, you can also execute the increment in a constructor.
  4. Or in a constructor.

Interfaces

Define an interface, with a default method and a static method when supported.

interface Person {
String getName(); // #1

void give(Item item);

default String greet() {
return "Hello " + getName();
}

static boolean checkName(String name) {
return name!=null && !name.isBlank();
}
}
interface Person {
var name: String

fun give(item: Item)

fun greet() = "Hello $name"

companion object {
fun checkName(name: String) =
!name.isBlank()
}
}
interface Person {
name: string;

give(item: Item): void;

// Default or static methods
// are not supported
}

Implement an interface.

class User implements Person {
private String name;

public User(String name) {
this.name = name;
}

@Override
public String getName() { // #1
return name;
}

@Override
public void give(Item item) {
// Some code
}

@Override
public String greet() {
return Person.super.greet()
+ ", welcome";
}
}
class User(
override val name: String
) : Person {
override fun give(item: Item) {
// Some code
}

override fun greet() =
super.greet() + ", welcome"
}
class User implements Person {
constructor(public name: string) { }

give(item: Item): void {
// Some code
}
}

// Or directly "instantiate" the interface
const user: Person = {
name: 'Alice',
give: (item: Item) => {
// Some code
}
};

Notes:

  1. Name setter omitted for brevity.

Lambda

Define and execute a lambda expression, also named arrow function in TypeScript.

Function<Integer, Integer> inc =
a -> a + 1;

int incremented = inc.apply(1);

BiFunction<Integer, Integer, Integer> add
= (a, b) -> a + b;

int sum = add.apply(1, 2);
val inc = { x: Int -> x + 1 }
val inc: (Int) -> Int = { x -> x + 1 }
val inc: (Int) -> Int = { it + 1 }

val incremented = inc(1)

val add = { x: Int, y: Int -> x + y }

val sum = add(1, 2)
const inc = (a: number) => a + 1;
const inc = (a: number): number => a + 1;

const incremented = inc(1);

const add = (a: number, b: number)
=> a + b;

const sum = add(1, 2);

Define and use a custom lambda type.

// With a Java functional interface
@FunctionalInterface // #1
interface Operator {
int operate(int a, int b);
}

Operator add = (a, b) -> a + b;

int sum = add.operate(1, 2);
// With a Kotlin single abstract
// method (SAM) interface
fun interface Operator1 {
fun operate(x: Int, y: Int): Int
}

val add1 = Operator1 { x, y -> x + y }

val sum1 = add1.operate(1, 2)

// With a type alias
typealias Operator2 = (Int, Int) -> Int

val add2: Operator2 = { x, y -> x + y }

val sum2 = add(1, 2)
// With a type alias
type Operator = (a: number, b: number)
=> number;

const add: Operator = (a, b) => a + b;

val sum = add(1, 2);

Notes:

  1. @FunctionalInterface is not required, but helps making sure the interface is functional.

Method reference

Use a method reference, for example instead of a lambda expression.

// For an instance method
User u = new User("Alice");
Supplier<String> nameGetter = u::getName;
String name = nameGetter.get();

// For a static method
Supplier<Integer> countGetter =
User::getCount;
int count = countGetter.get();
// For an instance method
val u = User("Alice")
val nameGetter = u::getName
val name = nameGetter()

// For a companion object method
val countGetter = User::getCount
val count = countGetter()
// For an instance method
const u = new User('Alice');
const nameGetter = u.getName.bind(u);
const name = nameGetter();

// For a static method
const countGetter = User.getCount;
const count = countGetter();

Records

Define a record (in Java) or a data class (in Kotlin).

public record Point(int x, int y) { }

public record Named<T>(
String name,
T value
) {
public String getDescription() {
return name + ": " + value;
}
}
data class Point(val x: Int, val y: Int)

data class Named<T>(
val name: String,
val value: T
) {
fun getDescription() = "$name: $value"
}
// No exact equivalent, TypeScript
// classes miss features such as
// equals, hashCode, toString.

Use a record or data class.

Named named = new Named<>("Size", 15);
Integer value = named.value();
String desc = named.getDescription();
val named = Named("Size", 15)
val value = named.value
val desc = named.getDescription()
// Not exact equivalent

Enums

Basic enumeration.

enum Color {
RED,
GREEN,
BLUE
}
enum class Color {
RED,
GREEN,
BLUE
}
enum Color { // #1
RED,
GREEN,
BLUE
}

Enumeration with an attribute and a method.

enum Color {
RED("#FF0000"),
GREEN("#00FF00"),
BLUE("#0000FF");

private final String hex;

Color(String hex) {
this.hex = hex;
}

public String getHex() { // #2
return hex;
}
}
enum class Color(val hex: String) { // #2
RED("#FF0000"),
GREEN("#00FF00"),
BLUE("#0000FF");
}
// Not supported

Notes:

  1. TypeScript enums are numeric by default (first value is 0, then 1, etc, but can be changed, e.g. RED = 10). String enums are also supported, e.g. RED = 'RED'. It is also possible to mix and match numeric and string values.
  2. Methods in Java and Kotlin enums can also be implemented on a per-value basis instead of per-class.

Equality

Reference equality.

x == y
x === y
x === y // #1

Structure equality.

x.equals(y)
x == y
// Not supported, #2

Notes:

  1. Similar to Java or Kotlin counterparts, but not identical.
  2. Not supported by the language, but implemented by some libraries.

Cast

Check the type of an object and use smart cast when supported.

if (x instanceof String) {
// Some code
}

if (x instanceof String s) {
// Some code (smart cast is supported)
}

// Destructuring is supported for records
if (x instanceof Pair(int x, int y)) {
// Some code using x and y
}
if (x is String) {
// Some code (smart cast is supported)
}
// For a primitive type
if (typeof x === 'string') {
// Some code
}

// For a class or a constructor function
if (x instanceof MyClass) {
// Some code (smart cast is supported)
}

Explicitly cast an object to a type.

String s = (String) x;
s: String = x as String
s: String? = x as String?
s: String? = x as? String
s: string = x as string;
s: string = <string> x;

Null handling

Declare a non-nullable variable.

int i = 1;
i: Int = 1
i: number = 1;

Declare a nullable variable.

Integer i = null;
i: Int? = null
i: number | undefined = undefined; // #1
i: number | null = null; // #1
i?: number // #2

Safe call operator (Kotlin) and optional chaining (TypeScript).

// No dedicated operator
a?.b
a?.b

Elvis operator (Kotlin) and nullish coalescing (TypeScript).

// No dedicated operator
a ?: "default"
a ?? 'default' // #3
a || 'default' // #4

Not-null assertion (Kotlin) and non-null assertion (TypeScript).

// No dedicated operator
a!!.b
a!.b

Notes:

  1. Using |undefined is generally preferred over |null.
  2. This is an optional parameter, for example in a method definition or in an interface, and defaults to undefined.
  3. ?? is nullish coalescing. It returns the right value when left is nullish (null or undefined).
  4. || is a logical or. It returns the right value when left is falsy (null, undefined, false, 0, '', etc).

Ternary operator

int min = a < b ? a : b;
// Not explicitly supported,
// use an if expression instead
val min = if (a < b) a else b
let min = a < b ? a : b;

If

If statement.

if (condition1) {
// Some code
} else if (condition2) {
// Some code
} else {
// Some code
}
if (condition1) {
// Some code
} else if (condition2) {
// Some code
} else {
// Some code
}
if (condition1) {
// Some code
} else if (condition2) {
// Some code
} else {
// Some code
}

Notes:

  1. As seen in the ternary operation section, a Kotlin if can return a value.

For

Increment from 0 to 9 included. break and continue are supported.

for (int i = 0; i < 10; i++) {
// Some code using 'i'
}
for (i in 0 until 10) { // #1
// Some code using 'i'
}

for (i in 0..<10) { // #1
// Some code using 'i'
}

for (i in 0..9) { // #1
// Some code using 'i'
}
for (let i = 0; i < 10; i++) {
// Some code using 'i'
}

Decrement from 10 to 0 included using a step of 2. break and continue are supported.

for (int i = 10; i >= 0; i -= 2) {
// Some code using 'i'
}
for (i in 10 downTo 0 step 2) { // #1
// Some code using 'i'
}
for (let i = 10; i >= 0; i -= 2) {
// Some code using 'i'
}

Iterate over an array or collection using imperative style. break and continue are supported.

for (Item item : items) {
// Some code using 'item'
}
for (item in items) {
// Some code using 'item'
}
for (let item of items) { // #2
// Some code using 'item'
}

Iterate over an array or collection using functional style. break and continue are not supported.

items.forEach(item -> {
// Some code using 'item'
});
items.forEach {
// Some code using 'it'
}
items.forEach(item => {
// Some code using 'item'
});

Notes:

  1. Kotlin uses the notion of range: for numbers, for characters, with step, first, last, min, max, sum, and so on.
  2. for ... of iterates over the values of an iterable object, while for ... in iterates over the property keys of an object.

While

While loop. break and continue are supported.

while (condition) {
// Some code
}
while (condition) {
// Some code
}
while (condition) {
// Some code
}

Do-while loop. break and continue are supported.

do {
// Some code
} while (condition);
do {
// Some code
} while (condition)
do {
// Some code
} while (condition);

Switch

Switch statement (in Java and TypeScript) or when statement (in Kotlin).

// Old syntax
switch (count) {
case 1:
case 2:
foo("Low");
break;
case 3:
foo("High");
break;
default:
throw new Exception("Bad"); // #1
}

// Additional old branch samples
case "Red":
case MONDAY:

// New syntax
switch (obj) {
case 1 -> foo("1")
case 2,3 -> foo("2 or 3");
default -> throw new Exception("Bad");
}

// Additional new branch samples
case "Red" -> foo("Red");
case MONDAY -> foo("Monday");
case String s -> foo(s.trim());
case String[] arr -> foo(arr.length);
case Point(int x, int y)
-> foo(x + y); // #2
case List l when l.size() > 50 ->
foo("Big list");
case null -> foo(null);
when (count) { // #3
1 -> println("1")
2, 3 -> println("2 or 3)"
in 4..6 -> println("Between 4 and 6")
else -> throw Exception("Bad") // #1
}

// Additional branch samples
"Red" -> println("Red")
Day.MONDAY -> println("Monday")
in myRange -> println("In range")
!in 5..10 -> println("Not in range")
is String -> println(obj.trim()) // #4
switch (count) {
case 1:
case 2:
console.log('Low');
break;
case 3:
console.log('High');
break;
default:
throw new Error('Bad'); // #1
}

// Additional branch samples
case 'Red':
case Day.MONDAY:

Switch/when expression, i.e. a switch/when that returns a value.

var result = switch (count) { // #5
case 1, 2 -> "Low";
case 3 -> {
System.out.println("Hello");
yield "High";
}
default -> throw new Exception("Bad");
}
val result = when (count) { // #5
1, 2 -> "Low"
2 -> "High"
else -> throw Exception("Bad")
}
// Not supported

Notes:

  1. The default/else branch does not have to throw an exception/error and can execute regular code.
  2. This syntax is supported for records only.
  3. Kotlin also supports when without an argument, i.e. when { ... }, and conditions that evaluate to a boolean.
  4. Smart cast is supported by is expressions.
  5. The switch expressions support the same condition syntax that the switch statements.

Exception handling

Throw an exception (in Java and Kotlin) or error (in TypeScript).

throw new SomeException("Some message");
throw SomeException("Some message")
throw new SomeError('Some message');

Catch an exception or error.

try {
// Throw an exception
} catch (SomeException e) { // #1
// Handle the exception
} finally {
// Always executed
}
try {
// Throw an exception
} catch (e: SomeException) { // #1
// Handle the exception
} finally {
// Always executed
}
try {
// Throw an exception
} catch (e) { // #2
// Handle the error
} finally {
// Always executed
}

Notes:

  1. Java and Kotlin support multiple catch clauses per try.
  2. TypeScript catch clause does not support typing, instead use if (error instanceof SomeError) { ... } in the catch statement.

© 2007-2025 Florian Beaufumé