Why is it not possible to use the constructor parameter (not property) directly as variables for a member function? - kotlin

Following example will describe my misunderstanding the best I think:
class myExampleClass (
myString: String,
val myInt: Int,
) {
fun memberFunction() {
val memberFunctionValA = myString // does not work
val memberFunctionValB = myInt // does work
}
}
Is there a specific reason? Do we always have to declare parameters as properties to use them inside the class?

For declaring properties and initializing them from the primary constructor, Kotlin has a concise syntax:
class Person(val firstName: String, val lastName: String, var age: Int) { /*...*/ }
I found this on https://kotlinlang.org/docs/reference/classes.html
As far as I can understand you missed a val keyword in the first parameter.
class myExampleClass (
val myString: String, // this might work
val myInt: Int,
) {
fun memberFunction() {
val memberFunctionValA = myString // does not work
val memberFunctionValB = myInt // does work
}
}

Related

kotlin data class constructors not getting picked up

I am creating a data class in kotlin as such
data class User(val name: String, val age: Int)
{
constructor(name: String, age: Int, size: String): this(name, age) {
}
}
In my main function, I can access the objects as such:
fun main(){
val x = User("foo", 5, "M")
println(x.name)
println(x.age)
println(x.size) // does not work
}
My problem is that I can't get access to size.
What I am trying to do is, create a data class where top level params are the common items that will be accessed, and in the constructors, have additional params that fit certain situations. The purpose is so that I can do something like
// something along the lines of
if (!haveSize()){
val person = User("foo", 5, "M")
} else {
val person = User("foo", 5)
}
}
Any ideas?
In Kotlin you do not need separate constructors for defining optional constructor params. You can define them all in a single constructor with default values or make them nullable, like this:
data class User(val name: String, val age: Int, val size: String = "M")
fun main(){
val x = User("foo", 5, "L")
val y = User("foo", 5)
println(x.size) // "L" from call site
println(y.size) // "M" from default param
}
You can not access size variable, because this is from secondary construct, but we have alternative variant.
data class User(var name: String, var age: Int) {
var size: String
init {
size = "size"
}
constructor(name: String, age: Int, size: String) : this(name, age) {
this.size = size
}
}
In short, you want to have one property that can be one of a limited number of options. This could be solved using generics, or sealed inheritance.
Generics
Here I've added an interface, MountDetails, with a generic parameter, T. There's a single property, val c, which is of type T.
data class User(
val mountOptions: MountOptions,
val mountDetails: MountDetails<*>,
)
data class MountOptions(
val a: String,
val b: String
)
interface MountDetails<T : Any> {
val c: T
}
data class MountOneDetails(override val c: Int) : MountDetails<Int>
data class MountTwoDetails(override val c: String) : MountDetails<String>
Because the implementations MountDetails (MountOneDetails and MountTwoDetails) specify the type of T to be Int or String, val c can always be accessed.
fun anotherCaller(user: User) {
println(user.mountOptions.a)
println(user.mountOptions.b)
println(user.mountDetails)
}
fun main() {
val mt = MountOptions("foo", "bar")
val mountOneDetails = MountOneDetails(111)
anotherCaller(User(mt, mountOneDetails))
val mountTwoDetails = MountTwoDetails("mount two")
anotherCaller(User(mt, mountTwoDetails))
}
Output:
foo
bar
MountOneDetails(c=111)
foo
bar
MountTwoDetails(c=mount two)
Generics have downsides though. If there are lots of generic parameters it's messy, and it can be difficult at runtime to determine the type of classes thanks to type-erasure.
Sealed inheritance
Since you only have a limited number of mount details, a much neater solution is sealed classes and interfaces.
data class User(val mountOptions: MountOptions)
sealed interface MountOptions {
val a: String
val b: String
}
data class MountOneOptions(
override val a: String,
override val b: String,
val integerData: Int,
) : MountOptions
data class MountTwoOptions(
override val a: String,
override val b: String,
val stringData: String,
) : MountOptions
The benefit here is that there's fewer classes, and the typings are more specific. It's also easy to add or remove an additional mount details, and any exhaustive when statements will cause a compiler error.
fun anotherCaller(user: User) {
println(user.mountOptions.a)
println(user.mountOptions.b)
// use an exhaustive when to determine the actual type
when (user.mountOptions) {
is MountOneOptions -> println(user.mountOptions.integerData)
is MountTwoOptions -> println(user.mountOptions.stringData)
// no need for an 'else' branch
}
}
fun main() {
val mountOne = MountOneOptions("foo", "bar", 111)
anotherCaller(User(mountOne))
val mountTwo = MountTwoOptions("foo", "bar", "mount two")
anotherCaller(User(mountTwo))
}
Output:
foo
bar
111
foo
bar
mount two
This is really the "default values" answer provided by Hubert Grzeskowiak adjusted to your example:
data class OneDetails(val c: Int)
data class TwoDetails(val c: String)
data class MountOptions(val a: String, val b: String)
data class User(
val mountOptions: MountOptions,
val detailsOne: OneDetails? = null,
val detailsTwo: TwoDetails? = null
)
fun main() {
fun anotherCaller(user: User) = println(user)
val mt = MountOptions("foo", "bar")
val one = OneDetails(1)
val two = TwoDetails("2")
val switch = "0"
when (switch) {
"0" -> anotherCaller(User(mt))
"1" -> anotherCaller(User(mt, detailsOne = one))
"2" -> anotherCaller(User(mt, detailsTwo = two))
"12" -> anotherCaller(User(mt, detailsOne = one, detailsTwo = two))
else -> throw IllegalArgumentException(switch)
}
}

Kotlin - Data class type String or some Object

Say I have this data class in Kotlin:
#Document(collection = Approval.COLLECTION)
data class Approval(
#Id
val id: String,
val detailId: <UNSURE HERE>
) {
companion object {
const val COLLECTION: String = "approval"
}
}
That detailID can either be a String or an object like so:
data class AIDConfiguration(
val sId: String,
val cId: String
)
However how do go about setting that type - as I can't use something like
val detailId: AIDConfiguration | String
I thought maybe make an interface, but not sure any syntax of getting that to be just a String
interface ParentConfiguration
data class AIDConfiguration(
val sId: String,
val cId: String
): ParentConfiguration
And then
val detailId: ParentConfiguration
Any help appreciated.
Thanks.
One way could be:
val detailId: Any
This is loose cause it will allow any type to be assigned to detailId.
So before usage you would have to check for the type.
fun useConfig(detailId: Any) {
if (detailId is AIDConfiguration) {
//Use detailId.sId and detailId.cId. Compiler smart casts to AIDConfiguration
} else if (detailId is String)
//Use detailId. Compiler will smart cast to String
} else {
//throw some exception here.
}
}
You might want to use some validations when setting the configuration as well. Check whether the type is AIDConfiguration or String.
A little more tighter would be to have a parent configuration class. Such as ParentConfiguration and have AIDConfiguration and StringConfiguration as subclasses.
So then it becomes:
interface ParentConfiguration
data class AIDConfiguration(
val sId: String,
val cId: String
): ParentConfiguration
data class StringConfiguration(
val conf: String
): ParentConfiguration
data class Approval(
val id: String,
val detailId: ParentConfiguration
)
val stringConfigApproval = Approval(id = "Test1", detailId = StringConfiguration("String Conf"))
val aidConfApproval = Approval(id = "Test2", detailId = AIDConfiguration(sId = "SID", cId = "CID"))
Would recommend checking out the kotlin docs.
https://kotlinlang.org/docs/tutorials/kotlin-for-py/inheritance.html
https://kotlinlang.org/docs/reference/typecasts.html
Kotlin being a statically typed language, you can't just specify multiple types. An interface is a good option to do so, but since String is not implemented by you, you cannot change its signature.
One of the work around should be to use Either like this:
sealed class Either<out L, out R> {
data class Left<out L>(val a: L) : Either<L, Nothing>()
data class Right<out R>(val b: R) : Either<Nothing, R>()
/**
* Returns true if this is a Right, false otherwise.
* #see Right
*/
val isRight get() = this is Right<R>
/**
* Returns true if this is a Left, false otherwise.
* #see Left
*/
val isLeft get() = this is Left<L>
/**
* Applies fnL if this is a Left or fnR if this is a Right.
* #see Left
* #see Right
*/
fun fold(fnL: (L) -> Any, fnR: (R) -> Any): Any =
when (this) {
is Left -> fnL(a)
is Right -> fnR(b)
}
}
And then specify your variable like this:
val detailId: Either<AIDConfiguration, String>
And when you want to do some specific operation, just call the fold method like:
detailId.fold({ /* use $it as AIDConfiguration */ }, { /* use $it as String */ })
EDIT: You could also make aliases for your purpose in your projects for better readability check https://kotlinlang.org/docs/reference/type-aliases.html

