Kotlin basic syntax 11, lambba expression analysis 2 (object expression)

Sometimes, we need to create an object of a class that slightly changes a certain class without explicitly declaring a new subclass. Kotlin handles this situation with object expressions and object declarations.

1. Object expression

Mainly used to create objects of anonymous inner classes.
Let’s review the characteristics of anonymous inner classes:

  1. ) Anonymous inner classes are classes without names
  2. ) We generally only want to use anonymous inner classes once locally, and for this reason we are not willing to redefine a new class.
  3. ) Anonymous inner classes must inherit a certain parent class or implement a certain interface to complete the functions required by the local code.
  4. ) The runtime treats the anonymous inner class as the interface it implements or the parent class it inherits.
  5. ) Anonymous inner classes simplify the code structure and code volume because it bypasses the definition of the class and directly gets an object.

1.1) Declaration syntax:

We use the object keyword to declare an object expression (that is, an anonymous inner class) . Kotlin inheritance uses a colon declaration, so it is followed by a colon: and the inherited parent class or interface. If there are multiple inherited parent classes and interfaces, between Use commas to separate
declaration format:
object: inherited parent class or implemented interface, use commas to separate { methods that need to be implemented }


Example:

class AnonymousClass {
    
    
    private lateinit var listener : OnClickListener
    fun setOnClickListener(listener : OnClickListener) {
    
    
        this.listener = listener
    }
    fun click() {
    
    
        listener.onClick()
    }
}
interface OnClickListener {
    
    
    fun onClick()
}

使用:
AnonymousClass().setOnClickListener(object :OnClickListener{
    
    
    override fun onClick() {
    
    
    }
})

The above code is equivalent to us declaring a new class separately, inheriting the OnClickListener interface, and then creating an object and passing it to setOnClickListener. However, if we use object expressions, it greatly simplifies the code structure and code volume. Without using object expressions Examples are as follows:

class MyClick :OnClickListener{
    
    
    override fun onClick() {
    
    
    }
}
AnonymousClass().setOnClickListener(MyClick())

1.2) If the parent class has a constructor, the appropriate constructor parameters must be passed to the object expression. Multiple parent classes can be specified by a comma-separated list followed by a colon

open class A(x: Int) {
    
    
    public open val y: Int = x
}interface B {
    
     /*……*/ }

val ab: A = object : A(1), B {
    
    
    override val y = 15
}

1.3.) Anytime, if we just need "just an object" and don't need a special supertype, we can omit the colon:

Example:

fun foo() {
    
    
    val adHoc = object {
    
    
        var x: Int = 0
        var y: Int = 0
    }
    print(adHoc.x + adHoc.y)
}

1.4) Note that anonymous objects can be used as types declared only in local and private scopes. If you use an anonymous object as the return type of a public function or as the type of a public property, the actual type of the function or property will be the supertype declared by the anonymous object. If you do not declare any supertype, it will be Any. Members added in anonymous objects will not be accessible.

class C {
    
    
    // 私有函数,所以其返回类型是匿名对象类型
    private fun foo() = object {
    
    
        val x: String = "x"
    }

    // 公有函数,所以其返回类型是 Any
    fun publicFoo() = object {
    
    
        val x: String = "x"
    }

    fun bar() {
    
    
        val x1 = foo().x        // 没问题
        val x2 = publicFoo().x  // 错误:未能解析的引用“x”
    }
}

1.5) Code within an object expression can access variables from the scope that contains it.

Example:

fun countClicks(window: JComponent) {
    
    
    var clickCount = 0
    var enterCount = 0

    window.addMouseListener(object : MouseAdapter() {
    
    
        override fun mouseClicked(e: MouseEvent) {
    
    
            clickCount++
        }

        override fun mouseEntered(e: MouseEvent) {
    
    
            enterCount++
        }
    })
    // ……
}

2. Object declaration

The singleton pattern is often used in Java
as follows:

public class RetrofitManager {
    
    
    private static RetrofitManager mInstance;
    public static RetrofitManager getInstance() {
    
    
        if (mInstance == null) {
    
    
            synchronized (RetrofitManager.class) {
    
    
                if (mInstance == null) {
    
    
                    mInstance = new RetrofitManager();
                }
            }
        }
        return mInstance;
    }
}

In Kotlin, you can use object declarations to quickly implement the singleton pattern.

2.1) Define the object declaration through the object keyword. An object declaration effectively defines a class and its variables in one sentence. The object declaration cannot have any constructors, and properties, methods, and initialization statements can be added.

as follows:

object DataProviderManager {
    
    
    fun registerDataProvider(provider: DataProvider) {
    
    
        // ……
    }

    val allDataProviders: Collection<DataProvider>
        get() = // ……
}

2.2) Object declaration is not an expression and cannot be used on the right side of an assignment statement.

2.3) The initialization process of object declaration is thread-safe and occurs on first access.

2.4) If we need to reference the object, we can directly use its name:

DataProviderManager.registerDataProvider(……)

2.5) Object declarations can have supertypes

object DefaultListener : MouseAdapter() {
    
    
    override fun mouseClicked(e: MouseEvent) {
    
     …… }

    override fun mouseEntered(e: MouseEvent) {
    
     …… }
}

Note: Object declarations cannot be in local scope (i.e. nested directly inside functions), but they can be nested within other object declarations or within non-inner classes.

2.6) How does object declaration implement the singleton pattern?

As follows, we define an object declaration. We want to implement a user-managed singleton. This singleton has a user object loginUser, which is assigned a value during initialization, and has two methods to operate the object.

