1、什么是委托
现实生活中经常遇到委托别人帮忙做某事,软件编程成也有此场景,委托的语义是将一个类的一部分或者全部实现委托给另一个类来实现。
kotlin委托的作用在于把类的实现委托给另一个类来实现;java中的代理是代理类代理另一个类实现其实现。一个是使用其他类帮自己实现,一个是帮自己其他类实现。
2、语法及使用
2.1、语法
类的委托声明语法如下:
interface Base {
[params/funs]
}
class BaseImpl : Base[,suerlist....] {
[override list]
[params/funs]
}
class Derived(b: Base) : Base by b[,superlist] {
[override list]
[params/funs]
}
语义上是将Derived类的Base的实现部分委托给对象b实现,也就是Derived可以不用实现Base的成员,当Derived调用Base的成员时都是委托给委托类实例对象b来实现。
注意:
- 委托只能对接口进行委托,不能对类
- 如果被委托的类Derived实现重写了接口的成员,则调用被委托的类Derived重写成员时,调用的则是被委托的类Derived重写的成员,而不会委托给委托类实现。
2.2、使用举例
interface Base {
val str: String
fun print()
}
class BaseImpl: Base {
override val str: String = "abc"
override fun print() {
println(str)
}
}
class TDerived(b: Base) : Base by b { //将Base的实现委托给b实现
override val str = "derived" //重写了str,所以TDerived调用str时调用的是TDerived重写的str,而不是委托给b
}
class DerivedClient() {
fun main() {
val impl = BaseImpl()
val derived = TDerived(impl) //impl作为TDerived的Base委托对象
derived.print() //调用derived.print()实际委托给b即BaseImpl的print方法,所以会打印出abc
println(derived.str) //TDerived重写了str,所以此处调用str时调用的是TDerived重新的str,而不是委托给b,所以此处打印的是derived
}
}
3、原理
此章节我们以2.2中的举例为按理分析其原理
源码中被委托的类TDerived并没有实现接口,内部也没有像代理一样调用代理来实现,它们是怎么委托给另一个对象来实现的呢?我们先来分析TDerived类编译之后的字节码:
//实现了Base 接口
public final class com/java/test/kt/TDerived implements com/java/test/kt/Base {
// compiled from: TDerived.kt
//省略.....
//重写Base接口中的str属性,并赋予derived字符串作为其值
// access flags 0x12
private final Ljava/lang/String; str = "derived"
@Lorg/jetbrains/annotations/NotNull;() // invisible
//声明Base的委托对象$$delegate_0
// access flags 0x1012
private final synthetic Lcom/java/test/kt/Base; $$delegate_0
// access flags 0x1
//生成str 的get方法,返回的是str的值
public getStr()Ljava/lang/String;
@Lorg/jetbrains/annotations/NotNull;() // invisible
L0
LINENUMBER 17 L0
ALOAD 0 //载入第1个局部变量即this到栈顶
//使用栈顶即this对象,获取字段str的值,并放入栈顶
GETFIELD com/java/test/kt/TDerived.str : Ljava/lang/String;
ARETURN //返回栈顶的值,即返回this.str
//省略.....
// access flags 0x1
//生成init方法,用主构造函数的参数b:Base 作为init方法的参数
public <init>(Lcom/java/test/kt/Base;)V
// annotable parameter count: 1 (invisible)
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
ALOAD 1 //将第2个局部变量即init方法参数b:Base载入栈顶
LDC "b" //将常量池中的常量"b"载入栈顶
//调用Intrinsics.checkParameterIsNotNull方法,参数分别是对象b和字符串b,检测对象b是否为空,如果未空,则抛出消息为字符串b空的异常
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 16 L1
ALOAD 0 //将第1个局部变量即this载入栈顶
//调用父类的即Object的init方法
INVOKESPECIAL java/lang/Object.<init> ()V
ALOAD 0 //将第1个局部变量即this载入栈顶
ALOAD 1/将第2个局部变量即init方法参数b:Base载入栈顶
//给字段赋值,即this.$$delegate_0 = b
PUTFIELD com/java/test/kt/TDerived.$$delegate_0 : Lcom/java/test/kt/Base;
L2
LINENUMBER 17 L2
ALOAD 0 //将第1个局部变量即this载入栈顶
LDC "derived" //将常量池中的字符串常量"derived"载入栈顶
//给字段赋值,即this.str = "derived"
PUTFIELD com/java/test/kt/TDerived.str : Ljava/lang/String;
RETURN
//省略.....
//实现Base接口中的print方法
// access flags 0x1
public print()V
L0
ALOAD 0 //将第1个局部变量即this载入栈顶
//获取字段,栈顶即this作为作用对象,获取栈顶this的$$delegate_0属性,并放入栈顶
GETFIELD com/java/test/kt/TDerived.$$delegate_0 : Lcom/java/test/kt/Base;
//调用栈顶对象即this.$$delegate_0的print 方法
INVOKEINTERFACE com/java/test/kt/Base.print ()V (itf)
RETURN
//省略.....
}
根据分析上述TDerived 编译之后的字节码文件,我们得出以下结论:
- 类委托其实会实现接口的所有成员
- 类委托内部会自动生成并保存委托对象
- 类委托未被重写的接口成员方法内部会调用委托对象的对应方法