Why this code can be compiled and executed without erros?
val map = HashMap<Int, Long>()
val key :Int? = null
map.remove(key)
In MutableMap remove declared as accepting only non nullable key, so it shouldn't even compile. Is it a Kotlin type inference bug or am I missing something?
public fun remove(key: K): V?
Your code is perfectly fine as remove() allows nullable arguments - your map contents definition got nothing to it. When remove() is invoked, it would try to find matching requested key in the map and as it's not there (it's completely irrelevant why it's not there - it's valid case for key to be not present) nothing will happen. Where compiler will complain is on any attempt to put such key into your map. Then map definition kicks in and since it's known that nullable keys not allowed, such code won't even compile as this is clearly buggy code.
In this case, map.remove(key) doesn't not calls
public fun remove(key: K): V?
It calls an extension remove function:
public inline fun <#OnlyInputTypes K, V> MutableMap<out K, V>.remove(key: K): V? =
#Suppress("UNCHECKED_CAST") (this as MutableMap<K, V>).remove(key)
This function documentation says that it allows to overcome type-safety restriction of remove that requires to pass a key of type K.
It allows overcoming type-safety restriction because the key of the entry you are removing does not have to be the same type as the object that you pass into remove(key); the specification of the method only requires that they be equal. This follows from how the equals() method takes in an Any as a parameter, not just the same type as the object.
Although it may be commonly true that many classes have equals() defined so that its objects can only be equal to objects of its own class, there are many places where this is not the case. For example, the specification for List.equals() says that two List objects are equal if they are both Lists and have the same contents, even if they are different implementations of List. So, for example, according to the specification of the method, it is possible to have a MutableMap<ArrayList<Something>, Something> and call remove(key) with a LinkedList as an argument, and it should retrieve the key which is a list with the same contents. This would not be possible if this extension remove(key) didn't exist.[1]
Kotlin could warn or refuse to compile (would be good), but it doesn't (for now).
The reason for it being not as bad as it looks from a first glance is that you cannot put an Int? into a MutableMap<Int, Long> because
val map = HashMap<Int, Long>()
val key :Int? = null
map.put(key, 1) // <--- WON'T COMPILE [Type mismatch: inferred type was Int? but Int was expected]
map.remove(key)
Nevertheless, I think you are right by wondering about that method being compiled.
Eventually asking this question helped to find another question with explanation. In short, what actually happens is call of the extension function which have it's own type inference.
Related
The Map interface in Kotlin (using V1.6.21) has a signature of
interface Map<K, out V>
Why is K invariant instead of covariant (out K)?
The documentation of type parameter K says:
The map is invariant in its key type, as it can accept key as a parameter (of containsKey for example) and return it in keys set.
However, interface Set is covariant in the element type, so the the last part ("return it in keys set") is not applicable, at least not immediately.
Further, the type parameter K is used only at occurrences where the map state is not modified, for lookup purposes (methods containsKey, get, getOrDefault). At these places, isn't it safe to use #UnsafeVariance? After all, that same technique was employed to Map's value type parameter V, for example in containsValue, to allow making V covariant.
My guess would be that using a Map<KSubtype, V> as a Map<KSupertype, V> (where KSubtype : KSupertype) does not really make a lot of sense because the former, by construction, cannot contain entries with keys other than KSubtype.
So a proper implementation should return null from all calls to get(kSupertype) as well as return false from those to containsKey(kSupertype).
In the case of Set<out E> it's only the contains function that needs unsafe variance, and Map would also require unsafe variance on get. This might have been too much of a peculiarity to support, compared to the value of supporting the use case.
Why does Kotlin in one case infer type returned from Java to be nullable and in another case it is can be either, nullable or non-nullable?
I've checked both HashMap.get and JsonNode.get and I could not identify any #NotNull-like annotations neither in calsses nor anywhere in inheritance chain. What makes Kotlin treating those 2 calls differently?
I have read documentation https://kotlinlang.org/docs/java-interop.html#null-safety-and-platform-types but it explanation use "Platform Types" without explaining what those are and it does not explain differences in behavior anyway.
import com.fasterxml.jackson.databind.JsonNode
private fun docType(node: JsonNode, map: java.util.HashMap<String,String>) {
val x: JsonNode = node.get("doc_type") // DOES compile and can throw NPE at runtime
val y: JsonNode? = node.get("doc_type") // DOES compile and Kotlin's type system will force you to check for null
val z: String = map.get("a") // ERROR: Type mismatch: inferred type is String? but String was expected
}
Kotlin provides seamless interoperability with Java, without compromising its own null-safety... almost. One exception is that Kotlin assumes that all types that are defined in Java are not-null.
To understand, let's look at JsonNode.get()
Platform types
public JsonNode get(String fieldName) { return null; }
Note that JsonNode is defined in Java, and is a therefore 'platform type' - and Kotlin does not 'translate' it to JsonNode?, even though that would be technically correct (because in Java all types are nullable).
When calling Java from Kotlin, for convenience it's assumed that the platform type is non-nullable. If this wasn't the case, you would always have to check that any instance of any platform type is not null.
So, to answer your question about what a 'platform type' is, it's a term that means
some type that is defined in an external target language,
you can't mention it explicitly in Kotlin code (but there's probably a synonymous Kotlin equivalent),
and we're going to assume that it's non-nullable for convenience.
Also the notation is <type>!, for example String! - which we can take to mean String or String?
Nullability annotations
The closest Java equivalent of Kotlin's nullable ? symbol are nullability annotations, which the Kotlin compiler can parse and take into account. However, none are used on JsonNode methods. And so Kotlin will quite happily assume that node.get("") will return JsonNode, not JsonNode?.
As you noted, there are none defined for HashMap.get(...).
So how does Kotlin know that map.get("a") returns a nullable type?
Type inference
Type inference can't help. The (Java) method signature
public V get(Object key) {
//...
}
indicates that a HashMap<String, String> should return String, not String?. Something else must be going on...
Mapped types
For most Java types, Kotlin will just use the definition as provided. But for some, Kotlin decides to treat them specially, and completely replace the Java definition with its own version.
You can see the list of mapped types in the docs. And while HashMap isn't in there, Map is. And so, when we're writing Kotlin code, HashMap doesn't inherit from java.util.Map - because it's mapped to kotlin.collections.Map
Aside: in fact if you try and use java.util.Map you'll get a warning
So if we look at the code for the get function that kotlin.collections.Map defines, we can see that it returns a nullable value type
/**
* Returns the value corresponding to the given [key], or `null` if such a key is not present in the map.
*/
public operator fun get(key: K): V?
And so the Kotlin compiler can look at HashMap.get(...) and deduce that, because it's implementing kotlin.collections.Map.get(...), the returned value must be a nullable value, which in our case is String?.
Workaround: External annotations
For whatever reason, Jackson doesn't use the nullability annotations that would solve this problem. Fortunately IntelliJ provides a workaround that, while not as strict, will provide helpful warnings: external annotations.
Once I follow the instructions...
Alt+Enter → 'Annotate method...'
Select 'Nullable' annotation
Save annotations.xml
Now node.get("") will show an warning.
This annotation isn't visible to the Kotlin compiler, so it can only be a warning - not a compilation error.
java.util.HashMap.get implements the interface method java.util.Map.get. Kotlin maps some Java types to its own types internally. The full table of these mappings is available on the website. In our particular case, we see that java.util.Map gets mapped internally to kotlin.collections.Map, whose get function looks like
abstract operator fun get(key: K): V?
So as far as Kotlin is concerned, java.util.Map is just a funny name for kotlin.collections.Map, and all of the methods on java.util.Map actually have the signatures of the corresponding ones from kotlin.collections.Map (which are basically the same except with correct null annotations).
So while the first two node.get calls are Java calls and return platform types, the third one (as far as Kotlin is concerned) is actually calling a method Kotlin understands: namely, get from its own Map type. And that type has an explicit nullability annotation already available, so Kotlin can confidently say that that value can be null and needs to be checked.
I am going to use the following method from Spring Data Kotlin extensions:
inline fun <reified T : Any> MongoOperations.bulkOps(bulkMode: BulkMode, collectionName: String? = null): BulkOperations
The question is: can I somehow avoid specifying T assuming I do not want to provide entity class name (that's because I will explicitly specify collectionName, and in this case class type can be null). I would like to type something like:
val ops = mongoTemplate.bulkOps<null>(BulkOperations.BulkMode.UNORDERED, collectionName = "i_know_better")
Is there a type literal for null with which I can parameterize bulkOps?
I think the short answer is no.
You seem to confuse types with values. null is a value and not a type so it cannot be used as a type in generic methods.
In your specific example, even if you could use null, looking at the code what would you expect to happen?
#Suppress("EXTENSION_SHADOWED_BY_MEMBER")
inline fun <reified T : Any> MongoOperations.bulkOps(bulkMode: BulkMode, collectionName: String? = null): BulkOperations =
if (collectionName != null) bulkOps(bulkMode, T::class.java, collectionName)
else bulkOps(bulkMode, T::class.java)
As you can see there's always T::class.java being called. What would be the result of null::class.java?
I'm unfamiliar with the Spring Data so I can't really provide an alternative, but I'd say you either need to search for another method or use an appropriate class here. The generic type is marked as T : Any so presumably it can be any non-nullable type. I wonder if Unit would work. Again, I'm not sure what this class is used for.
To answer the question in general, you can use Nothing? to represent the type that only contains the value null.
That being said, as #Fred already said, the method you're considering here explicitly states T : Any, meaning only non-nullable types are allowed. And it makes sense given that the function is accessing the class of T.
I'm coming to kotlin after working in mostly dynamically typed languages for years, so I get a lot of what I'm seeing, but I'm still tripping up a bit over reading some of the type annotations.
Most of them make sense (I've written some C++ and typescript so I'm not wholey familiar with more strictly type languages). so stuff like annotating the parameters and return types for functions, variable declaration, stuff like that makes sense.
What I'm having trouble with is the more complex annotations like looking at this explanation of the fold method when talking about higher order functions:
fun <T, R> Collection<T>.fold(
initial: R,
combine: (acc: R, nextElement: T) -> R
): R {
var accumulator: R = initial
for (element: T in this) {
accumulator = combine(accumulator, element)
}
return accumulator
}
I get that:
the Collection refers to an arbitrary collection with elements that are of type T
the fold method call takes an value of type R named initial as the first argument and a callable function labeled combine as the second argument
the callable function will be called for each element of the collection with an accumulator of type R labeled acc and the next element of the collection of type T (since it's a collection of Ts) labeled nextElement
The callable function will return a type R in the end
The fold method will return a type R in the end
And I can use it like this:
val greetings = listOf("hey", "hi", "yo", "what's up")
val personalized = greetings.fold("", { carry, current -> "${carry}\n$current, Chris." })
println(personalized)
That all makes sense, but what does the <T, R> between the fun and the Collection mean? What is that part called? (It's hard to search for an explanation when you don't know what the thing you're looking for is called :P)
And more importantly, is there a section of the documentation that specifically talks about how to read these annotations or what each are called? I've been looking through the docs and searching in general for an explanation of how to read the type annotations and I can't find anything.
It feels like a silly question, but to the uninitiated it's kind of daunting and the docs are written as if you already understand that part of the language.
As Alexey already said, these names between angled brackets after the fun keyword are called "type parameters". They are used to declare generic functions.
the Collection refers to an arbitrary collection with elements that are of type T
Here you can see that Collection and T play different roles: Collection is a well-known defined type that you are referencing, while T is just a name that you arbitrarily choose for the definition of this function.
We want the compiler to check that Collection is a type that is defined and imported, and if you make a typo there will be a compile error.
On the other hand, we don't want that for T and R, so it is necessary to mention them in a special syntactic place so that the compiler knows you're just making up arbitrary names for the sake of the function definition.
It is nice to draw a parallel between the type parameters and the method arguments. The method arguments are also arbitrary names that you define in the signature and use in the function body, as opposed to class members like properties, which you can access without declaring them as arguments.
Just like the values of the arguments are passed when you call a method, and can be different for each different invocation, the "values" of the type parameters are also given at the call site, and can be different for each invocation (they are often inferred, though, so you don't see them).
Note that the "value" of a type parameter is a type (e.g. String), not a value in the usual sense like the string "abc". You can actually specify these types explicitly on the call site if you want:
listOf(1, 2, 3).fold<Int, Int>(42) { acc, e -> acc + e }
The syntax on the call site is similar to the declaration site, it uses <>, except that it's written after the function name.
In general, these types are easily inferred by the compiler using the argument types or the return type in the context of the call site, that's why it's often unnecessary to explicitly specify them.
Difference with generics at the class level
It may seem weird that the methods in the interface List don't need to declare such type parameters, despite the fact that they use generic types:
interface MutableList<T> {
fun add(element: T): Boolean {
//....
}
}
This is because T is already "well-defined" when using it for the method declaration: it was already defined as a type parameter for the List interface itself. The mechanism is the same, but the difference is the scope of the definition: class-level type parameters are defined by the instance of the class (you can create a List<Int> or a List<String>, and this is chosen when you create your instance), while function type parameters are defined by each call to the function.
You can even combine both:
interface List<T> {
fun <R> map(transform: (T) -> R): List<R> {
//...
}
}
Here T will be determined by the list instance on which you call map, but R can be different for each call to map even on the same list instance.
<T, R> are the type parameters. Since you are familiar with C++, it's like
template <typename T, typename R>
It just happens to be placed after the fun keyword in Kotlin (and after the type name when declaring a generic class/interface/type alias) instead of before the function definition.
I am running some experiments on Kotlin's reflection.
I am trying to get a reflection object of a generic class with its argument.
In Java, that would be a ParameterizedType.
The way to get such a thing using Java's reflection API is a bit convoluted: create an anonymous subclass of a generic class, then get its super-type first parameter.
Here's an example:
#Suppress("unused") #PublishedApi
internal abstract class TypeReference<T> {}
inline fun <reified T> jGeneric() =
((object : TypeReference<T>() {}).javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0]
When I println(jGeneric<List<String?>>()), it prints java.util.List<? extends java.lang.String>, which is logical considering that Kotlin's List uses declaration-site out variance and that Java types have no notion of nullability.
Now, I would like to achieve the same kind of result, but with the Kotlin reflection API (that would, of course, contain nullability information).
Of course, List<String>::class cannot work since it yields a KClass. and I am looking for a KType.
However, when I try this:
inline fun <reified T> kGeneric() =
(object : TypeReference<T>() {})::class.supertypes[0].arguments[0].type
When I println(kGeneric<List<String?>>()), it prints [ERROR : Unknown type parameter 0], which is quite... well, anticlimactic ;)
How can I get, in Kotlin, a KType reflecting List<String> ?
To create a KType instance in Kotlin 1.1, you have two options:
To create a simple non-nullable type out of a KClass, where the class is either not generic or you can substitute all its type parameters with star projections (*), use the starProjectedType property. For example, the following creates a KType representing a non-nullable type String:
val nonNullStringType = String::class.starProjectedType
Or, the following creates a KType representing a non-nullable type List<*>:
val nonNullListOfSmth = List::class.starProjectedType
For more complex cases, use the createType function. It takes the class, type arguments and whether or not the type should be nullable. Type arguments are a list of KTypeProjection which is simply a type + variance (in/out/none). For example, the following code creates a KType instance representing List<String>:
val nonNullStringType = String::class.starProjectedType
val projection = KTypeProjection.invariant(nonNullStringType)
val listOfStrings = listClass.createType(listOf(projection))
Or, the following creates the type List<String>?:
val listOfStrings = listClass.createType(listOf(projection), nullable = true)
Both starProjectedType and createType are defined in package kotlin.reflect.full.
We're planning to introduce the possibility of getting a KType instance simply from a reified type parameter of an inline function which would help in some cases where the needed type is known statically, however currently it's not entirely clear if that's possible without major overhead. So, until that's implemented, please use the declarations explained above.