Kotlin inc() operator overloading - kotlin

I have a little problem to overload inc() operator, precisely to make a postfix and a prefix one.
Here my data class
data class Person(val firstName: String, val name: String, var age: Int) {
operator fun inc(): Person {
val tmp = this
this.age++;
return tmp
}
}
With this, age change before returning so it's only working for prefix version.
How can I do a postfix version of inc() operator ?

inc is expected to return a new, incremented instance of the class. Since you've got a dataclass, we can use Kotlin's convenience functions that work on dataclasses to get a new instance for you relatively effortlessly.
data class Person(val firstName: String, val name: String, var age: Int) {
operator fun inc(): Person =
this.copy(age = this.age + 1)
}
Person.copy is one of several dataclass methods generated for you. It takes the same arguments as your primary constructor, with each argument defaulting to the current value on this (i.e. any arguments not passed will be the same as the corresponding values on this). So by passing only the age parameter by name, we modify only the one we want to and leave the others untouched.

There is no way to do what you’re trying to do. You are breaking the contract that the increment operator must not mutate the class. It must return a new instance of the class.

Related

Is there a way to make the first digit of int always start with 1 in Kotlin

Let's say I have the following class constructor:
class Car(val brand: Brand,val modelName: String, val version: Int){}
If for example, I want the version number to always start with 1. Is there a way to manipulate it in the class body to achieve this ?
Meaning:
val firstdigit:Int = abs(version).ToString().Substring(0,1)
And then parse it to Int. But how to replace the original first digit after that?
I'm just learning Kotlin and I got a bit stuck with this
Is this what you had in mind?
class Car(val brand: Brand, val modelName: String) {
val version = getNextVersion()
companion object {
private var nextVersion = 0
private fun getNextVersion(): Int {
nextVersion++
if (nextVersion.toString()[0] != '1') {
nextVersion = (10.0.pow(ceil(log10(nextVersion.toDouble())))).toInt()
}
return nextVersion
}
}
}
You already said in the comments that you want the number to increment per instance, so the caller shouldn't be providing that number in the first place really! But just generally, here's two approaches to sanitising your input parameters:
1) Make it the caller's responsibility to provide valid data
init {
require(version.toString().first() == '1') { "Needs to start with 1 thanks" }
}
require throws an IllegalArgumentException if it fails, which is the standard exception for "the value of this argument is invalid". Should the class be responsible for taking bad data and trying to "fix" it, or should the caller be handling that - and maybe not constructing an instance at all if it doesn't have valid data?
2. create a newInstance function that uses valid data, and keep the constructor private
class Thing private constructor(val number: Int){
companion object {
fun newInstance(num: Int): Thing {
return Thing(abs(num))
}
}
}
fun main() {
Thing.newInstance(-2).let { println(it.number)}
}
If it makes sense for the class itself to sanitise the input parameters, you can delegate construction to a function that takes care of that, and prevent things from calling the constructor directly with potentially bad data.
This can cause issues with e.g. serialisation libraries (which want to call the constructor directly) but in that case you could leave the constructor public, and just advise callers to call newInstance instead. Not ideal, but it's an option!

Comapring data class in Kotlin using == operator

I am trying to write an assertion by comparing two Kotlin data classes.I am just simplifying the question by using a minimal class.
data class X(val isThatSo: Boolean) {
val name:String = "xyz"
}
In my test
val s = """
{
"isThatSo": "true",
"name": "Ankit"
}
"""
assert(Gson().fromJson(s, X::class.java) == X(true))
Looks to me that the name field is not compared at all because the value in both the objects is different. Is my understanding correct?
From the documentation:
data class User(val name: String, val age: Int)
The compiler automatically derives the following members from all properties declared in the primary constructor:
equals()/hashCode() pair
toString() of the form "User(name=John, age=42)"
componentN() functions corresponding to the properties in their order of declaration.
copy() function.
To exclude a property from the generated implementations, declare it inside the class body:
data class Person(val name: String) {
var age: Int = 0
}
You're comparing two instances of a class, which are not identical. You can compare the name variable within X for equality.

Why isn't Kotlin complaining about ambiguity? And why is it calling the secondary constructor?

