We know in Kotlin that syntactic sugar exists to solve some existing problem in Java or to simplify code. We’ve talked about syntactic sugar before, how they are implemented and how they can be optimized. One of the most common third party libraries we use is the proxy mode, but it is a bit cumbersome to write. Fortunately, Kotlin supports the proxy at the language level, so let’s take a look at it.
First of all, what is the agent, agent is to deal with the meaning, we have to do things entrusted to all the house, by all the house to help us complete, daily we please cleaning, looking for driving, can say that they are our agent. Proxies are also divided into class proxies and proxy properties. Today we will focus on class proxies.
To give you an intuition, let’s look at a small example:
interface Calculator {
fun calculate(a):Int
}
Copy the code
We then let two classes implement this interface:
class CalculatorBrain:Calculator {
override fun calculate(a): Int {
TODO("not implemented")}}class SuperCalculator(val calculator: Calculator) : Calculator {
override fun calculate(a): Int {
return calculator.calculate()
}
}
Copy the code
Here, the two classes implement interfaces and their own Calculate methods, except that the SuperCalculator implementation calls other methods.
Finally we can use it like this:
class Main {
fun main(args: Array<String>) {
val calculatorBrain = CalculatorBrain()
val superCalculator = SuperCalculator(calculatorBrain)
superCalculator.calculate()
}
}
Copy the code
In the example above, calculatorBrain is the superCaculator’s proxy, and whenever we ask superCalculator to do something it’s forwarded to calculatorBrain, in fact, Any class that implements the Calculator interface can be passed to the SuperCalculator. This way of explicitly passing the proxy object to other objects is called an explicit proxy.
Explicit agent any object-oriented language can achieve, and every time we need to implement the same interface, was introduced into proxy objects, and then rewrite method invokes the proxy object, the method of feeling is also a big Bob repeated labor and the Kotlin on grammatical level using the support by provides us with the keywords, agency is implicit language level support.
Let’s see what our example above looks like using Kotlin’s features:
class SuperCalculator(val calculator: Calculator) : Calculator by calculator
Copy the code
That’s right, just change the SuperCalculator class to this with “by”! Instead of implementing the interface method and calling the corresponding method of the proxy object, let’s look at the bytecode:
public final class SuperCalculator implements Calculator {
// access flags 0x12
private final LCalculator; calculator
@Lorg/jetbrains/annotations/NotNull; (a)// invisible
// access flags 0x11
public final getCalculator(a)LCalculator;
@Lorg/jetbrains/annotations/NotNull; (a)// 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<init>(LCalculator;) V// annotable parameter count: 1 (visible)
// annotable parameter count: 1 (invisible)
@Lorg/jetbrains/annotations/NotNull; (a)// invisible, parameter 0
L0
ALOAD 1
LDC "calculator"INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object; Ljava/lang/String;) V L1 LINENUMBER1 L1
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()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(a)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
}
Copy the code
Well, it’s pretty much the same as our own Java proxy implementation, but with less rework, the compiler does the implementation for us. For those of you who don’t understand, let’s decompile into Java code:
public final class SuperCalculator implements Calculator {
@NotNull
private final Calculator calculator;
@NotNull
public final Calculator getCalculator(a) {
return this.calculator;
}
public SuperCalculator(@NotNull Calculator calculator) {
Intrinsics.checkParameterIsNotNull(calculator, "calculator");
super(a);this.calculator = calculator;
}
public int calculate(a) {
return this.calculator.calculate(); }}Copy the code
This makes sense. Note that the generated bytecode only has the get method and not the set method. Because I declared Calculator as val in the constructor, I also modified it with final in the decomcompiled code. Explicit proxy since the proxy objects are passed in by ourselves we can add a set method to implement arbitrary substitution, so I’m going to change val to var, can we dynamically replace the proxy objects? Let’s try it:
class SuperCalculator(var calculator: Calculator) : Calculator by calculator
Copy the code
Let’s get straight to the decompiled Java code:
public final class SuperCalculator implements Calculator {
@NotNull
private Calculator calculator;
// $FF: synthetic field
private finalCalculator ? delegate_0;@NotNull
public final Calculator getCalculator(a) {
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(a);this.? delegate_0 = calculator;this.calculator = calculator;
}
public int calculate(a) {
return this.?delegate_0.calculate();
}
}
Copy the code
We see that there is indeed a set method, but there are two Calculator objects in the generated class. Looking at the generated Calculate method, you can also see what is actually at work. Delegate_0, which is not assigned when the set method is called, is declared using final. This means Kotlin’s proxy does not support dynamic replacement at run time, which is a bit different from implementing explicit proxies ourselves. We also recommend using val in the constructor of kotlin proxies to avoid unnecessary fields and methods generated by the compiler and reduce overhead.
Suppose we had a new quantum calculator:
class QuantumCalculator(val calculator: Calculator) : Calculator by calculator
Copy the code
We can also use it like this:
fun main(args: Array<String>) {
val calculatorBrain = CalculatorBrain()
val superCalculator = SuperCalculator(calculatorBrain)
superCalculator.calculate()
val calculatorBrain1 = CalculatorBrain()
val quantumCalculator = QuantumCalculator(calculatorBrain1)
quantumCalculator.calculate()
}
Copy the code
Here, each time we create a new Calculator object, we create a new CalculatorBrain object, but in this scenario, the behavior of the incoming proxy object is the same, they are all CalculatorBrain. In order to save precious memory, We can reuse an existing CalculatorBrain and use it when we need to create other objects:
fun main(args: Array<String>) {
val calculatorBrain = CalculatorBrain()
val superCalculator = SuperCalculator(calculatorBrain)
superCalculator.calculate()
val quantumCalculator = QuantumCalculator(calculatorBrain)
quantumCalculator.calculate()
}
Copy the code
In our case, we knew from the beginning that for SuperCalculator and QuantumCalculator classes, we would not pass in any other object than CalculatorBrain as a proxy, so we would have to pass in the same proxy object every time we used it, which would be tedious. Also boilerplate code, we can create a CalculatorBrain singleton directly:
object CalculatorBrain: Calculator{
override fun calculate(a): Int {
TODO("not implemented")}}Copy the code
It then integrates directly into the declarations of these classes:
class SuperCalculator() : Calculator by calculatorBrain
class QuantumCalculator() : Calculator by calculatorBrain
Copy the code
Look, the code is much cleaner.
Let’s look at the decomcompiled Java code for Quantum:
public final class QuantumCalculator implements Calculator {
// $FF: synthetic field
private finalCalculatorBrain ? delegate_0;public QuantumCalculator(a) {
this.? delegate_0 = CalculatorBrain.INSTANCE; }public int calculate(a) {
return this.?delegate_0.calculate();
}
}
Copy the code
We can see that in the default constructor, we have assigned a value to a proxy declared with final from CalculatorBrain.INSTANCE, which is a singleton from the naming point of view, so let’s look at its code:
public final class CalculatorBrain implements Calculator {
public static final CalculatorBrain INSTANCE;
public int calculate(a) {
String var1 = "not implemented";
throw (Throwable)(new NotImplementedError("An operation is not implemented: " + var1));
}
private CalculatorBrain(a) {}static {
CalculatorBrain var0 = newCalculatorBrain(); INSTANCE = var0; }}Copy the code
There are no secrets to the source code, and this is really a singleton.
Now we can use these classes more simply:
fun main(args: Array<String>) {
SuperCalculator().calculate()
QuantumCalculator().calculate()
}
Copy the code
Okay, so much for the class proxy. Most of us know that composition is better than inheritance, but proxies are a good and flexible alternative to inheritance. Through this study we should all understand the use of class proxy, next time, we will scratch a scratch proxy attributes.