Kotlin data class constructor annotation

Creating multiple constructors for Data classes in Kotlin

It’s been a month since I joined Annyce in migrating our codebase away from Java to Kotlin. Every time she sent a PR, the amount of code she added was always remarkably lower then the amount she had taken away while still accomplishing the tasks at hand.

I started out with all our data classes so that I could get the hang of working with Kotlin, that’s after I personally went through the Kotlin Koans and read Kotlin for Android Developers by Antonio Leiva. I later moved on to migrating more complex stuff before finally graduating to writing Kotlin from scratch.

I’ll attempt to highlight all the areas that stood out to me during this period. It’s obvious but worth mentioning again that your codebase wasn’t built in a day so it’ll definitely take some time to achieve a 100% Kotlin codebase.

Creating multiple constructors for Data classes in Kotlin

aka Secondary constructors with multiple parameters

Читайте также:  Пример быстрой сортировки питон

Data classes in Kotlin are immutable and it’s easy enough to create a constructor for a data class with multiple fields. Note that it’s compulsory to have a primary constructor in a data class. It’s also compulsory to have the val or var keyword before the variable name, which you can get away with in normal classes and secondary constructors.

data class Person(val name: String, val age: Int)

What if you need several constructors in the same data class? You can use Secondary constructors to achieve this. According to the documentation,

“If the class has a primary constructor, each secondary constructor needs to delegate to the primary constructor, either directly or indirectly through another secondary constructor(s). Delegation to another constructor of the same class is done using the this keyword”

data class Person(val name: String) constructor(name: String, age: Int) : this(name)
>

Another option is the @JvmOverloads annotation which generates multiple constructors based on the arguments in the constructor. Again, according to the documentation, @JvmOverloads

“Instructs the Kotlin compiler to generate overloads for this function that substitute default parameter values.

If a method has N parameters and M of which have default values, M overloads are generated: the first one takes N-1 parameters (all but the last one that takes a default value), the second takes N-2 parameters, and so on.”

data class Person @JvmOverloads constructor(val name: String, 
val age: Int? = 0)

Going by the excerpt above, the only overloaded constructor generated in this case has one parameter — val name.

Note that when you include annotations in your class declaration, you have to explicitly write out the constructor keyword just before the parenthesis. This is a lot more concise than the first option and it works as intended.

Thanks to Michael Obi and Segun Famisa for reviewing.

Thought this was great? Please don’t forget to “Recommend” and “Share”.

Источник

Instantiate a Kotlin Data Class Using an Empty Constructor

announcement - icon

As a seasoned developer, you’re likely already familiar with Spring. But Kotlin can take your developer experience with Spring to the next level!

  • Add new functionality to existing classes with Kotlin extension functions.
  • Use Kotlin bean definition DSL.
  • Better configure your application using lateinit.
  • Use sequences and default argument values to write more expressive code.

By the end of this talk, you’ll have a deeper understanding of the advanced Kotlin techniques that are available to you as a Spring developer, and be able to use them effectively in your projects.

1. Overview

As data containers, Kotlin’s data classes, have implemented a few valuable methods by default, such as equals(), toString(), copy(), and so on.

In this tutorial, let’s explore how to instantiate a Kotlin data class using an empty constructor.

2. Introduction to the Problem

We’ve mentioned that as a data container, Kotlin’s data class brings many advantages. However, one requirement of a data class is that its primary constructor must have at least one parameter.

This means it doesn’t have a Java-like “default constructor” or “empty constructor”.

As data classes target the use cases of holding data, a data class may have quite a few parameters. Therefore, when we want to create an instance of a data class, we may pass dozens of parameters to the constructor. It’s a kind of inconvenience. We may miss the default constructor or empty constructor of a class.

In this tutorial, we’ll address a couple of ways to instantiate a data class using an empty constructor. Moreover, we’ll introduce Kotlin’s no-arg compiler plugin and discuss its use case.

For simplicity, we’ll use unit test assertions to verify the instances we create.

3. Adding an Empty Secondary Constructor

We’ve known that a Kotlin data class’s primary constructor must have at least one argument. Let’s see an example:

data class ArticleWithoutDefault( var title: String, var author: String, var abstract: String, var words: Long )

In the data class above, the primary constructor has four arguments. We need to provide all of them if we want to instantiate the ArticleWithoutDefault class.

However, Kotlin allows us to create secondary constructors. For example, to instantiate the ArticleWithoutDefault class through an empty constructor, we can create one secondary constructor that calls the primary constructor with default values:

data class ArticleWithoutDefault( var title: String, var author: String, var abstract: String, var words: Long )

In this way, we can invoke the newly created constructor to get an instance of ArticleWithoutDefault. Of course, the instance will contain the initial values defined in the secondary constructor:

val myInstance = ArticleWithoutDefault() assertThat(myInstance).isInstanceOf(ArticleWithoutDefault::class.java) .extracting("title", "author", "abstract", "words") .containsExactly("dummy title", "dummy author", "dummy abstract", 0L) 

When we run the test, it passes. This means the instance is successfully created through the empty constructor.

4. Adding Default Values to the Primary Constructor

We’ve learned to add an empty secondary constructor that calls the primary one with initial property values. Actually, Kotlin allows us to set default values for function parameters. It works for constructors, too:

data class ArticleWithDefault( var title: String = "default title", var author: String = "default author", var abstract: String = "", var words: Long = 0L ) 

As we can see in the data class above, we can define default values for the primary constructor’s parameters. Thus, we don’t need to create a secondary constructor. Instead, we can simply instantiate the data class using an empty constructor to apply all default values:

