Interfaces is a great OOP concept. But there is one potential problem. If you publish an interface, other write classes implementing that interface and you add methods to the interface then those classes will no longer compile.
Various solutions has been created to handle that problem.
Java 8 added methods to a lot of core interfaces as part of the introduction of streams and lambda expressions.
To keep the compatibility a new feature default (defender) methods in interfaces was also added.
JSR 335:
This allows new functionality to be added to existing (and perhaps already widely-distributed) interfaces.
The Java Tutorials - Default Methods:
Default methods enable you to add new functionality to existing interfaces and ensure binary compatibility with code written for older versions of those interfaces. In particular, default methods enable you to add methods that accept lambda expressions as parameters to existing interfaces
Once published, it is impossible to add methods to an interface without breaking existing
implementations. The longer the time since a library has been published, the more likely
it is that this restriction will cause grief for its maintainers.
...
In this document, we outline a mechanism for adding new methods to existing interfaces,
which could be called virtual extension methods. Existing interfaces could be added to
without compromising backward compatibility by adding extension methods to the
interface, whose declaration would contain instructions for finding the default
implementation in the event that implementers do not provide one.
C# 3.0 and .NET 3.5 needed to add methods to a lot of core interfaces as part of the introduction of LINQ and lambda expressions.
Microsoft chose to handle it via extension methods defined in extension classes not default methods in the interfaces.
Extension methods are simple, but they have a huge downside - they are not supporting polymorphism and are therefore not real OO'ish.
So in C# 8.0 .NET 4.8 default methods in interfaces was added.
There are still some surprised hidden in default methods in interfaces, so be careful.
These has always been part of the Kotlin language. But the Java compatibility changed a few time in the early versions.
These has always been part of the Kotlin language.
There is one big difference between default methods and extension methods. Default methods do support polymorphism - extensions do not support polymorphism.
DMFun.java:
package compapi;
public class DMFun {
public static interface I1 {
default public void m() {
System.out.println("I1.m");
}
}
public static interface I2 extends I1 {
default public void m() {
System.out.println("I2.m");
}
}
public static class C implements I2 {
public void m() {
System.out.println("C.m");
}
}
public static void main(String[] args) {
I1 a = new C();
a.m();
I2 b = new C();
b.m();
C c = new C();
c.m();
var d = new C();
d.m();
}
}
C.m C.m C.m C.m
DMFun.kt:
package compapi.dm
interface I1 {
fun m(): Unit {
println("I1.m")
}
}
interface I2 : I1 {
override fun m(): Unit {
println("I2.m")
}
}
class C : I2 {
override fun m(): Unit {
println("C.m")
}
}
fun main(): Unit {
val a: I1 = C()
a.m()
val b: I2 = C()
b.m()
val c: C = C()
c.m()
val d = C()
d.m()
}
C.m C.m C.m C.m
EMFun.cs:
using System;
namespace EMFun
{
public interface I1
{
}
public static class EC1
{
public static void M(this I1 o)
{
Console.WriteLine("I1.M");
}
}
public interface I2 : I1
{
}
public static class EC2
{
public static void M(this I2 o)
{
Console.WriteLine("I2.M");
}
}
public class C : I2
{
public void M()
{
Console.WriteLine("C.M");
}
}
public class Program
{
public static void Main(string[] args)
{
I1 a = new C();
a.M();
I2 b = new C();
b.M();
C c = new C();
c.M();
var d = new C();
d.M();
Console.ReadKey();
}
}
}
I1.M I2.M C.M C.M
EMFun.kt:
package compapi.em
interface I1 {
}
fun I1.m(): Unit {
println("I1.m")
}
interface I2 : I1 {
}
fun I2.m(): Unit {
println("I2.m")
}
class C : I2 {
fun m(): Unit {
println("C.m")
}
}
fun main(): Unit {
val a: I1 = C()
a.m()
val b: I2 = C()
b.m()
val c: C = C()
c.m()
val d = C()
d.m()
}
I1.m I2.m C.m C.m
We will look at an example with:
And the B project want to add a method m2.
UML:
This is the starting point where everything is working.
Test.java:
package compapi;
public interface Test {
public void m();
}
TestA.java:
package compapi;
public class TestA implements Test {
@Override
public void m() {
System.out.println("TestA.m works");
}
}
DemoA.java:
package compapi;
public class DemoA {
public static void main(String[] args) {
Test a = new TestA();
a.m();
}
}
TestB.java:
package compapi;
public class TestB implements Test {
@Override
public void m() {
System.out.println("TestB.m works");
}
}
DemoB.java:
package compapi;
public class DemoB {
public static void main(String[] args) {
Test b = new TestB();
b.m();
}
}
ITest.cs:
public interface ITest
{
public void M();
}
TestA.cs:
using System;
public class TestA : ITest
{
public void M()
{
Console.WriteLine("TestA.M works");
}
}
DemoA.cs:
public class DemoA
{
public static void Main(string[] args)
{
ITest a = new TestA();
a.M();
}
}
TestB.cs:
using System;
public class TestB : ITest
{
public void M()
{
Console.WriteLine("TestB.M works");
}
}
DemoB.cs:
public class DemoB
{
public static void Main(string[] args)
{
ITest b = new TestB();
b.M();
}
}
Test.kt:
package compapi
interface Test {
fun m(): Unit
}
TestA.kt:
package compapi
class TestA : Test {
override fun m(): Unit {
println("TestA.m works")
}
}
DemoA.kt:
package compapi
fun main(): Unit {
val a = TestA()
a.m()
}
TestB.kt:
package compapi
class TestB : Test {
override fun m(): Unit {
println("TestB.m works")
}
}
DemoB.kt:
package compapi
fun main(): Unit {
val b = TestB()
b.m()
}
Now B project just add m2 to the Test interface. And it works for B project, but A project does no longer compile.
Test.java:
package compapi;
public interface Test {
public void m();
public void m2();
}
TestB.java:
package compapi;
public class TestB implements Test {
@Override
public void m() {
System.out.println("TestB.m works");
}
@Override
public void m2() {
System.out.println("TestB.m2 works");
}
}
DemoB.java:
package compapi;
public class DemoB {
public static void main(String[] args) {
Test b = new TestB();
b.m();
b.m2();
}
}
ITest.cs:
public interface ITest
{
public void M();
public void M2();
}
TestB.cs:
using System;
public class TestB : ITest
{
public void M()
{
Console.WriteLine("TestB.M works");
}
public void M2()
{
Console.WriteLine("TestB.M2 works");
}
}
DemoB.cs:
public class DemoB
{
public static void Main(string[] args)
{
ITest b = new TestB();
b.M();
b.M2();
}
}
Test.kt:
package compapi
interface Test {
fun m(): Unit
fun m2(): Unit
}
TestB.kt:
package compapi
class TestB : Test {
override fun m(): Unit {
println("TestB.m works")
}
override fun m2(): Unit {
println("TestB.m2 works")
}
}
DemoB.kt:
package compapi
fun main(): Unit {
val b = TestB()
b.m()
b.m2()
}
One way to solve the problem is to create a default method for m2, because then A project still compile.
Test.java:
package compapi;
public interface Test {
public void m();
public default void m2() {
throw new RuntimeException("Test.m2 not implemented");
}
}
TestB.java:
package compapi;
public class TestB implements Test {
@Override
public void m() {
System.out.println("TestB.m works");
}
@Override
public void m2() {
System.out.println("TestB.m2 works");
}
}
DemoB.java:
package compapi;
public class DemoB {
public static void main(String[] args) {
Test b = new TestB();
b.m();
b.m2();
}
}
ITest.cs:
using System;
public interface ITest
{
public void M();
public void M2()
{
throw new Exception("Test.M2 not implemented");
}
}
TestB.cs:
using System;
public class TestB : ITest
{
public void M()
{
Console.WriteLine("TestB.M works");
}
public void M2()
{
Console.WriteLine("TestB.M2 works");
}
}
DemoB.cs:
public class DemoB
{
public static void Main(string[] args)
{
ITest b = new TestB();
b.M();
b.M2();
}
}
Test.kt:
package compapi
interface Test {
fun m(): Unit
fun m2(): Unit {
throw Exception("Test.m2 not implemented")
}
}
TestB.kt:
package compapi
class TestB : Test {
override fun m(): Unit {
println("TestB.m works")
}
override fun m2(): Unit {
println("TestB.m2 works")
}
}
DemoB.kt:
package compapi
fun main(): Unit {
val b = TestB()
b.m()
b.m2()
}
Another way to solve the problem is not to change the interface, but create an extension method instead.
TestExt.cs:
using System;
public static class TestExt
{
public static void M2(this ITest o)
{
Console.WriteLine("Test.M2 works");
}
}
DemoB.cs:
public class DemoB
{
public static void Main(string[] args)
{
ITest b = new TestB();
b.M();
b.M2();
}
}
TestExt.kt:
package compapi
fun Test.m2(): Unit {
println("Test.m2 works")
}
DemoB.kt:
package compapi
fun main(): Unit {
val b = TestB()
b.m()
b.m2()
}
Besides the before mentioned approaches with default mentods and extension methods then there actually is a third approach: version the interface.
It is a solution practicallyt never seen in real life for interfaces, because it sort of mess up the API. But it still works.
TestV2.java:
package compapi;
public interface TestV2 extends Test {
public void m2();
}
TestB.java:
package compapi;
public class TestB implements TestV2 {
@Override
public void m() {
System.out.println("TestB.m works");
}
@Override
public void m2() {
System.out.println("TestB.m2 works");
}
}
DemoB.java:
package compapi;
public class DemoB {
public static void main(String[] args) {
TestV2 b = new TestB();
b.m();
b.m2();
}
}
ITestV2.cs:
public interface ITestV2 : ITest
{
public void M2();
}
TestB.cs:
using System;
public class TestB : ITestV2
{
public void M()
{
Console.WriteLine("TestB.M works");
}
public void M2()
{
Console.WriteLine("TestB.M2 works");
}
}
DemoB.cs:
public class DemoB
{
public static void Main(string[] args)
{
ITestV2 b = new TestB();
b.M();
b.M2();
}
}
TestV2.kt:
package compapi
interface TestV2 : Test {
fun m2(): Unit
}
TestB.kt:
package compapi
class TestB : TestV2 {
override fun m(): Unit {
println("TestB.m works")
}
override fun m2(): Unit {
println("TestB.m2 works")
}
}
DemoB.kt:
package compapi
fun main(): Unit {
val b = TestB()
b.m()
b.m2()
}
| Version | Date | Description |
|---|---|---|
| 1.0 | Novermber 9th 2025 | Initial version |
See list of all articles here
Please send comments to Arne Vajhøj