kotlin data class property not in equals method - kotlin

example:
data class Test(val name: String, val age: Int)
val a = Test("a", 21)
val b = Test("b", 31)
val result = a == b // false
want result return true, what to do?

As per comments, it's not clear what you're aiming for here.
By default, two instances of data class are considered equal when all the properties in their primary constructor are equal. So in the question, two Tests will be equal when they have the same name and age:
println(Test("a", 1) == Test("a", 1)) // prints ‘true’
println(Test("a", 1) == Test("b", 1)) // prints ‘false’
println(Test("a", 1) == Test("a", 2)) // prints ‘false’
It looks like you want two Tests to be considered equal when they have the same name, even if they have different ages. To do that, you have two main options:
Override their equals() and hashCode()* methods:
data class Test(val name: String, val age: Int) {
override fun equals(other: Any?) = other is Test && other.name == name
override fun hashCode() = name.hashCode()
}
That gives Test a natural sense of equality, which will apply everywhere two Tests are checked, whether you're using ==, or putting them in a Set, or whatever.
Check the names explicitly at that point in your code:
val result = a.name == b.name
Obviously, that will only apply to that particular check, and won't affect anything else. So it's simpler for this one case, but you'd have to remember to repeat it anywhere else you want to check for equality in that way.
Which you choose depends on what Test means in your case, and whether checking only the name is the obvious, natural behaviour that you'll want by default.
(However, in the question, that will still result in a != b, because a and b have different names as well as different ages.)
(* It won't give you an error if you fail to override hashCode() as well, but that breaks the contract for equals() and is likely to give strange behaviour, such as not being able to find Test elements/keys you've added to sets/maps, errors or infinite loops when trying to sort a list of Tests, and so on.)

Related

Kotlin - Type mismatch: inferred type is Any? but Boolean was expected

I'm trying my hands on Kotlin. Being from a Python background is really giving me a tough time to get the knack of the Kotlin syntax. I'm trying to do a simple dictionary (Mutable Map) operation. However, its giving me exceptions.
This is what I tried. Kotlin compiler
Adding the code snippet for reference.
fun main() {
val openActivityMap = mutableMapOf<String, MutableMap<String, Any>>()
val packageName = "amazon"
val currentTime = 23454321234
if(openActivityMap.containsKey(packageName)){
if(openActivityMap[packageName]?.get("isAlreadyApplied")){
if((openActivityMap[packageName]?.get("lastAppliedAt") - currentTime) > 3600){
openActivityMap[packageName]?.put("isAlreadyApplied", false)
}
}
else{
openActivityMap[packageName]?.put("isAlreadyApplied", false)
}
}
}
I'm a bit late to the party, but I'd like to point out another solution here.
As I commented on the OP, heterogeneous maps with fixed string keys like this are usually better expressed with classes in Kotlin. For instance, in your case, the class for your main map's values could be the following:
data class PackageInfo(
var isAlreadyApplied: Boolean,
var lastAppliedAt: Long,
)
(you could obviously add more properties if need be)
This would save you all the casts on the final values.
Another point I'd like to make is that if you access the value for a key anyway, you don't need to check up front the existence of the key with containsKey. Maps return null for keys that are not associated with any value (this is why you need to check for null after getting the value).
The compiler cannot see the correlation between containsKey and the subsequent get or [] access. However, it's smart enough to understand a null check if you simply get the value first and then check for null.
This always applies unless you want to tell the difference between keys that aren't in the map and keys that are in the map but associated null values (which is quite rare).
All in all, I would write something like that:
fun main() {
val openActivityMap = mutableMapOf<String, PackageInfo>()
val packageName = "amazon"
val currentTime = 23454321234
val packageInfo = openActivityMap[packageName]
if (packageInfo != null) { // the key was found and the value is smart cast to non-null in the next block
if (packageInfo.isAlreadyApplied) {
if ((packageInfo.lastAppliedAt - currentTime) > 3600) {
packageInfo.isAlreadyApplied = false
}
} else {
packageInfo.isAlreadyApplied = false
}
}
}
data class PackageInfo(
var isAlreadyApplied: Boolean,
var lastAppliedAt: Long,
)
I would recommend writing tests first and working in small increments, but this should fix your compilation issues:
fun main() {
val openActivityMap = mutableMapOf<String, MutableMap<String, Any>>()
val packageName = "amazon"
val currentTime = 23454321234
if(openActivityMap.containsKey(packageName)){
if(openActivityMap[packageName]?.get("isAlreadyApplied") as Boolean){
if((openActivityMap[packageName]?.get("lastAppliedAt") as Long - currentTime) > 3600){
openActivityMap[packageName]?.put("isAlreadyApplied", false)
}
}
else {
openActivityMap[packageName]?.put("isAlreadyApplied", false)
}
}
}
EDIT: Also I prefer to avoid nullable variables and mutable objects in general, but I suppose there's an exception to every rule.
Couldn't you just declare your Map<String, Any> to return a Boolean instead of Any? So,
val openActivityMap = mutableMapOf<String, MutableMap<String, Boolean>>()
It looks like you're trying to use your second Map to store both Booleans and Ints, which is complicating the logic. You'll need to typecast if you decide to approach it without Typing.
There's a problem with the 2 statement below
if(openActivityMap[packageName]?.get("isAlreadyApplied"))
if((openActivityMap[packageName]?.get("lastAppliedAt") - currentTime) > 3600)
As we all know, an IF statement requires a boolean value for it's param. The types of both statement are unknown at compilation time as they are of a Generic type, Any. As such,
openActivityMap[packageName]?.get("isAlreadyApplied") could be a null or of type Any (Not Boolean).
openActivityMap[packageName]?.get("lastAppliedAt") could be a null or of type Any (an Int was expected here for computation).
This would throw compilation errors as the compiler does not know the types to go with. What could be done is to cast to it's proper types.
Solution
openActivityMap[packageName]?.get("isAlreadyApplied") as Boolean ?: false
((openActivityMap[packageName]?.get("lastAppliedAt") as Int ?: 0) - currentTime)
Giving a default value if it's null.
maybe you can try something like this
if (openActivityMap.containsKey(packageName)) {
val packageMap = openActivityMap[packageName]!!
val applyRequired = (packageMap["lastAppliedAt"] as Long - currentTime) > 3600
packageMap["isAlreadyApplied"] = packageMap.containsKey("isAlreadyApplied") && !applyRequired
}
btw. do you really want to have lastAppliedAt to be in te future? otherewise it will never be > 3600