val myInstance = ArticleWithDefault() assertThat(myInstance).isInstanceOf(ArticleWithDefault::class.java) .extracting("title", "author", "abstract", "words") .containsExactly("default title", "default author", "", 0L) 

When a class constructor’s parameters have default values, it’s pretty flexible for creating instances of that class. For example, with the ArticleWithDefault data class, we can pass only title and words to the primary constructor and leave author and abstract with default values:

val myArticle = ArticleWithDefault(title="A Great Article", words=42L) assertThat(myArticle).isInstanceOf(ArticleWithDefault::class.java) .extracting("title", "author", "abstract", "words") .containsExactly("A Great Article", "default author", "", 42L) 

5. About the no-arg Compiler Plugin

Kotlin ships with a no-arg compiler plugin, which generates an additional empty constructor for classes with a specific annotation during compilation. It sounds like a great solution to our problem. However, it’s for other purposes. So next, let’s take a closer look at the no-arg plugin.

5.1. Configuring the no-arg Plugin

We need to do some configuration work on the Maven or Gradle side to enable the no-arg plugin. In this tutorial, we’ll take Maven as an example.

Let’s add the plugin to the element:

 kotlin-maven-plugin org.jetbrains.kotlin  no-arg      org.jetbrains.kotlin kotlin-maven-noarg $  

In the configuration above, the setting is pretty important. There, we’ve referenced the annotation class for the no-arg plugin. In this example, we’ve defined com…emptyConstructorOfDataCls.NoArg as the annotation class. This means if we mark a class with the @NoArg annotation, the Kotlin compiler will add an empty constructor to this class.

Next, let’s create the annotation class.

5.2. Creating the @Noarg Annotation and Applying It to a Data Class

First, let’s create the NoArg annotation under the defined package:

package com.baeldung.kotlin.emptyConstructorOfDataCls annotation class NoArg

Next, let’s create a new data class and annotate it as @NoArg:

@NoArg data class Person(var firstName: String = "a nice name", var midName: String?, var lastName: String, var age: Int)

As the code above shows, our Person class has a primary constructor with four arguments. The firstName parameter has a default value. Further, except for midName, all parameters are not-null.

Now, if we compile the class, we may expect the no-arg plugin to create an empty constructor for us so that we can instantiate our Person class through something like val person = Person().

Next, let’s see if it works as expected.

5.3. Calling the Empty Constructor Directly From Kotlin

First, let’s call the empty constructor in a test method:

However, when we compile the code, we’ll see it doesn’t compile:

[INFO] . [INFO] --- kotlin-maven-plugin:1.6.0:test-compile (test-compile) @ core-kotlin-lang-oop-2 --- [INFO] Applied plugin: 'no-arg' [ERROR] /. /EmptyConstructorOfDataClsUnitTest.kt: (53, 29) No value passed for parameter 'midName' [ERROR] /. /EmptyConstructorOfDataClsUnitTest.kt: (53, 29) No value passed for parameter 'lastName' [ERROR] /. /EmptyConstructorOfDataClsUnitTest.kt: (53, 29) No value passed for parameter 'age' [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE [INFO] ------------------------------------------------------------------------ 

The build log above shows that the no-arg plugin has already been enabled. But why does it still complain about the constructor parameters? This is because the generated constructor is synthetic. In other words, it can’t be directly called from Java or Kotlin. So, it’s mainly for the reflection API.

5.4. Calling the Empty Constructor Using the Kotlin Reflection API

Next, let’s call the empty constructor using Kotlin reflection:

val person = Person::class.createInstance()

This time, the code compiles. However, when we execute the test method, the test fails:

ERROR! java.lang.IllegalArgumentException: Class should have a single no-arg constructor: class com.baeldung.kotlin.emptyConstructorOfDataCls.Person at com.baeldung.kotlin.emptyConstructorOf. (EmptyConstructorOfDataClsUnitTest.kt:55) 

This is because Kotlin’s reflection API works with Kotlin code elements. The synthetic empty constructor is not a part of the Kotlin code. Thus, the empty constructor that the no-arg plugin created cannot be recognized by the Kotlin reflection API.

5.5. Calling the Empty Constructor Using the Java Reflection API

Finally, let’s call the empty constructor using the Java Reflection API:

val myInstance = Person::class.java.getConstructor().newInstance() assertThat(myInstance).isInstanceOf(Person::class.java) .extracting("firstName", "midName", "lastName", "age").containsExactly(null, null, null, 0) 

When we execute the test above, it passes. Therefore, only Java reflection can use the synthetic constructor generated by the no-arg plugin.

Moreover, let’s revisit the parameters of Person‘s primary constructor:

var firstName: String = "a nice name", var midName: String?, var lastName: String, var age: Int

The assertion in the test indicates that even if the empty constructor can be used by Java reflection, the created instance doesn’t follow the Kotlin class’s contract:

  • Kotlin’s not-null properties such as firstName and lastName can hold a null value anyway.
  • Default values of parameters, such as the one we specified for firstName, are not applied, either.

Basically, the no-arg compiler plugin is only useful for library development. For example, Java Persistence API (JPA) instantiates a class through its empty constructor. This plugin allows JPA to instantiate a Kotlin class, although no empty constructor is defined.

6. Conclusion

In this article, we’ve explored two approaches to instantiate a Kotlin data class using an empty constructor.

Further, we’ve discussed the no-arg compiler plugin. We should note that the empty constructor generated by the no-arg plugin can only be used by Java reflection.

As always, the full source code used in the article can be found over on GitHub.

Источник

Оцените статью