我们知道在Kotlin里语法糖的存在都是为了解决之前Java某种现有的问题或者是简化代码,之前我们已经讨论了诸多语法糖,了解它们的实现以及如何优化。在我们常用的第三方库中,一个比较常见的东西就是代理模式,但是这个东西写起来略繁琐,好在到了Kotlin这里,在语言层面上支持代理,我们一起来了解下。
首先,什么是代理,代理就是代为处理的意思,我们把要做的事情委托给万事屋,由万事屋来帮我们完成,日常我们请保洁,找代驾,都可以说她们是我们的代理。代理也分为类代理跟代理属性,今天我们主要讨论的是类代理。
为了让大家有个直观的理解,我们来看个小示例:
interface Calculator {
fun calculate():Int
}
然后我们让两个类来实现这个接口:
class CalculatorBrain:Calculator {
override fun calculate(): Int {
TODO("not implemented")
}
}
class SuperCalculator(val calculator: Calculator) : Calculator {
override fun calculate(): Int {
return calculator.calculate()
}
}
在这里,两个类实现了接口并实现了自己的calculate
方法,只不过SuperCalculator
的实现是调用了其它方法。
最后我们就可以这样用啦:
class Main {
fun main(args: Array) {
val calculatorBrain = CalculatorBrain()
val superCalculator = SuperCalculator(calculatorBrain)
superCalculator.calculate()
}
}
在上面例子中,calculatorBrain
就是superCaculator
的代理,任何时候我们让superCalculator
做事它都会转发给calculatorBrain
来做,事实上,任何实现了Calculator
接口的类都可以传给SuperCalculator
,这种把代理对象显式传给其他对象的方式,我们就称之为显式代理
。
显式代理任何面向对象的语言都能实现,而且每次都需要我们实现相同的接口,传入代理对象,然后在重写的方法中调用代理对象的方法,感觉又是一大波重复劳动,而Kotlin在语法层面上使用了by
关键字给我们提供了支持,语言级支持的代理就是隐式代理
。
我们来看看我们上面的例子使用Kotlin的特性改写是什么样的:
class SuperCalculator(val calculator: Calculator) : Calculator by calculator
没错,只要用by
把SuperCalculator
类改成这样就行了!不用再实现接口方法再调用代理对象对应的方法了,老规矩我们来看看字节码:
public final class SuperCalculator implements Calculator {
// access flags 0x12
private final LCalculator; calculator
@Lorg/jetbrains/annotations/NotNull;() // invisible
// access flags 0x11
public final getCalculator()LCalculator;
@Lorg/jetbrains/annotations/NotNull;() // invisible
L0
LINENUMBER 1 L0
ALOAD 0
GETFIELD SuperCalculator.calculator : LCalculator;
ARETURN
L1
LOCALVARIABLE this LSuperCalculator; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public (LCalculator;)V
// annotable parameter count: 1 (visible)
// annotable parameter count: 1 (invisible)
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
ALOAD 1
LDC "calculator"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 1 L1
ALOAD 0
INVOKESPECIAL java/lang/Object. ()V
ALOAD 0
ALOAD 1
PUTFIELD SuperCalculator.calculator : LCalculator;
RETURN
L2
LOCALVARIABLE this LSuperCalculator; L0 L2 0
LOCALVARIABLE calculator LCalculator; L0 L2 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1
public calculate()I
L0
ALOAD 0
GETFIELD SuperCalculator.calculator : LCalculator;
INVOKEINTERFACE Calculator.calculate ()I (itf)
IRETURN
L1
LOCALVARIABLE this LSuperCalculator; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
}
咳咳,换汤不换药,跟我们自己用Java实现代理大体相似,只不过减少了我们的重复劳动,编译器为我们做了实现。可能有些同学看不太明白,我们直接反编译成Java代码看看:
public final class SuperCalculator implements Calculator {
@NotNull
private final Calculator calculator;
@NotNull
public final Calculator getCalculator() {
return this.calculator;
}
public SuperCalculator(@NotNull Calculator calculator) {
Intrinsics.checkParameterIsNotNull(calculator, "calculator");
super();
this.calculator = calculator;
}
public int calculate() {
return this.calculator.calculate();
}
}
这样就比较清楚了,注意生成的字节码只有get方法没有set方法,因为我在构造器里把calculator
声明成val,所以反编译的代码里面也使用final修饰了它。显式代理由于代理对象都是我们自己传入的我们可以自己添加set方法来实现随意替换的手段,那这里我把val改成var,能不能动态替换代理对象呢?我们来试一下:
class SuperCalculator(var calculator: Calculator) : Calculator by calculator
直接来看反编译后的Java代码:
public final class SuperCalculator implements Calculator {
@NotNull
private Calculator calculator;
// $FF: synthetic field
private final Calculator $$delegate_0;
@NotNull
public final Calculator getCalculator() {
return this.calculator;
}
public final void setCalculator(@NotNull Calculator var1) {
Intrinsics.checkParameterIsNotNull(var1, "");
this.calculator = var1;
}
public SuperCalculator(@NotNull Calculator calculator) {
Intrinsics.checkParameterIsNotNull(calculator, "calculator");
super();
this.$$delegate_0 = calculator;
this.calculator = calculator;
}
public int calculate() {
return this.$$delegate_0.calculate();
}
}
我们看到确实多了一个set方法,但是生成的类里面却有两个Calculator
对象,查看生成的calculate方法也可以看到实际起作用的是$$delegate_0
,而调用set方法的时候并不会给它赋值,它使用final来声明。这意味着Kotlin的代理是不支持在运行时动态替换的,这跟我们自己实现显式代理有些区别。也建议大家在用kotlin写代理时,构造器里使用val,避免编译器给我们生成无谓的字段跟方法,减少开销。
假设我们这时候又多了一个新型的量子计算器:
class QuantumCalculator(val calculator: Calculator) : Calculator by calculator
我们也可以这样使用它了:
fun main(args: Array) {
val calculatorBrain = CalculatorBrain()
val superCalculator = SuperCalculator(calculatorBrain)
superCalculator.calculate()
val calculatorBrain1 = CalculatorBrain()
val quantumCalculator = QuantumCalculator(calculatorBrain1)
quantumCalculator.calculate()
}
在这里我们每次创建一个新的Calculator
对象,就会新创建一个CalculatorBrain
对象,但是在这个场景下,传入的代理对象行为都是一样的,都是CalculatorBrain
,为了节约宝贵的内存,我们可以复用一个已有的CalculatorBrain
,在创建其他对象需要时使用它即可:
fun main(args: Array) {
val calculatorBrain = CalculatorBrain()
val superCalculator = SuperCalculator(calculatorBrain)
superCalculator.calculate()
val quantumCalculator = QuantumCalculator(calculatorBrain)
quantumCalculator.calculate()
}
像我们这里,我们一开始就知道对于SuperCalculator
跟QuantumCalculator
这些类,我们不会传入CalculatorBrain
外的其他对象作为代理,那每次使用时还要再传入代理对象,而且是相同的代理对象,也很繁琐,也算得上是样板代码,我们可以直接创建个CalculatorBrain
的单例:
object CalculatorBrain: Calculator{
override fun calculate(): Int {
TODO("not implemented")
}
}
然后直接集成到这些类的声明里面:
class SuperCalculator() : Calculator by calculatorBrain
class QuantumCalculator() : Calculator by calculatorBrain
看,代码更加简洁明了了。
我们来看看反编译后Quantum
的Java代码:
public final class QuantumCalculator implements Calculator {
// $FF: synthetic field
private final CalculatorBrain $$delegate_0;
public QuantumCalculator() {
this.$$delegate_0 = CalculatorBrain.INSTANCE;
}
public int calculate() {
return this.$$delegate_0.calculate();
}
}
我们可以看到,在默认构造函数里,我们给一个用final声明的代理赋值了,而值来自于CalculatorBrain.INSTANCE
,从命名来看,这是一个单例,我们这就来看看它的代码:
public final class CalculatorBrain implements Calculator {
public static final CalculatorBrain INSTANCE;
public int calculate() {
String var1 = "not implemented";
throw (Throwable)(new NotImplementedError("An operation is not implemented: " + var1));
}
private CalculatorBrain() {
}
static {
CalculatorBrain var0 = new CalculatorBrain();
INSTANCE = var0;
}
}
源码面前无秘密,这确实是一个单例。
现在,我们可以更简单地使用这些类了:
fun main(args: Array) {
SuperCalculator().calculate()
QuantumCalculator().calculate()
}
好了,关于类代理就探究这么多了。我们大多都知道组合优于继承,其实代理也是继承很好的替代方案,而且很灵活。通过本次学习大家应该都明白了类代理的使用方法,下一次,我们一起来扒一扒代理属性。