内容简介:在前几篇的基础上,大家如果认真的阅读,并跟着思路实践的话,应该可以收获很多的,前面基本已经覆盖了Kotlin语言中常见的使用方法,下面让我们来进一步,在前面的基础上深深的扩展一下尽管到目前为止,我们已经讲了很多关于Kotlin的新技术,但远远是不够的,让我们进一步了解更多的Kotlin的新知识所谓的数据结构,就是将对象中的数据解析成相应的独立变量,也就是脱离原来的对象存在
在前几篇的基础上,大家如果认真的阅读,并跟着思路实践的话,应该可以收获很多的,前面基本已经覆盖了Kotlin语言中常见的使用方法,下面让我们来进一步,在前面的基础上深深的扩展一下
1. Kotlin的技术拓展其一
尽管到目前为止,我们已经讲了很多关于Kotlin的新技术,但远远是不够的,让我们进一步了解更多的Kotlin的新知识
1.1 数据结构与集合
1.1.1 数据结构
所谓的数据结构,就是将对象中的数据解析成相应的独立变量,也就是脱离原来的对象存在
data class Person(var name:String, var age :Int,var salary:Float)
var person = Person("Bill",30,120f)
var (name,age,salary)=person //数据解构
Log.i("tag",name+age+salary)
输出
Bill20120
复制代码
有很多的对象,可以保存一组值,并可以通过for...in的语句,解构出值
var map = mutableMapOf<Int,String>()
map.put(10,"Devin")
map.put(20,"Max")
for ((key,values) in map){
Log.d("tag",key.toString() +";;;;"+values)
}
输出
10;;;;Devin
20;;;;Max
//其中这些对象都是通过数据类实现的,当然我们自己也可以实现的,这里就不做展示了,自己可以下去试试
复制代码
1.1.2 集合
尽管Kotlin可以使用JDK中提供的集合,但Kotlin标准库也提供了自己的集合,与之不同的是,Kotlin提供的集合分为可修改和不可修改的,这一点和Apple的CocoaTouch类似。在Kotlin只读包括LIst、Set、Map;可写的包括MutableList、MutableSet、MutableMap等
public interface List<out E> : Collection<E> {
...
}
public interface Set<out E> : Collection<E> {
...
}
public interface Map<K, out V> {
...
}
很显然上面的都是out修饰的,前面学的out声明,泛型如果使用了,那么该泛型就能只用于读操作
val nums = mutableListOf<Int>(1,2,3)
var reNums :List<Int> = nums;
nums.add(4)//可以增加;reNums只能读取
复制代码
从这个代码可以看出,集合并没有提供构造器创建集合对象,提供了一些函数来创建
listOf; setOf; mapOf; mutableListOf; mutableSetOf; mutableMapOf
val nums = mutableListOf<Int>(1,2,3) var toList = nums.toList()//通过此方法可以把读写的专为只读的 var toMutableList = toList.toMutableList()//只读的也可以转为读写的 复制代码
1.2 范围值
1.2.1 值范围的应用
值范围表达式用rangTo函数实现,该函数的操作形式是(..),相关的操作符in和!in
var n =20
if(n in 1..10){
Log.d("tag","满足条件")
}
if (n !in 30..80){
Log.d("tag","满足条件")
}
复制代码
整数的值范围(IntRange、LongRange、CharRange)还有一种额外的功能,就是可以对这些值范围进行遍历。编译器会负责将这些代码转换为 Java 中基于下标的for循环,不会产生不必要的性能损耗
for(i in 1..10){
Log.i("tag",i.tostring())
}
//相当于Java中的
//for(int i=1; I<=10;i++)
for(i in 10..1){
//如果按照倒序的话,什么都不会输出的
Log.i("tag",i.tostring())
}
//但是非要按照倒序输出,只要使用标准库中的downTo函数就可以了
for(i in 10 downTo 1){
Log.i("tag",i*i) //输出100到1共10个数
}
//在前面的代码中,i的顺序加1或减1,也就步长为1;如果要是改变步长的话,可以使用step函数
for(i in 1..10 step 2){
Log.i("tag",i.toString())
}
输出:1,3,5,7,9
//在前面的代码,使用的范围都是闭区间,要是这种的形式[1,10)
for(i in 1 until 10){
//不包含10的
Log.i("tag",i.toString())
}
复制代码
1.2.2 常用 工具 函数
(1)rangTo,整数类型上定义的rangTo操作符,只是简单地调用*Rang类的构造函数
class Int
{
public operator fun rangeTo(other: Int): IntRange
public operator fun rangeTo(other: Long): LongRange
}
复制代码
(2)downTo,扩展函数可以用于一对整数类型,下面就是通过扩展函数添加的downTo函数
public infix fun Long.downTo(to: Long): LongProgression {
return LongProgression.fromClosedRange(this, to, -1L)
}
public infix fun Byte.downTo(to: Long): LongProgression {
return LongProgression.fromClosedRange(this.toLong(), to, -1L)
}
复制代码
(3)reversed,对于每个*Progression类都定义了reversed扩展函数,所有的这些函数都会返回相反的数列
public fun IntProgression.reversed(): IntProgression {
return IntProgression.fromClosedRange(last, first, -step)
}
复制代码
(4)对于每个*Progression类都定义了step扩展函数,所有这些函数都会返回使用新的step值,步长值参数要求永远是整数,因此这个函数不会改变数列遍历的方向
public infix fun IntProgression.step(step: Int): IntProgression {
if (!isPositive) throw IllegalArgumentException("Step must be positive, was: $step.")
return IntProgression.fromClosedRange(first, last, if (this.step > 0) step else -step)
}
复制代码
注意:函数返回的数列last值可能与原始数列的last的值不同,这是为了保证(last-first)%increment==0原则
1.3 类型检查与类型转换
1.3.1 is 与 !is操作符
var obj: Any = 234
if (obj is String) {
}
if (obj is Int){
}
if (obj !is Int){
}
复制代码
如果is表达式满足条件,Kotlin编译器会自动转换is前面的对象到后面的数据类型
var obj: Any = 234
if (obj is Int){
obj.rangeTo(4)//Int类型才有的,自动转换了
}
//注意的是,对象is后面类型要兼容,如果不兼容的话,无法编译通过
var obj = 234
if (obj is String) {//编译不过
obj.rangeTo(4)
}
复制代码
1.3.2 智能类型转换
var a :Any = "max"
//&&的右侧已经转换成了string
if (a is String && a.length>0){
}
// ||的右侧也已经转换为string
if (a !is String ||a.length<0){
}
//这种类型的转换对于when和while同样有效果的
var x :Any ="sfs"
when(x){
is Int -> Log.i("tag", (x+1).toString())
is String -> Log.i("tag", x.length.toString())
}
复制代码
1.3.3 强行类型转换
如果类型强制转换,而且类型不兼容,类型转换操作符通常会抛出一个异常,称之为不安全的,而不安全的类型转换使用中缀操作符as
var a :Any ="max"
val x :Int = a as Int //java.lang.ClassCastException
//为了避免抛出异常,我们可以使用安全的类型转换操作符 as?,当类型转换失败时,它会返回null
var a :Any? =null
val x : Int? = a as Int?
Log.i("tag", x.toString())// null
var a :Any? ="max"
val x : Int? = a as? Int? //as后面也要加?不然还是会抛异常
Log.i("tag", x.toString())// null
复制代码
1.3.4 this表达式
为了访问外层范围内的this,我们使用this@lable,其中@lable是一个标签,代表this所属范围
class A{
var A =13
inner class B{
fun Int.foo(){
val a =this@A //指向A的this
val b = this@B //指向B的this
val c =this//指向foo()函数接收者,一个Int值
val d =this@foo //指向foo()函数接收者,一个Int值
val funLit = {
s:String ->
val e = this//指向foo()函数接收者,因为包含当前代码的Lambda表达式没有接收者
}
}
}
}
复制代码
2. Kotlin的技术拓展其二
本章将会继续探索null值安全性、异常类、注解以及反射 #####2.1 null值安全性 在Java中,经常遇到空指针的困扰,表脑瓜子疼,对于这个Kotlin使用一些新的语法糖,会尽可能避免null异常带来的麻烦
2.1.1 可为null与不可为null类型
var a:String =null //编译错误,不能为null var b:String = "abc" b=null //编译错误,不能为null 复制代码
要允许null值,我们可以将变量声明为null的字符串类型:String ?
var a :String ="abcd" var b:String? = "abc" b =null var len = a.length //由于a不允许为null,因此不会产生NPE val len1 = b.length //编译出错,因为b可能为null //要是必须访问的话,使用if语句进行判断 var len = if (b==null) -1 else b.length; //第二种就是使用安全调用操作符:? print(b?.length) //输出为null //当然可以使用在类中的调用 bob?.depart?.head?.name //这样的链式调用,只有属性链中任何一个属性为null,整个表达式就会返回null 复制代码
2.1.3 Elvis操作符
假设我们有一个可为null的引用r,我们可以认为:如果不为空,就是用,否则使用其他的值
//如果"?:"左侧的表达式不是null,Elvis操作符就会返回它的值,否则,返回右侧表达式的值,注意,只有在左侧表达式为null,才会计算右侧表达式的值
var len1 = b?.length ?:-1
复制代码
在Kotlin中,由于throw和return都是表达式,因此可以用在右侧
var len1 = b?.length ?:throw NullPointerException()
var len2 = b?.length ?:return
复制代码
2.1.4 !!操作符
对于NPE的忠实粉丝,还可以写!!b,对于b不为null的情况,这个表达式会返回一个非null的值,如果是null,就会抛出NPE
var len2 = b!!.length 复制代码
#####2.2 异常类 Kotlin中所有的异常类都是Throwable的子类,要抛出异常,可以使用throw表达式
//和Java的使用区别不是太大,这里就不说了
try { }
catch (e: NullPointerException) {
null
}
finally {
}
复制代码
2.3 注解(Annotations)
注解是用来为代码添加元数据(metadata)的一种手段,要声明一个注解,需要在类之前添加annotation修饰符
annotation class Fancy
注解的其他属性,可以通过向注解类添加元注解(meta-annotation)的方式指定 (1)@Target 指定这个注解可被用于哪些元素(类、函数、属性和表达式) (2)@Retention指定这个注解的信息是否被保存到编译后class文件中,以及在运行时是否可以通过反射访问到它(默认情况下,这两个设定都是true) (3)@Repetable允许在单个元素上多次使用同一注解 (4)@MustBeDoucumented表示这个注解是公开API的一部分,在自动产生的API文档的类或者函数签名中,应该包含这个注解的信息
@Target(AnnotationTarget.CLASS ,AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
@Repeatable
annotation class MyAnnotationClass{
}
复制代码
2.3.1 使用注解
注解可以在类、函数、函数参数和函数返回值中使用
@ MyAnnotationClass
class Foo {
@ MyAnnotationClass fun bazz(@MyAnnotationClass foo : Int):Int{
return (@MyAnnotationClass l)
}
}
//如果需要对一个类的主构造器加注解,那么必须在主构造器声明中添加constructor关键字,然后在这个关键字之前添加注解
class Foo @MyAnnotationClass constructor(n:Int){
// ...
}
复制代码
2.3.2 注解类的构造器
注解类可以拥有带参数的构造器
annotation class Special(val why :String)
使用Special("example") class Foo{}
并不是所有类型的参数都允许在注解类的构造器中使用,注解构造器只允许使用下面类型的参数 (1)与Java基本类型对应的数据类型(Int、Long) (2)String (3)枚举类 (4)KClass (5)其他注解类
2.4 反射(Reflection)
尽管Kotlin是基于JVM的编程语言,但在Kotlin中使用反射,需要引用额外的库(kotlin-reflect.jar)
2.4.1 类引用
val c = MyClass::class 类引用是一个KClass类型的值
注意,Kotlin的类引用不是一个Java的类引用,要得到Java的类引用,可使用KClass对象实例的java属性
val c =MyClass::class.java
2.4.2 枚举类成员
反射最常用的功能之一就是枚举的成员,如类的属性、方法等。
class Person(val name :String,val num :Int){
fun process(){
}
}
var c = Person :: class
// 获取Person类中所有的成员列表(属性和函数)
println("成员数:" +c.members.size)
for(member in c.members){
// 输出每个成员的名字和返回类型
print(member.name +"" +member.returnType)
println()
}
//获取Person类中所有属性的个数
println("属性个数:" +c.memberProperties.size)
//枚举Person类中所有的属性
for(property in c.memberProperties){
//输出当前属性的名字和返回类型
print(property.name+""+property.returnType)
println()
}
//获取Person类中所有函数的个数
println("函数个数:"+c.memberFunctions.size)
for(function in c.memberFunctions){
//输出当前函数的名字和返回类型
println(function.name+" " +function.returnType)
}
执行这段代码,会输出如下内容
成员数:6
num kotlin.Int
value kotlin.String
process kotlin.Unit
equals kotlin.Boolean
hashCode kotlin.Int
toString kotlin.String
属性个数:2
num kotlin.Int
value kotlin.String
函数个数:4
process kotlin.Unit
equals kotlin.Boolean
hashCode kotlin.Int
toString kotlin.String
复制代码
2.4.3 动态调用成员函数
反射的另外一个重要应用就是可以动态调用对象的成员,如成员函数、成员函数、成员属性,所谓的动态调用,就是根据成员名字进行调用,可以动态指定成员的名字,通过::操作符,可以直接返回类的成员
class Person(val name:String ,val num:Int){
fun process(){
println("name:${value} num:${num}")
}
}
// 获取process函数对象
var p = Person::process
// 调用invoke函数执行process函数
p.invoke(person("abc",20))
//利用Java的反射机制指定process方法名字
var method = Person::class.java.getMethod("process")
//动态调用process函数
method.invoke(Person("Bill",30))
输出:
name : abc num: 20
value : Bill num: 30
复制代码
2.4.4 动态调用成员属性
Kotlin类的属性与函数一样,也可以使用反射动态调用,不过Kotlin编译器在处理Kotlin类属性时,会将器转换为getter和setter方法,而不是与属性同名的Java字段。
class Person
{
var name :String = "Devin"
get() = field
set(v){
field = v
}
}
复制代码
很明显,name属性变成了getName和setName方法,因此,在使用反射技术访问Kotlin属性时,仍然需按成员函数处理,如果使用Java的反射技术,仍然要使用getMethod方法获取getter和setter方法对象,而不能使用getField方法获取字段
class Person
{
var name :String = "Devin"
get() = field
set(v){
field = v
}
}
var person = Person()
// 获得属性对象
var name = Person::name
// 读取属性值
println(name.get(person))
// 设置属性值
name.set(person,"Mike")
println(name.get(person))
//无法使用getField方法获得name字段值,因为根本就没生成name字段,只有getName和setName方法
var field = Person::class.java.getField("name")
field.set(person,"Json")
println(field.get(person))
//利用Java反射获取getName方法
var getName = Person::class.java.getMethod("getName")
//利用 Java反射获取SetName方法,注意,getMethod方法的第2个参数可变的
//需要传递setName参数类型的class
//这里不能指定Kotlin中的String,而要指定java.lang.String
var setName = Person::class.java.getMethod("setName",java.lang.String().javaClass)
//动态设置name属性的值
setName.invoke(person,"John")
//动态获取name属性的值
println(getName.invoke(person))
复制代码
总结
通过这一篇,更深入的了解kotlina的更多新的知识,以及语法糖,和Java区别还是比较大的,这篇讲的,实际开发中很是有用的,可能将的还是有限的,毕竟一些知识,还得在实践的更深入的掌握
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Design systems
Not all design systems are equally effective. Some can generate coherent user experiences, others produce confusing patchwork designs. Some inspire teams to contribute to them, others are neglected. S......一起来看看 《Design systems》 这本书的介绍吧!