Why is my Kotlin comparable not makign correct equality comparisons?

I am currently trying to make a comparable object and working on the compareTo() function, for which I wrote the following code
class InfoAcad(e: String, m: String, c: Int): Comparable<InfoAcad> {
override operator fun compareTo(other: InfoAcad): Int {
if (this.e < other.e) return -1
if (this.e > other.e) return 1
if (this.e == other.e && this.m < other.m) return -1
if (this.e == other.e && this.m > other.m) return 1
return 0
}
}
The idea is that e is an ID number inputted as a string, which always follows the format XX-XXX where every X character is an integer between 0 and 9, and m is a course code following the format LL-XXX where each L character is a capital letter between A and Z and the X characters are integers between 0 and 9 like in the ID numbers. The objects are first compared by their ID number, and if the ID numbers are equal they are then compared by the course code, if both values are the same then the objects are equal, the c parameter is not taken into account in the comparison. I found out yesterday that I could compare strings directly in Kotlin in < and > relations, so I decided to try using that to make the task of comparing the InfoAcad objects a bit easier on myself, however when I make a main function to test the comparisons, the equality always returns a false value independently of what is in the string values of the InfoAcad objects. Here's said main function:
fun main() {
var A = InfoAcad("18-10125", "CI-2526", 3)
var B = InfoAcad("18-10125", "CI-2526", 5)
println("A = B: " + (A == B).toString()) //true
println("A < B: " + (A < B).toString()) //false
ptintln("A > B: " + (A > B).toString()) //false
}
When I change characters in the ID and course code values the inequality relations work just as intended, so what could be causing the equality relation to always return false? I appreciate and thank any responses in advance.
Note: I have also tried giving A and B the same c value, the equality part still returned false.
Override the equals function as well, or use a data class.
compareTo is only used for the < and > operators. The == operator is implemented by the separate equals function.
You can find the available operators, and the functions you need to override for each, in the Operator overloading section of the Kotlin docs.
If you don't override the equals function, the default behaviour is for it to use object identity. That means that two different objects, even if they contain the same fields, will never be considered equal.
There is however a nice shortcut for what you want to do! Kotlin will automatically generate an equals function for you if you make your class a data class. It's a good fit for classes like yours, whose main purpose is to hold data.
Because == and != translates to a call to equals(other: Any?): Boolean method, from kotlinlang
Expression Translated to
a == b a?.equals(b) ?: (b === null)
a != b !(a?.equals(b) ?: (b === null))
These operators only work with the function equals(other: Any?): Boolean, which can be overridden to provide custom equality check
implementation. Any other function with the same name (like
equals(other: Foo)) will not be called.

Is this a good solution for a flexible data class to compare two objects?

