I started my studies in Kotlin and I'm trying to solve this problem:
First: Create a class to represent a person with the private attributes of name, year of birth and height.
Second: Create one public method for sets/gets and another one to print all the data from the person.
Third: Create a method to calculate the age.
When I turned private the attributes, I couldn't access them anymore. How can I use the getters and setters to access the attributes?
My code:
fun main() {
val dataJohn = Data()
dataJohn.name = "John"
dataJohn.height = 1.83
dataJohn.birth_year= 1948
println(dataJohn.birth_year)
println(dadosJoao.height)
println(dadosJoao.calculateAge())
}
class Data {
var name = ""
var age = 0
var height = 0.0
var birth_year = 0
fun calculateAge(age: Int) {
age = 2021 - birth_year
return this.age
}
}
That’s because what is done in Java (I.e making variables final and exposing via getters and setters) is creating a property. This is done in Kotlin by just declaring your variable with val or var. When marking it as private though the property is only accessible within the class and its like making the getters and setters private in Java. You can therefore omit the private part.
Related
I have a class with several properties. The properties, themselves, can also have their own properties. I would like to count the total number of properties. That includes both the properties in the "main" class and the properties' properties.
For instance, consider the following class
class Person {
val firstname: String = "Jurgen"
val lastname: String = "Klopp"
val address: Address = Address("Liverpool", "England")
}
where
class Address (
val city: String,
val coubtry: String
) { }
I would like the counting to add up to 5, since the Person class "contains" firstname, lastname, address, city and country. Note that the Address class also could have a another class (with its own properties) as its property. These properties should also be counted. Is it possible to count the total number of properties?
Please further note that the counting is intended to be applied to complex/multi-leveled AVRO structures (i.e. auto-generated AVRO classes).
I'm basing this off my other post on reflection, found here: How to get the relative class name of data classes
I created your two classes in a package called complexclasses.
My Main.kt looks like this:
import complexclasses.Person
import kotlin.reflect.KClass
import kotlin.reflect.full.memberProperties
fun main(args: Array<String>) {
val foundParams = Person::class.getAllMembers()
println(foundParams)
}
private fun KClass<*>.getAllMembers(): MutableList<String> {
val params = mutableListOf<String>()
memberProperties.forEach { member ->
params.add(member.name)
if(member.returnType.toString().substring(0, 7) == "kotlin.") {
return#forEach
}
val clazz = Class.forName(member.returnType.toString()).kotlin
params.addAll(clazz.getAllMembers())
}
return params
}
When I run the program, it outputs:
[address, city, country, firstname, lastname]
Using reflection (and a little string manipulation) I can recursively crawl non-kotlin objects and print out the names of all their members.
I didn't test this with more complicated data structures, like Lists or Maps. It's possible those would require more work, but I think this is enough to get you started, at least.
I'm not familiar with AVRO so hopefully this works with it.
Let me know if you hit any issues, I'm glad to help if this doesn't meet your requirements.
It seems that a simple extension property like the following does not work.
var Dog.age = 0;
What is the recommended way to implement this? I have tried the following, and it worked, but this will prevent any Dog object from cleaned up by the Garbage Collector, won't it?
class Dog
{
}
val dogAgeMap=HashMap<Dog, Int>();
var Dog.age:Int
get() = dogAgeMap[this]?: 0;
set(value){ dogAgeMap[this] = value}
class PetShop
{
fun work()
{
val d1 = Dog();
d1.age = 100;
val d2 = Dog();
d2.age = 200;
println("${d1.age}, ${d2.age}");
}
}
fun main(args:Array<String>)
{
PetShop().work();
}
Correct, this will prevent the Dog instances on which the age setter has been called to be GCed inside the scope of where the dogAgeMap is defined. If you defined the Dog.age extension property (and thus dogAgeMap) in a limited scope with a limited (short) lifespan, then you are okay.
However, if that is not the case, and you need the age info all across you application, then age should just be part of the original class definition and you don't ever run into this problem.
Solution in this case
class Dog(val age: Int)
If you need the age information only in one part of your application, then a better way would be to create the lookup (the HashMap) only for that part, or to simply use an enriched class with age (or a wrapper class with age) instead of the Dog class in that part of your application. And when you are done with work there, you clean up the map or the enriched class instances. In that way no instances will leak.
But if you really really want to do it with an extension property across the whole application, and thus you need to keep the reference to the dogAgeMap all the time, then you need to take care of leaking memory if you have a lot of instances that you go through and set their age.
If that is your case you can use a WeakHashMap<Dog, Int> instead. A WeakHashMap only keeps weak references and it won't prevent Dog instances to be GCed (once your strong references are no longer retained).
import java.util.WeakHashMap
val dogAgeMap = WeakHashMap<Dog, Int>()
var Dog.age: Int
get() = dogAgeMap[this] ?: 0
set(value) {
dogAgeMap[this] = value
}
Note however, that WeakHashMap is a Java class and not part of Kotlin core library, so if you use Kotlin for multiplatform, this won't work. In that case you would need a WeakHashMap implementation (library) on each platform.
An alternative way to do this if your data for dogs also contains an ID for each dog, would be to use the ID as the lookup key instead. That would be possible to port to all platforms. The implementation would then change to
// I am using a Long here, but it could be whatever type that
// is small enough to not cause memory concerns, since
// these keys would still exist in memory because a normal HashMap is used.
class Dog(val id: Long) {}
val dogAgeMap = HashMap<Long, Int>()
var Dog.age: Int
get() = dogAgeMap[id] ?: 0
set(value) {
dogAgeMap[id] = value
}
I want to set up two values that hold immutable references to each other. Example:
data class Person(val other: Person)
val jack = Person(jill), jill = Person(jack) // doesn't compile
Note: lateinit doesn't seem to work with data class primary constructors.
Any ideas?
You could get away with something like this:
class Person() {
private var _other: Person? = null
private constructor(_other: Person? = null) : this() {
this._other = _other
}
val other: Person
get() {
if (_other == null) {
_other = Person(this)
}
return _other ?: throw AssertionError("Set to null by another thread")
}
}
And then you would be able to do:
val jack = Person()
val jill = jack.other
Using a data class here does not work for multiple reasons:
First because a data class can't have an empty constructor.
Even if that wasn't a problem, the generated methods would end up having a cyclic dependency and will fail in runtime with java.lang.StackOverflowError. So you'd have to overwrite toString, equals, etc. which kind of defeats the purpose of using data class in the first place.
Here is the trick (note, this is really a trick, you need a good reason to use it in real code).
Unfortunately it won't work with data classes, as they seem to be secured against this kind of hacks.
But if you have java-stile classes, you may use two things to your advantage:
You can initialize vals in the constructor (same as with final in java)
You have access to this inside the constructor (and you may leak it outside if you really want)
Which means that you can create another Person inside the constructor of the first person and finalize the creation of both classes before the constructor finishes.
Once again: exposing this as I did below is a bad idea. When otherFactory is called, it's parameter is only half-initialized. This may lead to nasty bugs, especially if you try to publish such reference in multithreaded environment.
A bit safer approach is to create both Persons inside the constructor of the first Person (you'll need to supply the fields of both entities as arguments). It's safer because you're in control of the code that uses half-initialized this reference.
class Person {
val name: String
val other: Person
constructor(name: String, other: Person) {
this.name = name
this.other = other
}
// !! not very safe !!
constructor(name: String, otherFactory: (Person) -> Person) {
this.other = otherFactory(this)
this.name = name
}
// a bit safer
constructor(name: String, otherName: String) {
this.other = Person(otherName, this)
this.name = name
}
}
val person1 = Person("first") {
Person("second", it)
}
val person2 = person1.other
print(person1.name) // first
print(person2.name) // second
val person3 = Person("third", "fourth")
val person4 = person3.other
print(person3.name)
print(person4.name)
Thanks for your suggestions everybody. I came up with an alternative and would like to hear your insights:
open class Person {
open val other: Person by lazy { Person2(this) }
class Person2(override val other: Person): Person()
}
val jack = Person()
val jill = jack.other
Here we have one person lazily instantiating the other on demand, using an internal subclass that implements other differently (i.e. it is just given it directly in its constructor).
Thoughts most welcome.
for example , I want to change all setters this way:
this.a = StringUtils.trim(a);
If it's a java bean, I can do this by modifying the code generating template of the ide. But Intellij seems not support to atomically add getter/setter for kotlin data class.
Is there a way to do this?
There is not a way to do this as of Kotlin 1.1.
A Kotlin data class, for the most part, is a class "to do nothing but hold data".
I think the closest you can get is to validate your data upon class initialization and make your data class properties read-only values. e.g.:
data class Data(val a: String) {
init {
require(a == a.trim())
}
}
The following won't throw an exception:
val a = Data("ab")
val b = a.copy(a = "abc")
While the following will:
val c = a.copy(a = "abc ")
It looks like if you declare the property as private, you can create your own getter/setters for accessing it. This example works for me.
fun main(args: Array<String>) {
var t = test("foo")
t.setHello("bar")
println(t)
}
data class test(private var hello: String) {
fun setHello(blah: String) {
this.hello = blah
}
}
But you will still have an issue when the property is passed in to the constructor. You will probably need to rethink how you are doing this, either declaring the field private and trimming it in the getter, or not using a data class for this instance.
It's easy to write extension methods in Kotlin:
class A { }
class B {
fun A.newFunction() { ... }
}
But is there some way to create extension variable? Like:
class B {
var A.someCounter: Int = 0
}
You can create an extension property with overridden getter and setter:
var A.someProperty: Int
get() = /* return something */
set(value) { /* do something */ }
But you cannot create an extension property with a backing field because you cannot add a field to an existing class.
No - the documentation explains this:
Extensions do not actually modify classes they extend. By defining an extension, you do not insert new members into a class, but merely make new functions callable with the dot-notation on instances of this class.
and
Note that, since extensions do not actually insert members into classes, there’s no efficient way for an extension property to have a backing field. This is why initializers are not allowed for extension properties. Their behavior can only be defined by explicitly providing getters/setters.
Thinking about extension functions/properties as just syntactic sugar for calling a static function and passing in a value hopefully makes this clear.
However, if you really, really want to do something like this...
As stated above regarding efficiency, an additional backing field added directly to the class is the best way to store data non-derivable from existing non-private members from the class. However, if you don't control the implementation of the class and are dead-set on creating a new property that can store new data, it can be done in a way that is not abysmally inefficient by using separate external tables. Use a separate map that keys on object instances of this class with values that map directly to the value you want to add then define an extension getter and/or setter for this property which uses your external table to store the data associated with each instance.
val externalMap = mutableMapOf<ExistingClass, Int>()
var ExistingClass.newExtensionProperty : Int
get() = externalMap[this] ?: 0
set(value:Int) { externalMap[this] = value }
The additional map lookups will cost you - and you need to consider memory leaks, or using appropriately GC-aware types, but it does work.
There's no way to add extension properties with backing fields to classes, because extensions do not actually modify a class.
You can only define an extension property with custom getter (and setter for var) or a delegated property.
However, if you need to define an extension property which would behave as if it had a backing field, delegated properties come in handy.
The idea is to create a property delegate that would store the object-to-value mapping:
using the identity, not equals()/hashCode(), to actually store values for each object, like IdentityHashMap does;
not preventing the key objects from being garbage collected (using weak references), like WeakHashMap does.
Unfortunately, there is no WeakIdentityHashMap in JDK, so you have to implement your own (or take a complete implementation).
Then, based on this mapping you can create a delegate class satisfying the property delegates requirements. Here's an example non-thread-safe implementation:
class FieldProperty<R, T : Any>(
val initializer: (R) -> T = { throw IllegalStateException("Not initialized.") }
) {
private val map = WeakIdentityHashMap<R, T>()
operator fun getValue(thisRef: R, property: KProperty<*>): T =
map[thisRef] ?: setValue(thisRef, property, initializer(thisRef))
operator fun setValue(thisRef: R, property: KProperty<*>, value: T): T {
map[thisRef] = value
return value
}
}
Usage example:
var Int.tag: String by FieldProperty { "$it" }
fun main(args: Array<String>) {
val x = 0
println(x.tag) // 0
val z = 1
println(z.tag) // 1
x.tag = "my tag"
z.tag = x.tag
println(z.tag) // my tag
}
When defined inside a class, the mapping can be stored independently for instances of the class or in a shared delegate object:
private val bATag = FieldProperty<Int, String> { "$it" }
class B() {
var A.someCounter: Int by FieldProperty { 0 } // independent for each instance of B
var A.tag: String by bATag // shared between the instances, but usable only inside B
}
Also, please note that identity is not guaranteed for Java's primitive types due to boxing.
And I suspect the performance of this solution to be significantly worse than that of regular fields, most probably close to normal Map, but that needs further testing.
For nullable properties support and thread-safe implementation please refer to here.
You can't add a field, but you can add a property, that delegates to other properties/methods of the object to implement its accessor(s). For example suppose you want to add a secondsSinceEpoch property to the java.util.Date class, you can write
var Date.secondsSinceEpoch: Long
get() = this.time / 1000
set(value) {
this.time = value * 1000
}
If you are extending View you can do it quite easily like this...
This is example how I create some my custom class Event property in EditText class extension:
Define id for key :
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="EditTextEventOnClearTagKey" type="id" />
</resources>
Define one reusable extension like this:
fun <T : Any> View.tagProperty(#IdRes key: Int, onCreate: () -> T): T {
#Suppress("UNCHECKED_CAST")
var value = getTag(key) as? T
if (value.isNull) {
value = onCreate()
setTag(key, value)
}
return value!!
}
Use it in wherever View extension you need:
val EditText.eventClear get() = tagProperty(R.id.EditTextEventOnClearTagKey) { event<Unit>() }