Skip to main content

CS 3100: Program Design and Implementation II

Lecture 36: Kotlin: A Better Java

©2026 Ellen Spertus, CC-BY-SA

Learning Objectives

After this lecture, you will be able to explain how these Kotlin features address Java pain points, such as:

  • Nullable types
  • Named and default arguments
  • Top-level functions
  • Type inference
  • Conditional expressions
    • if expressions
    • switch expressions

Semester Grades: Labs

GradeTotal PointsIndividual AssignmentsGroup AssignmentsExamsLabsParticipation
A≥900≥240 (80%)≥160 (80%)≥280 (70%)≥11 completed≥40 (80%)
B≥800≥210 (70%)≥140 (70%)≥220 (55%)≥9 completed≥25 (50%)
C≥700≥180 (60%)≥120 (60%)≥200 (50%)≥7 completed
D≥600
F<600 or fails to meet above minimums

There are 14 labs; each is worth 5 points, with the total capped at 50 points (allowing you to miss a few without penalty).

For example, if you attend 10 labs, you get 50 points and can earn a B (possibly a B+).

Semester Grades: Attendance/Participation

Missed PollsParticipation Points
0–650 (full credit)
7–1142
12–1732
18–2320
24–2910
30+0

Participation Grading: You may miss up to 6 classes (or participating in 6 days' activities) with no penalty. Beyond 6 absences, your participation score decreases on a non-linear scale—missing a few extra classes has minimal impact, but missing many has a larger effect.

This scale is designed so that missing up to half the semester's lectures (17 classes) still qualifies you for a B in participation. However, students who attend regularly perform significantly better on exams, and missing all 50 participation points means you must score higher elsewhere to reach your target grade.

If you received a 0 for attendance on a day you think you participated, check your PollEV account, which lists all your responses. Send me a screenshot showing if you completed at least 50% of surveys.

Poll: Why do/don't you attend class/lecture when ungraded?

Poll Everywhere QR Code or Logo

Text espertus to 22333 if the
URL isn't working for you.

https://pollev.com/espertus

This poll is anonymous but counts toward participation.

Poll: What was your favorite puzzler?

A. Time for a Change: System.out.println(2.00 - 1.10);

B. A Change is Gonna Come: BigDecimal payment = new BigDecimal(2.00);

C. Animal Farm: "Animals are equal: " + pig == dog

D. Histogram Mystery: Math.abs(pair.hashCode()) % histogram.length

E. Ping Pong: t.run()

F. Long Division: long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000;

G. It's Elementary: System.out.println(12345 + 5432l); System.out.println(01234 + 43210);

H. Dang, I missed it.

Poll Everywhere QR Code or Logo

Text espertus to 22333 if the
URL isn't working for you.

https://pollev.com/espertus

Poll: What's your least favorite thing about Java?

Poll Everywhere QR Code or Logo

Text espertus to 22333 if the
URL isn't working for you.

https://pollev.com/espertus

This is a word cloud poll, so answer briefly, with hyphens separating words (e.g., null-pointers)

Common Criticisms of Java

  • Verbosity and boilerplate code

  • Everything must be in a class

  • Lack of null-safety

  • Checked exceptions

What Is Kotlin?

  • Created by JetBrains (makers of IntelliJ IDEA), released 2016
  • Designed to be fully interoperable with Java — runs on the JVM
  • Goals: fix Java's pain points while staying familiar to Java developers
  • In 2017, Google named it a first-class language for Android
  • Today used in Android, backend (Spring), and multiplatform development

Java and Kotlin on the JVM

Java

static int cube(int x) {
return x * x * x;
}
public static void main(String[] args) {
System.out.println(cube(3));
}

Kotlin

fun cube(x: Int) = x * x * x

fun main() {
println(cube(3))
}
⬇️ compiler ⬇️

Byte Code

static cube(I)I        public static main(Ljava/lang/String;)V
ILOAD 0 L0
ILOAD 0 LINENUMBER 8 L0
IMUL GETSTATIC System.out : Ljava/io/PrintStream;
ILOAD 0 ICONST_3
IMUL INVOKESTATIC Main.cube (I)I
IRETURN INVOKEVIRTUAL java/io/PrintStream.println (I)V
JVM ➡️27

A Closer Look

Java

public class MyClass {
static int cube(int x) {
return x * x * x;
}

public static void main(String[] args) {
System.out.println(cube(3));
}
}

Kotlin

fun cube(x: Int) = x * x * x

fun main() {
println(cube(3))
}
  • Code outside class (top-level functions)
  • Visibility defaults to public
  • fun keyword
  • Different parameter lists
    • name followed by : and type
    • all types are capitalized (objects)
  • A function body can be an expression
  • Return type can be inferred

Kotlin Koans

An interactive Kotlin tutorial

https://play.kotlinlang.org/koans/Introduction/Hello,%20world!/Task.kt

Complete Koan 1: Hello, world!

Remember Telescoping Constructors (L17)?

public class Recipe {
// Telescoping constructors — each adds one more parameter
Recipe(String name) { this(name, List.of(), List.of(), List.of(), null, false); }
Recipe(String name, List<Ingredient> ingredients) { this(name, ingredients, List.of(), List.of(), null, false); }
Recipe(String name, List<Ingredient> ingredients, List<String> instructions) { ... }
Recipe(String name, List<Ingredient> ingredients, List<String> instructions, List<String> notes) { ... }
Recipe(String name, List<Ingredient> ingredients, List<String> instructions, List<String> notes, String source) { ... }
Recipe(String name, List<Ingredient> ingredients, List<String> instructions, List<String> notes, String source, boolean isVegan) { ... }
}

Default Arguments

Java

void greet(String name, String greeting, int times) {
for (int i = 0; i < times; i++) {
System.out.printf("%s, %s\n", greeting, name);
}
}
void greet(String name, String greeting) { greet(name, greeting, 1); }
void greet(String name) { greet(name, "Hello", 1); }

Kotlin

fun greet(name: String, greeting: String = "Hello", times: Int = 1) {
repeat(times) { println("$greeting, $name!") }
}

As in Python and JavaScript

  • Parameters can have default values
  • String templates are built-in

Kotlin Koan: Named Arguments

Java

void createUser(String firstName,
String lastName, String email) { ... }

createUser("John", "Smith",
"john@example.com");

// or is it:
createUser("Smith", "John",
"john@example.com");

Kotlin

fun createUser(firstName: String,
lastName: String, email: String) { ... }

createUser(
firstName = "John",
lastName = "Smith",
email = "john@example.com")

// This also works:
createUser(
lastName = "Smith",
firstName = "John",
email = "john@example.com")

Named Arguments: joinToString

joinToString is declared with default parameter values:

fun joinToString(
separator: String = ", ",
prefix: String = "",
postfix: String = "",
/* ... */
): String

Make joinOptions() return JSON format (e.g. [a, b, c]) using only two arguments:

fun joinOptions(options: Collection<String>) =
options.joinToString(TODO())

https://play.kotlinlang.org/koans/Introduction/Named%20arguments/Task.kt

Kotlin Koan: Default Arguments

Replace all these Java overloads:

public String foo(String name, int number, boolean toUpperCase) {
return (toUpperCase ? name.toUpperCase() : name) + number;
}
public String foo(String name, int number) {
return foo(name, number, false);
}
public String foo(String name, boolean toUpperCase) {
return foo(name, 42, toUpperCase);
}
public String foo(String name) {
return foo(name, 42);
}

with one Kotlin function so that useFoo() compiles:

fun useFoo() = listOf(
foo("a"),
foo("b", number = 1),
foo("c", toUpperCase = true),
foo(name = "d", number = 2, toUpperCase = true)
)

https://play.kotlinlang.org/koans/Introduction/Default%20arguments/Task.kt

Nullable Types

In Java, any reference can be null:

String name = null;
System.out.println(name.length()); // NullPointerException at runtime

In Kotlin, non-nullable is the default:

var name: String = null  // does not compile
var name: String? = null // ok — question mark indicates nullability

Safe Call Operator ?.

Kotlin won't let you call methods on a nullable type without handling the null case:

var name: String? = null
println(name.length) // does not compile

The safe call operator returns null instead of throwing an exception:

println(name?.length)  // prints null

Chains short-circuit at the first null:

println(user?.address?.city)  // prints null if any part is null

Kotlin Koan: Nullable Types

Rewrite this Java code so it only has one if:

public void sendMessageToClient(
@Nullable Client client,
@Nullable String message,
@NotNull Mailer mailer
) {
if (client == null || message == null) return;
PersonalInfo personalInfo = client.getPersonalInfo();
if (personalInfo == null) return;
String email = personalInfo.getEmail();
if (email == null) return;
mailer.sendMessage(email, message);
}
class Client(val personalInfo: PersonalInfo?)
class PersonalInfo(val email: String?)
interface Mailer {
fun sendMessage(email: String, message: String)
}

fun sendMessageToClient(
client: Client?, message: String?, mailer: Mailer
) {
TODO()
}

https://play.kotlinlang.org/koans/Introduction/Nullable%20types/Task.kt

when: Kotlin's Switch Expression

Java (pre-14)

String describe(int n) {
String result;
switch (n) {
case 0:
result = "zero";
break;
case 1:
case 2:
result = "small";
break;
default:
result = "other";
}
return (n < 0 ? "negative " : "")
+ result;
}

Java (14+, 2020)

String describe(int n) {
String result = switch (n) {
case 0 -> "zero";
case 1, 2 -> "small";
default -> "other";
};
return (n < 0 ? "negative " : "")
+ result;
}
String describe(int n) {
return (n < 0 ? "negative " : "")
+ switch (n) {
case 0 -> "zero";
case 1, 2 -> "small";
default -> "other";
};
}

Kotlin (2016)

fun describe(n: Int): String {
val result = when (n) {
0 -> "zero"
1, 2 -> "small"
else -> "other"
}
return (if (n < 0) "negative " else "")
+ result
}
fun describe(n: Int) =
(if (n < 0) "negative " else "") +
when (n) {
0 -> "zero"
1, 2 -> "small"
else -> "other"
}

L5: Before Records: 30 Lines for "I Want x and y"

public final class Point {           // final = can't extend
private final int x; // final = immutable
private final int y;

public Point(int x, int y) { this.x = x; this.y = y; }

public int x() { return x; } // accessors
public int y() { return y; }

@Override public boolean equals(@Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof Point other)) return false;
return x == other.x && y == other.y;
}

