One of the ways of obtaining a static behavior like in java, as per the examples floating around here.
To me private companion seems to go against the very idea of a companion object. Why is this a thing? Just because it works? Or are there some meaningful reasons why you would choose a private companion?
For example one so called reason that I’ve come across is that you can reference private members directly in your class without a prefix name - opposed to a private object. But in that case the 2nd snippet would suffice
I think it is a matter of personal taste, but the first variant can be useful to organize code. For example, you could put all objects related to caching across instances in a private companion object.
Take an example : we try to mimic a mail server that can give you the mail box of an user. You also want to be able to read some configuration information about the mail server. Now, you can cache any value that the mail server can provide. To “isolate” the caches from core logic, and ease code reading, you could use a private companion object.
data class User(val id : String)
class MailBox(val user : User) {
// TODO: implement
}
class MailServer() {
private companion object Caches {
val cachedMailBoxes : (User) -> MailBox = { TODO("use cache lib") }
val cachedConfs : (ConfigKey) -> String = { TODO("use cache lib") }
}
public enum class ConfigKey {
host, owner, version
}
fun getMailBox(user: User) = runCatching { Caches.cachedMailBoxes(user) }
.getOrNull() ?: loadMailbox(user)
private fun loadMailbox(user: User) : MailBox = TODO("Load Mailbox from somewhere")
fun getConfigValue(key: ConfigKey) = runCatching { Caches.cachedConfs(key) }
.getOrNull() ?: loadConf(key)
private fun loadConf(key: ConfigKey) : String = TODO("Load Configuration from somewhere")
}
Interesting, I was about to post about suggestions to avoid having to use private companion object for validated value type subclasses that use expensive (ie regex based) validators.
So while you’re asking “what are they good for”, I’m asking “this is currently the only sane way to do what I want, and I don’t like it”.
class ValidatedStringWithExpensiveValidatorSpecs {
class AwsStackNameInefficient(unsafe: String) : ValidatedString(
allOf(
length(4, 128),
regex("cf-[a-z0-9-]+".toRegex())
), unsafe
)
class AwsStackNameVerbose(unsafe: String) : ValidatedString(validator, unsafe) {
private companion object {
private val validator = allOf(
length(4, 128),
regex("cf-[a-z0-9-]+".toRegex())
)
}
}
@Test
fun isPossibleButEitherInefficientOrVerbose() {
println(AwsStackNameInefficient("cf-mystack-inefficient"))
println(AwsStackNameVerbose("cf-mystack-verbose"))
}
}
Putting the validator in a private val outside the class will cause the file containing the class to have a .kt extension in the intellij file explorer (i will see ValidatedStringWithExpensiveValidatorSpecs.kt in this case), so it’s not an option for me either.
Isn’t the purpose of a companion object to be accessible outside of the class. And to allow access via class name to public members. So that you don’t have to make an instance of the class?
I think the recommended way is to put private val outside the class and don’t care about .kt in IntelliJ. In Kotlin this is pretty normal and even encouraged to have files with multiple top-level elements. Coding conventions mention this:
Placing multiple declarations (classes, top-level functions or properties) in the same Kotlin source file is encouraged as long as these declarations are closely related to each other semantically, and the file size remains reasonable (not exceeding a few hundred lines).
Also, remember that you don’t have to use companions - you can create normal objects like this:
class Foo {
private object Bar {
val baz = ""
}
}
And contrary to companions, you can have any number of such objects.
Which raises another question for me: why does intellij show it with .kt when there are only two top-level declarations, one of which private, and therefore invisible to the rest of the project? But I could probably live with this solution, which is still the least verbose.
That will save me the typing of the companion keyword, but the solution would still be too verbose for me.
private val validatorCachedAtCompilationUnitLevel = allOf( length(4, 128), regex("cf-[a-z0-9-]+".toRegex()) )
class ValidatedStringWithExpensiveValidatorSpecs {
class AwsStackNameInefficient(unsafe: String) : ValidatedString(
allOf(
length(4, 128),
regex("cf-[a-z0-9-]+".toRegex())
), unsafe
)
class AwsStackNameAffectsFileName(unsafe: String) : ValidatedString(validatorCachedAtCompilationUnitLevel, unsafe)
class AwsStackNameVerboseObject(unsafe: String) : ValidatedString(Validator, unsafe) {
private object Validator : StringValidator {
private val validatorCachedAtTheObjectLevel = allOf(
length(4, 128),
regex("cf-[a-z0-9-]+".toRegex())
)
override fun validate(unsafe: String): String = validatorCachedAtTheObjectLevel.validate(unsafe)
}
}
class AwsStackNameVerboseCompanionObject(unsafe: String) : ValidatedString(validatorCachedAtCompanionObjectLevel, unsafe) {
private companion object {
private val validatorCachedAtCompanionObjectLevel = allOf(
length(4, 128),
regex("cf-[a-z0-9-]+".toRegex())
)
}
}
@Test
fun isPossibleButEitherInefficientOrVerbose() {
println(AwsStackNameInefficient("cf-mystack-inefficient"))
println(AwsStackNameAffectsFileName("cf-mystack-short-but-affects-filename"))
println(AwsStackNameVerboseObject("cf-mystack-verbose-object"))
println(AwsStackNameVerboseCompanionObject("cf-mystack-verbose-companion-object"))
}
}
I was hoping to be able to compute the expensive validator once, and let the compiler know that the object graph that makes up the validator is closed world (it’s a pure function that takes a String input), thread-safe, and shared between all AwsStackName instances, without using object tricks. I don’t expect that is is possible in the current state of the kotlin language. But maybe there is a way to signal to the compiler that something need only be done once in some other way I’m not aware of.
It really sounds to me like your are overcomplicating things. Using object isn’t some kind of a trick - this is exactly the way to store static/cached values. Members in Kotlin are as simple as:
file scope (top-level) - static
class scope - non-static
object scope - static (kind of)
So you can’t really keep static values using class members only.
If I would want to avoid top-level declarations then I would probably do something like this:
class AwsStackName(unsafe: String) : ValidatedString(validator, unsafe)
companion object {
private val validator = ...
}
Or, if I would like to put both of them close to each other, I would create private object and put validator and the class inside it. Your above solution seems confusing to me, but maybe this is just me.
edit: or even better, I would not at all define AwsStackName class, but only a function that creates ValidatedString object. But it really depends on your specific case.
class A {
val myVal = CONST
companion object {
private const val CONST = "const"
}
}
class B {
val myVal = CONST
private companion object {
const val CONST = "const"
}
}
fun A.Companion.iCanExist() = Unit
fun B.Companion.thisIsIllegal() = Unit