Focus on the strong and generic parts.
Let's say I have this extension function:
fun <E> Collection<E>.myContains(item: E) : Boolean {
// quite pointless, I know, but a simple example
return item in this
}
the intention is to write a function that only accepts types of the collection elements (E
), but this is not validated by the compiler?!
val isItInside: Boolean = listOf(1, 2).myContains("1")
happily compiles. My guess is that E
is inferred to be Any
.
How can I enforce this restriction within the Kotlin type system/generics?
(Kotlin version 1.3.41)
An exercise to try to write a small assertion framework. A bit more complicated, but tried to get the simplest repro above.
class Asserter<T>(val value: T)
infix fun <T> T.should(block: Asserter<T>.() -> Unit) =
Asserter(this).block()
fun <T : Collection<*>> Asserter<T>.haveSize(size: Int) {
check(this.value.size == size) {
"expecting ${this.value} to be of size $size"
}
}
fun <E, T : Collection<E>> Asserter<T>.contain(item: E) {
check(item in this.value) {
"$item does not exist in $item"
}
}
class ShouldTest {
@Test fun intList() {
listOf(1, 2) should {
haveSize(2)
contain(1)
contain("2") // this shouldn't compile
}
}
@Test fun stringList() {
listOf("1", "2") should {
haveSize(2)
contain(1) // this shouldn't compile
contain("2")
}
}
}
Generics type checks and castsif (something is List<*>) { something. forEach { println(it) } // The items are typed as `Any?` } The same syntax but with the type arguments omitted can be used for casts that do not take type arguments into account: list as ArrayList .
In Kotlin we use a single colon character ( : ) instead of the Java extends keyword to extend a class or implement an interface. We can then create an object of type Programmer and call methods on it—either in its own class or the superclass (base class).
Yes, extention functions can be declared anywhere you want. For functions of general utilities (regarding lists), I whould place them under something like /company/app/util/lists. kt .
This appears to be due to the variance on the Collection
interface's parameter, which is defined as Collection<out E>
.
This means that Collection<Any>
is a supertype of Collection<E>
, and so (apparently) the Collection<Any>.myContains()
extension can be called on a Collection<Int>
.
You can confirm this by replacing it with the invariant MutableCollection
(and also the listOf()
with mutableListOf()
); you then get the compile-time ‘Type mismatch’ error as expected.
This surprised me, though. I guess the compiler must infer E
using both the receiver type and the parameter type. (Can anyone confirm this?) And, as you point out, it has the annoying effect of preventing stricter type-safety.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With