异常处理
-
与Java的异常处理机制相比,Koltin抛弃了checked异常,相当于所有异常都是runtime异常,这意味着开发者想要捕获异常就捕获,不想捕获异常也行,不需要使用throws关键字声明抛出异常。
-
如果在执行try块中的业务逻辑代码时出现了异常,系统将自动生成一个异常对象,该异常对象会被提交到运行时环境,这个过程被称为抛出异常。
-
除非在try块、catch块中调用了退出虚拟机(System.exit(1))的方法,否则不管在try块、还是catch块中执行怎样的代码,出现怎样的情况,异常处理的finally块总会被执行。
-
如果try或catch块中包含return或throw语句,finally块中不包含return或throw语句,则只有当finally块执行完毕之后,系统才会再次跳回来执行try或catch块中的return或throw语句。
如果在finally块中也使用了return或throw等导致方法终止的语句,finally块已经中止了方法,系统将不会跳回去执行try块、catch块中的任何代码。
注意:尽量避免在finally块中使用return或throw等导致方法中止的语句。否则可能出现一些奇怪的情况。
-
Kotlin提供了kotlin.Throwable类,所有的异常类都是Throwable的子类。在进行异常捕获时,一定要记住先捕获小异常,再捕获大异常。
-
与if语句类似,Kotlin的try语句也是表达式,因此try语句也可用于对变量赋值。
try表达式的返回值是try块中的最后一个表达式的值,或是被执行的catch块中的最后一个表达式的值,finally块中的内容不会影响表达式的结果。
val a:Int? = try {Integer.parseInt(input)} catch(e:NPE){null}
上述表达式的值有两个:整数值或null
-
与Java类似,Kotlin允许程序自行抛出异常,使用throw语句完成
由于Kotlin没有checked异常(即使某个异常在Java中原本是checked异常,在Kotlin中它也不是checked异常),因此Kotlin抛出异常的语句无需放在try块中,程序即可以显式使用try...catch来捕获并处理该异常,也可以完全不理会该异常,把异常交给该方法的调用者处理。
-
自定义异常类
自定义的异常类都应该继承自Exception基类,通常需要提供两个构造器:无参构造器和带一个字符串参数的构造器,该字符串将作为该异常对象的描述信息。
-
异常链
异常链通过cause对象来实现的。Kotlin的Throwable类及其子类在构造器中都可以接受一个cause对象作为参数。这个cause就用来表示原始异常,这样可以把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,我们也能通过这个异常链追踪到异常最初发生的位置。
fun calSal(){
try{
}catch(sqle:SQLException){
.......
throw SalException(sqle)
}catch(e:Exception){
......
throw SalException(e)
}}
class SalException:Exception{
constructor(){}
constructor(msg:String):super(msg){}
constructor(t:Throwable):super(t){}
}
-
Kotlin的throw语句也是表达式,但由于throw表达式的类型比较特殊,是nothing类型。Nothing表示程序无法真正得赋值到表达式。
val name:String = user.name?:throw NPE("目标对象不能为null")
如果user.name为null,程序将会抛出异常,不会对name赋值,故可将name声明为String类型。
泛型
Kotlin提供了泛型支持。对于简单的泛型类、泛型函数的定义,Kotlin和Java的差别不大。
Kotlin支持声明处型变和使用处型变。而Java只支持使用处型变。
Kotlin和Java一样,都无法在运行时保留泛型信息。类型擦出。
1、由于Kotlin存在类型推断,因此下面三种泛型的使用的都是正确的:
open class Apple<T>{
open var info:T?
constructor(){
info = null
}
constructor(info:T){
this.info = info
}}
var a1:Apple<String> = Apple<String>()//实际的类型,显式传入
var a2:Apple<Int> = Apple(3)// 声明变量时指定
var a3:Apple = Apple(3.3)//完全不指定类型参数的实际类型,而由系统根据参数进行推断
2、从泛型类派生子类
当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类,或者派生子类。当使用这些接口和父类时,不能再包含泛型形参。比如如下示例就是错误的:
class A :Apple<T>()
在定义类、接口、方法时可以声明泛型形参,但是在使用类、接口、方法时应该为泛型形参传入实际的类型。
class A :Apple<String>(){}
与Java不同,Kotlin要求始终为泛型参数明确指定类型,而不管是通过显式指定还是让系统推断。
3、型变
Java的泛型是不支持型变的,Java采用通配符来解决这个问题的。而Kotlin则采用安全的型变代替了Java中的通配符。
回顾:
Collection<? extends E>:集合中的元素至少是E的子类,但程序不能向集合中添加元素(传入对象);《==通配符上限 ----- 泛型协变 -- 从中取出对象是安全的,但传入是不可靠的
Collection<? super E>:集合中的元素一定是E的父类,比如E是Integer,则集合中的元素可能是Number,也可能是Object。因此程序总可以向集合中传入元素,但是取出对象是不安全的;《==通配符下限 ---- 泛型逆变 -- 向其中传入对象是安全的,但取出对象是不可靠的
Kotlin抛弃了通配符的语法,而利用in、out来让泛型支持型变。
3.1、声明处型变
Kotlin处理泛型型变的规则:
-
如果泛型只需要出现在方法的返回值声明中(不出现在形参声明中),那么该方法就只是取出泛型对象,因此该方法就支持泛型协变。(相当于通配符上限)。如果整个类的所有方法都支持泛型协变,那么该类的泛型参数可使用out修饰。
-
如果泛型只需要出现在方法的形参声明中(不出现在返回值声明中),那么该方法就只是传入泛型对象,因此该方法就支持泛型逆变。(相当于通配符下限)。如果整个类的所有方法都支持泛型逆变,那么该类的泛型参数可使用in修饰。
class User<out T>{//T只能出现在返回值中,而不能出现在方法的形参中
val info:T//不能使用var,var由setter方法,会导致T出现在形参中
constructor(val info:T){
this.info = info
}
fun test():T{
return info
}}
==>
var u = User<String>(“”)
var u2:User<Any> = u//由于程序声明了T支持协变,因此可以将User<String>当成User<Any>使用。即一旦声明了泛性类支持协变,程序即可安全的将User<String>、User<Int>赋值给User<Any>,只要尖括号中的类型是Any的子类即可。逆变的例子不再举了。
上面定义的User类在声明时使用out(或in)指定泛型支持型变,因此这种方式被称为“声明处型变”。
3.2、使用处型变:类型投影
声明处型变虽然方便,但是有一个限制:要么该类的所有方法都只用泛型声明返回值类型(out);要么所有方法都只用泛型声明形参类型(in);
如果一个类中有的方法使用泛型声明返回值类型,有的方法使用泛型声明形参类型。那么就不能使用声明处泛型了。此时,可以使用“使用处泛型”。
所谓的使用处泛型就是在使用泛型时对其使用out或in修饰。
比如fun copy(from:Array<out Any>,to:Array<Any>),from使用了使用处协变,也就是说from的参数可以是Array<Int>、Array<String>等,只要尖括号里面的类型是Any的子类即可。即相当于Java的通配符上限。
再比如 fun fill(dest:Array<in String>,value:String),dest使用处逆变,也就是说dest参数可以是Array<Any>、Array<CharSequence>等类型,只要尖括号中的类型是String的父类即可。
其实使用处型变相当于以前Java中的如下方法参数形式:==Java中的使用处型变
private void copy(Array<? extends Object> from,Array<Object> to)
ptivate void fill(Array<? super String> dest,String value)
3.3、星号投影
星号投影是为了处理Java的原始类型,比如下面的Java代码:
ArrayList list = new ArrayList();
虽然Java的List、ArrayList都有泛型声明,但程序并没有为它们传入类型参数,这在Java中是允许的,但是在Kotlin中要写成如下形式:
//<*>必不可少,相当于Java的原始类型
var list:ArrayList<*> = arrayListOf(1,"kotlin")
==》示例说明:
Foo<out T>,则Foo<*>相当于Foo<out Any?>
Foo<in T>,则Foo<*>相当于Foo<in Nothing>
4、泛型函数
fun <T,S> 函数名(形参列表):返回值类型{
//函数体
}
在fun和函数名之间,进行泛型声明,这样即可在函数的形参声明或返回值中使用T,S来代表类型了。
fun <T> test(t:T){
println(t)
}
泛型函数支持扩展函数,如下:
fun <T> T.toBookString():String{
.....
}
由于T可以是任意的类型,这相当于为所有的类型扩展了一个toBookString方法。
4.1、具体化类型参数
Kotlin允许在内联函数中使用reified形参修饰泛型形参,这样即可将该泛型形参变成一个具体化的类型参数。即在函数中可以直接将T当成普通类型。
inline fun <reified T> findData():T{
for(ele in db){
//在函数中将T作为普通类型
if(ele is T){
return ele
}}
return null
}
如果不这么做,那我们可以需要这么写:
fun <T> findData(clazz:Class<T>):T?{
for(ele in db){
if(clazz.isInstanceOf(ele)){
return ele as? T
}}
return null
}
5、设定形参上限
class Apple<T:Number>//表示T的类型为Number或者Number的子类,相当于java的class Apple<T extends String>。
此处有一种比较极端的情况,需要为类型形参设定多个上限(至多一个父类上限,可以是多个接口上限)
class Apple<T> where T :Compareable<T> ,T : Cloneable{}
fun <T> cloneWhenGreater(list:List<T>,threshold:T) where T :Compareable<T> ,T : Cloneable{}