Understanding secondary constructor in Kotlin - kotlin

I don't understand the use of this and how the object is created or constructor is called and what's happening in the below code.
class Person {
var children: MutableList<Person> = mutableListOf<Person>();
constructor(parent: Person) {
parent.children.add(this)
}
}
class Person(val name: String) {
var children: MutableList<Person> = mutableListOf<Person>();
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
source : https://kotlinlang.org/docs/reference/classes.html

: this(...) call means call to another constructor, so each object was constructed with the primary constructor too.
class Person /* a */ (val name: String) {
var children: MutableList<Person> = mutableListOf<Person>();
/* b */ constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
In this example, there is primary constructor after a, and secondary one after b, so there are two ways to instantiate this class.
Call the primary constructor: Person("abc").
Call the secondary constructor: Person("abc", Person("dfg")), and it is guaranteed that both primary and secondary constructor will be called.

Related

How to use Kotlin generics to access same fields in sibling classes

data class Type1(val str: String)
data class Type2(val str: String)
interface Person
data class Child1(val name: Type1) : Person
data class Child2(val name: Type2) : Person
fun main() {
val foo = Child1(Type1("foo"))
val bar = Child2(Type2("bar"))
printName(foo)
printName(bar)
}
fun printName(person: Person) {
// Option 1: would like to do this
// println(person.name) // Unresolved reference: name
// Option 2: works but if I have lots of other code,
// it's unnecessary duplication of code
when (person) {
is Child1 -> {
println(person.name)
// lots of other code
}
is Child2 -> {
println(person.name)
// lots of other code
}
}
}
Inside printName(), I would like to be able to use a single println(person.name) call and have it print the name of either Child1 or Child2, whichever is passed in. What are the different ways I can make this happen in Kotlin?
UPDATE:
I don't own the base classes so not able to change the inheritance.
The names of each child are of different types.
You need to have name in your interface, like
interface Person {
val name: String
}
data class Child1(override val name: String) : Person
data class Child2(override val name: String) : Person
to be able to get it from other Child classes...
If not every child class that extends Person will contain name, you can introduce an intermediate interface, like
interface Person
interface NamedPerson : Person {
val name: String
}
data class Child1(override val name: String) : NamedPerson
data class Child2(override val name: String) : NamedPerson
In any other case, there should be an instance check, like
when (person) {
is Child1 -> println(person.name)
is Child2 -> println(person.name)
}
You can create an extension function if you don't own those classes
data class Type1(val str: String)
data class Type2(val str: String)
interface Person
data class Child1(val name: Type1) : Person
data class Child2(val name: Type2) : Person
// extension
fun Person.getNameStr() : String {
return when(this){
is Child1 -> name.str
is Child2 -> name.str
else -> ""
}
}
// or
val Person.nameStr : String
get() = when(this){
is Child1 -> name.str
is Child2 -> name.str
else -> ""
}
fun printName(person: Person) {
println(person.getNameStr())
//or
println(person.nameStr)
}
Importance
this extension will not valid if you want to return the class itself (Type1 and Type2)

enum polymorphism implementation

Consider an interface:
interface class IPage {
}
and two enums implementing this interface as
enum class Page1 : IPage{ ..... }
enum class Page2 : IPage{ ..... }
Now I have a method
fun getPage(isSomeCondition : Boolean) : IPage{
if(isSomeCondition) return Page1
else return Page2
}
However I am getting a compile time error:
Classifier Page1 does not have a companion object and must be initialized here, I believe that is happening because of the interface, but I am out of ideas on how to solve it!
More explanation:
Both of these enums hold values which are similar, basis the boolean condition I want to load either of the enums and work on the values contained inside of that enum.
consider for example my enums with an updated signature:
enum Page1(val title: String, val data : Data)
Now my Data class has various implementations like ChildData | ParentData | FriendData etc
example:
enum Page1(val title: String, val data : Data){
PARENT("Heiachi", ParentData(...)),
CHILD("JIN", ChildData(...))
}
enum Page2(val title: String, val data : Data){
PARENT("Hworang", FriendData(...)),
CHILD("Yoshimitsu", FoeData(....))
}
Where
class ParentData : Data
class ChildData : Data
class FriendData : Data
class FoeData : Data
Maybe you want to iterate the children of the enum that you've been returning? If so, you can return the implementations of the enums, not the enum definitions:
fun getPage(isSomeCondition : Boolean) : Iterable<IPage>{
if(isSomeCondition) return Page1.values()
else return Page2.values()
}
Alternatively, you can make a class that defines a parent or child. You can then optionally store thes in a single enum or just make them conform to another interface.
class PageType(val title: String, val data : Data)
enum Page(val parent: PageType, val child: PageType) {
Page1(PageType("Heiachi", ParentData(...)), PageType(("JIN", ChildData(...))),
Page2(PageType("Hworang", FriendData(...)), PageType("Yoshimitsu", FoeData(....))
}
fun getPage(isSomeCondition : Boolean) : Page {
if(isSomeCondition) return Page.Page1
else return Page.Page2
}
// Alternative to enum:
interface Page {
val parent: PageType
val child: PageType
}
object Page1: Page {
override val parent = PageType("Heiachi", ParentData(...))
override val child = PageType(("JIN", ChildData(...))
}
object Page2: Page {
override val parent = PageType("Hworang", FriendData(...))
override val child = PageType(("Hworang", FoeData(...))
}

Kotlin, jackson: cannot annotate #JsonCreator in primary constructor

I want to annotate with #JsonCreator using a primary constructor, something like this:
// error
#JsonCreator class User(
#JsonProperty("username") var username: String,
#JsonProperty("password") var password: String
) {
// ...
}
But the #JsonCreator annotation gives an error "This annotation is not applicable to target 'class'".
Using a secondary constructor works, but is it the only (or best) way?:
// works, but is there a better way?
class User #JsonCreator constructor(
#JsonProperty("username") var username: String,
#JsonProperty("password") var password: String
) {
// ...
}
What you describe here:
class User #JsonCreator constructor(
#JsonProperty("username") var username: String,
#JsonProperty("password") var password: String
) {
// ...
}
is actually explicitly specifying the primary constructor. You can differentiate the primary from the secondary by looking at the class declaration:
class User constructor(/** **/) { // <-- primary
constructor(/** ... **/) { // <-- secondary
}
}
if the constructor is part of the class header it is a primary constructor, if it is part of the class declaration (it is after the {) it is a secondary one.

Data class constructor with two different constructor in Kotlin

I am new to Kotlin. I want to write a class which holds data. I want two constructor. What i want is something like this
class InstituteSearchDetails (var centerId: String) {
lateinit var centerId: String;
lateinit var instituteName: String;
lateinit var city: String;
init {
this.centerId=centerId
}
constructor( instituteName: String, city: String)
{
this.instituteName=instituteName;
this.city=city;
}
}
But on Secondary constructor line it says primary constructor call is required. I know some delegation is required which call primary constructor form there. I cant call primary constructor from here. I am sorry if i am doing some silly mistake. I am new to this thing
From the doc:
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:
Example:
class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
Your code:
constructor( instituteName: String, city: String) : this("centerId"){
this.instituteName=instituteName;
this.city=city;
}
But it doesn't look like you have the centerId value in the secondary constructor.
You can have two secondary constructors:
class InstituteSearchDetails {
lateinit var centerId: String;
lateinit var instituteName: String;
lateinit var city: String;
constructor(centerId: String) {
this.centerId = centerId
}
constructor( instituteName: String, city: String)
{
this.instituteName=instituteName;
this.city=city;
}
}
But be aware that, for instance, centerId wouldn't have been initialized if you use the second constructor and you will get an exception (UninitializedPropertyAccessException) if you try to access the centerId in that case.
Edit:
This is not possible in data class because data class requires a primary constructor with at least one val or var. If you have the primary constructor, then your secondary constructor should delegate to the primary constructor as well. Perhaps you can have all properties in a single primary constructor of a data class but with nullable properties. Or see Sealed class.
sealed class InstituteSearchDetails {
data class InstituteWithCenterId(val centerId: String): InstituteSearchDetails()
data class InstituteWithNameAndCity(val name: String, val city: String): InstituteSearchDetails()
}
fun handleInstitute(instituteSearchDetails: InstituteSearchDetails) {
when (instituteSearchDetails) {
is InstituteSearchDetails.InstituteWithCenterId -> println(instituteSearchDetails.centerId)
is InstituteSearchDetails.InstituteWithNameAndCity -> println(instituteSearchDetails.name)
}
}

Call super class constructor in Kotlin, Super is not an expression

I have two classes Entity and Account as
abstract class Entity(
var id: String? = null,
var created: Date? = Date()) {
constructor(entity: Entity?) : this() {
fromEntity(entity)
}
fun fromEntity(entity: Entity?): Entity {
id = entity?.id
created = entity?.created
return this;
}
}
and
data class Account(
var name: String? = null,
var accountFlags: Int? = null
) : Entity() {
constructor(entity: Entity) : this() {
super(entity)
}
}
Which gives me the error
Super is not an expression, it can be only used in the left-hand side of
a dot '.'
Why cannot I do that?
The following will pass the compilation error, but I am not sure if it is correct.
constructor(entity: Entity) : this() {
super.fromEntity(entity)
}
You have a couple of problems in your code.
First, this is the correct syntax, to call a super constructor from a secondary constructor:
constructor(entity: Entity) : super(entity)
Second, you can't call a super constructor from a secondary constructor if your class has a primary constructor (which your class does).
Solution 1
abstract class Entity(
var id: String,
var created: Date
)
class Account(
var name: String,
var accountFlags: Int,
id: String,
created: Date
) : Entity(id, created) {
constructor(account: Account) : this(account.name, account.accountFlags, account.id, account.created)
}
Here, the copy constructor is in the child class which just delegates to the primary constructor.
Solution 2
abstract class Entity(
var id: String,
var created: Date
) {
constructor(entity: Entity) : this(entity.id, entity.created)
}
class Account : Entity {
var name: String
var accountFlags: Int
constructor(name: String, accountFlags: Int, id: String, created: Date) : super(id, created) {
this.name = name
this.accountFlags = accountFlags
}
constructor(account: Account) : super(account) {
this.name = account.name
this.accountFlags = account.accountFlags
}
}
Here I'm only using secondary constructors in the child class which lets me delegate them to individual super constructors. Notice how the code is pretty long.
Solution 3 (most idiomatic)
abstract class Entity {
abstract var id: String
abstract var created: Date
}
data class Account(
var name: String,
var accountFlags: Int,
override var id: String,
override var created: Date
) : Entity()
Here I omitted the copy constructors and made the properties abstract so the child class has all the properties. I also made the child class a data class. If you need to clone the class, you can simply call account.copy().
You can also move your primary constructor down into the class like this:
data class Account: Entity {
constructor(): super()
constructor(var name: String? = null, var accountFlags: Int? = null): super()
constructor(entity: Entity) : super(entity)
}
Advantage of this is, compiler will not require your secondary constructor to call primary constructor.
Another option is to create companion object and provide factory method e.g.
class Account constructor(
var name: String? = null,
var accountFlags: Int? = null,
id: String?,
created: Date?
) : Entity(id, created) {
companion object {
fun fromEntity(entity: Entity): Account {
return Account(null, null, entity.id, entity.created)
}
}
}
Use this super<Entity>.fromEntity(entity) to call super class methods.
As Documentation says:
In Kotlin, implementation inheritance is regulated by the following rule: if a class inherits many implementations of the same member from its immediate superclasses, it must override this member and provide its own implementation (perhaps, using one of the inherited ones). To denote the supertype from which the inherited implementation is taken, we use super qualified by the supertype name in angle brackets, e.g. super.
constructor(entity: Entity) : this() {
super<Entity>.fromEntity(entity)
}
To know more read Overriding Rules