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.