Effective Kotlin Item 45: Consider extracting non-essential parts of your API into extensions
When we define final methods in a class, we need to make a decision if we want to define them as members, or if we want to define them as extension functions.
Both approaches are similar in many ways. Their use and even referencing them via reflection is very similar:
There are also significant differences though. They both have their pros and cons, and one way does not dominate over another. This is why my suggestion is to consider such extraction, not necessarily to do it. The point is to make smart decisions, and for that, we need to understand the differences.
Thanks to the fact that extensions need to be imported, we can have many extensions with the same name on the same type. This is good because different libraries can provide extra methods, and we won’t have a conflict. On the other hand, it would be dangerous to have two extensions with the same name, but having different behavior. For such cases, we can cut the Gordian knot by making a member function. The compiler always chooses member functions over extensions1.
Another significant difference is that extensions are not virtual, meaning that they cannot be redefined in derived classes. The extension function to call is selected statically during compilation. This is a different behavior from member elements, that are virtual in Kotlin. Therefore, we should not use extensions for elements that are designed for inheritance.
This behavior is the result of the fact that extension functions under the hood are compiled into normal functions where the extension’s receiver is placed as the first argument:
Another consequence of this fact is that we define extensions on types, not on classes. This gives us more freedom. For instance, we can define an extension on a nullable or generic type:
The last important difference is that extensions are not listed as members in the class reference. This is why they are not considered by annotation processors and why, when we process a class using annotation processing, we cannot extract into extensions elements that should be processed. On the other hand, if we extract non-essential elements into extensions, we don’t need to worry about them being seen by those processors. We don’t need to hide them, because they are not in the class anyway.
The most important differences between members and extensions are:
- Extensions need to be imported
- Extensions are not virtual
- Member has a higher priority
- Extensions are on a type, not on a class
- Extensions are not listed in the class reference
To summarize it, extensions give us more freedom and flexibility. Although they do not support inheritance, annotation processing, and it might be confusing that they are not present in the class. Essential parts of our API should rather stay as members, but there are good reasons to extract non-essential parts of your API as extensions.
The only exception is when an extension in the Kotlin stdlib has kotlin.internal.HidesMembers internal annotation