Kotlin Contravariance (in) Tutorial

beginner
16 min

Kotlin Contravariance (in) Tutorial

Welcome back to CodeYourCraft! Today, we're diving into the fascinating world of Kotlin and learning about Contravariance (in). This concept is an essential part of type theory that will help you write more flexible and efficient code. Let's get started! 🎯

Understanding Contravariance (in)

Contravariance is a principle in Kotlin that allows us to use supertype references where subtype objects are passed as arguments. In simpler terms, it means we can pass a subtype where a supertype is expected in function arguments. 💡 Pro Tip: Contravariance is denoted by the in keyword in Kotlin.

Real-world Example

Let's illustrate this with a practical example. Suppose we have a Printable interface and two classes Book and Newspaper that implement it. Now, we have a printAll function that takes an array of Printable objects.

kotlin
interface Printable { fun print() } class Book(val title: String) : Printable { override fun print() { println("Printing Book: $title") } } class Newspaper(val headline: String) : Printable { override fun print() { println("Printing Newspaper: $headline") } } fun printAll(printables: Array<Printable>) { for (printable in printables) { printable.print() } }

Now, let's say we want to create a new function printBooksAndNewspapers that takes an array of both Book and Newspaper objects. Since our printAll function accepts an array of Printable, we can use it to achieve this.

kotlin
fun printBooksAndNewspapers(booksAndNewspapers: Array<out Printable>) { printAll(booksAndNewapapers) }

In the above code, we used the out keyword to indicate that the Array<out Printable> can contain subtypes of Printable. Now, we can call the printBooksAndNewspapers function with an array containing both Book and Newspaper objects.

kotlin
fun main() { val books = arrayOf(Book("Learning Kotlin"), Book("Advanced Kotlin")) val newspapers = arrayOf(Newspaper("Tech News"), Newspaper("Sports News")) printBooksAndNewspapers(books + newspapers) }

Contravariance (in) with Functions

Contravariance can also be applied to function types. In this case, we can use a subtype where a supertype is expected as the return type of a function.

kotlin
interface Printer { fun print(): String } class BookPrinter(val printer: Printer) : Printer { override fun print(): String { val bookPrintOutput = printer.print() + " (Book)" return bookPrintOutput } } class NewspaperPrinter(val printer: Printer) : Printer { override fun print(): String { val newspaperPrintOutput = printer.print() + " (Newspaper)" return newspaperPrintOutput } } fun printPrinter(printer: Printer): String { return printer.print() }

Now, we can use our BookPrinter and NewspaperPrinter classes to create new printers that extend the base Printer class. Since our printPrinter function returns a Printer, we can use it to create a function printBookAndNewspaper that takes a function returning a Printer.

kotlin
fun printBookAndNewspaper(printFunction: (Printer) -> Printer): String { val bookPrinter = BookPrinter(printFunction(NewspaperPrinter(PrintingText("Default Printer")))) val newspaperPrinter = NewspaperPrinter(printFunction(BookPrinter(printFunction(NewspaperPrinter(PrintingText("Default Printer")))))) return printPrinter(bookPrinter) + "\n" + printPrinter(newspaperPrinter) } fun main() { val result = printBookAndNewspaper { it } println(result) }

In the above example, we used the (Printer) -> Printer function type to indicate that printBookAndNewspaper expects a function taking a Printer and returning a Printer.

Quiz

Quick Quiz
Question 1 of 1

What does the `out` keyword indicate in Kotlin?

That's all for today! Contravariance (in) is a powerful concept that will help you write more flexible and reusable code in Kotlin. In the next lesson, we'll explore the concept of Covariance (out) and how it differs from Contravariance. Stay tuned! 📝 Note: If you have any questions or need clarification, feel free to ask in the comments section below. Happy coding! ✅