Declaring Extension Functions
Declaring an extension function looks and works a lot like declaring any other function. The difference is that the type the extension function is being declared upon is included as a prefix to the function name, separated by .
.
private val EMAIL_REGEX = Regex("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+[.]+[a-zA-Z0-9-.]+(\s)*")
fun String.isValidEmail() = EMAIL_REGEX.matches(this)
fun main() {
println("martians-so-do-not-exist@commonsware.com".isValidEmail())
println("this is not an email address".isValidEmail())
}
Here, as with the JavaScript and Ruby examples, we are adding an isValidEmail()
function to Kotlin’s existing String
type. We can then call isValidEmail()
on candidate email addresses, whether those come from users or are hard-coded test values, such as "martians-so-do-not-exist@commonsware.com"
.
The Syntax
The extension function behaves a lot like a regular function that you might have on the class. In particular, this
is how you refer to the instance of the type. So, in String.isValidEmail()
, this
is the String
on which you called isValidEmail()
.
The type on which you declare the extension function can be any valid Kotlin type. This includes nullable types, though the function implementation may wind up being more complex as a result:
private val EMAIL_REGEX = Regex("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+[.]+[a-zA-Z0-9-.]+(\s)*")
fun String?.isValidEmail() = this?.let { EMAIL_REGEX.matches(this) } ?: false
fun main() {
println("martians-so-do-not-exist@commonsware.com".isValidEmail())
println("this is not an email address".isValidEmail())
println(null.isValidEmail())
}
Here, we declare isValidEmail()
on String?
instead of String
, allowing us to call isValidEmail()
on null
as well as on an actual String
. However, matches()
on a Regex
does not accept a null
parameter, so we use let()
and the Elvis operator to supply false
as a default value for the null
case.
The Location and Visibility
You can declare an extension function anywhere that you can declare a regular function. So, in the above example, the extension function is a top-level function.
You can make extension functions be public (the default) or private
, as needed.
This leads to three common patterns:
- Public top-level extension functions
- Private top-level extension functions
- Private member functions
Public Top-Level
A public top-level extension function adds that function to the designated type for anything using your code. This is good for utility functions that will be used widely in the project.
Private Top-Level
A private
top-level extension function is usable only within the source file that contains that function. This is good for utility functions whose implementation is fairly tightly tied to the code in the source file and would not be useful elsewhere. The private
nature means that you will not collide with other private
implementations in other source files.
Class Members
You can also have class members that are extension functions, limiting their access to instances of that class:
private val EMAIL_REGEX = Regex("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+[.]+[a-zA-Z0-9-.]+(\s)*")
data class User(val userId: String)
class UserRegistration {
private fun String.isValidEmail() = EMAIL_REGEX.matches(this)
fun isValidUser(user: User) = user.userId.isValidEmail()
}
fun main() {
val registrar = UserRegistration()
println(registrar.isValidUser(User(userId = "martians-so-do-not-exist@commonsware.com")))
println(registrar.isValidUser(User(userId = "this is not an email address")))
}
Here, isValidEmail()
is an extension function on String
, but that extension function is only accessible to instances of UserRegistration
.
This could just as easily be written as:
class UserRegistration {
private fun isValidEmail(value: String) = emailRegex.matches(value)
fun isValidUser(user: User) = isValidEmail(user.userId)
}
Certainly, this second approach is more conventional. In the end, it comes down to object design. In this case, should a String
know if it is a valid email address? Or is that really a responsibility of something else, such as UserRegistration
?
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.