💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] ## Kotlin相比于Java | 相比于java | 描述                        | | ------- | ------------------------- | | 方式更多    | 类型检查,除了提供了**is运算符**还提供了**!is运算符** | | 更智能     | 类型检查,会进行变量类型的智能转换         | | 方式更多    | 类型转换,提供了**as运算符**和**as?运算符**      | | 更安全     | 类型转换,使用as?运算符可以避免转换异常     | ## **类型检查(is和!is运算符)** ### 回顾Java中instanceof 在**Java 中,instanceof 运算符用来在运行时检测对象是否是特定类的一个实例**。基本格式如下: ``` boolean result = someObj instanceof Classs ``` * result,返回结果,true表示是Class的实例,false表示不是Class的实例; * someObj,必选项,任意对象; * Class,必选项,任意对象类; 我们编写一个简单案例,回顾下instanceof运算符的使用,参考代码: ~~~ class InstanceOfDemo { public static void main(String[] args) { String str = "hello Kotlin"; getStringLength(str); } public static void getStringLength(Object obj) { if (obj instanceof String) { System.out.println("参数是字符串,长度是:" + ((String) obj).length()); } else { System.out.println("参数不是字符串类型"); } } } ~~~ 运行结果 ``` 参数是字符串,长度是:12 Process finished with exit code 0 ``` 针对以上代码,我们注意第18行,我们把object当做字符串使用,这个地方进行了类型的强制转换。 ### **is运算符和!is运算符** 在Kotlin中,可以使用is运算符用来在运行时检测对象是否是特定类的一个实例。基本格式如下: ~~~ boolean result =someObj is Class ~~~ * result,返回结果,true表示是Class的实例,false表示不是Class的实例; * someObj,必选项,任意对象; * Class,必选项,任意对象类; 我们编写一个简单案例,看看`is`运算符的使用,参考代码: ~~~ fun main(args: Array<String>) { val str = "hello kotlin" getStringLength(str) getStringLength2(str) } fun getStringLength(obj: Any) { if (obj is String) { //直接调用String方法,无需转换 println("字符串的长度是:" + obj.length)//不用强转换,kotlin已完成智能转换 } else { println("参数不是字符串类型") } } fun getStringLength2(obj: Any) { if (obj !is String) { println("参数不是字符串类型") } else { //直接调用String方法,无需转换 println("字符串的长度是:" + obj.length)//不用强转换,kotlin已完成智能转换 } } ~~~ 运行结果 ``` 字符串的长度是:12 字符串的长度是:12 Process finished with exit code 0 ``` 针对以上代码第4行代码,直接使用了String类的length方法。`!is`是一个与is操作符相对应的操作符,也就是类型不匹配的意思。我们直接参考代码,如上面代码所示。 针对以上代码第2行使用了`!is`运算符。第6行代码,直接使用了String类的length方法。 * **PS**:如果一个不可变的局部变量或属性已经判断出为某类型,那么检测后的分支中可以直接当作该类型使用,无需显式转换: ``` fun getLength(obj: Any): Int? { var result = 0 if (obj is String) { // `obj` 在该条件分支内自动转换成 `String` println(obj::class) //class java.lang.String result = obj.length println(result) } // 在离开类型检测分支后,`obj` 仍然是 `Any` 类型 println(obj::class) // class java.lang.Object return result } ``` ### **智能转换** **如果使用了`is`或者`!is`运算符,Kotlin就会对类型进行智能转换,不用像Java还需要进行强转类型转换**,我们看到之前的代码,obj没有转换为String类型,直接调用了String类的length方法。 总结起来,在if语句、else语句、逻辑或、逻辑与、when表达式都能感受到is和!is运算符带来的智能转换,参考代码: ~~~ fun testFun(obj: Any) { //if条件中,智能转换 if (obj is String) { obj.length } //else条件中,智能转换 if (obj !is String) { } else { obj.length } //逻辑或运算符右侧,智能转换 if (obj !is String || obj.length == 0) { } //逻辑与运算符右侧,智能转换 if (obj is String && obj.length > 0) { } //when表达式,智能转换 when (obj) { is Int -> print(obj + 1) is String -> print(obj.length + 1) is IntArray -> print(obj.sum()) } } ~~~ ## **强制转换:`as`和`as?`运算符** 我们看到,智能转换后,就可以调用某一个类的特定方法。我们**还可以通过`as`和`as?`运算符强制类型转换**,然后调用对应类的对应方法。 **as运算符(“不安全的”转换操作符)** 通常,如果转换是不可能的,转换操作符会抛出一个异常。因此,我们称之为*不安全的*。 Kotlin 中的不安全转换由中缀操作符*as*(参见[operator precedence](http://www.kotlincn.net/docs/reference/grammar.html#precedence))完成 ``` val x: String = y as String ``` >[info]注意: 一、*null*不能转换为`String`因该类型不是[可空的](http://www.kotlincn.net/docs/reference/null-safety.html), 即如果`y`为空,上面的代码会抛出一个异常。 为了匹配 Java 转换语义,我们必须在转换右边有可空类型,就像`val x: String? = y as String?` 二、as运算符用于执行引用类型的显式类型转换。如果要转换的类型与指定的类型兼容,转换就会成功进行;如果类型不兼容,使用 as? 运算符就会返回值null。 三、在Kotlin中,子类是禁止转换为父类型的。 按照Liskov替换原则,父类转换为子类是对OOP的严重违反,不提倡、也不建议。严格来说,父类是不能转换为子类的,子类包含了父类所有的方法和属性,而父类则未必具有和子类同样成员范围,所以这种转换是不被允许的,即便是两个具有父子关系的空类型,也是如此。 我们参考如下案例: ~~~ fun main(args: Array<String>) { val a = "5" val b = a as String println(b.length) } ~~~ 结果如下 ``` 1 Process finished with exit code 0 ``` 针对以上代码第3行完成了类型的转换,第4行我们在使用String类的length方法的时候IDE没有提示错误。代码也成功的输出了结果。 我们对变量a的值稍作改变转换,再看如下结果: ~~~ fun main(args: Array<String>) { val a = 5 val b = a as String println(b.length) } ~~~ 报错如下 ``` Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at day02.AsDemoKt.main(AsDemo.kt:12) Process finished with exit code 1 ``` 针对以上代码第3行完成了类型的转换,第4行我们在使用String类的length方法的时候IDE同样没有提示错误。但是,代码在运行的时候就提示了类型转换异常。甚至,IDE在as关键字的地方,也有相应的提示,参考截图: ![](https://i.loli.net/2019/04/19/5cb9a53451b96.png) 到这里,我们得到两个结论。 第一,Kotlin里面,Int类型不能使用as转换为String类型。 第二,使用as运算符进行类型转换,如果转换错误,会抛出异常。 **安全转换操作符as?** 至此,我们知道使用as运算符进行类型转换,和常规的Java 类型转换一样,如果被转换的值不是你试图转换的类型,就会抛出ClassCastException 异常,当然可以结合is 检查来确保这个值拥有合适的类型。但是作为一种安全简洁的语言, Kotlin 没有更优雅的解决方案吗?当然有。使用as?,`as?`运算符尝试把值转换成指定的类型, 如果值不是合适的类型就返回null ![](https://box.kancloud.cn/0199aac75ac7467ff784be69544d7150_488x147.png) “as?”运算符,是kotlin提供的安全的类型运算符,使用“as?”进行类型转换的时候,如果转换失败,则会返回null,而不会抛出异常,参考代码: ~~~ fun main(args: Array<String>) { val a = 5 val b = a as? String println(b?.length) } ~~~ 运行结果 ``` null Process finished with exit code 0 ``` 针对以上代码第3行进行类型转换的时候使用了安全转换符“?.”。通过运行结果,我们得到结论,Int类型变量不能通过安全转换符“as?”转换为String类。但是Int确实无法转String,所以返回了null。 **使用安全转换实现equals** ~~~ package A基础 /** *使用安全转换实现equals * 安全转换as?和Elvis运算符结合使用 */ class Student(val firstName:String,val lastName:String){ override fun equals(other: Any?): Boolean { //检查类,如果匹配就返回false val otherStudent=other as? Student ?:return false //在安全转换之后,变量otherStudent被智能地转换为Student类型 return otherStudent.firstName== firstName &&otherStudent.lastName== lastName } override fun hashCode(): Int =firstName.hashCode()*37+lastName.hashCode() } fun main(args: Array<String>) { val s1=Student("Alex","Wang") val s2=Student("Alex","Wang") println(s1==s2)//运算符会调用"equals"方法 println(s1.equals(s2)) println(s1.equals(42)) } ~~~ 运行结果 ``` true true false Process finished with exit code 0 ``` 使用这种模式,可以非常容易地检查实参是否是适当的类型,转换它,并在它的类型不正确时返回false ,而且这些操作全部在同一个表达式中。当然,这种场景下智能转换也会生效:当你检查过类型并拒绝了null 值,编译器就确定了变量otherStudent值的类型是Student并让你能够相应地使用它。