The articles
Kotlin Jetpack: The Beginning
00. The Kotlin Pit Guide for Java Developers
01. Start with a Demo of worshiping a God
02. What is it like for Kotlin to write Gradle scripts?
03. Kotlin programming’s Triple Realm
04. Kotlin higher-order Functions
preface
This is a general introductory course that all liberal arts students can read. (My girlfriend could read it.)
This article introduces Kotlin generics and their immutability in the form of a story, declaring and using generic variations, and finally, with a practical session, applying generics to our Demo.
preparation
- Update the Android Studio version to the latest
- Clone our Demo project locally and open it with Android Studio:
Github.com/chaxiu/Kotl…
- Switch to branch:
chapter_05_generics
- I strongly suggest that you follow this article to practice, the actual combat is the essence of this article
The body of the
1. Remote Control Story: Generics
Girlfriend: I really want a universal remote control.
Me: How about I teach you to implement one using Kotlin’s generics?
Girlfriend: Cut, trying to trick me into learning Kotlin again. [eyes]
Me: It’s really easy. I promise you will.
A generic class 1-1
Me: This is a universal remote control with a generic parameter
// Class generic parameters (parameters)
/ / left
class Controller<T>() {
fun turnOn(obj: T){... }fun turnOff(obj: T){... }}Copy the code
Me: It’s also easy to use. If you want to control something, you just pass in the corresponding generics, just like selecting a pattern:
// TV as a generic argument
/ / left
val tvController: Controller<TV> = Controller<TV>()
val tv = TV()
// Control the TV
tvController.turnOn(tv)
tvController.turnOff(tv)
// Electric fan as a generic argument
/ / left
val fanController: Controller<Fan> = Controller<Fan>()
val fan = Fan()
// Control electric fan
fanController.turnOn(fan)
fanController.turnOff(fan)
Copy the code
With The help of Kotlin’s top-level function, the Controller class can even be dispensing with generic functions:
1-2 Generic functions
// The generic argument to the function
/ / left left
fun <T> turnOn(obj: T){... }fun <T> turnOff(obj: T){... }Copy the code
Generic functions are also simple to use:
// Control the TV
val tv = TV()
turnOn<TV>(tv)
turnOff<TV>(tv)
// Control the fan
val fan = Fan()
turnOn<Fan>(fan)
turnOff<Fan>(fan)
Copy the code
Girlfriend: I know how to use it! Isn’t it?
val boyFriend = BoyFriend()
turnOff<BoyFriend>(boyFriend)
Copy the code
I:…
2. The Hiring story: Invariant
Girlfriend: I want to recruit some college students to do part-time jobs. Could you recommend some universities?
Me: Ok, but I’m going to recommend it to you via Kotlin generics.
Girlfriend: Uh… What you said about generics is pretty simple, what’s new this time?
Me: You’ll see.
Me: A little preparation:
// open class Student() // FemaleStudent: Student() // University<T>(val name: Fun get(): T {... } fun put(student: T){ ... }}Copy the code
Me: Your hiring needs can be described with code like this:
// Notice here
// Girlfriend needs a college (variable declaration) ↓
lateinit var university: University<Student>
// Notice here
// I randomly recommend a college ↓
university = University<Student>("A university")
val student: Student = university.get(a)/ / recruitment
Copy the code
Girlfriend: Turns out Kotlin wasn’t that hard…
Girlfriend: Can I assign a “women’s university”?
Me: No, will report an error.
// Notice here
/ / left
lateinit var university: University<Student>
// This is the cause of the error
/ / left
university = University<FemaleStudent>("Women's University")
val student: Student = university.get(a)// Compiler error!!
/* Type mismatch. Required: University
Found: University
*/
Copy the code
Girlfriend: What the hell…
Me: There is no relationship between University<Student> and University<FemaleStudent>, although Student and FemaleStudent are parent and child. This is called the invariance of generics.
Girlfriend: That doesn’t make sense! Are women’s universities recruiting students not students?
Me: Hiring is logical, of course, but don’t forget the University also has a put method.
Me: How do you prevent someone from putting a male student in a women’s college?
Me: Let’s see what happens if we can use a “women’s university” as a “normal university” :
// The declared type is: general university, however, the actual type is: women's university.
/ / left left
var university: University<Student> = University<FemaleStudent>("Women's University")
val maleStudent: Student = Student()
// Male students are admitted to women's universities! Is not reasonable.
university.put(maleStudent)
Copy the code
Girlfriend: I see, so this is the reason for generic invariance, it really saves a lot of trouble.
// By default, the compiler only allows this
// Declare the generic parameters to be the same as the actual onesLeft leftvarNormalUniversity: University<Student> = University<Student> ↓ ↓var wUniversity: University<FemaleStudent> = University<FemaleStudent>
Copy the code
3. Nail the hiring: Covariant
Girlfriend: If I delete the put method in the University class, can I use the “women’s University” assignment? So you don’t have to worry about putting men in women’s universities.
Me: That’s not enough, we also need to add a keyword out to tell the compiler that we only fetch from the University class, not from it. At this point, University
can be treated as a subclass of University
.
Me: This is called covariant generics.
Open class Student() class FemaleStudent: Student() // ↓ Class University<out T>(val name: String) {fun get(): T { ... }}Copy the code
Girl friend: I try, sure enough good!
// No more errors
var university: University<Student> = University<FemaleStudent>("Women's University")
val student: Student = university.get(a)Copy the code
Me: It’s a waste of you not writing code.
4. Fill in the story: Generic Contravariant
Girlfriend: my younger sister just finished the college entrance examination, immediately want to fill a volunteer, you recommend a university.
Me: We just saw the generic covariant, why don’t you try to solve this problem yourself? Professor: Well, there’s a “put” method at University.
Girlfriend: I’ll try it like a gourd… Enroll my sister in an all-women’s college.
open class Student() class FemaleStudent: Student() class University<T>(val name: String) { fun get(): T { ... Fun put(student: T){... } } val sister: FemaleStudent = FemaleStudent() val university: University<FemaleStudent> = University<FemaleStudent>(" FemaleStudent ") university.put(sister)// fill in female UniversityCopy the code
Girlfriend: Perfect!
Me: Cool.
Girlfriend: Can you sign up for an ordinary comprehensive university?
Me: No, did you forget generic immutability?
val sister: FemaleStudent = FemaleStudent()
// Error cause: the declaration type is: women's university the assignment type is: normal university
/ / left left
val university: University<FemaleStudent> = University<Student>("Ordinary university")
university.put(sister)
/ / an error
/* Type mismatch. Required: University
Found: University
*/
Copy the code
Girl friend: my younger sister can sign up for female university, incredibly can’t sign up for common comprehensive university? That doesn’t make sense!
Me: Don’t forget University also have a get method? Ordinary comprehensive university get out of the students are not always female.
Girlfriend: Oh. Why don’t I delete the get method and add a keyword?
I: that’s right. Delete the get method and add a keyword: in. What it does is tell the compiler that we’re only going to put it in the University class, not take it out. At this point, University
can be considered a subclass of University
.
Me: This is actually called the inverse of generics, their inheritance is reversed.
// ↓ Class University<in T>(val name: String) {fun put(student: T){... }} val sister: FemaleStudent = FemaleStudent University<FemaleStudent> = University<Student>(" 大学") university.put(sister)Copy the code
Girlfriend: Generics are fun.
Me: Covariant and contravariant mentioned above. They are all implemented by modifying the generic declaration of the University class, so they are collectively referred to as: declarative type-variant, a concept that Kotlin only had, not found in Java.
5. Use-site Variance
Girlfriend: What if the University is provided by a third party and we can’t modify it? Can you do the same without modifying the University class?
I: ok, this is about to use the use of the type changed. They are also divided into: use covariant, use contravariant.
open class Student(a)class FemaleStudent: Student(a)// Assume that University cannot be modified
class University<T>(val name: String) {
fun get(a): T { ... }
fun put(student: T){... }}Copy the code
5-1 use of covariance
Me: Add the out keyword in front of the generic argument to indicate that we only take out of the University, not put in. In doing so, you achieve the use of covariance.
/ / look here
/ / left
fun useSiteCovariant(university: University<out Student>) {
val femaleStudent: Student? = university.get(a)// error: Require Nothing? found Student?
// university.put(femaleStudent)
}
Copy the code
Girlfriend: That’s easy to understand. What about using the inverse? Add in?
5-2 use place inverter
I: that’s right. Add the in keyword to the argument of the generic type to indicate that we only put in from University, not out. In doing so, the inverse of the use is realized.
/ / look here
/ / left
fun useSiteContravariant(universityIn: University<in FemaleStudent>) {
universityIn.put(FemaleStudent())
// error: Require FemaleStudent? found Any?
// val femaleStudent: FemaleStudent? = universityIn.get()
}
Copy the code
Girlfriend: The idea is the same.
Girlfriend: If you’re recruiting students from a University, you’re taking them out, and in this case it’s covariant, and you can substitute University
for University
, because the women that you’re taking out of a women’s University, and the women that you’re taking out of a regular University, They are all students.
Girlfriend: FemaleStudent = FemaleStudent = FemaleStudent = FemaleStudent = FemaleStudent = FemaleStudent = FemaleStudent = FemaleStudent = FemaleStudent = FemaleStudent = FemaleStudent
Me: You summed it up well. And, by the way, Kotlin’s name, which is awful, is Type Projections.
See this for more details on the code aboveGitHub Commit.
5-3 Kotlin vs. Java
Me: Since you feel no pressure to understand Kotlin generics, I’ll give you another meal to compare the use of Java.
Girlfriend: Uh… What the hell is Java?
I: have no matter, you be to see a joy.
Use at covariant | In-use inverter | |
---|---|---|
Kotlin | University<out Student> | University<in FemaleStudent> |
Java | University<? extends Student> | University<? super FemaleStudent> |
Me: Is it simple and clear?
Girlfriend: Again Kotlin’s easy to understand: “Out” means “get”, “in” means “put”.
Me: That’s right.
Girlfriend: In comparison, Java expressions are really lame. (-_ -)
// Java this hot chicken covariant syntax
/ / left
University<? extends Student> covariant = new University<FemaleStudent>("Women's University");
Student student = covariant.get();
/ / an error
covariant.put(student);
// Java this spicy chicken inverse syntax
/ / left
University<? super FemaleStudent> contravariant = new University<Student>("Ordinary university");
contravariant.put(new FemaleStudent())
/ / an error
Student s = contravariant.get();
Copy the code
See this for more details on the code aboveGitHub Commit.
6. Kotlin generics
Me: Here’s a Demo of Kotlin, why don’t you see where generics can be optimized?
Girlfriend: That’s too much! You let me do Kotlin, and you want me to write code for you?
Girlfriend: You write it and I’ll read it.
Me: er… Listen to the leader.
The 6-1 generic version of the Apply function
Me: This is code from the previous section. The Apply function can actually be simplified by generics, so that all classes can use it.
// substitution substitution substitution
/ / left left left
fun User.apply(block: User. () - >Unit): User{
block()
return this} user? .apply {this: User ->
...
username.text = this.name
website.text = this.blog
image.setOnClickListener { gotoImagePreviewActivity(this)}}Copy the code
Me: The apply function looks like this after using generics instead:
// generics generics
// ↓ ↓ ↓ ↓
fun <T> T.apply(block: T. () - >Unit): T{
block()
return this
}
Copy the code
Girlfriend: Is Kotlin’s official apply function implemented this way?
Me: Almost the same, it just has a contract, you don’t understand it yet.
Girlfriend: Uh… Any other examples?
6-2 Generic version of THE HTML builder
Me: In the last chapter, I implemented a simple type-safe HTML builder with quite a bit of repetitive code.
Girlfriend: We can use generics to eliminate duplicate code, right?
Me: That’s right.
class Body : BaseElement("body") { fun h1(block: () -> String): H1 {val content = block() val H1 = H1(content) this.children += H1 return H1} () -> String): P {val content = block() val P = P(content) this.children += P return P}} BaseElement("head") { fun title(block: () -> String): Title { val content = block() val title = Title(content) this.children += title return title } }Copy the code
Me: Let’s use generics to optimize:
open class BaseElement(var name: String, var content: String = "") : Element {
// Add a common generic method to the parent class
protected fun <T : BaseElement> initString(element: T.init: T. () - >String): T {
val content = element.init()
element.content = content
children.add(element)
return element
}
}
class Body : BaseElement("body") {
fun h1(block: H1. () - >String) = initString(H1("h1"), block)
fun p(block: P. () - >String) = initString(P("p"), block)
}
class Head : BaseElement("head") {
fun title(block: Title. () - >String) = initString(Title(), block)
}
Copy the code
Me: There’s another place with duplicate code:
class HTML : BaseElement("html") {
fun head(block: Head. () - >Unit): Head {
val head = Head()
head.block()
this.children += head
return head
}
/ / write
// Look at the duplicate template code
/ / left
fun body(block: Body. () - >Unit): Body {
val body = Body()
body.block()
this.children += body
return body
}
}
Copy the code
Me: After optimization:
open class BaseElement(var name: String, var content: String = "") : Element {
// Add a common generic method to the parent class
protected fun <T : Element> init(element: T.init: T. () - >Unit): T {
element.init()
children.add(element)
return element
}
}
class HTML : BaseElement("html") {
fun head(block: Head. () - >Unit) = init(Head(), block)
fun body(block: Body. () - >Unit) = init(Body(), block)
}
Copy the code
Girlfriend: Well, much better!
See this for more details on the code aboveGitHub Commit.
7. To summarize
- Due to space limitations, the rest of Kotlin’s generic knowledge will be covered later, and this article is sufficient for the time being as a primer. Generics would take a book to cover, and that’s not the purpose of this article.
- The idea of generics is the same, as is migrating to Java once you understand the Kotlin variant.
- Kotlin’s generics, borrowed from another language (C#), are actually much easier to understand than Java.
- Finished reading the article, go to the code: github.com/chaxiu/Kotl…
- Find this branch:
chapter_05_generics
8. Questions to consider:
What are the advantages and disadvantages of Kotlin’s declaration and use of modification?
All see this, give it a thumbs up!
【Kotlin Jetpack 】