I was testing something in Kotlin and now I won't be able to sleep properly if I don't find an answer.
Look at this class:
data class Person(
val name: String,
val age: Int? = 0
) {
constructor(
secondName: String,
secondAge: Int = 5
) : this(
name = "$secondName 2",
age = secondAge
)
}
fun main() {
val firstCase = Person("Lorem") // Complaints about ambiguity.
val secondCase = Person("Lorem", 42) // Calls secondary constructor.
}
Complaining about ambiguity in the firstCase is understandable, but why it is not happening the same in the secondCase? And why did it decide to call the secondary constructor and not the main constructor?
Now if I add another parameter on the main constructor with a default value:
data class Person(
val name: String,
val age: Int? = 0,
val isAdult: Boolean = false
) {
constructor(
secondName: String,
secondAge: Int = 5
) : this(
name = "$secondName 2",
age = secondAge
)
}
fun main() {
val thirdCase = Person("Lorem") // Calls secondary constructor.
}
I was expecting to have ambiguity for the thirdCase, just like in the firstCase. But no! It calls the secondary constructor. Why?!
The rules of method resolution in Kotlin (and in Java) can be a bit arcane. Thankfully, they do the obvious thing in nearly all situations — which is clearly the point! — but there are a few surprising corner cases.
The general principle is that it picks the most specific method that could match, and only gives an error if there isn't a single winner.
So in your second case, the arguments are String and Int. The candidates are the primary constructor (taking String and Int?), and the secondary constructor (taking String and Int). The latter is a more specific match, because Int is a subtype of Int?, and so it picks that one.
But in your first case, the only argument provided is a String, which matches both constructors equally, so there's no clear winner, and it flags up the ambiguity.
Your third case is even less obvious. However, the section of the Kotlin spec that discusses how to choose the most specific candidate from all the overloads says:
For each candidate we count the number of default parameters not specified in the call (i.e., the number of parameters for which we use the default value). The candidate with the least number of non-specified default parameters is a more specific candidate
I think that's what's happening in your third case: it picks the secondary constructor (which would leave only one parameter with its default value) over the primary (which would leave two).
Kotlin gives more priority to a function that is more specific. Consider this example:
foo(dog) // invokes foo(Dog)
fun foo(animal: Animal) {}
fun foo(dog: Dog) {}
In this case foo(dog) is also ambiguous, but in fact, it uses foo(Dog) function. To use the other one, we have to explicitly upcast:
foo(dog as Animal)
Because Int is a subtype of Int?, your secondary constructor is more specific than the primary constructor and this is why it is preferred.
In your first example, there is no ambiguity when you use two arguments, because it selects the constructor with the most specific type matching your parameter. There is no ambiguity to choose between the one with Int and the one with Int? because Int is more specific than Int?.
When you provide a single argument, it is ambiguous since you are not providing any argument that can help distinguish whether you want Int or Int?.

Kotlin data class with additional properties not in constructor

Starting out with Kotlin and wanting to make a data class
data class Person(val Email: String, val firstName: String, val lastName: String)
But let's say I want to add additional properties that I don't know at the time when I am using the constructor but I want to store this data at a later point when I am aware of it for example a person's mood (Represented as a String)
In Java I would make a data class like this. I would be able to not include it in the Constructor and make a getter where I could set it at a later time.
public class Person{
private String email;
private String firstName;
private String lastName;
private String mood;
public person (String email, String firstName, String lastName){
this.email = email;
this.firstName = firstName;
this.lastName = lastName;
}
public setMood(String mood){
this.mood = mood;
}
}
Kotlin doesn't appear to have an answer on this or if it does I do not know how to phrase correctly. Hence why this question could already be answered and I am unable to find it.
I do understand that by not including mood in the data class line Kotlin may not be able to identify mood as part of the data class but aside from including it in the constructor and setting it to null I'm not sure what else to do or is that what I am supposed to do?
You should be able to just add it as a property to Person. In Kotlin, a data class is still a class, it just comes with some extras (toString, copy constructors, hashCode/equals, etc). You can still define any properties that you want.
data class Person(val Email: String, val firstName: String, val lastName: String) {
var mood: String? = null
}
In this case it is nullable, because as you stated, you might not know the mood until later.
Kotlin's data class must have first constructor, you can avoid it by not using the data keyword.
If you still want to add another property to the data class you can do the following:
data class Person(val email: String, val firstName: String, val lastName: String){
var mood: String = ""
}
This way you can do person.mood = "happy" without including it in the constructor.
Kotlin only considers the values passed to the primary constructor in terms of giving you the "for free" features that a Data class provides. Beyond that, you can add whatever additional properties you desire, but they aren't accounted for in the special code that Kotlin writes by way of you marking a class as data.
Per the Kotlin docs:
Note that the compiler only uses the properties defined inside the
primary constructor for the automatically generated functions. To
exclude a property from the generated implementations, declare it
inside the class body:
Per this, declaring properties outside of the primary constructor actually has benefits. You might be able to declare a property via the primary constructor, but choose not to.
Not only do you have to provide a primary constructor, but it has to include at least one property declaration. If you didn't do this, there would be no benefit to making the class a data class. But marking a class so does not limit what else you can do with that class.
Have you tried:
data class Person(val Email: String, val firstName: String, val lastName: String) {
var mood: String? = null
}
An alternative to #Todd's and #jingx's answers is
data class Person(val Email: String, val firstName: String, val lastName: String, var mood: String? = null)
The difference is that this way mood participates in toString/equals/hashCode/copy and that you can set mood in the constructor call. Even if that's probably not desirable for this specific case, it can be useful in others.