@Override public int hashCode() { return Objects.hash(x, y); }

@Override public String toString() { return "Point[x=" + x + ", y=" + y + "]"; }
}

All of this just to say: "I have an immutable point with x and y."

L5: Records (Java 16, 2021): Data Classes Without Boilerplate

A record is all 30 lines in one:

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

<p style={{fontSize: '0.85em', marginTop: '0.5em'}}>
You get automatically:
</p>

<ul style={{fontSize: '0.8em'}}>
<li>Constructor: <code>new Point(1, 2)</code></li>
<li>Accessors: <code>point.x()</code>, <code>point.y()</code></li>
<li>Correct <code>equals</code>, <code>hashCode</code>, <code>toString</code></li>
<li><strong>Immutability</strong>: all fields are <code>final</code>, class is <code>final</code></li>
</ul>

<aside className="notes">
Kotlin was introduced in 2011. Kotlin 1.0 came out in 2016.
Kotlin had it sooner -- and better.
</aside>

</Slide>

<Slide>

## Kotlin Data Classes

```kotlin
data class BankAccount(private val accountNumber: String, var balance: BigDecimal)

Kotlin data classes predate Java records and are more powerful.

Java RecordKotlin Data Class
MutabilityAlways final (immutable)val (immutable) or var (mutable)
Field VisibilityAlways privateConfigurable (private, public, etc.)
Accessor VisibilityAlways publicn/a

A Mob Class

Java

public class Mob {
public enum Behavior {
Passive, Neutral, Hostile, Boss
}
public enum Status {
Healthy, Injured, Dead
}

private final String type;
private final int maxHearts;
private final Behavior behavior;
private int hearts;

public Mob(String type, int maxHearts,
Behavior behavior) {
this.type = type;
this.maxHearts = maxHearts;
this.behavior = behavior;
this.hearts = maxHearts;
}

public String getType() { return type; }
public int getMaxHearts() { return maxHearts; }
public Behavior getBehavior() { return behavior; }
public int getHearts() { return hearts; }

public Status getStatus() {
if (hearts == maxHearts) return Status.Healthy;
if (hearts == 0) return Status.Dead;
return Status.Injured;
}

@Override
public String toString() { return type; }
}

Kotlin

class Mob(val type: String, val maxHearts: Int, val behavior: Behavior) {
enum class Behavior {
Passive, Neutral, Hostile, Boss
}
enum class Status {
Healthy, Injured, Dead
}

override fun toString() = type

var hearts = maxHearts
private set

val status: Status
get() = when (hearts) {
maxHearts -> Status.Healthy
0 -> Status.Dead
else -> Status.Injured
}

fun main() {
val zombie = Mob("zombie", 20, Mob.Behavior.Hostile)
println("$zombie has ${zombie.hearts} hearts and is ${zombie.status}.")
println(zombie.takeDamage(2, 5))
println("$zombie has ${zombie.hearts} hearts and is ${zombie.status}.")
}
}

https://pl.kotl.in/PcAVCiIIx

Kotlin Koan: Smart Casts

interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr

fun eval(expr: Expr): Int =
when (expr) {
is Num -> TODO()
is Sum -> TODO()
else -> throw IllegalArgumentException(
"Unknown expression")
}

https://play.kotlinlang.org/koans/Classes/Smart%20casts/Task.kt

Kotlin Koan: Sealed Classes

fun eval(expr: Expr): Int =
when (expr) {
is Num -> TODO()
is Sum -> TODO()
}

interface Expr
class Num(val value: Int) : TODO()
class Sum(val left: Expr, val right: Expr) : TODO()

https://play.kotlinlang.org/koans/Classes/Sealed%20classes/Task.kt

Practice Using Smart Cast in When (if time)

// Describe a value using "when"
// Expected output:
// 42 is an Int
// hello is a String with 5 characters
// 3.14 is a Double
// true is something else

fun describe(value: Any): String =
when (value) {
TODO()
}

fun main() {
println(describe(42))
println(describe("hello"))
println(describe(3.14))
println(describe(true))
}

https://pl.kotl.in/7idyhIvkJ

Extension Functions

Can you add a new method to an existing class whose source code you don't have?

Not in Java, but in Kotlin...

fun String.truncate(maxLength: Int): String {
return if (this.length <= maxLength) this else take(maxLength - 3) + "..."
}

// Hint: Use the String method reversed()
fun String.isPalindrome(): Boolean = TODO()

fun main() {
val shortUsername = "KotlinFan42"
val longUsername = "JetBrainsLoverForever"

println("Short username: ${shortUsername.truncate(15)}")
println("Long username: ${longUsername.truncate(15)}")

// println("wow".isPalindrome()) // true
// println("woo".isPalindrome()) // false
}

https://pl.kotl.in/RWcXCXM3p

Poll: Which Kotlin feature excites you most?

A. Null safety (no more NullPointerExceptions)

B. Default and named arguments (no more overloads)

C. Data classes (no more boilerplate)

D. Extension functions (add methods to existing classes)

E. Smart casts (no more explicit casting)

F. String templates (no more concatenation)

G. Conditional expressions (if/when)

H. I don't like any of them

Poll Everywhere QR Code or Logo

Text espertus to 22333 if the
URL isn't working for you.

https://pollev.com/espertus

Poll: How do you feel about Java?

Poll Everywhere QR Code or Logo

Text espertus to 22333 if the
URL isn't working for you.

https://pollev.com/espertus

Bonus Slide

Futurama hypnotoad with caption 'ALL HAIL KOTLIN'