kotlin學習筆記:object關鍵字介紹與java中的靜態變量與靜態方法的實現以及@JvmField和@JvmStatic的使用

java中,靜態變量靜態方法是我們經常需要用到的東西,但是我們在kotlin中,並不能找到static關鍵字。其實目前在kotlin中,也的確是static概念的,那麼我們該如何在kotlin中實現靜態變量靜態方法呢?這時就要用到kotlin中的object關鍵字了,下面文章的內容會將object的用法和靜態的實現一起詳細講解

Tip: 想要自己驗證本文內容的小夥伴,請看文章《Kotlin學習筆記:如何將kotlin編譯成java(必備小技能)》

kotlin中的object

從字面意思看,object的意思即是對象,實際上也確實如此,但是如此解釋也未免過於抽象了!所以我們通過幾個例子來看object

首先,我們要知道object的幾種使用場景:

  • 對象聲明
  • 伴生對象
  • 對象表達式

一、對象聲明

object Test{
    
}

一般聲明一個類,我們用class,此處我們使用object來聲明一個類,而在此同時也聲明瞭它的一個對象。我們看一下,將kotlin轉換成java後的代碼:

public final class Test {
   public static final Test INSTANCE;

   static {
      Test var0 = new Test();
      INSTANCE = var0;
   }
}

從轉換成的java代碼中,我們可以清楚的看到,在Test類中,同時聲明瞭一個Test的靜態變量對象INSTANCE,不僅如此,還形成了一個簡單的單例模式,如果覺得這個不像,那麼轉換一下:

public final class Test {
   private static final Test INSTANCE = new Test();

   public static Test getInstance(){
       return INSTANCE; 
   }
   // 必須注意的一點
   private Test(){
   
   }
}

看到這個,是否覺得有點眼熟了呢?
不過有一點要注意,可能你們也發現了,kolin轉換成的java代碼中,沒有將構造參數設爲private,而我自己轉換的卻將構造參數設爲了private,這是爲什麼呢?下面請看對Test類的調用
在這裏插入圖片描述
這下寫的很明白了,直接提示private,所以其實第二段java代碼纔是kotlin轉換後的完整版,至於爲什麼編譯器轉換出來的沒有寫明構造函數爲private,就不得而知了。

所以,總結object在以上的使用中,有兩點

  • object聲明一個類時,該類自動成爲一個簡單的單例模式
  • object聲明的類,無法在外部用new的方式重新實例化

代替static的第一種方法

看了上面的object對象聲明,下面就可以來說一下,第一種代替靜態的方法啦!沒錯,就是使用object類

下面是kotlin中的代碼

object Test {
    var code = 1

    fun getData(){
        
    }
}

編譯成java代碼

public final class Test {
   private static int code;
   public static final Test INSTANCE;

   public final int getCode() {
      return code;
   }

   public final void setCode(int var1) {
      code = var1;
   }

   public final void getData() {
   }

   static {
      Test var0 = new Test();
      INSTANCE = var0;
      code = 1;
   }
}

可以看到,在轉換成的java代碼中,int型的code變量getCode()方法都變成靜態的了,下面再來看看如何調用
kotlin中調用

private fun check() {
      val code = Test.code
      Test.getData()
}

java中調用

private void check(){
    Test.INSTANCE.getCode();
    Test.INSTANCE.getData();
}

我們可以看到,在java中調用時,我們必須通過INSTANCE來進行,並且code的獲取使用了get方法,其實這點在上面轉換代碼中就可以看到轉換成的codeprivate的,並不是靜態變量,並且自動生成了gettersetter方法。而對於getData()方法,其實也不是真正的靜態方法,都是基於單例來實現的

對於這點,有些人可能是接受不了的,並且覺得內部的java實現很糟糕,想要渴求真正的靜態,那麼該如何解決呢?這下就得我們的@JvmField@JvmStatic註解出場的時候了😏

@JvmField與@JvmStatic的出場

我們先看代碼,
首先是kotlin代碼:

object Test {
    @JvmField
    var code = 1

    @JvmStatic
    fun getData(){

    }
}

然後是轉換後的java代碼:

public final class Test {
   @JvmField
   public static int code;
   public static final Test INSTANCE;

   @JvmStatic
   public static final void getData() {
   }

   static {
      Test var0 = new Test();
      INSTANCE = var0;
      code = 1;
   }

我們發現,code變成真正的靜態變量,而getData()方法也變成了真正的靜態方法,下面是一些注意點

  • @JvmField消除了變量的gettersetter方法
  • @JvmField修飾的變量不能是private屬性的
  • @JvmStatic只能在object類或者伴生對象companion object中使用,而@JvmField沒有這些限制
  • @JvmStatic一般用於修飾方法,使方法變成真正的靜態方法;如果修飾變量不會消除變量的gettersetter方法,但會使gettersetter方法和變量都變成靜態,看例子

kotlin代碼

object Test {
    @JvmStatic
    var code = 1
}

轉換後的java代碼

public final class Test {
   private static int code;
   public static final Test INSTANCE;

   /** @deprecated */
   // $FF: synthetic method
   @JvmStatic
   public static void code$annotations() {
   }

   public static final int getCode() {
      return code;
   }

