Here's the code:
class Person(var firstName: String, var lastName: String) {
var fullName: String = firstName + lastName
fun fullName() = firstName + lastName
override fun toString(): String {
return fullName()
}
}
fun main(){
val test = Person("test", "fortest1")
test.lastName = "fortest2"
println(test.fullName)
}
The result will only be testfortest1.
It looks like you are working with a copy of test once test is created.
This is because fullName is not observing any changes to firstName or lastName. It is initialized when Person is created and stays the same unless explicitly modified.
One easy fix for this is to provide a custom getter like this:
val fullName get() = firstName + lastName
Now it will work as you expect because everytime you read fullName that expression will be evaluated and the result will be returned.
(Also, prefer using vals over vars in data class fields)
Related
I have a class like this:
open class User(var firstName: String, var lastName: String) {
var fullName = "$firstName $lastName"
get() = "Name: $field"
set(value){
if(value.startsWith("Jon")){
field = "Jon Doe"
}else{
field = value
}
}
}
Why fullNamedoes not change if I change the firstName like:
val person1 = User("Mark", "Zuck")
println(person1.fullName)
//Mark Zuck
person1.firstName = "Bill"
println(person1.firstName)
//Bill
println(person1.fullName)
//Mark Zuck
person1.lastName = "Gates"
println(person1.fullName)
//prints Mark Zuck
Is it because fullName are bounded by getters and setters that's why the only way to change it is by accessing fullname directly???
when we say
get() = "Name: $field"
it just return the value that have been initialize during the instantiation, to fix that I just change it to
get() = "$firstName $lastName"
so that it calculates again and return the updated value
full code:
var fullName = "$firstName $lastName"
get() = "$firstName $lastName"
set(value){
field = value
}
Have a read of this: Getters and setters - basically because you're referring to field in your getter (and the setter), there's a backing field storing a value. And your getter (which is a function) is reading from that backing field when it calculates a value to return.
The code you posted as an answer:
var fullName = "$firstName $lastName"
get() = "$firstName $lastName"
set(value){
field = value
}
is pretty much equivalent to this:
val fullName get() = "$firstName $lastName"
because your getter never references the backing field. Your setter really does nothing - it stores the value you pass in the backing field, but that field is never read. If you remove the reference to the backing field:
var fullName
get() = "$firstName $lastName"
set(value){ }
then no backing field is created, because your getter and setter both do their own thing without involving the field. So you don't need to initialise it with a value - there's no field to initialise! The getter was never using it anyway.
And since your setter does nothing, you may as well delete it and make your property a val since you could never (directly) change its value anyway - fullName = "Some Guy" had no visible effect, so making it a var is pretty misleading!
And that's how you end up with a simple getter that returns a value derived from other properties:
val fullName get() = "$firstName $lastName"
If you wanted, you could make that setter update firstName and lastName:
var fullName
get() = "$firstName $lastName"
set(value){
// you'd need to handle the possible errors here
val names = value.split(' ')
firstName = names[0]
lastName = names[1]
}
Again that's not using a backing field, the data is stored somewhere else (in firstName and lastName in this case) and the getter and setter are just functions that handle storing and retrieving the data in some form. This is just an example, you probably wouldn't want to do this here (updating firstName and lastName directly is way better and less work to handle) but this is just to show you could! Sometimes you do this kind of thing with a setter
I have seen Kotlin-examples using a Primary-constructor like this:
class Person(firstName: String, lastName: String) {}
And I have seen example with the var or val keyword, like this:
class Person(val firstName: String, val lastName: String) {}
What the difference? When do I have to use which variation?
Regarding the documentation, with var or val in the constructor you create a property in the class. If you do not write it, then it is only a parameter that is passed to the constructor. As an example:
class Person(val firstName: String, lastName: String) {
// firstName and lastName accessible
fun getFirstName() = firstName // firstName accessible
fun getLastName() = lastName // lastName not accessible
}
So if you want to continue to use the firstName and lastName, I would make a property out of it.
class student1(firstName : String, lastName : String){
var id : Int = -1
val firstName = firstName
val lastName = lastName
init {
println("initialized")
}
constructor(firstName : String, lastName : String, extraParam : Int) : this(firstName, lastName){
this.id = extraParam
}
fun callme(){
print(firstName + lastName)
}
}
class student2(firstName : String, lastName : String){
val firstName = firstName
val lastName = lastName
fun callme() {
print(firstName + lastName)
}
}
fun main() {
val p1 = student1("shubham", "sharma")
println(p1.firstName)
println(p1.lastName)
println(p1.callme())
val p2 = student1("shubham", "sharma")
println(p2.firstName)
println(p2.lastName)
println(p2.callme())
}
here in both the class, the output is the same with the same parameter then why we need to use the secondary constructor? What is the main difference between these two class please let me know with one example. will be appreciated!
the first one has two constructors that get an additional variable but does not use it, that's why you don't see the difference. the student1 class has an optional id which is -1 by default. if you don't use it somewhere else you must remove it. actually, we don't create classes like this in kotlin you can move the var, val keywords in the constructor:
class Student1(val firstName : String, val lastName : String) {
var id = -1
init {
println("initialized")
}
constructor(firstName : String, lastName : String, id: Int = -1) : this(firstName, lastName) {
this.id = id
}
fun callme() {
print(firstName + lastName)
}
}
you can even make this shorter with default arguments and remove the secondary constructor and make id a val (if you don't want to change it):
class Student1(val firstName : String, val lastName : String, val id: Int = -1) {
init { println("initialized") }
fun callme() { print(firstName + lastName) }
}
There is no difference because you use only student1 !
val p2 = student1("shubham", "sharma")
instead of
val p2 = student2("shubham", "sharma")
To see a difference, you have to make it visible :
class student1 ...
fun callme(){
print(firstName + lastName + id)
}
Then use student1 with the secondary constructor :
val p3 = student1("shubham", "sharma", 2021)
println(p3.firstName)
println(p3.lastName)
println(p3.callme())
will output
shubhamsharma2021kotlin.Unit
Updated: added some clarifications from the comments
I would like to use the same 'mapping' code for the primary constructor and copy() method of an immutable data class. How can I do this without creating an empty object first, and then using copy() on it?
The issue with how it is now is that if I add a new attribute with default value to Employee and EmployeeForm it would be easy to only add it in one of the two mapping functions and forget about the other (toEmployeeNotReusable / copyEmployee).
These are the data classes I'd like to map between:
#Entity
data class Employee(
val firstName: String,
val lastName: String,
val jobType: Int,
#OneToMany(mappedBy = "employee", cascade = [CascadeType.ALL], fetch = FetchType.EAGER)
private val _absences: MutableSet<Absence> = mutableSetOf(),
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0 // prevents #Joffrey's answer from working
) {
init {
_absences.forEach { it.employee = this }
}
val absences get() = _absences.toSet()
fun addAbsence(newAbsence: Absence) {
newAbsence.employee = this
_absences += newAbsence
}
#Entity
#Table(name = "absence")
data class Absence(
// ... omitted fields
) {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "employee_id")
lateinit var employee: Employee
}
}
data class EmployeeForm(
var firstName: String = "",
var lastName: String = "",
var jobType: Int = 0
) {
// not reusable
fun toEmployeeNotReusable(): Employee {
return Employee(firstName, lastName, jobType)
}
// works but hacky
fun toEmployee(): Employee {
return copyEmployee(Employee("", "", 0))
}
fun copyEmployee(employee: Employee): Employee {
return employee.copy(
firstName = firstName,
lastName = lastName,
jobType = jobType
)
}
}
While mutability would be fine, in my case, I'd be interested to know how this would be possible.
One way to avoid listing the attributes 4 times would be to declare Employee as an interface instead, and use the "mutable" version, the form, as the only data class implementing it. You would have the "read-only" view using the interface, but you would technically only use the mutable instance behind the scenes.
This would follow what Kotlin designers have done for List vs MutableList.
interface Employee {
val firstName: String
val lastName: String
val jobType: Int
}
data class EmployeeForm(
override var firstName: String = "",
override var lastName: String = "",
override var jobType: Int = 0
): Employee {
fun toEmployee(): Employee = this.copy()
fun copyEmployee(employee: Employee): Employee = this.copy(
firstName = firstName,
lastName = lastName,
jobType = jobType
)
}
However, this implies that the form has all fields of an employee, which you probably don't want.
Also, I would personally prefer what you had done in the beginning, listing twice the field would not be a problem, just write tests for your functions, and when you want to add functionality, you'll add tests for that functionality anyway.
You should be able to do this using reflection: check list of properties in Employee and EmployeeForm, call the constructor by the matching names (using callBy to handle default parameters). The drawback, of course, is that you won't get compile-time errors if any properties are missing (but for this case, any test would probably fail and tell you about the problem).
Approximate and untested (don't forget to add the kotlin-reflect dependency):
inline fun <reified T> copy(x: Any): T {
val construct = T::class.primaryConstructor
val props = x::class.memberProperties.associate {
// assumes all properties on x are valid params for the constructor
Pair(construct.findParameterByName(it.name)!!,
it.call(x))
}
return construct.callBy(props)
}
// in EmployeeForm
fun toEmployee() = copy<Employee>(this)
You can make an equivalent which is compile-time checked with Scala macros, but I don't think it's possible in Kotlin.
Just learning Kotlin In the first code down below there is the val keyword right in the other code there is not,
what is the different here if the val and var is omitted?
class Person(val firstName: String, val lastName: String) {
}
class Person(firstName: String, lastName: String) {
}
If val or var is omitted then they won't be properties, but parameters passed to constructor. You won't be able to work with them outside of constructor.
If you omit val or var in in a constructor, then the only places that can access these parameters are initialization statements that are evaluated at construction time. See https://kotlinlang.org/docs/reference/classes.html
This is useful when you want to do something with a value before storing it. In Java you would put that code a constructor body
class Person(firstName: String, lastName: String) {
// directly in val / var declarations
val firstName = firstName.capitalize()
val lastName = lastName
// in init blocks
val fullName: String
init {
fullName = "$firstName $lastName"
}
// secondary constructors can only see their own parameters
// and nothing else can access those
constructor(fullName: String) : this("", fullName)
}
But it also works for delegation using by
interface Named {
fun getName(): String
}
class Human(private val fname: String, private val lname: String) : Named {
override fun getName() = "$fname + $lname" // functions need val since
// value is resolved after construction
}
class Person2(firstName: String, lastName: String) : Named by Human(firstName, lastName)
class Person3(human: Human) : Named by human {
constructor(firstName: String, lastName: String): this(Human(firstName, lastName))
}
Or in property delegation
class Person4(firstName: String, lastName: String) {
val fullName: String by lazy { "$firstName $lastName" }
}
Note: the closure is captured at initialization time, the values are therefore still available when lazy evaluates eventually.