first, I'm a kotlin neebie ^^.
I want to compare to objects from a data class. But the objects have variables that can be changed.
Is the code example a good practice to solve this or is there a problem that i can't see?
Ty
data class Test1(val id : Int, var name: FlexibleProperty<String>)
class FlexibleProperty<T>(var value: T) {
override fun equals(other: Any?) = true
override fun hashCode() = 1
}
fun main() {
val test1 = Test1(1, FlexibleProperty("Hans"))
val test2 = test1.copy()
println("test1 == test2 ${test1 == test2}")
println("test1 === test2 ${test1 === test2}")
test2.name = FlexibleProperty("Dieter")
println("test1 == test2 ${test1 == test2}")
println("test1 === test2 ${test1 === test2}")
}
EDIT:// Sry, I was a little confused ^^. My detailed problem is: I want to add these objects into a set. If I use normal string variables, the objects are different, so the set has 2 objects. But if I add test1 and check set.contains(test2) with my FlexiableProperty, the result is true, so I have to update the object. I don't want to check the id outside of the objects (with maybe a map and the id as key)
Here the code snippet with a set:
data class Test1(val id : Int, val name: FlexibleProperty<String>)
data class FlexibleProperty<T>(var value: T) {
override fun equals(other: Any?) = true
override fun hashCode() = 1
}
fun main() {
val test1 = Test1(1, FlexibleProperty("Hans"))
val test2 = test1.copy(name = FlexibleProperty("Dieter"))
val setTest = mutableSetOf(test1)
if (setTest.contains(test2)) {
setTest.remove(test1)
}
setTest.add(test2)
println("set $setTest")
}
There's no specific problem with your solution per see, but it could be greatly improved.
First, name can still be a value, since you use copy() anyway:
data class Test1(val id : Int, val name: FlexibleProperty<String>)
val test2 = test1.copy(name = FlexibleProperty("Dieter"))
Having no mutable properties make your class thread safe, and easier to reason about.
Second, when you use data class at the top level, it makes a lot of sense to make all classes it encapsulates also data classes. That would also solve your second problem with the need of overriding equals and hashCode:
data class FlexibleProperty<T>(var value: T)
Also, there's no reason to check referential equality with ===, at least with the examples you provide.

Smart cast of map access proven with `when in`

I’m trying to use Kotlin’s when block to look up an element in different maps. After confirming the element exists, the code subsequently does not smart-cast the resulting lookup in the map to not null.
Below is a minimum working example: is it possible to rework it such that !! is not needed?
fun main(args: Array<String>) {
val string = "abc"
val map1 = mapOf('a' to 5)
val map2 = mapOf('b' to 4)
when (val char = string.firstOrNull()) {
null -> println("Nothing to find")
in map1 -> println("Found in map1: ${map1[char]!!+1}")
in map2 -> println("Found in map2: ${map2[char]!!-1}")
else -> println("Unrecognised character $char")
}
}
Unfortunately, in Kotlin, functions can't have contracts of the form "if f returns true, then g doesn't return null." Hence, the compiler doesn't use information about definitely successful contains calls.
The workaround with !! is OK in this case because you can be sure that get returns not null. Implementation of complex patterns in when (KT-186) would cover this use case by allowing declaring a variable inside when clauses and providing static guarantees that it's not null.

Comparing two optionals in Kotlin

Consider a class with id field which might be null until stored in database:
class IdableK<T : IdableK<T>> : Comparable<T> {
private var id : Long? = null
}
I am trying to implement a compareTo method as follows:
override fun compareTo(other: T): Int {
if (id == null) {
return -1;
}
if (other.id == null) {
return 1;
}
return id!!.compareTo(other.id!!)
}
Is this a correct way of doing it? Would there be a simple way of doing it?
Check out the kotlin.comparisons package. e.g. You can use compareValues:
class IdableK<T : IdableK<T>> : Comparable<T> {
private var id: Long? = null
override fun compareTo(other: T) = compareValues(id, other.id)
}
This is incorrect. If you have two instances with their ids set to null, both instances will return -1 when you call compareTo(other) on them, while if one returns -1 the other should return 1 in a correct implementation. I'm not sure if there are situations where it makes sense to implement compareTo based on nullable properties, but I can't imagine any. Maybe there's a better way for you too?
Also, you should avoid non-null assertions (!!). Since you're using vars, other threads may change the value to null so that even if you did a null check before, the value is now null and !! throws. Instead, you should store both ids in local variables and check these for null values.
If you absolutely have to use compareTo, I'd do it like this:
override fun compareTo(other: T): Int {
val thisId = id
val otherId = other.id
if (thisId == null && otherId == null) return 0
if (thisId == null && otherId != null) return -1
if (thisId != null && otherId == null) return 1
// thisId and otherId are now smart cast to Long
return thisId.compareTo(otherId)
}
Here is a simple way:
override fun compareTo(other: T) :Int {
return id?.compareTo(other.id ?: return 1) ?: -1
}
However this piece of code is very unfriendly to a novice kotlin programmer. It involves too much magic that make it look like scala. These 3 question marks make people puzzled, at least they must think for a minute or two before they could realize what is going on in this minimalistic one-liner. I still prefer your edition. It's more verbose, but clear.
And I'm really worried about the symmetric problem. This matters, and isn't just a design problem. If you don't compare nullable properties, there won't be this programming puzzle. It will just be override fun compareTo(other: T) = id.compareTo(other.id). Simple, clear, and no misleading.
I would rather throw away all null checking code and just live with those null assertions. Because mostly you won't compare there things until it is fully initialized. If these assertion fails, it means something really bad has happens.
Oh, BTW, I don't know about your project, and if it hits the rare cases that you have to compare nullable properties, I think you could write a special edition of Comparator that consider nulls instead of throwing NPEs. Don't mess with the natural order.