   public static final void setCode(int var0) {
      code = var0;
   }

   static {
      Test var0 = new Test();
      INSTANCE = var0;
      code = 1;
   }
}

二、伴生對象

kotlin中每個類都可以給自己構造一個伴生對象companion object,看代碼

class Test {
    // MyTest 是伴生對象的名字,可以不寫,不寫默認爲 companion 
    companion object MyTest{
        var code = 1

        fun getData(){

        }
    }
}

轉換後的java代碼

public final class Test {
   private static int code = 1;
   public static final Test.MyTest MyTest = new Test.MyTest((DefaultConstructorMarker)null);

   @Metadata(
      mv = {1, 1, 10},
      bv = {1, 0, 2},
      k = 1,
      d1 = {"\u0000\u001a\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\b\n\u0002\b\u0005\n\u0002\u0010\u0002\n\u0000\b\u0086\u0003\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002J\u0006\u0010\t\u001a\u00020\nR\u001a\u0010\u0003\u001a\u00020\u0004X\u0086\u000e¢\u0006\u000e\n\u0000\u001a\u0004\b\u0005\u0010\u0006\"\u0004\b\u0007\u0010\b¨\u0006\u000b"},
      d2 = {"Lcom/homeprint/module/mine/Test$MyTest;", "", "()V", "code", "", "getCode", "()I", "setCode", "(I)V", "getData", "", "production sources for module module_mine"}
   )
   public static final class MyTest {
      public final int getCode() {
         return Test.code;
      }

      public final void setCode(int var1) {
         Test.code = var1;
      }

      public final void getData() {
      }

      private MyTest() {
      }

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

可以看出,轉換後的java代碼中,生成了一個MyTest的靜態類,通過這個MyTest來管理我們的codegetData(),但其實這也沒有真正的實現我們想要的靜態
kotlin中的調用

private fun check() {
      // 方式 1
      val code = Test.code
      Test.getData()
      // 方式 2
      val code1 =Test.MyTest.code
      Test.MyTest.getData()
}

java中的調用

private void check(){
	  Test.MyTest.getCode();
	  Test.MyTest.getData();
}

可以看出,在kotlin中調用時,可以選擇或者不寫MyTest靜態類,兩種方式,但是在java中必須得寫MyTest。那麼如何實現我們想要的真正靜態呢?和上述 對象聲明 中使用一樣的方法(@JvmField和@JvmStatic)
kotlin的代碼:

class Test {
    companion object MyTest{
        @JvmField
        var code = 1

        @JvmStatic
        fun getData(){

        }
    }
}

轉換後的java代碼:

public final class Test {
   @JvmField
   public static int code = 1;
   public static final Test.MyTest MyTest = new Test.MyTest((DefaultConstructorMarker)null);

   @JvmStatic
   public static final void getData() {
      MyTest.getData();
   }

   @Metadata(
      mv = {1, 1, 10},
      bv = {1, 0, 2},
      k = 1,
      d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\b\n\u0000\n\u0002\u0010\u0002\n\u0000\b\u0086\u0003\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002J\b\u0010\u0005\u001a\u00020\u0006H\u0007R\u0012\u0010\u0003\u001a\u00020\u00048\u0006@\u0006X\u0087\u000e¢\u0006\u0002\n\u0000¨\u0006\u0007"},
      d2 = {"Lcom/homeprint/module/mine/Test$MyTest;", "", "()V", "code", "", "getData", "", "production sources for module module_mine"}
   )
   public static final class MyTest {
      @JvmStatic
      public final void getData() {
      }

      private MyTest() {
      }

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

可以看到,我們想要的真正的靜態有了。

三、靜態變量的另一種寫法

在上述我們講解了如何利用object實現真正的靜態,然而對於靜態常量,我們還有另一種實現方式

 const var code = 1
  • const只能修飾常量val
  • const只能在object類中或者伴生對象companion object中使用
  • const的效果等於@JvmField,兩者不能同時使用

四、對象表達式

object上面講了兩種場景,現在講最後一種場景對象表達式,此段內容與靜態無關。

我們在java中經常遇到這樣的場景:

// 定義一個接口
public interface OnTestCallback{
    void onTest();
}

// 然後這樣實現接口
myTest.setOnTestCallback(new OnTestCallback(){
     @Override 
     public void onTest(){
     
     }
});

我們可以看到java中直接聲明瞭一個匿名內部類來實現了接口,在而在kotlin中我們是沒有辦法使用new的,那麼怎麼辦呢?答案:使用對象表達式,看代碼

// 定義一個接口
interface OnTestCallback{
    fun onTest();
}

// 然後這樣實現接口
myTest.setOnTestCallback(object:OnTestCallback{
     override fun onTest(){
     
     }
});

轉換成java代碼

myTest.setOnTestCallback((OnTestCallback)(new OnTestCallback() {
     public void onTest() {
     
     }
}));

可以看出,在上述代碼中,我們藉助了object關鍵字來聲明瞭接口對象。所以,object關鍵字此時的作用就是幫助我們聲明匿名對象。

總結

關於object靜態的講解暫時就告一段落了,算是記錄一下之前學習所得(作爲一個強迫症,以前被它搞得很煩躁),防止以後遺忘!如果有不妥之處,歡迎指正!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章