kotlin笔记
kotlin和Java100%兼容,代码结尾不用加分号
字符串内嵌表达式
- ${}语法结构的表达式,支持在字符串的引号内放入变量值,表达式中仅有一个变量的时候可以将两边的括号省略
变量和函数
kotlin拥有类型推导机制,定义变量只有以下两种,推荐优先使用val定义变量,只有不可满足时才改用var
语法形式:val 变量名 : 数据类型 = 值
- var 声明一个非final变量,val name = “武则天” 等价于 val name:String = “武则天”
- val 声明一个final 变量
定义函数关键字是fun
编译时常量
- 只读变量并非绝对只读
编译时常量只能在函数之外定义,因为编译时常量必须在编译时赋值,而函数都是在运行时才调用,函数内的变量也是在运行时赋值,编译时常量要在这些变量赋值前就已存在
- 编译时常量只能是常见的基本数据类型:String、Int、Double、Float、Long、Short、Byte、Char、Boolean
函数特性
- 当一个函数中只有一行代码时,kotlin允许我们不必编写函数体,可以直接将唯一的一行代码写在函数定义尾部,中间用等号连接即可。
fun largerNumber(num1:Int,num2:Int){
return max(num1,num2)
}
使用特性替换成
fun largerNumber(num1:Int,num2:Int) = max(num1,num2)
Unit函数和反引号中的函数名
- 不是所有函数都有返回值,kotlin中没有返回值的函数叫Unit函数,因为void这种解决方案无法解释现代语言的一个重要特征-泛型
- Kotlin可以使用空格和特殊字符对函数命名,不过函数名要用一对反引号括起来。这个主要是为了支持Kotlin和Java互操作,而Kotlin和Java各自却有着不同的保留关键字,不能作为函数名
匿名函数(lambda)与隐式返回
-
定义时不取名字的函数,我们称之为匿名函数,匿名函数通常整体传递给其他函数,或者从其他函数返回,匿名函数也称为lambda,将它的定义称为lambda表达式,它返回的数据称为lambda结果,Lambda就是一小段可以作为参数传递的代码
-
匿名函数也有类型,匿名函数可以当作变量赋值给函数类型变量,就像其他变量一样,匿名函数可以在代码里传递。
-
和具名函数不一样,除了极少数情况外,匿名函数不需要return关键字来返回数据,匿名函数会隐式或自动返回函数体最后一行语句的结果
-
Lambda表达式的语法结构
{参数名1:参数类型,参数名2:参数类型 ->
函数体}
val test:() -> String = {
"hello world"
}
println(test())
- 和具名函数一样,匿名函数可以不带参数,也可以带1-n个任何类型的参数,但是需要带参数时,参数的类型放在匿名函数的类型定义中,而参数名则放在函数定义中
- 定义有且只有一个参数的匿名函数时,可以使用it关键字来表示参数名。
val test1:(Int) ->String = {day ->
"Today is $day"
}
println(test1(25))
//等价于下面这个
val test1:(Int) ->String = {
"Today is $it"
}
val test2:(String,Int) ->String = {name ,age ->
"$name is $age"
}
println(test2("zhuw",24))
//类型推断也支持带参数的匿名参数,但为了帮助编译器更准确地推断变量类型,匿名函数的参数名和类型必须有
//等价于上面的test2
val test2 = {name:String,age:Int ->
"$name is $age"
}
内联函数
-
在JVM上,定义的Lambda表达式被转换成匿名类的实现方式。相当于每定义一个Lambda表达式,都会创建一个新的匿名类实例,这就造成了额外的内存开销。Lambda的内存开销会带来严重的性能问题,为了解决这个问题,kotlin提供了内联函数的功能,可以将使用Lambda表达式带来的运行时开销完全消除。
-
使用内联函数的用法非常简单,只需要在定义高阶函数时加上inline关键字的声明即可
-
内联函数的作用就是:内联函数在编译时会自动的将函数体里面的代码自动的替换到其他位置去
inline fun num1AndNum2(num1:Int,num2:Int,operation:(Int,Int) -> Int):Int{ return operation(num1,num2) }
非局部返回
- 在kotlin中,我们只能对具名或匿名函数使⽤正常的、⾮限定的 return 来退出,因此内联函数所引用的Lambda表达式中是可以使用return关键字来进行函数返回的**(因为内联函数的本质是函数替换)**,而非内联函数只能进行局部返回
noinline和crossinline
-
noinline :如果希望只内联⼀部分传给内联函数的 lambda 表达式参数,那么可以⽤ noinline 修饰符标记不希望内联的函数参数
inline fun inlineTest(inlined: () -> Unit, noinline notInlined: () -> Unit) {}
-
⼀些内联函数可能调⽤传给它们的不是直接来⾃函数体、⽽是来⾃另⼀个执⾏上下⽂的 lambda表达式参数,例如来⾃局部对象或嵌套函数。在这种情况下,该 lambda 表达式中也不允许⾮局部返回,但是可以进行局部返回。为了标识这种情况,该 lambda 表达式参数需要⽤ crossinline 修饰符标记
inline fun runRunnable(crossinline block:() -> Unit){
val runnable = Runnable{
block()
//在这里不能进行非局部返回 return
//但是可以进行局部返回 return@Runnable
}
runnable.run()
}
程序的逻辑控制
if 语句
- kotlin中的if语句可以有返回值,返回值就是if语句每一个条件中最后一行代码的返回值
when语句
-
when语句类似于Java中的switch case 语句,但它比switch更加强大。 并且when语句也是可以有返回值的
-
when 语句允许传入一个任意类型的参数,然后可以在when的结构体中定义一系列的条件
-
格式时:匹配值 -> {执行逻辑} 一行代码时大括号可以省略
fun checkNumber(num:Number){
when(num){
is Int -> println("number is Int")
is Double -> println("number is Double")
else -> println("number not support")
}
}
is关键字相当于Java中的instancef关键字
还有一种不带参数的用法
fun getScore(name:String) = when {
name == "Tom" -> 99
name == "Jack" -> 100
else -> 0
}
循环语句
-
while循环和Java的一模一样 ,java 中的for-i 循环被舍弃了,for-each循环加强改为for-in循环
-
默认情况下,for-in 循环每次执行循环时会在区间范围内递增1,相当于i++,如果想跳过其中的一些元素,可以使用step关键字
fun main(){
//左闭右闭区间
for(i in 0..9){
print(i)
}
println("--------------------------")
//左闭右开区间
for(i in 0 until 10){
print(i)
}
println("--------------------------")
//step 2相当于i+=2
for(i in 0 until 10 step 2){
print(i)
}
println("--------------------------")
//创建一个降序的区间,使用downTo关键字
for(i in 9 downTo 1){
print(i)
}
println("--------------------------")
}
面向对象编程
- kotlin中实例一个类时去掉了new 关键字,之所以这么设计,是因为当你调用了某个类的构造函数时,你的意图只可能是对这个类进行实例化
- 在kotlin中任何一个非抽象类默认都是不可以被继承的,相当于java中给类声明了final关键字
- 想要类可以被继承的话,可以在 class 关键字之前使用open关键字,告诉kotlin编译器,该类是专门为继承而设计的
- kotlin中继承和实现去掉了 extends 和implement 关键字 改为使用**:**(分号)
- kotlin将构造函数分为两种:主构造函数和次构造函数,任何一个类只能有一个主构造函数,但是可以有多个次构造函数。次构造函数也可以用于实例化一个类,不过它是有函数体的
主构造函数
-
特点是没有函数体,直接定义在类名的后面即可,如果想在主构造函数中编写一些逻辑,kotlin提供了一个init结构体
-
根据继承特性的规定,子类的构造函数必须要调用父类的构造函数,但是主构造函数没有函数体,因此kotlin使用了一个简单但是可能不太好理解的设计方式:() ,子类的主构造函数调用父类中的哪个构造函数,在继承的时候通过括号来指定
//Person类后面的空括号表示Student类的主构造函数初始化的时候会调用Person类的无参数构造函数,即使在无参数的情况下括号也不能省略
open class Person(){
//...
}
class Student(val sno:String,val grade:Int):Person(){
init{
println("This is init{}")
}
}
//当person类没有无参数构造函数时,上面的Student类会报错
open class Person(val name:String,val age:Int){
//...
}
//在Student类的主构造函数增加name和age这两个字段时,不能再将它们声明成val,因为在主构造函数中声明成val或者var的参数将自动成为该类的字段,这就会导致和父类中同名的name和age字段造成冲突。因此这里不用加任何关键字,让它的作用域仅限定在主构造函数中即可
class Student(val sno:String,val gradle:Int,name:String,age:Int):Person(name,age){
//...
}
//实例化一个Student类
val student = Student("a123",5,"Jack",19)
次构造函数
- kotlin规定,当一个类既有主构造函数又有次构造函数时,所有的次构造函数都必须调用主构造函数(包括间接调用)。
- 次构造函数是通过constructor关键字来定义的
class Student(val sno:String,val gradle:Int,name:String,age:Int):Person(name,age){
//接收两个参数又通过this关键字调用了主构造函数
constructor(name:String,age:Int):this("",0,name,age){
}
//无参数并通过this关键字调用第一个次构造函数,间接调用了主构造函数
constructor():this("",0){
}
}
//三种构造函数都能对Student类进行实例化
val student1 = Student()
val student2 = Student("Jack",19)
val student3 = Student("a123",5,"Jack",19)
- 特殊情况:类中只有次构造函数,没有主构造函数,在kotlin中是允许的。当一个类没有显式的定义主构造函数且定义了次构造函数时,它就是没有主构造函数的。
//由于没有主构造函数,次构造函数只能直接调用父类的构造函数,因此将this关键字换成了super关键字,和java比较像.
class Student : Person {
constructor(name:String,age:Int):super(name,age){
//...
}
}
继承
类默认都是封闭的,要让某个类开放继承,必须使用open关键字修饰它
open class Product(private val name :String){
fun description() = "Product : $name"
//父类的函数也需要用open关键字修饰,子类才能覆盖它
open fun load() = "Nothing ... "
}
class LuxuryProduct:Product("Luxury"){
override fun load() = "Luxury load..."
}
初始化
延迟初始化
- 使用lateinit关键字,告诉kotlin编译器,会在晚些时候进行初始化,这样就不用一开始的时候给变量赋值
private lateinit var adapter:CustomAdapter
惰性初始化
- 延迟初始化并不是推后初始化的唯一方式,还有一种是惰性初始化,直到使用它时才初始化
val name by lazy { "Jason" }
kotlin层次
和Java一样,无须Any在代码中显式指定,每一个类都会继承一个共同的超类,但是这个超类不是Object 而是Any
类型检测
Kotlin的is运算符,相当于Java的instanceof 可以用来检查某个对象的类型
类型转换和智能类型转换
kotlin可以使用as操作符进行类型转换,如果通过is运算符确定了类型,后续可以直接使用
open class Product(private val name :String){
fun description() = "Product : $name"
open fun load() = "Nothing ... "
}
class LuxuryProduct:Product("Luxury"){
override fun load() = "Luxury load..."
}
fun sale(p:Product){
println(p.load())
}
fun main() {
val p = LuxuryProduct()
sale(p)
}
接口
kotlin的接口部分和java基本一致,但是kotlin增加了一个额外的功能:允许对接口中定义的函数进行默认实现。接口的后面不用加上括号,因为它没有构造函数可以去调用。
interface Study{
fun readBooks()
fun doHomework(){
println("do homework default implementation.")
}
}
函数的可见性修饰符
kotlin有4种,public、private、protected、internal。
修饰符 | Java | kotlin |
---|---|---|
public | 所有类可见 | 所有类可见(默认) |
private | 当前类可见 | 当前类可见 |
protected | 当前类、子类、同一包路径下的类可见 | 当前类、子类可见 |
default | 同一包路径下的类可见(默认) | 无此修饰符 |
internal | 无此修饰符 | 同一模块中的类可见 |
数据类与单例类
- 当在一个类前面声明了一个data关键字时,就表明这个类是一个数据类即通常所说的实体类,kotlin会根据主构造函数中的参数帮你将equals()、hashCode()、toString()等固定且无实际逻辑意义的方法自动生成
- kotlin中的单例类很简单,将class关键字换成object关键字即可
//当一个类中没有任何代码时,还可以将尾部的大括号省略
data class book(val name:String,val price:Float)
object Singleton{
//这就是一个单例类了
fun SingletonTest(){
println("singletonTest is called.")
}
}
//调用
Singleton.SingletonTests()
object关键字的其他用法
对象表达式
有时候你不一定非要定义新的命名类不可,也许你需要某个现有类的一种变体实例,但只需要一次就行了,事实上,对于这种用完就丢的类实例,连命名都可以省了,这个对象变达式是某个类的子类,这个匿名类依然遵循object关键字的一个规则,即一旦实例化,该匿名类智能有唯一一个实例存在
open class Player(){
open fun load() = "loading nothing . "
}
fun main() {
val p = object :Player(){
override fun load() = "test Object Player"
}
println(p.load())
}
类的伴生对象
如果你想将某个对象的初始化和一个类实例捆绑在一起,可以考虑使用伴生对象,使用companion object修饰符,可以在一个类定义里声明一个伴生对象,但是一个类里只能有一个伴生对象
open class Player(){
open fun load() = "loading nothing . "
//只有初始化Player类或调用load函数时,伴生对象的内容才会载入。
//而且无论实例化Player类多少次,这个伴生对象始终只有一个实例存在
companion object{
const val BOARD = 100
}
}
集合
- kotlin提供了内置的listOf()函数来简化初始化集合的写法,listOf()函数创建的是一个不可变的集合,无法对集合进行增删改,因此可以使用**mutableListOf()**函数创建一个可变的集合
- Set集合和list差不多,只是将创建集合的方式换成了setOf和mutableSetOf()函数。Set是无序的,List是有序的,Map和List、Set有较大的不同。
- kotlin不建议使用put和get方法来对Map进行添加和读取数据,推荐使用一种类似数组下标的语法结构:map[“apple”] = 1
- List集合支持解构语法特性,它允许你在一个表达式里给多个表达赋值,解构常用了简化变量的赋值
val list = ArrayList<String>()
list.add("apple")
val list = listOf("apple","banana","orange")
//利用解构语法特性,还能用_过滤不想用的元素
val (first:String ,_, end:String) = list
println("$first , $end")
//遍历时要获取索引,使用forEachIndexed
list.forEachIndexed(){index,name ->
println("$index,$name")
}
//forEach循环
list.forEach {
print(it)
}
val list = mutableListOf("Apple")
list.add("banana")
//还能使用+= -=操作上面的list
//删除元素的运算符,利用了函数的重载
list -= "Apple"
//添加元素运算符
list += "orange"
//set读取元素
val set:Set<String> = setOf("java","python","c++")
set.elementAt(0)
//list去重,distinct()函数
val list1:List<String> = listOf("java","python","c++","java")
list1.distinct()
//和Java类似的新建map
val map = HashMap<String,Int>()
map.put("Apple",1)
map["apple"] = 1
//使用mapOf()函数和mutableMapOf()函数新建map
val map = mapOf("apple" to 1 , "banana" to 2)或者mapOf(Pair("apple",20),Pair("banana",10))
//遍历map
for((fruit,number) in map){
//这里使用了字符串内嵌表达式
println("fruit is $fruit , number is $number")
}
infix函数
- 标有 infix 关键字的函数也可以使⽤中缀表⽰法(忽略该函数调⽤的点与圆括号)调⽤。中缀函数必须满⾜以下要求:
-
它们必须是成员函数或扩展函数
-
它们必须只有⼀个参数
-
其参数不得接受可变数量的参数且不能有默认值。
//给集合添加一个扩展函数
infix fun <T> Collection<T>.has(element: T) = contains(element)
val list = listOf("Apple" , "Banana" , "Orange" , "Pear")
if(list has "Pear"){
println("list is contain Pear")
}
//上下两个方法是相同的效果
if(list.has("Pear")){
println("list is contain Pear")
}
空指针检查
- kotlin将空指针异常的检查提前到了编译时期,并默认将所有的参数和变量都不可为空,
//默认不可为空
val age = 24
//加上一个问号表示该变量可为空
var age:Int? = null
- **?.**操作符 当对象不为空时正常调用相应的方法,对象为空不往后面走
- ?: 操作符 这个操作符左右两边都接受一个表达式,如果左边的表达式的结果不为空就返回左边表达式的结果,否则就返回右边表达式的结果
- 非空断言工具,写法是在对象后面加上**!!**
a?.doSomething()
val c = a ?: b
//两个操作符相结合 text为空时 text?.length返回一个null值 因此返回0,不为空时直接等于text.length
fun getTextLength(test:String?) = text?.length?:0
//有风险的写法,非常确信这里的对象不会为空,因此不用做空指针检查
val length = content!!.length
函数的参数默认值
- kotlin提供了给函数设定参数默认值的功能,它在很大程度上能够替代次构造函数的作用
- 具名函数传参 使用键值对的方式传参,不用按照参数定义的的顺序来传参
fun printParams(year:Int,str:String = "hello world"){
println("year is $year , str is $str")
}
printParams(str = "hello kotlin",year = 2021 )
标准函数
- kotlin的标准函数指的是Standard.kt文件中定义的函数,任何Kotlin代码都可以自由地调用所有的标准函数
with
with函数接收两个参数,第一个参数是任意类型的对象,第二个参数是一个Lambda表达式。with函数会在Lambda表达式中提供第一个参数对象的上下文,并使用Lambda表达式中的最后一行代码作为返回值返回
val str = "hello world"
val res = with(str) {
contains("hello")
}
print(res)
run
run函数是不能直接调用的,一定要调用某个对象的run函数才行,run函数只接收一个Lambda参数,并且会在Lambda表达式中提供调用对象的上下文,返回的结果是Lambda表达式中的最后一行
val str = "hello world"
val res2 = str.run {
contains("hello")
}
print(res2)
apply
apply函数和run函数类似,但是apply函数无法指定返回值,而是会自动返回调用对象本身
val res = StringBuilder().apply {
append("hello,")
append("world")
}
print(res)
also
also函数把调用者对象作为值参传入Lambda表达式中,它返回的是调用者本身
val str = " Today is Cloundy".also {
val substring = it.substring(6)
println(substring)
}
print(str)
let
let函数同样是把调用者对象作为值参参入Lambda表达式中,但是它返回的是Lambda的结果
takeif
takeif函数通过判断Lambda表达式中的结果来决定返回值,如果Lambda表达式中的结果是true,则返回调用者本身,反之则为null
val str = "hello"
str.takeIf { it.length>3 }?.let {
print(it)
}
takeUnless
takeUnless函数刚好和takeif函数相反,只有当Lambda表达式中的结果是false时,才会返回调用者对象本身,反之为null
val str = "hello"
str.takeUnless { it.length>6 }?.let {
print(it)
}
代数数据类型(ADT)
可以用来表示一组子类型的闭集,枚举类就是一种简单的ADT
enum class Status{
START,
LOADING,
FINISH
}
fun main() {
val status = getStatus(Status.START)
println(status)
}
fun getStatus(status: Status):String{
return when(status){
Status.START -> "开始"
Status.LOADING -> "加载"
Status.FINISH -> "结束"
}
}
对于更复杂的ADT,可以使用Kotlin的密封类来实现更复杂的定义
密封类
关键字是 sealed class,密封类可以有若干个子类,密封类及其所有子类只能定义在同一个文件的顶层位置,不能嵌套在其他类中,这是被密封类底层的机制所限制的
sealed class BaseViewHolder(view:View):RecyclerView.ViewHolder(view){
class LeftViewHolder(view: View):BaseViewHolder(view)
class rightViewHolder(view: View):BaseViewHolder(view)
}
泛型
泛型主要有两种定义方式:一种是泛型类,另一种是泛型函数,使用的语法结构都是,但是并不是固定的,可以使用其他字母表示,因为T代表type,通常情况下,T是一种约定俗成的泛型写法
定义泛型类
class MyClass<T> {
fun method(param: T): T {
return param
}
}
fun main() {
val myClass = MyClass<Int>()
val num = myClass.method(666)
val myClass2 = MyClass<String>()
val str = myClass2.method("Hello world")
println("$num , $str")
}
定义泛型方法
class MyClass {
fun <T> method(param: T): T {
return param
}
}
fun main() {
val myClass = MyClass()
//转入一个Int类型的参数,它能够自动推导出泛型类型就是Int型
val num = myClass.method(666)
val myClass2 = MyClass()
val str = myClass2.method("Hello world")
println("$num , $str")
}
我们还能指定泛型的上界
class MyClass {
fun <T:Number> method(param: T): T {
return param
}
}
fun main() {
val myClass = MyClass()
val num = myClass.method(666)
//只允许传入number类型的参数
// val myClass2 = MyClass()
// val str = myClass2.method("Hello world")
println("$num ")
}
也能有多个泛型参数
class MagicBox<T>(item: T) {
var available = false
private var subject: T = item
fun fetch(): T? {
return subject.takeIf { available }
}
//fetch接收一个参数是T,返回值是R的Lambda表达式然后返回一个可为空的R
fun <R> fetch(subjectModFunction: (T) -> R): R? {
return subjectModFunction(subject).takeIf { available }
}
fun fetch2(subjectMod: (T) -> Unit): Boolean {
subjectMod(subject)
return false
}
}
class Boy(val name: String, val age: Int)
data class Man(val name: String, val age: Int)
data class Girl(val name: String, val age: Int, val sex: String)
fun main() {
val box1: MagicBox<Boy> = MagicBox(Boy("Jack", 20))
box1.available = true
val man1 = box1.fetch() {
Man(it.name, it.age.plus(10))
}
val man2 = box1.fetch2 {
val girl = Girl(it.name.plus("-girl"), it.age.plus(2), "女")
val man = Man(it.name, it.age.plus(10))
println(girl)
println(man)
}
println(man2)
man1?.let {
println("${it.name} , ${it.age}")
}
}
可变参数和[]取值
vararg关键字 可以实现动态的参数,但是当方法中待传入的参数除了动态参数外,还有其它参数,则必须将动态参数方法在参数列表的最后面
class MagicBox<T>(vararg item: T) {
var available = false
private var subject: Array<out T> = item
//如果想通过[]操作符取值,需要重载运算符函数get函数
operator fun get(index:Int):T? = subject[index].takeIf { available }
fun fetch(index:Int): T? {
return subject[index].takeIf { available }
}
fun <R> fetch(index:Int,subjectModFunction: (T) -> R): R? {
return subjectModFunction(subject[index]).takeIf { available }
}
}
class Boy(val name: String, val age: Int)
data class Man(val name: String, val age: Int)
data class Girl(val name: String, val age: Int, val sex: String)
fun main() {
val box: MagicBox<Any> = MagicBox(Boy("Jack", 20),Girl("girl", 18, "女"))
box.available = true
val man1 = box.fetch(0)
val man2 = box.fetch(1)
printValue(man1)
printValue(man2)
printValue(box[0])
printValue(box[1])
}
fun <T> printValue(param:T?){
param?.let{
when (it) {
is Boy -> {
println("${it.name} , ${it.age}")
}
is Girl -> {
println("${it.name} , ${it.age}")
}
is Man -> {
println("${it.name} , ${it.age}")
}
}
}
}
泛型实化
-
基于JVM的语言,它们的泛型功能都是通过泛型擦除机制来实现的,首先需要了解什么是泛型擦除机制
-
泛型擦除机制:即泛型的类型约束只在编译时期存在,运行时还是按照JDK1.5之前的机制来运行
-
因此kotlin中无法使用a is T 或者T::class.java这样的语法,因为T 的实际类型在运行时已经被擦除了,但是kotlin提供了一个内联函数,内联函数中的代码会在编译的时候自动被替换到调用它的地方,这样也就不存在泛型擦除的问题了
-
泛型实化的使用,首先必须是内联函数,其次声明泛型的地方必须加上reified关键字来表示该泛型要进行实话
inline fun <reified T> getGenericType() = T::class.java
- 泛型实化的应用:
inline fun <reified T> startActivity(
activity: Activity,
finishLastActivity: Boolean = false,
block: Intent.() -> Unit
) {
val intent = Intent(activity, T::class.java)
intent.block()
activity.startActivity(intent)
if (finishLastActivity) {
activity.finish()
}
}
协变和逆变
-
首先应该先去了解Java泛型的? extends 和? super
-
out 即协变对应Java中?extend ,in 即逆变对应Java 中?super
-
out 表示变量或者参数只能作为输出,不能作为输入
泛型类只将泛型类型作为函数的返回(输出) - 协变 out
interface Production<out T>{
fun product():T
}
泛型类只将泛型类型作为函数的接收(输入) - 逆变 in
interface Production2<in T>{
fun product(item:T)
}
- 如果泛型类既将泛型类型作为函数参数,又将泛型类型作为函数的输出,那么既不用in 也用out
- 父类泛型对象可以赋值给子类泛型对象,用in
- 子类泛型对象可以赋值给父类泛型对象,用out
open class Food
open class FastFood:Food()
class FoodStore:Production<Food>{
override fun product(): Food {
println("Product food ")
return Food()
}
}
class FastFoodStore:Production<FastFood>{
override fun product(): FastFood {
println("Product FastFood ")
return FastFood()
}
}
interface Production<out T:Food>{
fun product():T
}
interface Consumer< in T>{
fun consume(item:T)
}
class EveryBody:Consumer<Food>{
override fun consume(item: Food) {
println("consume Food ")
}
}
class Chinese:Consumer<FastFood>{
override fun consume(item: FastFood) {
println("consume FastFood ")
}
}
fun main() {
val foodStore:Production<Food> = FoodStore()
//子类的泛型对象转换成父类的泛型对象,如果Production接口没有用out修饰则不支持转换
val foodStore1:Production<Food> = FastFoodStore()//实际上调用的是子类的类型中的方法
foodStore.product()
foodStore1.product()
val consumer:Consumer<FastFood> = Chinese()
//父类泛型对象可以赋值给子类泛型对象,必须用in
val consumer1:Consumer<FastFood> = EveryBody()//实际上调用的还是父类的类型中的方法
consumer.consume(FastFood())
consumer1.consume(FastFood())
}
//输出结果
Product food
Product FastFood
consume FastFood
consume Food
委托
-
委托是一种设计模式,它的基本理念是:操作对象自己不会去处理某段逻辑,而是把工作委托给另外一个辅助对象去处理
-
kotlin将委托功能分为两种:类委托和属性委托
-
委托的关键字是by
-
类委托:将一个类的具体实现委托给另一个类去完成
class MySet<T>(private val helpSet: HashSet<T>) : Set<T> { override val size: Int get() = helpSet.size override fun contains(element: T) = helpSet.contains(element) override fun containsAll(elements: Collection<T>) = helpSet.containsAll(elements) override fun isEmpty() = helpSet.isEmpty() override fun iterator() = helpSet.iterator() } //这样的MySet和MySet2是一个全新的数据结构类 class MySet2<T>(private val helpSet:HashSet<T>):Set<T> by helpSet{ fun newMethod() = println("This is a new data structure") } //两段代码实现的效果是一模一样的,但是使用了类委托的功能之后,代码明显简化了太多。另外,如果我们要对某个方法进行重新实现,只需要单独重写那一个方法就可以了,其他的方法仍然可以享受类委托所带来的便利
-
属性委托:将一个属性的具体实现委托给另一个类去完成,即由某个类的方法来进行 setter 和 getter。默认属性委托都是线程安全的。属性委托适合那些属性的需要复杂的计算但是计算过程可以被重用的场合。
class Delegate<T>(val block: () -> T) {
var propValue: Any? = null
operator fun getValue(myClass: Any?, prop: KProperty<*>): T {
if (propValue == null) {
propValue = block()
}
return propValue as T
}
operator fun setValue(myClass: Any, prop: KProperty<*>, value: Any?) {
propValue = value
}
}
class Person {
var name by Delegate { "jack" }
var age by Delegate {
24
}
}
fun main() {
val person = Person()
println("person.name = ${person.name} , person.age = ${person.age}")
person.name = "jason"
person.age = 18
println("person.name = ${person.name} , person.age = ${person.age}")
}
扩展函数
-
扩展函数表示即使在不修改某个类的源码的情况下,仍然可以向这个类添加新的函数
-
扩展函数的语法结构:
fun <T> T.methodName():T{ println(this) return this }
-
泛型扩展函数可以参考标准函数的实现
-
kotlin标准库提供的很多功能都是通过扩展函数和扩展属性来实现的,包含类扩展的标准库文件都是以类名加s后缀来命名的,例如Ranges.kt,Maps.kt
DSL(领域特定语言)
后续待加,感谢观看