在前几篇的基础上,你们若是认真的阅读,并跟着思路实践的话,应该能够收获不少的,前面基本已经覆盖了Kotlin语言中常见的使用方法,下面让咱们来进一步,在前面的基础上深深的扩展一下java
尽管到目前为止,咱们已经讲了不少关于Kotlin的新技术,但远远是不够的,让咱们进一步了解更多的Kotlin的新知识编程
所谓的数据结构,就是将对象中的数据解析成相应的独立变量,也就是脱离原来的对象存在安全
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的语句,解构出值bash
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
//其中这些对象都是经过数据类实现的,固然咱们本身也能够实现的,这里就不作展现了,本身能够下去试试
复制代码
尽管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()//只读的也能够转为读写的
复制代码
值范围表达式用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)rangTo,整数类型上定义的rangTo操做符,只是简单地调用*Rang类的构造函数ui
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原则
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)
}
复制代码
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())
}
复制代码
若是类型强制转换,并且类型不兼容,类型转换操做符一般会抛出一个异常,称之为不安全的,而不安全的类型转换使用中缀操做符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
复制代码
为了访问外层范围内的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表达式没有接收者
}
}
}
}
复制代码
本章将会继续探索null值安全性、异常类、注解以及反射 #####2.1 null值安全性 在Java中,常常遇到空指针的困扰,表脑瓜子疼,对于这个Kotlin使用一些新的语法糖,会尽量避免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
复制代码
假设咱们有一个可为null的引用r,咱们能够认为:若是不为空,就是用,不然使用其余的值
//若是"?:"左侧的表达式不是null,Elvis操做符就会返回它的值,不然,返回右侧表达式的值,注意,只有在左侧表达式为null,才会计算右侧表达式的值
var len1 = b?.length ?:-1
复制代码
在Kotlin中,因为throw和return都是表达式,所以能够用在右侧
var len1 = b?.length ?:throw NullPointerException()
var len2 = b?.length ?:return
复制代码
对于NPE的忠实粉丝,还能够写!!b,对于b不为null的状况,这个表达式会返回一个非null的值,若是是null,就会抛出NPE
var len2 = b!!.length
复制代码
#####2.2 异常类 Kotlin中全部的异常类都是Throwable的子类,要抛出异常,可使用throw表达式
//和Java的使用区别不是太大,这里就不说了
try { }
catch (e: NullPointerException) {
null
}
finally {
}
复制代码
注解是用来为代码添加元数据(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{
}
复制代码
注解能够在类、函数、函数参数和函数返回值中使用
@ MyAnnotationClass
class Foo {
@ MyAnnotationClass fun bazz(@MyAnnotationClass foo : Int):Int{
return (@MyAnnotationClass l)
}
}
//若是须要对一个类的主构造器加注解,那么必须在主构造器声明中添加constructor关键字,而后在这个关键字以前添加注解
class Foo @MyAnnotationClass constructor(n:Int){
// ...
}
复制代码
注解类能够拥有带参数的构造器
annotation class Special(val why :String)
使用Special("example") class Foo{}
并非全部类型的参数都容许在注解类的构造器中使用,注解构造器只容许使用下面类型的参数 (1)与Java基本类型对应的数据类型(Int、Long) (2)String (3)枚举类 (4)KClass (5)其余注解类
尽管Kotlin是基于JVM的编程语言,但在Kotlin中使用反射,须要引用额外的库(kotlin-reflect.jar)
val c = MyClass::class 类引用是一个KClass类型的值
注意,Kotlin的类引用不是一个Java的类引用,要获得Java的类引用,可以使用KClass对象实例的java属性
val c =MyClass::class.java
反射最经常使用的功能之一就是枚举的成员,如类的属性、方法等。
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
复制代码
反射的另一个重要应用就是能够动态调用对象的成员,如成员函数、成员函数、成员属性,所谓的动态调用,就是根据成员名字进行调用,能够动态指定成员的名字,经过::操做符,能够直接返回类的成员
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
复制代码
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区别仍是比较大的,这篇讲的,实际开发中非常有用的,可能将的仍是有限的,毕竟一些知识,还得在实践的更深刻的掌握