DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations

Trending

  • Harnessing the Power of Integration Testing
  • Batch Request Processing With API Gateway
  • Execution Type Models in Node.js
  • Reducing Network Latency and Improving Read Performance With CockroachDB and PolyScale.ai
  1. DZone
  2. Coding
  3. Languages
  4. Java-Friendly Kotlin: Default Arguments

Java-Friendly Kotlin: Default Arguments

Dan Newton user avatar by
Dan Newton
·
Jul. 13, 20 · Tutorial
Like (1)
Save
Tweet
Share
8.97K Views

Join the DZone community and get the full member experience.

Join For Free

Kotlin functions and constructors can define default arguments, allowing calls to them to skip any argument that has a default value.

This allows the following function to be called in a number of ways:

Kotlin
xxxxxxxxxx
1
14
 
1
fun doStuff(
2
  a: String = "Default value",
3
  b: Int = 1,
4
  c: Boolean = false
5
)
6
7
doStuff()
8
doStuff("here's a value")
9
doStuff("here's a value", 2)
10
doStuff("here's a value", 2, true)
11
doStuff("here's a value", c = true)
12
doStuff(b = 2)
13
doStuff(c = true)
14
doStuff(b = 2, c = true)


More information can be found in the Kotlin documentation.

For the rest of this post, we will look at how you can include default arguments in your API while still providing excellent Java compatibility.

Trying to Call an Unfriendly Kotlin Function

You are not going to have a good time calling a function or constructor with default arguments from Java. The only way to call the function shown previously from Java is to provide every argument that it asks for:

Java
xxxxxxxxxx
1
 
1
doStuff("here's a value", 2, true);


Without some help, there is no way for Java to understand the concept of default arguments.

Applying the @JvmOverloads Annotation

The @JvmOverloads annotation can be applied to functions and constructors to tell the compiler to generate extra overloads depending on their default arguments. The overloads are placed in the compiled byte code. You can then execute them from Java as you would with any other function or constructor.

Let’s have a look at the KDoc for @JvmOverloads which has a precise declaration of how it generates overloads:

Kotlin
xxxxxxxxxx
1
12
 
1
/**
2
 * Instructs the Kotlin compiler to generate overloads for this function that 
3
 * substitute default parameter values.
4
 *
5
 * If a method has N parameters and M of which have default values, M overloads 
6
 * are generated: the first one takes N-1 parameters (all but the last one that 
7
 * takes a default value), the second takes N-2 parameters, and so on.
8
 */
9
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR)
10
@Retention(AnnotationRetention.BINARY)
11
@MustBeDocumented
12
public actual annotation class JvmOverloads


In other words:

  • Starts at the last argument.
  • If the argument has a default value, an overload is generated without that argument, who uses the default value in its implementation.
  • If the penultimate argument has a default value, an overload without the last 2 arguments is generated.
  • Continues to apply this logic until it reaches the first argument or hits an argument that does not have a default value.

Some examples should help grasp this definition.

Below are the different ways to call the doStuff function from earlier in Java:

Kotlin
xxxxxxxxxx
1
 
1
// All arguments have default values
2
@JvmOverloads
3
fun doStuff(
4
  a: String = "Default value",
5
  b: Int = 1,
6
  c: Boolean = false
7
)
Java
xxxxxxxxxx
1
 
1
// Ways to call [doStuff] from Java
2
doStuff();
3
doStuff("here's a value");
4
doStuff("here's a value", 2);
5
doStuff("here's a value", 2, true);


There are now four options for Java to trigger (instead of 1 without the annotation). All the arguments have been removed, one by one, leaving 3 overloads where 1 requires no inputs at all.

It we remove one of the default values, then the generated options will change:

Kotlin
xxxxxxxxxx
1
 
1
// Remove default value from [b]
2
@JvmOverloads
3
fun doStuff(
4
  a: String = "Default value",
5
  b: Int,
6
  c: Boolean = false
7
)
Java
xxxxxxxxxx
1
 
1
// Ways to call [doStuff] from Java
2
doStuff("here's a value", 2);
3
doStuff("here's a value", 2, true);


Now, when creating overloads, the compiler hits b (second argument) and stops. It does not matter that a has a default value, the compiler will not progress from this point. Therefore, only a single extra overload is available to Java.

Applying the annotation to a class’ constructor requires a slight manipulation to its structure:

Kotlin
xxxxxxxxxx
1
 
1
class MyJvmOverloadsClass @JvmOverloads constructor(
2
  private val a: String = "Default value",
3
  private val b: Int = 1,
4
  private val c: Boolean = false
5
) {
6
7
  // Rest of the class
8
}


The annotation must be applied directly to one of the class’ constructor. In this case, as there is only a single constructor, the constructor keyword must be added (this can typically be omitted in a Kotlin class). Applying the annotation generates overloads in the same way that it does to a function.

The Annotation Isn’t Perfect

The issue with the @JvmOverloads annotation is that it does not generate every possible combination of arguments. Kotlin can bypass this as it has access to named arguments.

