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)
}
}
Related
class Greeter(name: String) {
fun greet() {
println("Hello, $name")
}
}
fun main(args: Array<String>) {
Greeter(args[0]).greet()
}
for above program I got this error
Unresolved reference: name
but when I add var or val
class Greeter(var name: String) {
or
class Greeter(val name: String) {
then program works fine, so why I need to add var or val to name, what is default type for constructor parameter val or var and why program gives me error when I not mention var or val
To use your value in the constructor like class Greeter(name: String), you can use init{}
class Greeter(name: String) {
var string:name = ""
init{
this.name = name
}
fun greet() {
println("Hello, $name")
}
}
or If you use val or var in the constructor it is more like class level variable and can be accessed anywhere inside the class
class Greeter(var name:String){
fun greet() {
println("Hello, $name")
}
}
The variable name can be used directly in the class then.
We can also give default values for the variables in both cases.
Adding val or var makes the parameter a property and can be accessed in the whole class.
Without this, it is only accessible inside init{}
The question is not making any sense, But the problem you are facing does make sense. In your case, the approach you are using is,
Wrong-Way:
// here name is just a dependency/value which will be used by the Greeter
// but since it is not assigned to any class members,
// it will not be accessible for member methods
class Greeter(name: String) {
fun greet(){} // can not access the 'name' value
}
Right-Way:
// here name is passed as a parameter but it is also made a class member
// with the same name, this class member will immutable as it is declared as 'val'
class Greeter(val name: String) {
fun greet(){} // can access the 'name' value
}
You can also replace val with var to make the name a mutable class member.
class Animal {
val name: String
constructor(name: String){
this.name = name // initialized via constructor
}
}
For the above class in Kotlin I am able to initialize a val property via secondary constructor but the same is not allowed for Data classes
data class User(val name: String, val postalCode: Int) {
val email: String
constructor( email: String): this("", 1){
this.email = email // error: value can not be reassigned
}
}
What I can't understand is, where is the email property is initialized already as I haven't declared any initializes?
If your class has a primary constructor, you have to initialize all of its properties "in the primary constructor" - either by directly initializing them at their declaration:
val email = "foo#bar.com"
Or in an initializer block:
val email: String
init {
email = "foo#bar.com"
}
The compiler forces you to forward all secondary constructor calls to the to the primary constructor, and since the primary constructor already has to initialize all properties inside the class (otherwise calling it would construct a partially initialized instance, like in your code example), it wouldn't make sense to also initialize them in the body of the secondary constructor, especially for a val which cannot be reassigned.
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.
Is there a way of defining common properties without using inheritance in Kotlin?
For example
If I have two classes that both require an "id" property.
class Dog() {
var id: UUID?
}
class Cat() {
var id: UUID?
}
The general JAVA way to solve this is introduce a super class
class Animal() {
var id: UUID?
}
class Dog: Animal()
class Cat: Animal()
But now "Dog" and "Cat" are of type "Animal". What if I introduce a "Chair" class that also requires a unique identifier.
Essentially what I want to the ability to create a set of properties I can include in a number of different classes for programming convenience only. I don't want all the problems associated with inheritance.
You can, of course, use an interface instead of a base class:
interface HasId {
val id: UUID
}
data class Dog(override val id: UUID) : HasId
data class Cat(override val id: UUID) : HasId
However, the above is still using inheritance. If you have more common properties that would be used in multiple classes it may be a sign that they should be grouped together to form a separate value object e.g.
data class Address(val city: String, val street: String, val country: String)
class Person(val name: String, val address: Address)
class School(val name: String, val address: Address, val studentsCount: Int)
And if you want to treat Person and School uniformly with regards to address property you can still use the interface to denote the common attribute:
interface HasAddress {
val address: Address
}
class Person(val name: String,
override val address: Address) : HasAddress
class School(val name: String,
override val address: Address,
val studentsCount: Int) : HasAddress
It might be possible that delegation will suit your needs:
interface WithId {
var id: Int
}
class IdStorage : WithId {
override var id: Int = 0
}
class Dog(withId: WithId) : WithId by withId {
constructor() : this(IdStorage()) {}
}
class Cat(withId: WithId) : WithId by withId {
constructor() : this(IdStorage()) {}
}
This code is rather verbose, but what it allows you to do is:
Avoid using superclass just for the sake of having id property, which allows you to extend other classes if you need
Usage of interface, which guarantees other pieces of code that your class has id
Allows to move implementation of your properties (or functions) to separate class, hence no need for duplicate code in case of complex property/function implementation
Allows implementing multiple properties/functions in a separate class
As was mentioned in the comments:
interface Animal {
var id: UUID?
}
class Dog: Animal
class Cat: Animal
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