Table of Contents
What is an Interface?
An interface defines a contract that classes or other interfaces can implement. It declares methods (and in some cases, constants or properties) that implementing classes must provide, but it does not contain concrete implementations (with exceptions, as we’ll see). Interfaces enable loose coupling by separating “what” a class does from “how” it does it.
In both Java and Kotlin, interfaces support multiple inheritance: a class can implement any number of interfaces. However, Kotlin expands on this foundation with features that address Java’s limitations.
Key Differences Between Kotlin and Java Interfaces
1. Default Methods
Java 8 introduced default methods to interfaces, allowing concrete method implementations with the default keyword. Kotlin also supports default methods but simplifies the syntax by omitting the default keyword entirely.
Java Default Methods
In Java, default methods require the default modifier:
public interface Greetable {
// Abstract method (must be implemented by classes)
String getGreeting();
// Default method (provides a concrete implementation)
default void greet() {
System.out.println(getGreeting());
}
}
Kotlin Default Methods
Kotlin interfaces drop the default keyword—simply define the method with a body:
interface Greetable {
// Abstract method
fun getGreeting(): String
// Default method (no 'default' keyword needed)
fun greet() {
println(getGreeting())
}
}
Conflict Resolution: If a class implements two interfaces with conflicting default methods, both languages require explicit resolution.
-
Java: Use
InterfaceName.super.method()to specify which interface’s method to inherit:public class DualGreet implements Greetable, FormalGreetable { @Override public void greet() { Greetable.super.greet(); // Explicitly choose Greetable's greet() } } -
Kotlin: Use
super<InterfaceName>.method()for the same purpose:class DualGreet : Greetable, FormalGreetable { override fun greet() { super<Greetable>.greet() // Explicitly choose Greetable's greet() } }
2. Properties in Interfaces
Java interfaces are limited to constants ( public static final fields). They cannot declare instance properties or abstract properties. Kotlin, however, allows properties in interfaces—with constraints.
Java: Only Constants
Java interfaces can define constants (implicitly public static final), but no instance properties:
public interface Config {
// Constant (public static final by default)
int MAX_RETRIES = 3;
// ERROR: Cannot declare instance properties
// String version;
}
Kotlin: Abstract and Concrete Properties
Kotlin interfaces support properties, but they cannot hold state (no backing fields). Properties must be:
- Abstract: Declared without an initializer (implementing classes must provide the value).
- Concrete: Defined with a custom getter (no backing field allowed).
Example: Kotlin Interface Properties
interface Config {
// Abstract property (must be implemented by classes)
val version: String
// Concrete property with a getter (no backing field)
val maxRetries: Int get() = 3 // Computed on the fly; no stored state
// ERROR: Cannot have a backing field (initializer not allowed)
// val minRetries: Int = 1
}
// Implementing class provides the abstract property
class AppConfig : Config {
override val version: String = "1.0.0"
}
3. Static Members
Java interfaces explicitly support static methods and constants. Kotlin avoids the static keyword entirely, using companion objects to encapsulate static-like members in interfaces.
Java: Static Methods and Constants
Java interfaces can declare static methods and constants directly:
public interface MathUtils {
// Static constant
static final double PI = 3.14159;
// Static method
static int square(int num) {
return num * num;
}
}
// Usage: Call directly on the interface
MathUtils.square(5); // Returns 25
Kotlin: Companion Objects for Static-Like Members
Kotlin interfaces use companion object blocks to define static-like members (since Kotlin has no static keyword):
interface MathUtils {
// Companion object replaces static members
companion object {
// Compile-time constant (similar to Java's static final)
const val PI: Double = 3.14159
// Static-like method
fun square(num: Int): Int = num * num
}
}
// Usage: Call directly on the interface (same as Java)
MathUtils.square(5) // Returns 25
4. Visibility Modifiers
Java interfaces enforce public visibility for all members (methods, constants). Kotlin interfaces support granular visibility modifiers: public (default), internal, or private.
Java: All Members Are Public
Java interfaces have no control over visibility—everything is implicitly public:
public interface Secure {
// Implicitly public
void encrypt(String data);
// Implicitly public static final
String ALGORITHM = "AES";
}
Kotlin: Flexible Visibility
Kotlin interfaces can restrict member visibility:
private: Only visible within the interface.internal: Visible within the same module.public: Visible everywhere (default).
Example: Kotlin Interface Visibility
interface Secure {
// Public by default (visible to all)
fun encrypt(data: String): String
// Internal (visible only within the module)
internal fun validateKey(key: String): Boolean
// Private (only visible inside this interface)
private fun logEncryption() {
println("Encryption performed")
}
}
5. Functional Interfaces (SAM Interfaces)
A “functional interface” (or SAM interface) has exactly one abstract method (Single Abstract Method). Both languages support SAM conversion (using lambdas to implement the interface), but Kotlin adds explicit syntax.
Java: Implicit SAM Interfaces
Java implicitly treats any interface with one abstract method as a SAM interface. You can use lambdas to implement them:
// SAM interface (one abstract method)
@FunctionalInterface // Optional annotation for clarity
public interface ClickListener {
void onClick();
}
// Usage with lambda (SAM conversion)
ClickListener listener = () -> System.out.println("Clicked!");
Kotlin: Explicit fun Interfaces
Kotlin requires the fun modifier to mark an interface as a SAM interface. This makes intent clear and enables SAM conversion:
Example: Kotlin SAM Interface
// Explicit SAM interface with 'fun' modifier
fun interface ClickListener {
fun onClick()
}
// Usage with lambda (SAM conversion)
val listener = ClickListener { println("Clicked!") }
Key Difference: Kotlin only allows SAM conversion for:
- Java SAM interfaces (no
funmodifier needed). - Kotlin interfaces marked with
fun.
Non-fun Kotlin interfaces with one abstract method do not support SAM conversion.
6. Inheritance and Multiple Interfaces
Both languages allow classes to implement multiple interfaces, but Kotlin simplifies method overrides with the override keyword.
Java: Implicit Overrides
Java does not require the @Override annotation for interface methods (though it’s recommended for clarity):
public class Button implements ClickListener {
// Override is optional (but good practice)
public void onClick() {
System.out.println("Button clicked");
}
}
Kotlin: Mandatory override
Kotlin enforces the override keyword for all overridden members, preventing accidental method shadowing:
class Button : ClickListener {
// 'override' is mandatory
override fun onClick() {
println("Button clicked")
}
}
7. Sealed Interfaces
Sealed interfaces restrict which classes/interfaces can implement them, enabling exhaustive when statements. Kotlin and Java both support sealed interfaces, but with syntax and rules differences.
Java: sealed + permits
Java 17 introduced sealed interfaces with the sealed keyword and a permits clause to list allowed implementations:
// Sealed interface: only permits Circle and Square
public sealed interface Shape permits Circle, Square {
double area();
}
// All implementations must be in the same package
public final class Circle implements Shape { ... }
Kotlin: sealed with Implicit Permits
Kotlin’s sealed interfaces use the sealed keyword and implicitly restrict implementations to the same package (or file, in older Kotlin versions):
// Sealed interface (no 'permits' clause)
sealed interface Shape {
fun area(): Double
}
// Implementations must be in the same package/module
class Circle(val radius: Double) : Shape {
override fun area() = Math.PI * radius * radius
}
Key Difference: Kotlin does not require a permits clause—implementations are automatically restricted to the same package/module.
8. Interface Delegation
Kotlin simplifies “delegation” (reusing an interface implementation from another object) with the by keyword. Java requires manual method forwarding.
Java: Manual Delegation
In Java, you must explicitly forward all interface methods to a delegate object:
public interface Printer {
void print(String text);
}
public class ConsolePrinter implements Printer {
@Override
public void print(String text) {
System.out.println(text);
}
}
// Wrapper that delegates to ConsolePrinter
public class LoggingPrinter implements Printer {
private final Printer delegate;
public LoggingPrinter(Printer delegate) {
this.delegate = delegate;
}
// Manual delegation: forward to delegate
@Override
public void print(String text) {
System.out.println("Logging: " + text);
delegate.print(text); // Forward call
}
}
Kotlin: Built-In Delegation with by
Kotlin’s by keyword automatically delegates all interface methods to a delegate object, eliminating boilerplate:
Example: Kotlin Interface Delegation
interface Printer {
fun print(text: String)
}
class ConsolePrinter : Printer {
override fun print(text: String) {
println(text)
}
}
// Delegate all Printer methods to 'delegate' via 'by'
class LoggingPrinter(delegate: Printer) : Printer by delegate {
// Override specific methods if needed
override fun print(text: String) {
println("Logging: $text")
super.print(text) // Call delegate's print()
}
}
Summary
Here’s a quick recap of the key differences:
| Feature | Java | Kotlin |
|---|---|---|
| Default Methods | Requires default keyword | No default keyword; method body suffices |
| Properties | Only public static final constants | Abstract properties or concrete getters (no state) |
| Static Members | static methods/variables | Companion objects (no static keyword) |
| Visibility | All members public by default | Supports public, internal, private |
| SAM Interfaces | Implicit (one abstract method) | Explicit with fun modifier |
| Override Keyword | Optional (@Override annotation) | Mandatory (override keyword) |
| Sealed Interfaces | sealed permits clause | sealed (implicit package restriction) |
| Interface Delegation | Manual method forwarding | Built-in with by keyword |