Java does not have this ability. Even with the @JvmOverloads annotation, this is what prevents Java from accessing all the same options as Kotlin.

It makes sense when you think about it.

A function or constructor can only be overloaded when it has different arguments from all other versions, for example (written in Kotlin but Java follows the same rules):

Kotlin
xxxxxxxxxx
1
 
1
fun doStuff(a: String = "Default value"): String
2
fun doStuff(a: String = "Default value", b: Int = 1): String
3
fun doStuff(a: String = "Default value", b: Int = 1, c: Boolean = false): String
4
// Return type can be different as long as it has different arguments
5
fun doStuff(a: String = "Default value", b: Int = 1, c: Boolean = false, d: Long = 2): Int


The difference between each overload is clear because the argument types are different. But if they were all the same type it becomes harder to use:

Kotlin
xxxxxxxxxx
1
 
1
fun doStuff(a: String = "Default value"): String
2
fun doStuff(a: String = "Default value", b: String = "Another value"): String
3
fun doStuff(a: String = "Default value", b: String = "Another value", c: String = "Another one"): String


Named arguments makes this bearable (assuming they are named better than a, b and c). Allowing you to omit some arguments due to their default values.

Without the use of named arguments, the is no way for the compiler to distinguish between the objects that you pass to this function. So all it can do is pass them in, one by one, in order, into the function. This is the problem that @JvmOverloads faces. It cannot generate all possible iterations because there is not enough information to differentiate the overloads from each other. If all the arguments have different types, then technically the compiler can do it, but having a rule that is loosely applied would become confusing.

This is the reason that @JvmOverloads only generates overloads starting from the last argument, who must also have a default value, and then moves onto the next (backwards towards the first argument).

Cooperating With the Annotation

You can work around the limitations of @JmvOverloads. Doing so requires you to think about how your functions will be used and what combinations of parameters will be frequently used.

Below are some points for you to consider when writing a function annotated with @JvmOverloads:

  1. Order the arguments in order of importance, the first as the most important and decreasing as they go on.

    Kotlin
    xxxxxxxxxx
    1
     
    1
    @JvmOverloads
    2
    fun doStuff(
    3
      superImportant: String = "I need this argument!",
    4
      reallyImportant: Int = 1,
    5
      somewhatImportant: Boolean = false,
    6
      dontReallyCareAboutThisOne: Long = 2
    7
    )
  2. Do not mix arguments with default values with ones that do not (easier to understand with the example).

    Kotlin
    xxxxxxxxxx
    1
    18
     
    1
    // Stops generating overloads when [doesNotHaveDefaultValue] is reached
    2
    @JvmOverloads
    3
    fun doStuff(
    4
      hasDefaultValue: String = "I need this argument!",
    5
      doesNotHaveDefaultValue: Int,
    6
      hasDefaultValue2: Boolean = false,
    7
      hasDefaultValue3: Long = 2
    8
    )
    9
    10
    11
    // No overloads can be generated
    12
    @JvmOverloads
    13
    fun doStuff(
    14
      hasDefaultValue: String = "I need this argument!",
    15
      doesNotHaveDefaultValue: Int,
    16
      hasDefaultValue2: Boolean = false,
    17
      doesNotHaveDefaultValue2: Long
    18
    )
  3. Manually create overloads if there are still combinations missing that you deem useful.

    Kotlin
    x
    16
     
    1
    // Original
    2
    @JvmOverloads
    3
    fun doStuff(
    4
      a: String = "I need this argument!",
    5
      b: Int = 1,
    6
      c: Boolean = false,
    7
      d: Long = 2
    8
    )
    9
    10
    // Calling with just [a] and [c] is useful
    11
    fun doStuff(a: String, c: Boolean) {
    12
      // Add in the missing parameters
    13
      // Copy the default values or maybe they are taken from properties of a class
    14
      // instead of being passed into the function
    15
      return doStuff(a, 1, c, 2)
    16
    }

The main piece of advice I want to give you here is to really think about the functions you create. When creating a public API as part of a library, especially when you want it to play well with Java, spending time considering how your functions will be leveraged will make everyone happier.

Developers consuming a Kotlin API when using the language themselves can use its features to get around potential problems in your code. Java, on the other hand, requires you to apply some of your brainpower to write functions that are friendly to use.

Summary

Adding the @JvmOverloads annotation to your functions and constructors persuades them to play nicer with Java. It does so by generating additional overloads and placing them in the bytecode that Java interacts with. Adding the annotation does not always make your API easily accessible to Java, in those situations, it is on you to put the work in and craft a well thought out API.


If you enjoyed this post or found it helpful (or both) then please feel free to follow me on Twitter at @LankyDanDev and remember to share with anyone else who might find this useful!

Kotlin (programming language) Java (programming language) Annotation

Published at DZone with permission of Dan Newton, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Trending

  • Harnessing the Power of Integration Testing
  • Batch Request Processing With API Gateway
  • Execution Type Models in Node.js
  • Reducing Network Latency and Improving Read Performance With CockroachDB and PolyScale.ai

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com

Let's be friends: