Private setter for var in primary constructor


#1

Is there a way to specify private set for a var in the primary constructor?

If not, is there a reason this is not possible? It’s the most common reason in our code why we have to move a member into the body of a method.


#2

No, it’s not possible because we don’t see a good syntax for it. If you have suggestions, feel free to show what you have in mind.


#3

You’re right. I wouldn’t mind having var field: String private set, but pretty is something else.


#4

I suggest something akin to Swift: private(set) var field: String, get on the other hand is public by default


#5

I think that property getters and setters are already bad, no need to get them worse. In my opinion, property accessors is the only ugly thing in kotlin design. The ugliness arises from that syntax is unique and does not appear anywhere else (3 special keywords and a whole new entity).

Assigning property with private setter requires only a few additional words. It does not worth breaking syntax for it.


#6

Normally you define a completely private variable as private var field: String, I don’t see how adding (set) would be that ugly if ugly at all. It’s simple and concise.


#7

I prefer this too (Swift style), both for constructor param specification, as well is in the class body. Currently, to define a private set var with kotlin in one line requires a semicolon.


#8

Data classes must have at least 1 argument in their primary constructor and you can’t make private set on it.

So, on a data class, no matter what you do, you can’t have a var that is internally (only) modifiable and expose just a getter. You automatically expose a setter which I don’t want

I want the java equivalent to:

public class Foo {

    private Long id;
    
    public Long getId() {return id;}
}

I don’t see how


#9

How about this?

data class Foo(private var idField: Long) {
    val id get() = idField
}

#10

Ok, that definitely gets me closer - When I throw an interface in things get tricky

interface Post  {

    val id: Long?

    val date: Date?

    val name: String?
}
data class Report( private var idData: Long? = null) : Post {
    override var id get() = idData
    override var date: Date? = null
    override var name:String? = null
}

Gets me: Error:(10, 5) Kotlin: Property must be initialized for id.


#11

Change the var of id to val, then it works.

Or, if you want to really edit it from the outside:

 override var id
        get() = idData
        set(value) {
              idData = value
        }

If you omit the setter like this:

 override var id = idData
     get() = idData

Then you get an additional backing-field which is set to idData on init. If you read id you will get idData, because of the getter, but if you set it from the outside, you will set the backing-field, which is never read anywere.

For sake of completeness if you want to set the backing-field AND the other field in the constructor you can do this:

 override var id = idData
     get() = idData
     set(value) {
          idData = value
          field = value
     }

#12

By the way, I don’t know what you need, but I wouldn’t use nullable references in an interface.

Are you sure you don’t need something like this:

    interface Post {
        val id: Long
        val date: Date
        val name: String
    }

    data class Report(override val id: Long) : Post {
        lateinit var dbRow: DBRow
        
        override val date 
             get() = dbRow.getDate(id)...
        override val name 
             get() = dbRow.getName(id)...
    }

If you want to set the fields on the Report from the outside, you can also use lateinit

interface Post {
    val id: Long
    val date: Date
    val name: String
}

data class Report(private var idData: Long) : Post {
    override val id get() = idData
    override lateinit var date: Date
    override lateinit var name: String
}

#13

I am trying to make an object compatible with hibernate. Hibernate is dirty and does member reflection and private member injection. This was of course always dirty (java 9 warns on this) and there is an open ticket with them to allow constructor hydration of objects.

My objective is to port some working Java code to demonstrate Kotlins capabilities as part of a push to modernize our development.

Now, I realize that hibernate is not ideal for kotlin, and I will push for something like Exposed (which currently doesn’t support hsql).

I am going to continue to mess with what you have supplied - it’s vastly superior to anything I have come up with.

probably best to continue this on the slack channel I am chb0kotlin


#14

In that case I would probably use kotlinx serialization. Write an adapter that takes the DB row, converts to jspn and reads back.


#15

Do you really need data class? Maybe something like this will work?

class Foo(id: Int, name: String) {
    var id: Int = id
        private set

    var name: String = name
        private set

    override fun toString(): String = "Foo(id=$id, name='$name')"
}

#16

Maybe instead of having an option for a custom getter/setter in the primary constructor it could be possible to annotate properties so they are treated as if they are part of the primary constructor. Something like this

data class Foo(id: Int, val name: String) {
    
    @Data
    val id: Int = id
}

In that case data classes would also need to allow parameters which are not directly defined as a property, but I don’t see any good reason why this must be the case.


#17

No, i don’t. But a data class come with equals and hash and copy. Equals and hash are required for what I am doing. I would end up using lombok or rolling my own if I didn’t.

I am starting to feel like Java actually does a better job than kotlin on this


#18

I’m not sure your annotation is the best approach. In terms of syntax it is clear that all this special stuff cannot really be done inside the primary constructor. For normal classes that isn’t really an issue, but for data classes this is. In both cases what you want to do is say that it is a property but it’s specification is deferred. Why not have something like:

data class Foo(deferred id:Int, val name: String) {
  deferred var id: Int
    private set
}

I’m not sure that repeating deferred on the definition as well is needed, but I think it is better as it signals that the value is set from the primary constructor as usual. For data classes the usual rules on data classes apply. Deferred properties obviously also have to have an underlying field.


#19

I like your idea better. That being said I agree with you. It would be enough to have the deferred keyword in the constructor I think. The only problem I see in that case is that you don’t actually see that the property is being set by the constructor. But I guess that only really matters in data classes which are really big and because data classes should not have any “complex” functionality this should not matter.


#20

The idea would apply to all classes, so I would still favour the extra annotation on the actual property but it is 100% a readability issue, the compiler wouldn’t need it.