Koltin - 异常处理、泛型

异常处理

  • 与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{}

猜你喜欢

转载自blog.csdn.net/json_it/article/details/81841003