The Programmers Guide To Kotlin - Covariance & Contravariance |
Written by Mike James | |||||||
Monday, 21 January 2019 | |||||||
Page 3 of 3
Type ProjectionsType projections solve the problem of changing the behavior of a generic class after it has been declared. It also allows you to take a generic entity and while it might not use the type parameter as a pure input or output you can promise to use it as strictly input or output. For example, consider the array. This is by default invariant and you cannot sensibly redefine its declaration to make it contravariant or covariant because it makes little sense to have array elements that are exclusively read- or write-only. Instead you can use in and out in type modifiers to mark types that will only act as consumers or producers and hence define the type’s variance. For example: var a:Array<Any> var b = Array<Int>(10,{0}) a=b produces a compile-time error because Array is invariant. You can't change the fact that arrays are invariant, but you can define the type of a so that it is a consumer: var a:Array<out Any> var b = Array<Int>(10,{0}) a=b The out modifier says that a will only be used to access array elements and this is safe because the underlying elements are ints which, as a derived type of Any, have all the methods and properties required. What is important here is to realize that this projection is mostly a trick to allow the upcast to pass the compiler's type checking. The elements of the array referenced by a are still ints. What stops you from assigning to a[i] even though you have promised you won’t? The simple answer is that a[i] is regarded as an Int so assigning, say, a string to it will fail because the types are incompatible. You can try the same trick with a covariant in: var a:Array<in Int> var b = Array<Any>(10,{0}) a=b without the in this too generates a compile-time error. With the in it compiles and you can store values of type Any in elements of a. The compiler allows the assignment but in this case there is nothing to stop you from using a as a source or a consumer of values. That is: a[1]="abcd" println(a[1]) works and if you expect a[1] to be an Int just because a is Array<in Int> you are going to be disappointed. This type projection doesn't work as well as it could. If you use a projection as a function parameter, then the compiler will also check that you are playing by the rules. For example: fun myFunc(myparam:MyClass<out Any>){ println(myparam.read()) myparam.write(10) } In this case the compiler will complain about the use of write which attempts to change the value. Similarly if you define the function as: fun myFunc(myparam:MyClass<in Int>){ println(myparam.read()+1) myparam.write(10) } then the compiler will complain about the + operator as we cannot guarantee that the value is numeric. The in and out modifiers used as type projections simply allow the up and downcasting of the type and tell you that it is safe to set or get elements. The * ProjectionFinal version in book Summary
This article is an extract from: Programmer's Guide To Kotlin Third Edition
You can buy it from: Amazon Contents
<ASIN:B0D8H4N8SK> To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.
Comments
or email your comment to: comments@i-programmer.info <ASIN:1871962536> <ASIN:1871962544> |
|||||||
Last Updated ( Monday, 21 January 2019 ) |