Secondary construction syntax kotlin

I have the following kotlin class with a primary constructor,
class Person(first: String, last: String, age: Int){
init{
println("Initializing")
}
}
I'd like to add a secondary constructor that parses a fullname into a first and last name and calls the primary constructor. However, I can't get the syntax right...
class Person(first: String, last: String, age: Int){
// Secondary constructor
constructor(fullname: String, age: Int):
this("first", "last", age)
{
println("In secondary constructor")
}
init{
println("Initializing")
}
}
This works fine, because I'm not actually parsing fullname in the secondary constructor. When I go ahead and try to parse fullname,
constructor(fullname: String, age: Int):
var first = fullname.split()[0];
...
{
println("In secondary constructor")
}
I get an unresolved reference: fullname. It doesn't exist in scope, but if I put it in the braces, then I cannot call the primary constructor via this,
constructor(fullname: String, age: Int):
{
var first = fullname
this(first, "foo", age)
println("In secondary constructor")
}
I get an error involving a missing invoke function.
Can't find a good example of this case on Kotlin docs, sorry.
The solution I use when I want a secondary constructor that needs to perform some calculations before passing the results to the primary constructor is to a function on the companion object. The code to do this would look like:
class Person(first: String, last: String, age: Int) {
companion object {
fun fromFullNameAndAge(fullname: String, age: Int) : Person {
println("In secondary constructor")
var bits = fullname.split()
// Additional error checking can (and should) go in here.
return Person(bits[0],bits[1],age)
}
}
init{
println("Initializing")
}
}
You can then use it like this
var p = Person.fromFullNameAndAge("John Doe", 27)
Which is not as neat as Person("John Doe", 27) but is IMO not too bad.
Constructor calls via this must be the first call. This is why it's handled as a delegate, rather than a normal method invocation. This means you cannot declare variables before the call is delegated.
You can solve this by simply inlining whatever values you planned on storing in variables:
constructor(fullName : String, age : int) : this(fullName.split(" ")[0], fullName.split(" ")[1])
But this can potentially index out of bounds if a last name wasn't specified, or if the client decided to use - or some other character as the delimiter. On top of that, it's an eye sore.
Design Analysis
The issue with your structure is giving the Person class the responsibility of determining the first and last name. This deteriorates the reusability of that class, as it'll be limited to one form of parsing. This is why the parsing of names should not be carried out by Person.
Instead, you should expose your primary constructor, then have the client of Person separate the first and last name.
Solution Example
Imagine we were reading names from a file. Each line in the file consists of a full name.
nameFile.forEachLine({ personList.add(Person(it)) })
This is the luxury you are attempting to give your clients: allow them to simply input a name, without worrying about parsing it.
The problem with this is the lack of safety: what if the line only contained a first name? What if the file didn't use whitespace to separate first and last name? You'd be forced to define new Person types just to handle different first/last name combinations.
Instead, the parsing should occur outside of the class:
file.forEachLine({
val firstName = ...
val secondName = ...
personList.add(Person(firstName, secondName))
})
Now that the responsibility has been taken out of Person, we can give the responsibility to a new object if we wanted:
val parser = NameParser(" ") //specify delimiter
file.forEachLine({
val firstName = parser.extractFirstName(it)
val lastName = parser.extractLastName(it)
personList.add(Person(firsrName, lastName))
})