object UserManager {
    
    
    private var loginUser : User? = null

    init {
    
    
        loginUser = User("张三" , 12)
    }

    fun setLoginUser(loginUser : User) {
    
    
        this.loginUser = loginUser
    }

    fun getLoginUserName() : String? =
            loginUser?.name

}

Use Android studio to compile the above kotlin code into java code. The steps are:
click the tool class Tools - Kotlin -Show Kotlin Bytecode in order. The Kotlin Bytecode panel will open. Click the Decompile button of the panel to convert the kotlin code of the current file into Java code.
Let’s take a look at what the above UserManager code looks like after being converted into Java code.

public final class UserManager {
    
    
   private static User loginUser;
   @NotNull
   public static final UserManager INSTANCE;

   public final void setLoginUser(@NotNull User loginUser) {
    
    
      Intrinsics.checkNotNullParameter(loginUser, "loginUser");
      UserManager.loginUser = loginUser;
   }

   @Nullable
   public final String getLoginUserName() {
    
    
      User var10000 = loginUser;
      return var10000 != null ? var10000.getName() : null;
   }

   private UserManager() {
    
    
   }

   static {
    
    
      UserManager var0 = new UserManager();
      INSTANCE = var0;
      loginUser = new User("张三", 12);
   }
}

As you can see, the compiled code uses static code blocks and static variables to implement the singleton pattern.
1. The constructor is private, which means that the object cannot be created externally, and
private UserManager() { } can only be created internally . 2. A static static object (INSTANCE) of the class itself is created. The variables of the class (loginUser in this example) are also static. private static User loginUser; public static final UserManager INSTANCE; Use static code block to complete initialization static { UserManager var0 = new UserManager(); INSTANCE = var0; loginUser = new User("Zhang San", 12); } We know Java static code The code in the block will run when the class is loaded into the JVM and will be executed only once, which means that the code can be called without instantiating the class. Generally speaking, if some code must be executed when the project starts, you need to use static code blocks, so static blocks are often used to initialize class attributes. The static code blocks here are used to initialize singletons.












3. Companion objects

3.1) Object declarations within a class can be marked with the companion keyword: such object declarations are called companion objects

Example:

class MyClass {
    
    
    companion object Factory {
    
    
        fun create(): MyClass = MyClass()
    }
}

3.2) Members of a companion object can be called by using only the class name as a qualifier:

val instance = MyClass.create()

3.3) The name of the companion object can be omitted, in which case the name Companion will be used

class MyClass {
    
    
    companion object {
    
     }
}

val x = MyClass.Companion

3.4) Even if the members of a companion object look like static members in other languages, at runtime they are still instance members of the real object and, for example, can also implement interfaces

interface Factory<T> {
    
    
    fun create(): T
}

class MyClass {
    
    
    companion object : Factory<MyClass> {
    
    
        override fun create(): MyClass = MyClass()
    }
}

val f: Factory<MyClass> = MyClass

3.5) Only one internal association object can be declared in a class, that is, the keyword companion can only be used once.

3.6) How are companion objects implemented?

We define a companion object:

class UserInfo {
    
    
    companion object {
    
    
        val id = "userId"
        fun getUserInfo() =
                id.uppercase()
    }
}

Similarly, we convert the above code into Java code

public final class UserInfo {
    
    
   @NotNull
   private static final String id = "userId";
   @NotNull
   public static final UserInfo.Companion Companion = new UserInfo.Companion((DefaultConstructorMarker)null);

   @Metadata(
      mv = {
    
    1, 7, 1},
      k = 1,
      d1 = {
    
    "\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u000e\n\u0002\b\u0004\b\u0086\u0003\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0007\u001a\u00020\u0004R\u0014\u0010\u0003\u001a\u00020\u0004X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u0005\u0010\u0006¨\u0006\b"},
      d2 = {
    
    "Lcom/jf/simple/other/UserInfo$Companion;", "", "()V", "id", "", "getId", "()Ljava/lang/String;", "getUserInfo", "JFUtils_master.app.main"}
   )
   public static final class Companion {
    
    
      @NotNull
      public final String getId() {
    
    
         return UserInfo.id;
      }

      @NotNull
      public final String getUserInfo() {
    
    
         String var1 = ((UserInfo.Companion)this).getId();
         if (var1 == null) {
    
    
            throw new NullPointerException("null cannot be cast to non-null type java.lang.String");
         } else {
    
    
            String var10000 = var1.toUpperCase(Locale.ROOT);
            Intrinsics.checkNotNullExpressionValue(var10000, "(this as java.lang.Strin….toUpperCase(Locale.ROOT)");
            return var10000;
         }
      }

      private Companion() {
    
    
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
    
    
         this();
      }
   }
}

1. The variables declared by the companion object become static variables of the class.
private static final String id = "userId";
2. A static class Companion is generated, the method declared by the companion object becomes the method of the same name of Companion, and the class generates a static object of Companion
public static final UserInfo.Companion Companion = new UserInfo.Companion((DefaultConstructorMarker)null);
What the above two steps actually do is to turn the variables and methods declared by the companion object into static objects and static methods of the class, so we can also understand it this way: a class The companion object is used to declare static variables and static methods for this class.

To sum up, object declaration and companion object are different things.
Object declaration: used to implement the singleton pattern of a class.
Companion object: used to declare static variables and static methods for the class, and is not used to implement the singleton of the class.

Guess you like

Origin blog.csdn.net/weixin_43864176/article/details/123744136