Why to put val or var in kotlin class constructors

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.

Delegate optional argument resolution in Kotlin

In Kotlin, if I have a function that calls a constructor (or another function) is there a way to delegate the determination of default argument to the underlying constructor/function?
This syntax isn't valid, but hopefully this illustrates what I'm trying to do:
fun buildMyObject(stringParam: String?, intParam: Int?): MyClass {
return MyClass(stringParam, intParam)
}
class MyClass(val stringParam: String = "Hello world!", val intParam: Int = 42)
There's no support for exactly what you want. However, if you have a bit of flexibility about where buildMyObject lives, you can have something like:
class MyClass(val stringParam: String = defaultString, val intParam: Int = defaultInt) {
private constructor(stringParam: String?, intParam: Int?) : this(
stringParam ?: defaultString,
intParam ?: defaultInt
)
companion object {
private const val defaultString = "Hello world!"
private const val defaultInt = 42
fun buildMyObject(stringParam: String?, intParam: Int?): MyClass {
return MyClass(stringParam, intParam)
}
}
}
This has a couple of nice characteristics:
Lets users still call the null-safe MyClass constructor
Doesn't duplicate where the default values are defined
Doesn't grow exponentially in the number of constructors you'd need if you'd try to use a when and call with or without each param (imagine what would happen if you'd add a third parameter!)
Keeps the nullable constructor private, so callers can only use nullables if they go through the factory method

What is the difference between properties and parameters in Kotlin?

Here is a simple example of a class with some code (properties) inside the bracket
class Person(firstName: String) {
....
}
Now here is an example of a function with some code (parameters) inside the bracket
fun double(x: Int) {
...
}
I know this is a fundamental question but I am quite confused as a beginner.
You pass parameters to functions and constructors, and classes have properties.
The constructor of the Person class in your example has a single parameter, and so does the double function. In this case, the firstName parameter is not a property!
To make it a property, you have to declare it so:
class Person(firstName: String) {
val firstName : String = firstName
}
Kotlin allows this to be shorter, which makes the firstName parameter serve as a property:
class Person(val firstName: String)
First, your firstName also is a parameter rather than a property in Person class.
// v-- a parameter declared on primary constructor
class Person(firstName: String)
you can access a parameter declared on primary constructor in init block or property/field declaration, for example:
class Person(firstName: String){
val first:String
init{ first=firstName }
}
class Person(firstName: String){
val first:String = firstName
}
class Person(firstName: String){
#JvmField val first:String = firstName
}
to make the firstName to a property you can using keyword val or var, for example:
// v--- immutable property
class Person(val firstName: String)
// v--- mutable property
class Person(var firstName: String)
a Kotlin property will generate getter/setter(?) and a backing field(?) to java byte code. Take an example of a mutable property to java byte code as below:
public final class Person{
private String firstName; // backing field
// v--- parameter
public Person(String firstName){ this.firstName = firstName; }
//getter
public final String getFirstName(){ return firstName; }
//setter
public final String setFirstName(String firstName){ this.firstName= firstName; }
}
a parameter only visible in function scope/constructor scope except parameter declared on primary constructor.
Note: a parameter is immutable like as java effective-final variables/parameters, so you can't reassign a parameter at all in Kotlin, for example:
fun foo(bar:String){
bar = "baz"
// ^--- compile error
}
properties and parameters are different thinks:
parameters : when we declare any function :
fun sum(a: Int, b: Int): Int {
return a + b
}
Function having two Int parameters with Int return type:
Properties and Fields:
Declaring Properties
Classes in Kotlin can have properties. These can be declared as mutable, using the var keyword or read-only using the val keyword.
class Address {
var name: String = ...
var street: String = ...
var city: String = ...
var state: String? = ...
var zip: String = ...
}
To use a property, we simply refer to it by name, as if it were a field in Java:
fun copyAddress(address: Address): Address {
val result = Address() // there's no 'new' keyword in Kotlin
result.name = address.name // accessors are called
result.street = address.street
// ...
return result
}
The full syntax for declaring a property is:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]