Needing to override abstract functions feels awkward

Disclaimer: I am still fairly new to kotlin, however, this is one part of the language where I don’t really understand the idea behind it

This is something I’ve seen multiple time in android dev, but I’ll use an exemple completely removed from android.

Here is a simple interface with a function:

interface ExempleInterface {
    fun toImplement(value: String)
}

I want to use this interface when creating a new simple object, in order to get a proper type def and understand what I need to actually implement

So naturally, my first instinct would be to do this:

val implementedInterface = object: ExempleInterface {
   fun toImplement(value: String) {
        Log.i("FROM_IMPLEMENT", value)
    }
}

But now, kotlin tells me that I need to actually override the toImplement function, and this is where I don’t quite understand the rationale here, what exactly am I overriding ? overriding implies that I am quite literally writting over an existing implementation, except that this isn’t the case !

Removing the function like so:

val implementedInterface = object: ExempleInterface {}

Even tells me Object is not abstract and does not implement abstract member public abstract fun toImplement(value: String), so there is nothing there to begin with, since this is all just purely type defs.

Is there any sources online that actually explains the reasoning behind this ? I couldn’t find any specific about this online, but maybe the kotlin specs actually have the infos I am looking for here :grinning:

2 Likes

Java and Kotlin requires you to add override / @Override modifier, so you don’t accidentally implement/override a method or you don’t accidentally do not override it.

Just let the IDE generate the code for you - it would add override automatically and everything would be fine.

2 Likes

A fair question, why does a language require you to explicitly override an abstract method instead of implicitly understanding that is the same method?

This is something a Java dev learns a bit differently so I’ll mention how Java does things:
In Java, there is no override modifier. Instead, Java provides an annotation @override. If you add this annotation to a method that isn’t in a parent class or interface then a compiler error is thrown.

Of course, there’s no compiler-enforced requirement to use this annotation and you could leave it out of your code. However, compiler warnings, IDEs, static analysis, and human review would all tell you that you should add it to every function that you intend to override no matter what. The reason for this is that it communicates that a method intends to implement or override a parent method. Failing to explicitly communicate this intention would be a huge loss and possibly lead to errors when those methods are changed–causing the methods to silently stop implementing those methods.

Java has an override mechanism that is weaker than Kotlin since it isn’t required and only produces compiler warnings when not used. The Java community essentially does require it as a best practice since it would be silly to make a method silently implement/override a parent.

Kotlin makes the logical choice to turn @Override into a function modifier. This is more powerful in that we’re required to clearly mark our intention if we are implementing a parent function and the compiler will reward us by enforcing it is correct.

You ask why you must use override here and the answer is, that you are overriding the unimplemented function in the interface. Just because there isn’t an existing interface doesn’t mean you can’t override which method is dispatched when it is called. The value is in the fact that we explicitly tell the compiler this function is tied to the function in a parent or interface. We gain all of the compiler errors when it doesn’t match, when it doesn’t exist, etc.

If you’re new to the wording of “override” and haven’t seen it in other languages then I can understand. Maybe it would be helpful to imagine the word as “implements”. This is something you’ll get used to as you see the wording used in Kotlin and other languages.

Another way to think of it is that you’re not overriding the implementation but the content of the function for that signature. So if the interface defined a function as void or null, you must override that function to provide even an empty implementation.

^ Anyways, the word makes sense to me but I have a hard time explaining how I understand it from scratch :stuck_out_tongue:

One more note about the errors you get:

Woohoo! This is good news! The compiler is saying that you were trying to create an invalid object that didn’t provide an implementation for your function. At a minimum, you need to define an empty implementation using override fun toImplement(value: String) {} otherwise the type would not really be a full ExampleInterface.

Without all of the functions being defined, we aren’t allowed to create an instance of a given type since it wouldn’t really be that type.

