Kotlin
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Null Safety
-
Section 4: Object-Oriented Kotlin
-
Section 5: Functional Kotlin
-
Section 6: Coroutines
-
Section 7: Collections Deep Dive
-
Section 8: Type System Deep Dive
-
Section 9: Interop and Tooling
-
Section 10: Kotlin DSLs and Patterns
-
Section 11: Testing and Quality
-
Section 12: Server-Side Kotlin
-
Section 13: Practical Projects
-
Section 14: Interview Practice
-
Section 15: More Practice Exercises
-
Section 16: More Standard Library
-
Section 17: Multiplatform Kotlin
-
Section 18: kotlin.collections In Depth
-
Section 19: kotlin.text In Depth
-
Section 20: kotlin.ranges and kotlin.sequences
-
Section 21: kotlin.io and File Handling
-
Section 22: kotlinx.coroutines Deep Dive
-
Section 23: kotlin.reflect
-
Section 24: Android Development with Kotlin Overview
-
Section 25: Kotlin Multiplatform Deep Dive
-
Section 26: Kotlin for Backend Deep Dive
-
Section 27: Kotlin Design Patterns
-
Section 28: Advanced Language Features
-
Section 29: More Practice Exercises
-
Section 30: More Interview Practice
-
Section 31: Kotlin Type System Deep Dive
-
Section 32: Kotlin Null Safety Advanced
-
Section 33: Kotlin Testing Deep Dive
-
Section 34: Kotlin Build Tooling Deep Dive
-
Section 35: Kotlin Serialization
-
Section 36: Kotlin Performance Considerations
-
Section 37: Kotlin Native Overview
-
Section 38: Kotlin for Data and Scripting
-
Section 39: More Coroutines Practice
-
Section 40: More Android-Adjacent Patterns
-
Section 41: More Practical Projects
-
Section 42: More Design and Architecture Practice
-
Section 43: Kotlin Language Evolution
-
Section 44: More Interview and Review
-
Section 45: Kotlin Delegation Patterns Deep Dive
-
Section 46: Kotlin Annotations Deep Dive
-
Section 47: Kotlin for Gradle Plugin Development
-
Section 48: Kotlin Concurrency Beyond Coroutines
-
Section 49: Kotlin Compiler Internals
-
Section 50: Real-World Kotlin Case Studies
-
Section 51: Final Practice Projects
-
Section 52: Kotlin for Server-Side Reactive Programming
-
Section 53: More Practice and Drills
-
Section 54: Kotlin Security Practices
48: Calling Java from Kotlin and Vice Versa
In the real world, you're rarely starting a project from a completely blank slate. More often than not, you're dropping Kotlin into a massive existing Java codebase or using a library that was written a decade ago in Java. The good news is that Kotlin was designed specifically to be "interoperable," which is a fancy way of saying they play very well together. They both compile to JVM bytecode, so as far as the virtual machine is concerned, they're speaking the same language.
To see this in action, let's imagine we're working on a retail app. We have a legacy Java class that handles loyalty point calculations—it's old, it's tested, and nobody wants to rewrite it. But we're building the new Customer Dashboard in Kotlin.
Dealing with the Legacy: Our Java Loyalty Engine
First, let's look at our Java class. I've kept it simple, but notice the classic Java patterns: private fields with public getters and setters.
// LoyaltyCalculator.java
public class LoyaltyCalculator {
private double multiplier = 1.5;
public double getMultiplier() {
return multiplier;
}
public void setMultiplier(double multiplier) {
this.multiplier = multiplier;
}
public int calculatePoints(int spendAmount) {
return (int) (spendAmount * multiplier);
}
}
Bringing Java into the Kotlin Fold
Now, let's use this in Kotlin. One of the best parts about this interop is that Kotlin treats Java getters and setters as properties. You don't have to call getMultiplier(); you just access multiplier.
// DashboardManager.kt
fun main() {
val calculator = LoyaltyCalculator()
// I can treat the Java getter/setter as a property!
calculator.multiplier = 2.0
val points = calculator.calculatePoints(100)
println("Customer earned $points points!")
}
It feels native, right? But there's a catch when we start moving in the other direction—calling Kotlin from Java.
The "Companion" Hiccup
I wanted to create a utility class in Kotlin to format the points for the UI, and I thought I'd use a companion object since that's where we usually put "static-like" methods in Kotlin. Here's what I wrote:
// PointsFormatter.kt
class PointsFormatter {
companion object {
fun format(points: Int): String {
return "$points Loyalty Points"
}
}
}
Then, I tried to call it from a Java class, expecting it to work like a static method:
// LegacyReport.java
public class LegacyReport {
public void printReport(int points) {
// ERROR: Cannot resolve method 'format' in PointsFormatter
String text = PointsFormatter.format(points);
System.out.println(text);
}
}
I hit a wall here. I forgot that Kotlin doesn't actually have static methods. The companion object is actually a singleton instance inside the class. To fix this, I have two choices: I could call PointsFormatter.Companion.format(points) in Java, but that looks ugly and leaks Kotlin's implementation details into Java. The professional way to handle this is using the @JvmStatic annotation.
// PointsFormatter.kt (Corrected)
class PointsFormatter {
companion object {
@JvmStatic
fun format(points: Int): String {
return "$points Loyalty Points"
}
}
}
Adding @JvmStatic tells the Kotlin compiler to generate a real static method in the bytecode. Now, the Java code PointsFormatter.format(points) works perfectly.
Handling Nullability Across the Border
One last thing to keep in mind: Java doesn't have Kotlin's strict null safety. When you call a Kotlin function from Java, Java can pass null into a parameter that you've marked as non-nullable. This can lead to a NullPointerException the moment the code enters the Kotlin function.
If you're calling Kotlin from a Java codebase you don't trust, you can use @NotNull or @Nullable annotations (from JetBrains or JSR-305). This helps the Kotlin compiler understand the Java intent and helps the Java IDE warn the developer before they pass a null value.
📋 Practical Task
Exercise: Bridging the Currency Converter
You are integrating a legacy Java CurrencyConverter into a new Kotlin FinanceApp. However, the bridge is currently broken due to nullability and visibility issues.
Your Task:
- Fix the Java call: In
FinanceApp.kt, you are trying to callconverter.getExchangeRate(). Change this to use Kotlin's property syntax instead of the explicit getter. - Fix the Kotlin utility: The
CurrencyUtils.ktfile has acompanion objectmethod calledformatCurrency(). The Java classLegacyAudit.javais failing to compile because it tries to callCurrencyUtils.formatCurrency(amount)as a static method. Add the necessary annotation to the Kotlin function to make this work. - Null Safety: The Java method
converter.setBaseCurrency(String currency)is being called with anullvalue inLegacyAudit.java, but the Kotlin side expects a non-nullableString. Update the Kotlin function signature to allow a nullableString?and add a null check that returns "Unknown Currency" if the input is null.
Starter Code:
// CurrencyConverter.java
public class CurrencyConverter {
private double rate = 1.2;
public double getExchangeRate() { return rate; }
public void setBaseCurrency(String currency) { /* logic */ }
}
// CurrencyUtils.kt
class CurrencyUtils {
companion object {
fun formatCurrency(double amount) = "$$amount"
}
}
// FinanceApp.kt
fun main() {
val converter = CurrencyConverter()
println(converter.getExchangeRate()) // FIX THIS
}
// LegacyAudit.java
public class LegacyAudit {
public void audit(double amount) {
System.out.println(CurrencyUtils.formatCurrency(amount)); // FAILS TO COMPILE
}
}
There are no comments for now.