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.
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;
}
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:
Byte
, Short
, Integer
, etc.U
prefix is used for the unsigned variant.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:
'Hello'
or "Hello"
.console
supports additional methods such as debug
, error
, etc.// Not supported, use concatenation
// or MessageFormat, printf, etc.
"Hello " + name
"Hello $name"
"Hello ${user.name}"
`Hello ${name}`
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:
let
is generally preferred over var
, they have similar behavior but different scopes.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:
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:
LinkedList
is better for insertions/removals, CopyOnWriteArrayList
is thread safe, etc.ConcurrentHashMap
is thread safe, etc.// Not supported
typealias Strings = Array<string>
type Strings = string[];
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:
name
property to a variable named otherName
.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:
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();
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);
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:
open
, i.e. open class User ...
.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:
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:
@FunctionalInterface
is not required, but helps making sure the interface is functional.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();
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
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:
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.Reference equality.
x == y
x === y
x === y // #1
Structure equality.
x.equals(y)
x == y
// Not supported, #2
Notes:
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;
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:
|undefined
is generally preferred over |null
.undefined
.??
is nullish coalescing. It returns the right value when left is nullish (null
or undefined
).||
is a logical or. It returns the right value when left is falsy (null
, undefined
, false
, 0
, ''
, etc).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 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:
if
can return a value.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:
for ... of
iterates over the values of an iterable object, while for ... in
iterates over the property keys of an object.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 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:
default
/else
branch does not have to throw an exception/error and can execute regular code.when
without an argument, i.e. when { ... }
, and conditions that evaluate to a boolean.is
expressions.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:
catch
clauses per try
.catch
clause does not support typing, instead use if (error instanceof SomeError) { ... }
in the catch
statement.© 2007-2025 Florian Beaufumé