It’s true that there is no implementation to begin with. This means we must provide that functions implementation otherwise we shouldn’t be allowed to claim we have a real instance of an ExampleInterface. We want to override the nonexistent/void/undefined implementation with our own implementation.

Hopefully, that was helpful. I’m sure someone else will be able to explain it more clearly. Is this your first language (or first OOP language)? Often it will be hard to find explanations of these concepts online because they are shared concepts with many other languages.

4 Likes

thanks for the detailed answer !

Another way to think of it is that you’re not overriding the implementation but the content of the function for that signature. So if the interface defined a function as void or null, you must override that function to provide even an empty implementation.

seeing it as a function that returns null does actually make it clearer !

I think part fo the confusion I am/was running into is that I mainly code in typescript on Angular applications, I’m used to seeing types as nothing more than synthetic sugar in a way (as it gets completely removed in the final JS build), but seeing them as something more… tangible than in typescript might help here

The main point of strong typed languages is to detect bugs at the compile time instead of crashing the application at runtime, because the developer made a typo. Explicit override just helps to detect some potential problems.

5 Likes

yeah, I mainly just expected the kotlin compiler to differentiate the interface from a class, and go “this is an interface, therefore its not going to implement any functionality, so this doesn’t need to override anything”

Is there a case where this wouldn’t be a thing ?

well there are default methods :slight_smile:

4 Likes

Just looked it up and this is exactly what was tripping me up, thank you for clearing that one ! :smiley:

Typescript doesn’t actually allow to specify the body of a function, only its type signature, and I though kotlin was the same in that regards, this makes way more sense now !

Cf: typescript default function inside interface - Stack Overflow

1 Like

Well, don’t stick to the thought that override is because of default methods. The reason is really different and it was described above by @arocnies - it is just to make implementing/overriding more explicit. Also, it would be pretty inconsistent if you need to use override in some cases and do not use it in others, depending if the super function is abstract or not.

If the technical internals are so important to you to understand the situation, then be aware that the abstract method is still a method in Java/Kotlin - just without a body. So technically speaking we override a method, even if we implement an interface.

BTW, @Override was in the Java language before default methods.

4 Likes

I might be misinterpreting this but I figured I’d mention it anyway :slight_smile:

It sounds like you’re saying that the anonymous object that gets saved into the variable implementedInterface doesn’t need to implement anything since it is(“it is” relation) the type of ExempleInterface which is only an interface and not a class.

This would be true if Kotlin had interfaces always provide default and empty implementations. Of course, a language could also do this for classes as well as interfaces if it wanted.

Kotlin does not automatically provide a default, empty, no-op implementation of abstract functions or interface functions. The author decides if they want to force their users to implement them or if they want to provide some default implementation–which is a pretty powerful tool for indicating to users their obligations at compile time instead of waiting for a runtime error.

In a conceptual sense, classes and interfaces are both the same “is a” relationship. Their only real difference is that classes are protocol & state & implementation while interfaces are just pure protocol. Java has you note which “is a” relationships are interfaces with the extends and implements keywords: public class MyClass extends SomeClass implements SomeInterface.
Kotlin improved this by removing the unneeded distinction between these “is a” relationships: class MyClass : SomeClass, SomeInterface.

A valid instance of any supertype must fully implement all of the functionality of the super types regardless of if it’s a class or interface. Even anonymous, unnamed types made using object : SomeType cannot have any “undefined” functions–undefined cannot(*) exist in Kotlin or Java

As broot points out,
Java added default implementation late in the language for the goal of enabling authors to expand an interface with new methods without causing a breaking change to all the users who already implemented it (since subtypes must fully implement the interface and “undefined” isn’t an option). IIRC, there was debate if it would be abused. With this change, interfaces went from pure protocol to protocol & implementation but still no state.

2 Likes

This thread definitely helped clear up some of my misconceptions about interface at the core, I got a much clearer picture of the requirement for the override now !

Thanks everyone ! :smiley:

2 Likes