伴生对象
在 Kotlin 中并不没有 static
这个关键字,该如何处理呢?这里需要用到 Kotlin 的伴生对象来处理。
类内部的对象声明可以用 companion 关键字标记:
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
该伴生对象的成员可通过只使用类名作为限定符来调用:
val instance = MyClass.create()
可以省略伴生对象的名称,在这种情况下将使用名称 Companion
:
class MyClass {
companion object { }
}
val x = MyClass.Companion
伴生对象的作用
类似于 Java 中使用类访问静态成员的语法。因为 Kotlin 取消了 static
关键字,所以 Kotlin 引入伴生对象来弥补没有静态成员的不足。可见,伴生对象的主要作用就是为其所在的外部类模拟静态成员。
在 Java 代码中调用伴生对象
如何在 Java 代码中调用 Kotlin 的伴生对象呢?
public static void main(String[] args) {
System.out.println(MyClass.Factory.create());
System.out.println(MyClass.Companion.create());
}
-
如果声明伴生对象有名称,则使用:
类名.伴生对象名.方法名() 类名.半生对象名.属性的setter,getter方法
-
如果声明伴生对象无名称,则采用 Companion 关键字调用:
类名.Companion.方法名() 类名.Companion.属性的setter,getter方法
@JvmField 和 @JvmStatic 的使用
在上面的例子中,我们知道了可以在 Java 代码中调用 Kotlin 中伴生对象的成员,类似于 Java 类中的静态成员。但是看上去和 Java 中的还是略有区别,因为类名和方法名/属性setter,getter方法名之间多了个伴生对象的名称或者 Companion 关键字。如何使其在调用的时候与 Java 中的调用看上去一样呢?
Kotlin 为我们提供了 @JvmField 和 @JvmStatic 两个注解。@JvmField 使用在属性上,@JvmStatic 使用在方法上。如:
class Test {
companion object {
@JvmField
val flag = true
@JvmStatic
fun add(a: Int, b: Int): Int {
return a + b
}
}
}
这样我们在 Java 代码中调用的时候就和 Java 类调用静态成员的形式一致了,Kotlin 代码调用方式不变:
System.out.println(Test.flag);
System.out.println(Test.add(1, 2));
const 关键字
在伴生对象中,我们可能需要声明一个常量,目的是等同于 Java 中的静态常量。有两种方式,一种是上面所提到的使用 @JvmField
注解,另一种则是使用 const
关键字修饰。这两种声明方式都等同于 Java 中 static final 所修饰的变量。如下代码:
companion object {
const val flag = true
@JvmStatic
fun add(a: Int, b: Int): Int {
return a + b
}
}
扩展属性和扩展方法
扩展函数
Kotlin的扩展函数可以让你作为一个类成员进行调用的函数,但是是定义在这个类的外部。这样可以很方便的扩展一个已经存在的类,为它添加额外的方法
下面我们为String添加一个toInt的方法
package com.binzi.kotlin
fun String?.toInt(): Int {
return java.lang.Integer.parseInt(this)
}
在这个扩展函数中,你可以直接访问你扩展的类的函数和属性,就像定义在这个类中的方法一样,但是扩展函数并不允许你打破封装。跟定义在类中方法不同,它不能访问那些私有的、受保护的方法和属性。
扩展函数的导入
我们直接在包里定义扩展函数。这样我们就可以在整个包里面使用这些扩展,如果我们要使用其他包的扩展,我们就需要导入它。导入扩展函数跟导入类是一样的方式。
import com.binzi.kotlin.toInt
或者
import com.binzi.kotlin.*
有时候,可能你引入的第三方包都对同一个类型进行了相同函数名扩展,为了解决冲突问题,你可以使用下面的方式对扩展函数进行改名
import com.binzi.kotlin.toInt as toInteger
扩展函数不可覆盖
扩展方法的原理
Kotlin 中类的扩展方法并不是在原类的内部进行拓展,通过反编译为Java代码,可以发现,其原理是使用装饰模式,对源类实例的操作和包装,其实际相当于我们在 Java中定义的工具类方法,并且该工具类方法是使用调用者为第一个参数的,然后在工具方法中操作该调用者
如:
fun String?.toInt(): Int {
return java.lang.Integer.parseInt(this)
}
反编译为对应的Java代码:
public final class ExtsKt {
public static final int toInt(@Nullable String $this$toInt) {
return Integer.parseInt($this$toInt);
}
}
扩展属性
类的扩展属性原理其实与扩展方法是一样的,只是定义的形式不同,扩展属性必须定义get和set方法
为MutableList扩展一个firstElement属性:
var MutableList.firstElement: T
get() { //扩展属性的get函数
return this[0]
}
set(value) { //扩展属性的set函数
this[0] = value
}
反编译后的java代码如下:
public static final Object getFirstElement(@NotNull List $this$firstElement) {
Intrinsics.checkParameterIsNotNull($this$firstElement, "$this$firstElement");
return $this$firstElement.get(0);
}
public static final void setFirstElement(@NotNull List $this$firstElement, Object value){
Intrinsics.checkParameterIsNotNull($this$firstElement, "$this$firstElement");
$this$firstElement.set(0, value);
}
内部类
kotlin的内部类与java的内部类有点不同java的内部类可以直接访问外部类的成员,kotlin的内部类不能直接访问外部类的成员,必须用inner标记之后才能访问外部类的成员
-
没有使用inner标记的内部类
class A{ var a = 0 class B{ //B类的内部是不能直接用a变量的 var b = 1 } }
反编译后的java代码
public final class A { private int a; public final int getA() { return this.a; } public final void setA(int var1) { this.a = var1; } public static final class B { private int b = 1; public final int getB() { return this.b; } public final void setB(int var1) { this.b = var1; } } }
-
用inner标记的内部类
class A{ var a = 0 inner class B{ //B类的内部可以直接用a变量 var b = a } }
反编译后的java代码
public final class A { private int a; public final int getA() { return this.a; } public final void setA(int var1) { this.a = var1; } public final class B { private int b = A.this.getA(); public final int getB() { return this.b; } public final void setB(int var1) { this.b = var1; } } }
从上面可以看出,没有使用inner标记的内部类最后生成的是静态内部类,而使用inner标记的生成的是非静态内部类
匿名内部类
匿名内部类主要是针对那些获取抽象类或者接口对象而来的。最常见的匿名内部类View点击事件:
//java,匿名内部类的写法
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
}
});
上面这个是java匿名内部类的写法,kotlin没有new关键字,那么kotlin的匿名内部类该怎么写呢?
btn.setOnClickListener(object : View.OnClickListener{
override fun onClick(v: View?) {
print("1111")
}
})
方法的参数是一个匿名内部类,先写object:,然后写你的参数类型View.OnClickListener{}
kotlin还有一个写法lambda 表达式,非常之方便:
btn.setOnClickListener { print("1111") }
数据类
在Java中没有专门的数据类,常常是通过JavaBean来作为数据类,但在Kotlin中提供了专门的数据类。
- Java
public class Person {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
从上面的例子中可以看到,如果要使用数据类,需要手动写相应的setter/getter方法(尽管IDE也可以批量生成),但是从代码阅读的角度来说,在属性较多的情况下,诸多的seeter/getter方法还是不利于代码的阅读和维护。
-
Kotlin
在Kotlin中,可以通过关键字data来生成数据类:data class Person(val name: String, val age: Int)
即在class关键字之前添加data关键字即可。编译器会根据主构造函数中的参数生成相应的数据类。自动生成setter/getter、toString、hashCode等方法
要声明一个数据类,需要满足:
主构造函数中至少有一个参数
主构造函数中所有参数需要标记为val或var
数据类不能是抽象、开发、密封和内部的
枚举类
枚举类是一种特殊的类,kotlin可以通过enum class关键字定义枚举类。
- 枚举类可以实现0~N个接口;
- 枚举类默认继承于kotlin.Enum类(其他类最终父类都是Any),因此kotlin枚举类不能继承类;
- 非抽象枚举类不能用open修饰符修饰,因此非抽象枚举类不能派生子类;
- 抽象枚举类不能使用abstract关键字修饰enum class,抽象方法和抽象属性需要使用;
- 枚举类构造器只能使用private修饰符修饰,若不指定,则默认为private;
- 枚举类所有实例在第一行显式列出,每个实例之间用逗号隔开,整个声明以分号结尾;
- 枚举类是特殊的类,也可以定义属性、方法、构造器;
- 枚举类应该设置成不可变类,即属性值不允许改变,这样更安全;
- 枚举属性设置成只读属性后,最好在构造器中为枚举类指定初始值,如果在声明时为枚举指定初始值,会导致所有枚举值(或者说枚举对象)的该属性都一样。
定义枚举类
/**
* 定义一个枚举类
*/
enum class EnumClass(val enumParam: String) {
MON("星期一"), TUES("星期二"), WED("星期三");//逗号隔开,分号结尾
/**
* 枚举类方法
*/
fun enumFun() {
println("枚举值:$this 枚举属性:$enumParam")
}
}
枚举类实现接口
-
枚举值分别实现接口的抽象成员
enum class EnumClass(val enumParam: String) : EnumInterface { MON("星期一") { override fun interfaceFun() { println(enumParam) } override val interfaceParam: String get() = "1" }, TUES("星期二") { override fun interfaceFun() { println(enumParam) } override val interfaceParam: String get() = "2" }, WED("星期三") { override fun interfaceFun() { println(enumParam) } override val interfaceParam: String get() = "3" };//逗号隔开,分号结尾 } interface EnumInterface { fun interfaceFun() val interfaceParam : String }
-
枚举类统一实现接口的抽象成员
enum class EnumClass(val enumParam: String) : EnumInterface { MON("星期一"), TUES("星期二"), WED("星期三");//逗号隔开,分号结尾 override fun interfaceFun() { } override val interfaceParam: String get() = { } }
-
分别实现抽象枚举类抽象成员
enum class AbstractEnumClass { MON { override val abstractParam: String get() = "星期一" override fun abstractFun() { println(abstractParam) } }, TUES { override val abstractParam: String get() = "星期二" override fun abstractFun() { println(abstractParam) } }, WED { override val abstractParam: String get() = "星期三" override fun abstractFun() { println(abstractParam) } }; abstract val abstractParam: String abstract fun abstractFun() }
委托
委托模式 是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。委托模式是一项基本技巧,许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式。委托模式使得我们可以用聚合来替代继承。
Java中委托:
interface Printer {
void print(String s)
}
class RealPrinter implements Printer {
@override
public void print(String s) {
System.out.println(s);
}
}
class PrintImpl implements Printer{
// 委托对象
RealPrinter rp = new RealPrinter();
@override
public void print(String s) {
rp.print(s);
}
}
class Demo {
public static void main(String[] args) {
Printer p = new PrintImpl();
p.print("hello world");
}
}
Kotlin:
interface Printer {
fun print(s: String)
}
class RealPrinter : Printer {
override fun print(s: String) {
println(s)
}
}
class PrintImpl(p: Printer) : Printer by p
fun main(args: Array) {
val ps = PrintImpl(RealPrinter())
ps.print("hello world")
}
by
表示 p 将会在 PrintImpl 中内部存储, 并且编译器将自动生成转发给 p 的所有 Printer 的方法。
委托属性
有一些常见的属性类型,虽然我们可以在每次需要的时候手动实现它们, 但是如果能够为大家把他们只实现一次并放入一个库会更好。例如包括:
- 延迟属性(lazy properties): 其值只在首次访问时计算;
- 可观察属性(observable properties): 监听器会收到有关此属性变更的通知;
- 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。
为了涵盖这些(以及其他)情况,Kotlin 支持 委托属性 。
委托属性的语法是:
val/var <属性名>: <类型> by <表达式>
在 by 后面的表达式是该 委托, 因为属性对应的 get()(和 set())会被委托给它的 getValue() 和 setValue() 方法。
标准委托:
Kotlin 标准库为几种有用的委托提供了工厂方法。
- 延迟属性 Lazy
lazy() 接受一个 lambda 并返回一个 Lazy
val lazyValue: String by lazy {
println("computed!")
"hello"
}
fun main(args: Array) {
println(lazyValue)
println(lazyValue)
}
// 输出:
// computed!
// hello
// hello
- 可观察属性 Observable
Delegates.observable() 接受两个参数:初始值和修改时处理程序(handler)。 每当我们给属性赋值时会调用该处理程序(在赋值后执行)。它有三个参数:被赋值的属性、旧值和新值:
class User {
var name: String by Delegates.observable("") {
prop, old, new ->
println("$old -> $new")
}
}
fun main(args: Array) {
val user = User()
user.name = "first"
user.name = "second"
}
// 输出
// -> first
// first -> second
如果想拦截赋的新值,并根据你是不是想要这个值来决定是否给属性赋新值,可以使用 vetoable() 取代 observable(),接收的参数和 observable 一样,不过处理程序 返回值是 Boolean 来决定是否采用新值,即在属性被赋新值生效之前 会调用传递给 vetoable 的处理程序。例如:
class User {
var num: Int by Delegates.vetoable(0) { property, oldValue, newValue ->
newValue > oldValue
}
}
fun main(args: Array) {
val user = User()
user.num = 10
println(user.num) // 10>0, 所以接受新值,输出 10
user.num = 5
println(user.num) // 5<10, 处理程序返回false,拒绝新值,输出 10
}
- 把属性存在map 中
一个常见的用例是在一个映射(map)里存储属性的值。 这经常出现在像解析 JSON 或者做其他“动态”事情的应用中。 在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。
例如:
class User(map: Map) {
val name: String by map
val age: Int by map
}
fun main(args: Array) {
val user = User(mapOf("name" to "whx", "age" to 18))
println(user.name) // whx
println(user.age) // 18
}
在上例中,委托属性会从构造函数传入的map中取值(通过字符串键——属性的名称),如果遇到声明的属性名在map 中找不到对应的key 名,或者key 对应的value 值的类型与声明的属性的类型不一致,会抛出异常。
内联函数
当一个函数被声明为inline
时,它的函数体是内联的,也就是说,函数体会被直接替换到函数被调用地方
inline函数(内联函数)从概念上讲是编译器使用函数实现的真实代码来替换每一次的函数调用,带来的最直接的好处就是节省了函数调用的开销,而缺点就是增加了所生成字节码的尺寸。基于此,在代码量不是很大的情况下,我们是否有必要将所有的函数定义为内联?让我们分两种情况进行说明:
- 将普通函数定义为内联:众所周知,JVM内部已经实现了内联优化,它会在任何可以通过内联来提升性能的地方将函数调用内联化,并且相对于手动将普通函数定义为内联,通过JVM内联优化所生成的字节码,每个函数的实现只会出现一次,这样在保证减少运行时开销的同时,也没有增加字节码的尺寸;所以我们可以得出结论,对于普通函数,我们没有必要将其声明为内联函数,而是交给JVM自行优化。
- 将带有lambda参数的函数定义为内联:是的,这种情况下确实可以提高性能;但在使用的过程中,我们会发现它是有诸多限制的,让我们从下面的例子开始展开说明:
inline fun doSomething(action: () -> Unit) {
println("Before doSomething...")
action()
println("After doSomething...")
}
假如我们这样调用doSomething
:
fun main(args: Array) {
doSomething {
pringln("Hello World")
}
}
上面的调用会被编译成:
fun main(args: Array) {
println("Before doSomething...")
println("Hello World")
println("After doSomething...")
}
从上面编译的结果可以看出,无论doSomething
函数还是action
参数都被内联了,很棒,那让我们换一种调用方式:
fun main(args: Array) {
val action:() -> Unit = { println("Hello World") }
doSomething(action)
}
上面的调用会被编译成:
fun main(args: Array) {
println("Before doSomething...")
action()
println("After doSomething...")
}
doSomething
函数被内联,而action
参数没有被内联,这是因为以函数型变量的形式传递给doSomething
的lambda在函数的调用点是不可用的,只有等到doSomething被内联后,该lambda才可以正常使用。
通过上面的例子,我们对lambda表达式何时被内联做一下简单的总结:
- 当lambda表达式以参数的形式直接传递给内联函数,那么lambda表达式的代码会被直接替换到最终生成的代码中。
- 当lambda表达式在某个地方被保存起来,然后以变量形式传递给内联函数,那么此时的lambda表达式的代码将不会被内联。
上面对lambda的内联时机进行了讨论,消化片刻后让我们再看最后一个例子:
inline fun doSomething(action: () -> Unit, secretAction: () -> Unit) {
action()
doSomethingSecret(secretAction)
}
fun doSomethingSecret(secretAction: () -> Unit) {
}
上面的例子是否有问题?是的,编译器会抛出“Illegal usage of inline-parameter”的错误,这是因为Kotlin规定内联函数中的lambda参数只能被直接调用或者传递给另外一个内联函数,除此之外不能作为他用;那我们如果确实想要将某一个lambda传递给一个非内联函数怎么办?我们只需将上述代码这样改造即可:
inline fun doSomething(action: () -> Unit, noinline secretAction: () -> Unit) {
action()
doSomethingSecret(secretAction)
}
fun doSomethingSecret(secretAction: () -> Unit) {
}
很简单,在不需要内联的lambda参数前加上noinline
修饰符就可以了。
以上便是我对内联函数的全部理解,通过掌握该特性的运行机制,相信大家可以做到在正确的时机使用该特性,而非滥用或因恐惧弃而不用。
Kotlin下单例模式
饿汉式实现
//Java实现
public class SingletonDemo {
private static SingletonDemo instance=new SingletonDemo();
private SingletonDemo(){
}
public static SingletonDemo getInstance(){
return instance;
}
}
//Kotlin实现
object SingletonDemo
懒汉式
//Java实现
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo(){}
public static SingletonDemo getInstance(){
if(instance==null){
instance=new SingletonDemo();
}
return instance;
}
}
//Kotlin实现
class SingletonDemo private constructor() {
companion object {
private var instance: SingletonDemo? = null
get() {
if (field == null) {
field = SingletonDemo()
}
return field
}
fun get(): SingletonDemo{
//细心的小伙伴肯定发现了,这里不用getInstance作为为方法名,是因为在伴生对象声明时,内部已有getInstance方法,所以只能取其他名字
return instance!!
}
}
}
上述代码中,我们可以发现在Kotlin实现中,我们让其主构造函数私有化并自定义了其属性访问器,其余内容大同小异。
- 如果有小伙伴不清楚Kotlin构造函数的使用方式。请点击 - - - 构造函数
- 不清楚Kotlin的属性与访问器,请点击 - - -属性和字段
线程安全的懒汉式
//Java实现
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo(){}
public static synchronized SingletonDemo getInstance(){//使用同步锁
if(instance==null){
instance=new SingletonDemo();
}
return instance;
}
}
//Kotlin实现
class SingletonDemo private constructor() {
companion object {
private var instance: SingletonDemo? = null
get() {
if (field == null) {
field = SingletonDemo()
}
return field
}
@Synchronized
fun get(): SingletonDemo{
return instance!!
}
}
}
大家都知道在使用懒汉式会出现线程安全的问题,需要使用使用同步锁,在Kotlin中,如果你需要将方法声明为同步,需要添加@Synchronized注解。
双重校验锁式
//Java实现
public class SingletonDemo {
private volatile static SingletonDemo instance;
private SingletonDemo(){}
public static SingletonDemo getInstance(){
if(instance==null){
synchronized (SingletonDemo.class){
if(instance==null){
instance=new SingletonDemo();
}
}
}
return instance;
}
}
//kotlin实现
class SingletonDemo private constructor() {
companion object {
val instance: SingletonDemo by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
SingletonDemo() }
}
}
哇!小伙伴们惊喜不,感不感动啊。我们居然几行代码就实现了多行的Java代码。其中我们运用到了Kotlin的延迟属性 Lazy。
Lazy内部实现
public fun lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
观察上述代码,因为我们传入的mode = LazyThreadSafetyMode.SYNCHRONIZED,
那么会直接走 SynchronizedLazyImpl,我们继续观察SynchronizedLazyImpl。
Lazy接口
SynchronizedLazyImpl实现了Lazy接口,Lazy具体接口如下:
public interface Lazy {
//当前实例化对象,一旦实例化后,该对象不会再改变
public val value: T
//返回true表示,已经延迟实例化过了,false 表示,没有被实例化,
//一旦方法返回true,该方法会一直返回true,且不会再继续实例化
public fun isInitialized(): Boolean
}
继续查看SynchronizedLazyImpl,具体实现如下:
SynchronizedLazyImpl内部实现
private class SynchronizedLazyImpl(initializer: () -> T, lock: Any? = null) : Lazy, Serializable {
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this
override val value: T
get() {
val _v1 = _value
//判断是否已经初始化过,如果初始化过直接返回,不在调用高级函数内部逻辑
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST") (_v2 as T)
}
else {
val typedValue = initializer!!()//调用高级函数获取其返回值
_value = typedValue //将返回值赋值给_value,用于下次判断时,直接返回高级函数的返回值
initializer = null
typedValue
}
}
}
//省略部分代码
}
通过上述代码,我们发现 SynchronizedLazyImpl 覆盖了Lazy接口的value属性,并且重新了其属性访问器。其具体逻辑与Java的双重检验是类似的。
到里这里其实大家还是肯定有疑问,我这里只是实例化了SynchronizedLazyImpl对象,并没有进行值的获取,它是怎么拿到高阶函数的返回值呢?。这里又涉及到了委托属性。
委托属性语法是: val/var <属性名>: <类型> by <表达式>。在 by 后面的表达式是该 委托, 因为属性对应的 get()(和 set())会被委托给它的 getValue() 和 setValue() 方法。 属性的委托不必实现任何的接口,但是需要提供一个 getValue() 函数(和 setValue()——对于 var 属性)。
而Lazy.kt文件中,声明了Lazy接口的getValue扩展函数。故在最终赋值的时候会调用该方法。
@kotlin.internal.InlineOnly
//返回初始化的值。
public inline operator fun Lazy.getValue(thisRef: Any?, property: KProperty<*>): T = value
静态内部类式
//Java实现
public class SingletonDemo {
private static class SingletonHolder{
private static SingletonDemo instance=new SingletonDemo();
}
private SingletonDemo(){
System.out.println("Singleton has loaded");
}
public static SingletonDemo getInstance(){
return SingletonHolder.instance;
}
}
//kotlin实现
class SingletonDemo private constructor() {
companion object {
val instance = SingletonHolder.holder
}
private object SingletonHolder {
val holder= SingletonDemo()
}
}
静态内部类的实现方式,也没有什么好说的。Kotlin与Java实现基本雷同。
补充
在该篇文章结束后,有很多小伙伴咨询,如何在Kotlin版的Double Check,给单例添加一个属性,这里我给大家提供了一个实现的方式。(不好意思,最近才抽出时间来解决这个问题)
class SingletonDemo private constructor(private val property: Int) {
//这里可以根据实际需求发生改变
companion object {
@Volatile private var instance: SingletonDemo? = null
fun getInstance(property: Int) =
instance ?: synchronized(this) {
instance ?: SingletonDemo(property).also { instance = it }
}
}
}
其中关于?:
操作符,如果 ?:
左侧表达式非空,就返回其左侧表达式,否则返回右侧表达式。 请注意,当且仅当左侧为空时,才会对右侧表达式求值。
Kotlin 智能类型转换
对于子父类之间的类型转换
- 先看这样一段 Java 代码
public class Person{
}
public class Student extends Person{
public void study(){
System.out.println("我在学习一门新的语言 Kotlin !");
}
}
public static void main(String[] args){
Person person = new Student();
if(person instanceof Student){
((Student) person).study();
}
}
- 尽管在 main 函数中,对
person
这个对象进行了类型判断,但是在使用的时候还是需要强制转换成Student
类型,这样是不是很不智能? - 同样的情况在 Kotlin 中就变得简单多了
fun main(args: Array) {
val person: Person = Student()
if (person is Student) {
person.study()
}
}
- 在 Kotlin 中,只要对类型进行了判断,就可以直接通过父类的对象去调用子类的函数了
安全的类型转换
- 还是上面的那个例子,如果我们没有进行类型判断,并且直接进行强转,会怎么样呢?
public static void main(String[] args) {
Person person = new Person();
((Student) person).study();
}
- 结果就只能是
Exception in thread "main" java.lang.ClassCastException
- 那么在 Kotlin 中是不是会有更好的解决方法呢?
val person: Person = Person()
val student:Student? =person as? Student
println(student) // null
- 在转换操作符后面添加一个
?
,就不会把程序 crash 掉了,当转化失败的时候,就会返回一个null
在空类型中的智能转换
- 需要提前了解 Kotlin 类型安全的相关知识(Kotlin 中的类型安全(对空指针的优化处理))
val aString: String? = "Hello Kotlin"
if (aString is String) {
println(aString.length)
}
-
aString
在定义的时候定义成了有可能为null
,按照之前的写法,我们需要这样写
val aString: String? = "Hello Kotlin"
println(aString?.length)
- 但是已经进行了是否为
String
类型的判断,所以就一定 不是 空类型了,也就可以直接输出它的长度了
T.()->Unit 、 ()->Unit
在做kotlin开发中,经常看到一些系统函数里,用函数作为参数
public inline fun T.apply(block: T.() -> Unit): T
{
block()
return this
}
public inline fun apply(block: () -> Unit)
{
block()
}
.()-Unit
与()->Unit
的区别是我们调用时,在代码块里面写this,的时候,两个this代表的含义不一样,T.()->Unit
里的this代表的是自身实例,而()->Unit
里,this代表的是外部类的实例