DEV Community

Magda Miu
Magda Miu

Posted on • Originally published at Medium on

Generics in Kotlin

Generics means we use a class or an implementation in a very generic manner. For example, the interface List allows us for code reuse. We are able to create a list of Strings, of integer values and we will have the same operations even if we have different types. So the list wraps a common functionality for each implementation.

Kotlin allows you to use parameters for methods and attributes, composing what is known as parameterized classes.

1️⃣Type vs Class vs Subtype

  • Type describes the properties that a set of objects may share.
  • Class is just an implementation of that type.
  • A subtype must accept at least the same range of types as its supertype declares.
  • A subtype must return at most the same range of types as its supertype declares.

2️⃣Variance

Variance refers to how subtyping between more complex types relates to subtyping between their components.

Conventions: E = element | T = type | K = key | V = value

👩🏻‍💻Code sample

data class Course(val name: String)
class OddList<T>(val list: List<T>) {
fun oddItems(): List<T> {
return list.filterIndexed { index, _ -> index % 2 == 1 }
}
}
fun main() {
val listOfStrings = listOf("Kotlin", "Java", "C#")
val resultOfStrings: OddList<String> = OddList(listOfStrings)
println(resultOfStrings.oddItems())
val listOfInts = listOf(1, 7, 8, 9, 12, 45)
val resultOfInts = OddList(listOfInts)
println(resultOfInts.oddItems())
val courses = listOf(
Course("Kotlin"),
Course("Java"),
Course("C#"),
Course("PHP"),
Course("C++")
)
var resultCourses = OddList(courses).oddItems()
println(resultCourses)
}

3️⃣Covariance

  • If C is a generic type with type parameter T and U is a subtype of T, then C is a subtype of C
  • Example: List is a subtype of List because Int is a subtype of Number.
  • Applies to types that are “producers”, or a “source” of T
  • T only appears only in “out” position, i.e., the return type of a function

👩🏻‍💻Code sample

class CovarianceSample<T>
fun main() {
val firstSample: CovarianceSample<Any> = CovarianceSample<Int>() // Error: Type mismatch
val secondSample: CovarianceSample<out Any> = CovarianceSample<String>() // OK , String is a subtype of Any
val thirdSample: CovarianceSample<out String> = CovarianceSample<Any>() // Error: Type mismatch
}

4️⃣Contravariance

  • If C is a generic type with type parameter T and U is a subtype of T, then C is a subtype of C
  • U subtype of T ⇒ C subtype of C
  • Example: Function1 is a subtype of Function1 because Int is a subtype of Number.
  • Applies to types that are “consumers” of T
  • T only appears only in “in” position, i.e., the type of a function argument

👩🏻‍💻Code sample

open class Vehicle
class Bicycle : Vehicle()
class Container<in T>
fun main() {
var containerBicycle: Container<Bicycle> = Container<Vehicle>() // OK
var containerVehicle: Container<Vehicle> = Container<Bicycle>() // Error: Type mismatch
}

5️⃣Invariance

  • If C is a subtype of C, then T = U
  • Example: Array is invariant in T
  • T appears in both “in position” and “out position”
  • Type is both a producer and consumer of T
  • To remember: Lambdas are contra-variant in their argument types and covariant in their return type

6️⃣Type projections

  • A type projection is a type that has been limited in certain ways in order to gain variance characteristics using use-site variance.

👩🏻‍💻Code sample

class KotlinConsumer<in T> {
fun toString(value: T): String {
return value.toString()
}
}
fun main() {
val inValue: KotlinConsumer<Number> = KotlinConsumer()
val copyOfInValue: KotlinConsumer<Int> = inValue
if (copyOfInValue is KotlinConsumer<Int>) {
println(copyOfInValue.toString())
}
}
class KotlinProducer<out T>(val value: T) {
init {
println(value)
}
}
fun main() {
val outValue = KotlinProducer("Just a string")
val copyOfOutValue: KotlinProducer<Any> = outValue
println(copyOfOutValue.value.toString())
}

7️⃣Star projection

  • They come in handy when we know nothing about the type argument, but need to use them in a safe way.
  • The safe way here is to define such a projection of the generic type, that every concrete instantiation of that generic type would be a subtype of that projection.
  • Instead of using C or C, you can just use C<*>. It produces the same effective interface as the other two approaches:
  • So then, we have three ways that we can accept any kind of a generic:
  • in-projection — 
  • out-projection — 
  • star-projection — <*>

👩🏻‍💻Code sample

val languages = arrayOf("Kotlin", "Java", "Generics", 1)
printArray(languages)

8️⃣Type erasure and reified type parameters

  • Java has limits on what types are considered reifiable — meaning they’re “completely available at run time” (see the Java SE specs on reifiable types)
  • The type safety checks that Kotlin performs for generic declaration usages are only done at compile time. At runtime, the instances of generic types do not hold any information about their actual type arguments.
  • Enforcing type constraints only at compile time and discarding the element type information at runtime.
  • The type of information is said to be erased.
  • In the case of reified type parameters in Kotlin, we can compare types and get Class objects.

Reified types parameters:

  • Work only with functions (or extension properties that have a get() function)
  • Work with those functions that are declared to be inline

Advantages of using reified types parameters:

  • type checks with is
  • casts without unchecked cast warnings
  • assign class objects by appending:class.java to the parameter name. For example: val a = T::class.java

👩🏻‍💻Code sample

// single param
inline fun <reified T> Any.isInstanceOf(): Boolean = this is T
fun main() {
val isStringAString = "String".isInstanceOf<String>()
val isIntAString = 1.isInstanceOf<String>()
}
// multiple params
inline fun <reified T, reified U> haveSameType(first: T, second: U) =
first is U && second is T
// extension properties, but the type parameter is used as the receiver type
inline val <reified T> T.theClass
get() = T::class.java

📚 Learn more

Enjoy and feel free to leave a comment if something is not clear or if you have questions. And if you like it please share it!

Thank you for reading! 🙌🙏😍✌

Follow me on:

https://twitter.com/MagdaMiu

Originally published at http://magdamiu.com on June 21, 2020.

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Immerse yourself in a wealth of knowledge with this piece, supported by the inclusive DEV Community—every developer, no matter where they are in their journey, is invited to contribute to our collective wisdom.

A simple “thank you” goes a long way—express your gratitude below in the comments!

Gathering insights enriches our journey on DEV and fortifies our community ties. Did you find this article valuable? Taking a moment to thank the author can have a significant impact.

Okay