内容简介:通过上章节的学习,了解到了枚举类,数据类,封闭类,泛型,扩展,对于一些使用的语法糖有了一定的认识,接下来让我们一起更加深入的了解一下更加晦涩的Kotline由于Kotlin没有静态成员的概念,因此Kotlin推出了一个有趣的语法糖:在Java中有一个匿名类概念,造创建的时候,无需指定类的名字
通过上章节的学习,了解到了枚举类,数据类,封闭类,泛型,扩展,对于一些使用的语法糖有了一定的认识,接下来让我们一起更加深入的了解一下更加晦涩的Kotline
1. 对象和委托
1.1 对象(可能有的又说,这么简单,莫急,你往后看看)
由于Kotlin没有静态成员的概念,因此Kotlin推出了一个有趣的语法糖: 对象 (这个对象对于Kotlin来说,是一个关键字,而且这个字段声明的类,不需要实力化,感觉应该是Kotlin自己会new 出一个对象)
1.1.1 对象表达式
在 Java 中有一个匿名类概念,造创建的时候,无需指定类的名字
public class MyClass { public String name; public MyClass(String name) { this.name = name; } public void verify() { } } class Test { public static void process(MyClass myClass) { myClass.verify(); } public static void main(String[] args) { process(new MyClass("nii") { //初始化匿名类 @Override public void verify() { super.verify(); Log.i("tag", "Test verify"); } }); } } 复制代码
在Kotlin中,也有类似的功能,但不是匿名类,而是对象
open class MyClassDemo(name: String) { open var name = name open fun verify() { Log.i("tag", "verify") } } fun process(mycl: MyClassDemo) { mycl.verify() } fun main(arrgs: Array<String>) { //process 参数是一个对象,该对象是MyClassDemo匿名子类的实例,并且在对象中重写verify函数 //要想建立一个对象,需要使用object关键字,该对象要继承的需要与object之间用冒号(:)分隔 process(object : MyClassDemo("heihei") { override fun verify() { Log.i("tag", "override verify") } }) } 复制代码
对象和类一样,只能有一个父类,但是可以实现多个接口
open class MyClassDemo(name: String) { open var name = name open fun verify() { Log.i("tag", "verify") } } interface MyInterface{ fun closeData(){ Log.i("tag","MyInterface ") } fun hellow() } fun process(mycl: MyClassDemo) { mycl.verify() if (mycl is MyInterface){ mycl.closeData() } } fun main(arrgs: Array<String>) { //process 参数是一个对象,该对象是MyClassDemo匿名子类的实例,并且在对象中重写verify函数 //要想建立一个对象,需要使用object关键字,该对象要继承的需要与object之间用冒号(:)分隔 process(object : MyClassDemo("heihei"),MyInterface { override fun hellow() {//实现抽象的方法 } override fun verify() { Log.i("tag", "override verify") } }) } 复制代码
1.1.2 声明匿名对象
匿名对象只能用在本地(函数)或者private声明中,如果将匿名对象用于public函数的返回值,或public属性类型,那么Kotlin编译器会将这些函数或者属性的返回类型重新定义为匿名对象的父类型,如果匿名对象没有实现任何接口,也没有从任何类继承,那么父类型就是Any,因此,添加在匿名对象的任何成员将无法访问
class TestDevin{ //private 函数,返回类型是匿名函数对象本身,可以访问x private fun foo() =object { val x :String = "x" } //public函数,由于匿名对象没有任何的父类型,因此函数返回类型是Any fun pubFoo() =object { val x:Int =1 } fun bar(){ var x1 = foo().x //可以访问 var x2 =pubFoo().x//编译错误,因为pubFoo是public方法,返回类型是Any } } 复制代码
1.1.3 访问封闭作用域内的变量
Java的代码
public class MyClass { public String name; public MyClass(String name) { this.name = name; } public void verify() { } } class Test { public static void process(MyClass myClass) { myClass.verify(); } public static void main(String[] args) { final int n =20; process(new MyClass("nii") { //初始化匿名类 @Override public void verify() { int m = n; n=30; //编译不通过 ,n的值不能修改 if (n==20){ } super.verify(); Log.i("tag", "Test verify"); } }); } } 复制代码
Kotlin的代码
在匿名对象中就可以任意访问变量n,包括修改n的值
open class MyClassDemo(name: String) { open var name = name open fun verify() { Log.i("tag", "verify") } } interface MyInterface{ fun closeData(){ Log.i("tag","MyInterface ") } fun hellow() } fun process(mycl: MyClassDemo) { mycl.verify() if (mycl is MyInterface){ mycl.closeData() } } fun main(arrgs: Array<String>) { var n :Int =20 process(object : MyClassDemo("heihei"),MyInterface { override fun hellow() {//实现抽象的方法 n=30 //可以修改 n的值 } override fun verify() { Log.i("tag", "override verify") } }) } 复制代码
1.1.4 访问封闭作用域内的变量
在Kotlin中并没有静态成员的概念, 并不等于不能实现类似静态成员的功能,陪伴对象(Companion objects)就是Kotlin用来解决这个问题的语法糖 如果在Kotlin类中定义对象,那么就称这个对象为该类的陪伴对象,陪伴对象要使用companion关键字声明
class BanSui{ companion object { fun create():BanSui = BanSui() } } //陪伴对象定义的成员变量是可以直接通过类名访问的 var create = BanSui.create() 复制代码
注意,虽然陪伴对象的成员看起来很像其他语言中的类的静态成员,但在运行期间,这些成员仍然是真实对象的实例的成员,他们与静态成员是不同的,不过使用@JvnStatic进行注释,Kotlin编译器会将其编译成Byte code真正的静态方法
2.1 委托
委托模式是软件 设计模式 中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。Kotlin 直接支持委托模式,更加优雅,简洁
2.1.1 类的委托
interface Base { fun print() } class BaseIml(val x: Int) : Base { override fun print() { Log.i("tag", "" + x) } } class Derived(b: Base) : Base by b { //Derived类使用关键字by将Base类的print函数委托给了一个对象 fun getHeName(): String { return "getHeName" } } fun goneDown() { var baseIml = BaseIml(20) Derived(baseIml).print() } 复制代码
2.1.2 委托属性
Kotlin允许属性委托,也就是将属性的getter和setter函数的代码放到一个委托类中,如果在类中声明属性,只需要指定属性委托类,这样大大的减少代码的冗余
class Delegate { operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return "$thisRef, 这里委托了 ${property.name} 属性" } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { Log.i("tag","$thisRef 的 ${property.name} 属性赋值为 $value") } } class Example { var p: String by Delegate() } class Example1{ var p1 :String by Delegate() } 复制代码
2.1.3 委托类的初始化函数
如果委托类有主构造器,也可以向主构造器传入一个初始化函数,这时,可以定义一个委托函数的返回值是委托类,并在委托的时候指定初始化函数。
class Delegate<T>(initializer:() ->T){ var name :String ="" var className = initializer() operator fun getValue(thisRef:Any?,property:KProperty<*>): String { Log.d("tag","$className.get 已经被调用") return name } operator fun setValue(thisRef:Any?,property:KProperty<*>,value :String){ Log.d("tag","$className.set 已经被调用") name =value } } public fun <T>delegate(initializer: () -> T):Delegate<T> = Delegate(initializer) class MyClass1{ var name :String by Delegate { Log.d("tag","MyClass1.name 初始化函数调用" ) "《MyClass1》" } } class MyClass2{ var name :String by Delegate { Log.d("tag","MyClass2.name 初始化函数调用" ) "《MyClass2》" } } var c1 = MyClass1() var c2 = MyClass2() c1.name ="Bill" c2.name ="Mike" Log.d("tag",c1.name) Log.d("tag",c2.name) 输出: MyClass1.name 初始化函数调用 MyClass2.name 初始化函数调用 heihei.set 已经被调用 shazizi.set 已经被调用 heihei.get 已经被调用 Bill shazizi.get 已经被调用 Mike 复制代码
上面这段代码,为委托类加了一个主构造器,并传入一个初始化函数,初始化函数的返回String类型的值
3.1 标准委托
3.1.1 惰性装载
lazy是一个函数,实现惰性加载属性,第一次调用get()时,将会执行从lazy函数传入的Lambda表达式,然后会记住这次执行的结果,之后所有对get()的调用都只会简单地返回以前记住的结果
val lazyValue: String by lazy { //LazyThreadSafetyMode.PUBLICATION Log.d("tag", "我是懒加载初始化...") "hellow" } Log.d("tag", lazyValue) Log.d("tag", lazyValue) 输出: 我是懒加载初始化... hellow hellow 复制代码
默认的情况下,惰性加载属性的执行时同步,属性值只会在唯一一个线程内执行,然后所有的线程得到同样的属性值。如果委托属性初始化函数不需要同步,多个线程可以同事执行初始化函数,那么可以向lazy函数传入一个LazyThreadSafetyMode.PUBLICATION参数。相反,如果你确信初始化函数值可能在一个线程执行,那么可以使用LazyThreadSafetyMode.NONE模式
3.1.2可观察属性
就是当属性变化时可以拦截其变化。实现观察属性值变化的委托函数Delegates.observable。
class UserDemo { //XIXI时name的属性初始值 var name :String by Delegates.observable("XIXI"){ property, oldValue, newValue -> Log.d("tag","属性: $property 旧值: $oldValue 新值: $newValue") } } var userDemo = UserDemo() userDemo.name = "Bill" userDemo.name="Devin" 输出: 属性: property name (Kotlin reflection is not available) 旧值: XIXI 新值: Bill 属性: property name (Kotlin reflection is not available) 旧值: Bill 新值: Devin 复制代码
3.1.3 阻止属性的赋值操作
如果你希望能够拦截属性的赋值操作,并且能够“否决”赋值操作,那么不要使用observable函数,而应该使用vetoable函数
class UserDemo2 { var name :String by Delegates.vetoable("XIXI"){ property, oldValue, newValue -> Log.d("tag","属性: $property 旧值: $oldValue 新值: $newValue") var result = true; if (newValue.equals("Mary")){ result =false Log.d("tag","name的属性值不能为Mary") } result //返回true 或false,表示允许或者否姐属性的赋值 } } 输出: 属性: property name (Kotlin reflection is not available) 旧值: XIXI 新值: Devin Devin 属性: property name (Kotlin reflection is not available) 旧值: Devin 新值: Mary name的属性值不能为Mary Devin 复制代码
3.1.4 Map的委托
有一种常见的使用场景时将Map中的key-value映像到对象的同名属性中
class UserDemo3(var map:Map<String,Any>){ val name :String by map //将map用作name属性的委托 val love :String by map val sex :Int by map } var map = mapOf("name" to "李雪","love" to "打球" ,"sex" to 2) //map中key-value直接映射到UserDemo3类的属性上,key的名称要和属性名字一样,不然就映射不到 var userDemo3 = UserDemo3(map) Log.d("tag",userDemo3.name) Log.d("tag",userDemo3.love) Log.d("tag",userDemo3.sex.toString()) 输出: 李雪 打球 2 复制代码
上面,UserDemo3使用val声明属性,这就意味这两个舒属性值时不可以修改,因为Map只有getValue方法,没有setValue方法,所以Map只能通过UserDemo3对象读取name等值,而不能通过UserDemo3设置属性值,但是可不可以射呢?
3.1.5 MutableMap委托
class UserDemo4(var map:MutableMap<String,Any>){ var name :String by map //将map用作name属性的委托 var sex :Int by map } var map = mutableMapOf("name" to "小明","sex" to 30) var userDemo4 = UserDemo4(map) Log.d("tag",userDemo4.name) Log.d("tag",userDemo4.sex.toString()) userDemo4.name = "小红"//修改类中的值,map中的值也会跟着变 Log.d("tag",userDemo4.name) Log.d("tag","map"+map["name"]) map.put("sex",80)//修改map中的值,类中的也跟着变 Log.d("tag",userDemo4.sex.toString()) 输出: 小明 30 小红 map小红 80 复制代码
可以看出上面的修改都是双向的,进行委托的时候
3. 高阶函数于Lambda表达式
3.1 高阶函数
高阶函数是一种特殊的函数,它接受函数作为参数,或者返回一个函数。
interface Product { var area: String fun sell(name: String) } class MobilePhone : Product { override var area: String = "" override fun sell(name: String) { } } fun mobilePhoneArea(name: String): String { return "$name 美国!" } fun processProduct(product: Product, area: (name: String) -> String): Product { product.area = area("HUAWEI") return product } var mobilePhone = MobilePhone() //将函数作为参数传入高阶函数,需要在函数前面加两个(::)作为标记 processProduct(mobilePhone, ::mobilePhoneArea) Log.d("tag",mobilePhone.area) 输出:tag: HUAWEI 美国! //Lambda的表达式,将值传入processProduct函数 processProduct(mobilePhone,{name -> "$name 美国"}) //Lambda表达式还提供了另一个表达式,如果Lambda表达式是函数的最后一个参数,可以将大括号写在外面 processProduct(mobilePhone){ name -> "$name 美国" } 复制代码
3.2 Lambda表达式与匿名函数
Lambda表达式,又称匿名函数,是一种“函数字面值”,也就是一盒没有声明的函数,但是可以作为表达式传递出去
3.2.1 函数类型
对于接受另一个函数作为自己参数的函数,必须针对这个参数指定一个函数类型
fun <T> max(coll: Collection<T>, less: (T, T) -> Boolean): T? { var max: T? = null for (it in coll) { if (max == null || less(max, it)) { //参数less的类型是(T,T)->Boolean,它是一个函数,接受两个T类型参数,并且返回一个Boolean类型结果 max = it } } return max } fun compare(a: String, b: String): Boolean = a.length < b.length var list = listOf("dfsf", "sfsfdsf", "sdfsfff", "sfsfff") var max = max(list, { a, b -> a.length < b.length }) Log.d("tag", "max的值:" + max) var max2 = max(list, ::compare) Log.d("tag", "max2的值:" + max2) 复制代码
3.2.2 Lambda表达式的语法
Lambda表达式包含在大括号之内,在完整语法形式中,参数声明在小括号之内,参数类型声明可选,函数体在“->”符号之后。如果表达式自动推断的返回值类型不是Unit,那么表达式函数体中,最后一条表达式的值被当作整个Lambda表达式的返回值
val sum = {x:Int, y:Int ->x+y} 复制代码
在很多的情况下,Lambda表达式只有唯一一个参数,如果Kotlin能够自行判断出Lambda表达式的参数定义,那么它将允许我们省略唯一一个参数的定义(“->”也可以省略),并且会为我们隐含地定义这个参数,使用参数名为it
processProduct(product){ "${it}美国" } 复制代码
3.2.3 匿名函数
上面讲到的Lambda表达式语法,还遗漏一点,就是可以指定函数的返回值类型,大多数情况下,不需要指定函数类型,因为可以自动推断得到,但是的确需要明确指定返回值函数,可以选择另种语法:匿名函数
fun (x:Int, y:Int):Int{ return x+y } 复制代码
参数和返回值类型的声明与通常的函数一样,但如果参数类型可以通过上下文推断得到,那么类型是可以省略;如果函数体是多条语句组成的代码段,则返回值类型必须明确指定(否则认为是Unit)
ints.filter(fun(item) = item >0) 复制代码
注意:匿名函数参数一定要在小括号内传递,允许将函数类型参数写在小括号之外语法,仅对Lambda表达式有效
4. 函数
4.1 函数用法
函数必须使用fun关键字开头,后面紧跟函数名字,以及一对小括号,如果有返回值,则需要在小括号后面加上(:),冒号后面是返回值类型
4.1.1 使用中缀标记法调用函数
Kotlin允许使用中缀表达式调用函数;所谓的中缀表达式,就是指将函数名称放到两个操作数中间,左侧是包含函数对象,右侧是函数的参数值,要满足3个条件: (1)成员函数或者扩展函数 (2)只有1个参数 (3)使用infix关键字声明函数
infix fun String.div(str :String):String{ return this.replace(str,"") } var str ="hello world" Log.d("tag",str.div("l")) //中缀表达式 Log.d("tag",str div "l") //中缀表达式可以连续使用 Log.d("tag",str div "l" div "o") 复制代码
4.1.2 单表达式函数
如果函数的函数体只有一条语句,而且是Return语句,哪个可以省略函数体的大括号,以及return关键字。
fun double(x :Int):Int = x*2 //如果Kotlin编译器可以推断等号右侧表达式的类型,那么可以省略函数的返回值类型 fun double(x:Int)=x*2 复制代码
4.2 函数参数和返回值
4.2.1 可变参数
fun <T> asListDemo(vararg ts :T) :List<T>{ //vararg使用这个关键字定义 val result = ArrayList<T>(); for (t in ts){ result.add(t) } return result } var asListDemo = asListDemo(1, 2, 3, "a", 4, 5) Log.d("tag",asListDemo.toString()) 输出: tag: [1, 2, 3, a, 4, 5] fun <T> asListDemo(vararg ts :T,value1: Int ,value2: String) :List<T>{ val result = ArrayList<T>(); for (t in ts){ result.add(t) } Log.d("tag",value1.toString()+value2) return result } var asListDemo = asListDemo(1, 2, 3, "a", 4, 5,value1 = 3,value2 = "xixi") Log.d("tag",asListDemo.toString()) //要传递数组的话,再前面加上一个*的符号 val a = arrayOf(1,2,3) var asListDemo = asListDemo(-1, 2, *a, 4) Log.d("tag",asListDemo.toString()) 复制代码
4.2.2 返回值类型
如果函数体为多行语句组成的代码段,那么就必须明确指定返回值类型,除非这个函数打算返回Unit,这时返回类型的声明可以省略
4.2.3 局部函数
在Kotlin中,函数可以定义在源代码的顶级范围内,这就意味着不像Java那样,创建一个类来容纳这个函数,除了顶级函数之外,Kotlin中的函数还可以定义为局部函数、成员函数以及扩展函数。
fun saveFile() { //局部函数 fun getFileName(fn: String): String { return "/user/$fn" } var filename = getFileName("test.txt") Log.d("tag", "$filename 已经保存成功!") } 复制代码
以上所述就是小编给大家介绍的《Kotlin的解析(下)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 每秒解析千兆字节的 JSON 解析器开源,秒杀一大波解析器!
- 注册中心 Eureka 源码解析 —— EndPoint 与 解析器
- 新一代Json解析库Moshi源码解析
- mybatis源码配置文件解析之三:解析typeAliases标签
- MySQL内核源码解读-SQL解析之解析器浅析
- Laravel 核心——IoC 服务容器源码解析(服务器解析)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。