Secondary construction syntax kotlin - 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))
})

Related

How to correctly cast list in Kotlin?

I have a list for example of type People. My list can contain only elements of type Student or only elements of type Worker:
interface People {
val name: String
val age: Int
}
data class Student(
override val name: String,
override val age: Int,
val course: Int
) : People
data class Worker(
override val name: String,
override val age: Int,
val position: String
) : People
At some point I need to know the exact type of the list (student or worker).
Can I safely find out the exact type? So far I've written this code, but it doesn't look very good:
fun someLogic(items: List<People>): List<People> {
return (items as? List<Student>) ?: (items as? List<Worker>)
?.filter {}
....
}
Also, I get a warning:
Unchecked cast
Can you please tell me how to perform such transformations correctly?
At runtime, the type parameter you used to create the list is not available. e.g. it is impossible to distinguish between the following two situations:
val students: List<People> = listOf<Student>(student1, student2)
val people: List<People> = listOf<People>(student1, student2)
This is because of type erasure.
The only information you have at runtime that can help determine a list's element type is the type of its elements.
So if a list has no elements, there is no way of knowing what type of list it is. Though in most situations, you don't need to anyway.
So assuming the list can only be a list of all students, or a list of all workers, but not a list containing a mixture of students and workers, you can determine the type of the list by checking the first element.
when (items.firstOrNull()) {
null -> { /* cannot determine the type */ }
is Student -> { /* is a list of students */ }
is Worker -> { /* is a list of worker */ }
// you can remove this branch by making the interface sealed
else -> { /* someone made another class implementing People! */ }
}
If you want to get a List<Student> or List<Worker> out of this on the other hand, you can just use filterIsInstance:
val students = items.filterIsInstance<Student>()
val worker = items.filterIsInstance<Worker>()
whichever list is not empty, then the type of items is the type of that list.
If you want to check that List<People> is List<Student> you can use this extension function:
fun List<People>.isStudentList(): Boolean {
// returns true if no element is not Student, so all elements are Student
return all { it is Student }
}
And if you want to cast List<People> to List<Student>, you can use map, and this cast is safe so let's say that there is some People that the are not Student so the cast is going to return null instead of Student because of as? and the mapNotNull is going to exclude null elements so in worst cases where you pass a list that doesn't contain any Student this function is going to return an empty list:
fun List<People>.toStudentList(): List<Student> {
// This is going to loop through the list and cast each People to Student
return mapNotNull { it as? Student }
}
Or you can just use filterIsInstance<Student> this will work the same as toStudentList above:
list.filterIsInstance<Student>()
And the same approach can be used for Worker
I would solve the problem with more specific classes.
You can define:
interface PeopleList<P : People> : List<P>
class StudentList : PeopleList<Student> {
// add implementation
}
class WorkerList : PeopleList<Worker> {
// add implementation
}
You can then easily check the types of these lists. Each of those classes can then provide guarantees that you are not mixing Student and Worker objects in the same List, something you can't do with plain List<People> objects.
Note also you are better off writing your code avoiding checking types if at all possible. Much better to add methods to the PeopleList interface and force the subclasses to implement them, for example:
interface PeopleList<P : People> : List<P> {
fun doSomethingGood()
}
Then you can call these methods at the appropriate time, instead of checking the type. This approach keeps the functionality associated with the subtypes alongside those subtypes and not scattered through the code at the various points where you have to check the type of PeopleList.

Kotlin inc() operator overloading

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.

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.

Why does Kotlin allow variable declarations with the same name as a parameter inside a method?

Why does Kotlin allow variable declarations with the same name as a parameter inside a method? Then also, is there any way to access the 'hidden' parameter?
For example:
fun main(args: Array<String>) {
val args = Any()
}
This is called shadowing and it is useful for decoupling your code from other parts of the system. It is possible because names are bound to the current scope.
Consider this:
You subclass a class Foo from someone else, let's say an API. In your code you introduce a variable bar. The author of Foo also updates his code and also adds a variable bar. Without the local scope, you would get a conflict.
By the way, this is also possible in other JVM bases languages including Java and commonly used within constructors or setters:
public TestClass(int value, String test) {
this.value = value;
this.test = test;
}
public void setFoo(String foo) {
this.foo = foo;
}
Shadowing does not only apply to parameters, other things can be shadowed too: fields, methods and even classes.
Most IDEs will warn you about shadowing as it can be confusing.
Recommendation for our own code:
try to avoid shadowing for two reasons:
your code becomes hard to read as two different things have the same name, which leads to confusion.
once shadowed, you can no longer access the original variable within a scope.
Kotlin does issue a warning about name shadowing which you can suppress with:
#Suppress("NAME_SHADOWING")
val args = Any()
Allowing for such shadowing may be handy in some cases e.g. throwing a custom exception after parameter validation:
fun sample(name: String?) {
#Suppress("NAME_SHADOWING")
val name = name ?: throw CustomArgumentRequiredException()
println(name.length)
}
It is unfortunately not possible to access the shadowed variable.
It is also not possible to turn a warning into an error at the moment.
Something also to note and if it isn't already realized or if anyone else new comes along. In kotlin you don't have access to the params if they are not prefixed with var/val until you add them as properties. So if a basic class was defined as this:
class Person(name: String, age: Int){
}
you can't use name or age until they are in scope; however it is unnecessary to shadow
with exceptions of desired reasons as miensol pointed out, but for the sake of being basic.
class Person(name: String, age: Int){
var name = name
var age = age
}
do these in the constructor
class Person(var name: String, private var age: Int){
}
you also will of course then have access based on the signature you gave on the object created.