【碼上開學】Kotlin 裏那些「不是那麼寫的」

本期做者:java

視頻:扔物線(朱凱)git

文章:Walker(張磊)github

你們好,我是扔物線朱凱。這是碼上開學 Kotlin 基礎部分的第二期:Kotlin 裏那些「不是那麼寫的」。話很少說,視頻伺候。編程

由於我一直沒有學會怎麼在掘金貼視頻,因此請點擊 這裏 去嗶哩嗶哩看,或者點擊 這裏 去 YouTube 看。數組

如下內容來自文章做者 Walker安全

上一篇咱們講了 Kotlin 上手最基礎的三個點:變量、函數和類型。你們都據說過,Kotlin 徹底兼容 Java,這個意思是用 Java 寫出來的代碼和 Kotlin 能夠完美交互,而不是說你用 Java 的寫法去寫 Kotlin 就徹底沒問題,這個是不行的。這期內容咱們就講一下,Kotlin 裏那些「不 Java」的寫法。性能優化

Constructor

上一篇中簡單介紹了 Kotlin 的構造器,這一節具體看看 Kotlin 的構造器和 Java 有什麼不同的地方:數據結構

  • Java架構

    ☕️
    public class User {
        int id;
        String name;
          👇   👇
        public User(int id, String name) {
            this.id = id;
            this.name = name;
        }
    }
    複製代碼
  • Kotlinide

    🏝️
    class User {
        val id: Int
        val name: String
             👇
        constructor(id: Int, name: String) {
     //👆 沒有 public
            this.id = id
            this.name = name
        }
    }
    複製代碼

能夠發現有兩點不一樣:

  • Java 中構造器和類同名,Kotlin 中使用 constructor 表示。
  • Kotlin 中構造器沒有 public 修飾,由於默承認見性就是公開的(關於可見性修飾符這裏先不展開,後面會講到)。

init

除了構造器,Java 裏經常配合一塊兒使用的 init 代碼塊,在 Kotlin 裏的寫法也有了一點點改變:你須要給它加一個 init 前綴。

  • Java

    ☕️
    public class User {
       👇
        {
            // 初始化代碼塊,先於下面的構造器執行
        }
        public User() {
        }
    }
    複製代碼
  • Kotlin

    🏝️
    class User {
        👇
        init {
            // 初始化代碼塊,先於下面的構造器執行
        }
        constructor() {
        }
    }
    複製代碼

正如上面標註的那樣,Kotlin 的 init 代碼塊和 Java 同樣,都在實例化時執行,而且執行順序都在構造器以前。

上一篇提到,Java 的類若是不加 final 關鍵字,默認是能夠被繼承的,而 Kotlin 的類默認就是 final 的。在 Java 裏 final 還能夠用來修飾變量,接下來讓咱們看看 Kotlin 是如何實現相似功能的。

final

Kotlin 中的 val 和 Java 中的 final 相似,表示只讀變量,不能修改。這裏分別從成員變量、參數和局部變量來和 Java 作對比:

  • Java

    ☕️
     👇
    final int final1 = 1;
                 👇  
    void method(final String final2) {
         👇
        final String final3 = "The parameter is " + final2;
    }
    複製代碼
  • Kotlin

    🏝️
    👇
    val fina1 = 1
           // 👇 參數是沒有 val 的
    fun method(final2: String) {
        👇
        val final3 = "The parameter is " + final2
    }
    複製代碼

能夠看到不一樣點主要有:

  • final 變成了 val。
  • Kotlin 函數參數默認是 val 類型,因此參數前不須要寫 val 關鍵字,Kotlin 裏這樣設計的緣由是保證了參數不會被修改,而 Java 的參數可修改(默認沒 final 修飾)會增長出錯的機率。

上一期說過,var 是 variable 的縮寫, val 是 value 的縮寫。

其實咱們寫 Java 代碼的時候,不多會有人用 final,但 final 用來修飾變量實際上是頗有用的,但你們都不用;可你若是去看看國內國外的人寫的 Kotlin 代碼,你會發現不少人的代碼裏都會有一堆的 val。爲何?由於 final 寫起來比 val 麻煩一點:我須要多寫一個單詞。雖然只麻煩這一點點,但就致使不少人不寫。

這就是一件頗有意思的事:從 finalval,只是方便了一點點,但卻讓它的使用頻率有了巨大的改變。這種改變是會影響到代碼質量的:在該加限制的地方加上限制,就能夠減小代碼出錯的機率。

val自定義 getter

不過 valfinal 仍是有一點區別的,雖然 val 修飾的變量不能二次賦值,但能夠經過自定義變量的 getter 函數,讓變量每次被訪問時,返回動態獲取的值:

🏝️
👇
val size: Int
    get() { // 👈 每次獲取 size 值時都會執行 items.size
        return items.size
    }
複製代碼

不過這個屬於 val 的另一種用法,大部分狀況下 val 仍是對應於 Java 中的 final 使用的。

static property / function

剛纔說到你們都不喜歡寫 final 對吧?但有一種場景,你們是最喜歡用 final 的:常量。

☕️
public static final String CONST_STRING = "A String";
複製代碼

在 Java 裏面寫常量,咱們用的是 static + final。而在 Kotlin 裏面,除了 final 的寫法不同,static 的寫法也不同,並且是更不同。確切地說:在 Kotlin 裏,靜態變量和靜態方法這兩個概念被去除了。

那若是想在 Kotlin 中像 Java 同樣經過類直接引用該怎麼辦呢?Kotlin 的答案是 companion object

🏝️
class Sample {
    ...
       👇
    companion object {
        val anotherString = "Another String"
    }
}
複製代碼

爲啥 Kotlin 越改越複雜了?不着急,咱們先看看 object 是個什麼東西。

object

Kotlin 裏的 object ——首字母小寫的,不是大寫,Java 裏的 Object 在 Kotlin 裏不用了。

Java 中的 Object 在 Kotlin 中變成了 Any,和 Object 做用同樣:做爲全部類的基類。

object 不是類,像 class 同樣在 Kotlin 中屬於關鍵字:

🏝️
object Sample {
    val name = "A name"
}
複製代碼

它的意思很直接:建立一個類,而且建立一個這個類的對象。這個就是 object 的意思:對象。

在代碼中若是要使用這個對象,直接經過它的類名就能夠訪問:

🏝️
Sample.name
複製代碼

這不就是單例麼,因此在 Kotlin 中建立單例不用像 Java 中那麼複雜,只須要把 class 換成 object 就能夠了。

  • 單例類

    咱們看一個單例的例子,分別用 Java 和 Kotlin 實現:

    • Java 中實現單例類(非線程安全):

      ☕️
      public class A {
          private static A sInstance;
          
          public static A getInstance() {
              if (sInstance == null) {
                  sInstance = new A();
              }
              return sInstance;
          }
      
          // 👇還有不少模板代碼
          ...
      }
      複製代碼

      能夠看到 Java 中爲了實現單例類寫了大量的模版代碼,稍顯繁瑣。

    • Kotlin 中實現單例類:

      🏝️
      // 👇 class 替換成了 object
      object A {
          val number: Int = 1
          fun method() {
              println("A.method()")
          }
      }    
      複製代碼

      和 Java 相比的不一樣點有:

      • 和類的定義相似,可是把 class 換成了 object
      • 不須要額外維護一個實例變量 sInstance
      • 不須要「保證明例只建立一次」的 getInstance() 方法。

      相比 Java 的實現簡單多了。

      這種經過 object 實現的單例是一個餓漢式的單例,而且實現了線程安全。

  • 繼承類和實現接口

    Kotlin 中不只類能夠繼承別的類,能夠實現接口,object 也能夠:

    🏝️
    open class A {
        open fun method() {
            ...
        }
    }
    
    interface B {
        fun interfaceMethod()
    }
      👇      👇   👇
    object C : A(), B {
    
        override fun method() {
            ...
        }
    
        override fun interfaceMethod() {
            ...
        }
    }
    複製代碼

    爲何 object 能夠實現接口呢?簡單來說 object 實際上是把兩步合併成了一步,既有 class 關鍵字的功能,又實現了單例,這樣就容易理解了。

  • 匿名類

    另外,Kotlin 還能夠建立 Java 中的匿名類,只是寫法上有點不一樣:

    • Java:

      ☕️                                              👇 
      ViewPager.SimpleOnPageChangeListener listener = new ViewPager.SimpleOnPageChangeListener() {
          @Override // 👈
          public void onPageSelected(int position) {
              // override
          }
      };
      複製代碼
    • Kotlin:

      🏝️          
      val listener = object: ViewPager.SimpleOnPageChangeListener() {
          override fun onPageSelected(position: Int) {
              // override
          }
      }        
      複製代碼

      和 Java 建立匿名類的方式很類似,只不過把 new 換成了 object:

      • Java 中 new 用來建立一個匿名類的對象
      • Kotlin 中 object: 也能夠用來建立匿名類的對象

      這裏的 newobject: 修飾的都是接口或者抽象類。

companion object

object 修飾的對象中的變量和函數都是靜態的,但有時候,咱們只想讓類中的一部分函數和變量是靜態的該怎麼作呢:

🏝️
class A {
          👇
    object B {
        var c: Int = 0
    }
}
複製代碼

如上,能夠在類中建立一個對象,把須要靜態的變量或函數放在內部對象 B 中,外部能夠經過以下的方式調用該靜態變量:

🏝️
A.B.c
  👆
複製代碼

類中嵌套的對象能夠用 companion 修飾:

🏝️
class A {
       👇
    companion object B {
        var c: Int = 0
    }
}
複製代碼

companion 能夠理解爲伴隨、伴生,表示修飾的對象和外部類綁定。

但這裏有一個小限制:一個類中最多隻能夠有一個伴生對象,但能夠有多個嵌套對象。就像皇帝后宮佳麗三千,但皇后只有一個。

這樣的好處是調用的時候能夠省掉對象名:

🏝️
A.c // 👈 B 沒了
複製代碼

因此,當有 companion 修飾時,對象的名字也能夠省略掉:

🏝️
class A {
                // 👇 B 沒了
    companion object {
        var c: Int = 0
    }
}
複製代碼

這就是這節最開始講到的,Java 靜態變量和方法的等價寫法:companion object 變量和函數。

  • 靜態初始化

    Java 中的靜態變量和方法,在 Kotlin 中都放在了 companion object 中。所以 Java 中的靜態初始化在 Kotlin 中天然也是放在 companion object 中的,像類的初始化代碼同樣,由 init 和一對大括號表示:

    🏝️
    class Sample {
           👇
        companion object {
             👇
            init {
                ...
            }
        }
    }
    複製代碼

top-level property / function 聲明

除了靜態函數這種簡便的調用方式,Kotlin 還有更方便的東西:「top-level declaration 頂層聲明」。其實就是把屬性和函數的聲明不寫在 class 裏面,這個在 Kotlin 裏是容許的:

🏝️
package com.hencoder.plus

// 👇 屬於 package,不在 class/object 內
fun topLevelFuncion() {
}
複製代碼

這樣寫的屬性和函數,不屬於任何 class,而是直接屬於 package,它和靜態變量、靜態函數同樣是全局的,但用起來更方便:你在其它地方用的時候,就連類名都不用寫:

🏝️
import com.hencoder.plus.topLevelFunction // 👈 直接 import 函數

topLevelFunction()
複製代碼

寫在頂級的函數或者變量有個好處:在 Android Studio 中寫代碼時,IDE 很容易根據你寫的函數前幾個字母自動聯想出相應的函數。這樣提升了寫代碼的效率,並且能夠減小項目中的重複代碼。

  • 命名相同的頂級函數

    頂級函數不寫在類中可能有一個問題:若是在不一樣文件中聲明命名相同的函數,使用的時候會不會混淆?來看一個例子:

    • org.kotlinmaster.library 包下有一個函數 method:

      🏝️
      package org.kotlinmaster.library1
                                 👆
      fun method() {
          println("library1 method()")
      }
      複製代碼
    • org.kotlinmaster.library2 包下有一個同名函數:

      🏝️
      package org.kotlinmaster.library2
                                 👆
      fun method() {
          println("library2 method()")
      }
      複製代碼

    在使用的時候若是同時調用這兩個同名函數會怎麼樣:

    🏝️
    import org.kotlinmaster.library1.method
                               👆
    fun test() {
        method()
                           👇
        org.kotlinmaster.library2.method()
    }
    複製代碼

    能夠看到當出現兩個同名頂級函數時,IDE 會自動加上包前綴來區分,這也印證了「頂級函數屬於包」的特性。

對比

那在實際使用中,在 objectcompanion object 和 top-level 中該選擇哪個呢?簡單來講按照下面這兩個原則判斷:

  • 若是想寫工具類的功能,直接建立文件,寫 top-level「頂層」函數。
  • 若是須要繼承別的類或者實現接口,就用 objectcompanion object

常量

Java 中,除了上面講到的的靜態變量和方法會用到 static,聲明常量時也會用到,那 Kotlin 中聲明常量會有什麼變化呢?

  • Java 中聲明常量:

    ☕️
    public class Sample {
                👇     👇
        public static final int CONST_NUMBER = 1;
    }
    複製代碼
  • Kotlin 中聲明常量:

    🏝️
    class Sample {
        companion object {
             👇                  // 👇
            const val CONST_NUMBER = 1
        }
    }
    
    const val CONST_SECOND_NUMBER = 2
    複製代碼

發現不一樣點有:

  • Kotlin 的常量必須聲明在對象(包括伴生對象)或者「top-level 頂層」中,由於常量是靜態的。
  • Kotlin 新增了修飾常量的 const 關鍵字。

除此以外還有一個區別:

  • Kotlin 中只有基本類型和 String 類型能夠聲明成常量。

緣由是 Kotlin 中的常量指的是 「compile-time constant 編譯時常量」,它的意思是「編譯器在編譯的時候就知道這個東西在每一個調用處的實際值」,所以能夠在編譯時直接把這個值硬編碼到代碼裏使用的地方。

而非基本和 String 類型的變量,能夠經過調用對象的方法或變量改變對象內部的值,這樣這個變量就不是常量了,來看一個 Java 的例子,好比一個 User 類:

☕️
public class User {
    int id; // 👈 可修改
    String name; // 👈 可修改
    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
}
複製代碼

在使用的地方聲明一個 static final 的 User 實例 user,它是不能二次賦值的:

☕️
static final User user = new User(123, "Zhangsan");
  👆    👆
複製代碼

可是能夠經過訪問這個 user 實例的成員變量改變它的值:

☕️
user.name = "Lisi";
      👆
複製代碼

因此 Java 中的常量能夠認爲是「僞常量」,由於能夠經過上面這種方式改變它內部的值。而 Kotlin 的常量由於限制類型必須是基本類型,因此不存在這種問題,更符合常量的定義。

前面講的 val 「只讀變量」和靜態變量都是針對單個變量來講的,接下來咱們看看編程中另一個常見的主題:數組和集合。

數組和集合

數組

聲明一個 String 數組:

  • Java 中的寫法:

    ☕️
    String[] strs = {"a", "b", "c"};
          👆        👆
    複製代碼
  • Kotlin 中的寫法:

    🏝️
    val strs: Array<String> = arrayOf("a", "b", "c")
                👆              👆
    複製代碼

能夠看到 Kotlin 中的數組是一個擁有泛型的類,建立函數也是泛型函數,和集合數據類型同樣。

針對泛型的知識點,咱們在後面的文章會講,這裏就先按照 Java 泛型來理解。

將數組泛型化有什麼好處呢?對數組的操做能夠像集合同樣功能更強大,因爲泛型化,Kotlin 能夠給數組增長不少有用的工具函數:

  • get() / set()
  • contains()
  • first()
  • find()

這樣數組的實用性就大大增長了。

  • 取值和修改

    Kotlin 中獲取或者設置數組元素和 Java 同樣,可使用方括號加下標的方式索引:

    🏝️
    println(strs[0])
       👇      👆
    strs[1] = "B"
    複製代碼
  • 不支持協變

    Kotlin 的數組編譯成字節碼時使用的仍然是 Java 的數組,但在語言層面是泛型實現,這樣會失去協變 (covariance) 特性,就是子類數組對象不能賦值給父類的數組變量:

    • Kotlin

      🏝️
      val strs: Array<String> = arrayOf("a", "b", "c")
                        👆
      val anys: Array<Any> = strs // compile-error: Type mismatch
                      👆
      複製代碼
    • 而這在 Java 中是能夠的:

      ☕️
      String[] strs = {"a", "b", "c"};
        👆
      Object[] objs = strs; // success
        👆
      複製代碼

    關於協變的問題,這裏就先不展開了,後面講泛型的時候會提到。

集合

Kotlin 和 Java 同樣有三種集合類型:List、Set 和 Map,它們的含義分別以下:

  • List 以固定順序存儲一組元素,元素能夠重複。
  • Set 存儲一組互不相等的元素,一般沒有固定順序。
  • Map 存儲 鍵-值 對的數據集合,鍵互不相等,但不一樣的鍵能夠對應相同的值。

從 Java 到 Kotlin,這三種集合類型的使用有哪些變化呢?咱們依次看看。

  • List

    • Java 中建立一個列表:

      ☕️
      List<String> strList = new ArrayList<>();
      strList.add("a");
      strList.add("b");
      strList.add("c"); // 👈 添加元素繁瑣
      複製代碼
    • Kotlin 中建立一個列表:

      🏝️            
      val strList = listOf("a", "b", "c")
      複製代碼

    首先能看到的是 Kotlin 中建立一個 List 特別的簡單,有點像建立數組的代碼。並且 Kotlin 中的 List 多了一個特性:支持 covariant(協變)。也就是說,能夠把子類的 List 賦值給父類的 List 變量:

    • Kotlin:

      🏝️
      val strs: List<String> = listOf("a", "b", "c")
                      👆
      val anys: List<Any> = strs // success
                     👆
      複製代碼
    • 而這在 Java 中是會報錯的:

      ☕️
      List<String> strList = new ArrayList<>();
             👆
      List<Object> objList = strList; // 👈 compile error: incompatible types
            👆  
      複製代碼

    對於協變的支持與否,List 和數組恰好反過來了。關於協變,這裏只需結合例子簡單瞭解下,後面的文章會對它展開討論。

    • 和數組的區別

      Kotlin 中數組和 MutableList 的 API 是很是像的,主要的區別是數組的元素個數不能變。那在何時用數組呢?

      • 這個問題在 Java 中就存在了,數組和 List 的功能相似,List 的功能更多一些,直覺上應該用 List 。但數組也不是沒有優點,基本類型 (int[]float[]) 的數組不用自動裝箱,性能好一點。

      • 在 Kotlin 中也是一樣的道理,在一些性能需求比較苛刻的場景,而且元素類型是基本類型時,用數組好一點。不過這裏要注意一點,Kotlin 中要用專門的基本類型數組類 (IntArray FloatArray LongArray) 才能夠免於裝箱。也就是說元素不是基本類型時,相比 Array,用 List 更方便些。

  • Set

    • Java 中建立一個 Set

      ☕️
      Set<String> strSet = new HashSet<>();
      strSet.add("a");
      strSet.add("b");
      strSet.add("c");
      複製代碼
    • Kotlin 中建立相同的 Set

      🏝️           
      val strSet = setOf("a", "b", "c")
      複製代碼

    List 相似,Set 一樣具備 covariant(協變)特性。

  • Map

    • Java 中建立一個 Map

      ☕️
      Map<String, Integer> map = new HashMap<>();
      map.put("key1", 1);
      map.put("key2", 2);
      map.put("key3", 3);
      map.put("key4", 3);
      複製代碼
    • Kotlin 中建立一個 Map

      🏝️         
      val map = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 3)
      複製代碼

    和上面兩種集合類型類似建立代碼很簡潔。mapOf 的每一個參數表示一個鍵值對,to 表示將「鍵」和「值」關聯,這個叫作「中綴表達式」,這裏先不展開,後面的文章會作介紹。

    • 取值和修改

      • Kotlin 中的 Map 除了和 Java 同樣可使用 get() 根據鍵獲取對應的值,還可使用方括號的方式獲取:

        🏝️
                         👇
        val value1 = map.get("key1")
                       👇
        val value2 = map["key2"]
        複製代碼
      • 相似的,Kotlin 中也能夠用方括號的方式改變 Map 中鍵對應的值:

        🏝️       
                      👇
        val map = mutableMapOf("key1" to 1, "key2" to 2)
            👇
        map.put("key1", 2)
           👇
        map["key1"] = 2    
        複製代碼

      這裏用到了「操做符重載」的知識,實現了和數組同樣的「Positional Access Operations」,關於這個概念這裏先不展開,後面會講到。

  • 可變集合/不可變集合

    上面修改 Map 值的例子中,建立函數用的是 mutableMapOf() 而不是 mapOf(),由於只有 mutableMapOf() 建立的 Map 才能夠修改。Kotlin 中集合分爲兩種類型:只讀的和可變的。這裏的只讀有兩層意思:

    • 集合的 size 不可變
    • 集合中的元素值不可變

    如下是三種集合類型建立不可變和可變實例的例子:

    • listOf() 建立不可變的 ListmutableListOf() 建立可變的 List
    • setOf() 建立不可變的 SetmutableSetOf() 建立可變的 Set
    • mapOf() 建立不可變的 MapmutableMapOf() 建立可變的 Map

    能夠看到,有 mutable 前綴的函數建立的是可變的集合,沒有 mutbale 前綴的建立的是不可變的集合,不過不可變的能夠經過 toMutable*() 系函數轉換成可變的集合:

    🏝️
    val strList = listOf("a", "b", "c")
                👇
    strList.toMutableList()
    val strSet = setOf("a", "b", "c")
                👇
    strSet.toMutableSet()
    val map = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 3)
             👇
    map.toMutableMap()
    複製代碼

    而後就能夠對集合進行修改了,這裏有一點須要注意下:

    • toMutable*() 返回的是一個新建的集合,原有的集合仍是不可變的,因此只能對函數返回的集合修改。

Sequence

除了集合 Kotlin 還引入了一個新的容器類型 Sequence,它和 Iterable 同樣用來遍歷一組數據並能夠對每一個元素進行特定的處理,先來看看如何建立一個 Sequence

  • 建立
    • 相似 listOf() ,使用一組元素建立:

      🏝️
      sequenceOf("a", "b", "c")
      複製代碼
    • 使用 Iterable 建立:

      🏝️
      val list = listOf("a", "b", "c")
      list.asSequence()
      複製代碼

      這裏的 List 實現了 Iterable 接口。

    • 使用 lambda 表達式建立:

      🏝️                          // 👇 第一個元素
      val sequence = generateSequence(0) { it + 1 }
                                        // 👆 lambda 表達式,負責生成第二個及之後的元素,it 表示前一個元素
      複製代碼

這看起來和 Iterable 同樣呀,爲啥要畫蛇添足使用 Sequence 呢?在下一篇文章中會結合例子展開討論。

可見性修飾符

講完了數據集合,再看看 Kotlin 中的可見性修飾符,Kotlin 中有四種可見性修飾符:

  • public:公開,可見性最大,哪裏均可以引用。
  • private:私有,可見性最小,根據聲明位置不一樣可分爲類中可見和文件中可見。
  • protected:保護,至關於 private + 子類可見。
  • internal:內部,僅對 module 內可見。

相比 Java 少了一個 default 「包內可見」修飾符,多了一個 internal「module 內可見」修飾符。這一節結合例子講講 Kotlin 這四種可見性修飾符,以及在 Kotlin 和 Java 中的不一樣。先來看看 public

public

Java 中沒寫可見性修飾符時,表示包內可見,只有在同一個 package 內能夠引用:

☕️                         👇
package org.kotlinmaster.library; 
// 沒有可見性修飾符
class User {
}
複製代碼
☕️                      // 👇 和上面同一個 package
package org.kotlinmaster.library;

public class Example {
    void method() {
        new User(); // success
    }
}
複製代碼
☕️
package org.kotlinmaster;
                    // 👆 和上面不是一個 package
import org.kotlinmaster.library.User;
                          👆
public class OtherPackageExample {
    void method() {
        new User(); // compile-error: 'org.kotlinmaster.library.User' is not public in 'org.kotlinmaster.library'. Cannot be accessed from outside package
    }
}
複製代碼

package 外若是要引用,須要在 class 前加上可見性修飾符 public 表示公開。

Kotlin 中若是不寫可見性修飾符,就表示公開,和 Java 中 public 修飾符具備相同效果。在 Kotlin 中 public 修飾符「能夠加,但不必」。

@hide

在 Android 的官方 sdk 中,有一些方法只想對 sdk 內可見,不想開放給用戶使用(由於這些方法不太穩定,在後續版本中頗有可能會修改或刪掉)。爲了實現這個特性,會在方法的註釋中添加一個 Javadoc 方法 @hide,用來限制客戶端訪問:

☕️
/** * @hide 👈 */
public void hideMethod() {
    ...
}
複製代碼

但這種限制不太嚴格,能夠經過反射訪問到限制的方法。針對這個狀況,Kotlin 引進了一個更爲嚴格的可見性修飾符:internal

internal

internal 表示修飾的類、函數僅對 module 內可見,這裏的 module 具體指的是一組共同編譯的 kotlin 文件,常見的形式有:

  • Android Studio 裏的 module
  • Maven project

咱們常見的是 Android Studio 中的 module 這種狀況,Maven project 僅做了解就好,不用細究。

internal 在寫一個 library module 時很是有用,當須要建立一個函數僅開放給 module 內部使用,不想對 library 的使用者可見,這時就應該用 internal 可見性修飾符。

Java 的「包內可見」怎麼沒了?

Java 的 default「包內可見」在 Kotlin 中被棄用掉了,Kotlin 中與它最接近的可見性修飾符是 internal「module 內可見」。爲何會棄用掉包內可見呢?我以爲有這幾個緣由:

  • Kotlin 鼓勵建立 top-level 函數和屬性,一個源碼文件能夠包含多個類,使得 Kotlin 的源碼結構更加扁平化,包結構再也不像 Java 中那麼重要。
  • 爲了代碼的解耦和可維護性,module 愈來愈多、愈來愈小,使得 internal 「module 內可見」已經能夠知足對於代碼封裝的需求。

protected

  • Java 中 protected 表示包內可見 + 子類可見。
  • Kotlin 中 protected 表示 private + 子類可見。

Kotlin 相比 Java protected 的可見範圍收窄了,緣由是 Kotlin 中再也不有「包內可見」的概念了,相比 Java 的可見性着眼於 package,Kotlin 更關心的是 module。

private

  • Java 中的 private 表示類中可見,做爲內部類時對外部類「可見」。
  • Kotlin 中的 private 表示類中或所在文件內可見,做爲內部類時對外部類「不可見」。

private 修飾的變量「類中可見」和 「文件中可見」:

🏝️
class Sample {
    private val propertyInClass = 1 // 👈 僅 Sample 類中可見
}

private val propertyInFile = "A string." // 👈 範圍更大,整個文件可見
複製代碼

private 修飾內部類的變量時,在 Java 和 Kotlin 中的區別:

  • 在 Java 中,外部類能夠訪問內部類的 private 變量:

    ☕️
    public class Outter {
        public void method() {
            Inner inner = new Inner();
                                👇
            int result = inner.number * 2; // success
        }
        
        private class Inner {
            private int number = 0;
        }
    }
    複製代碼
  • 在 Kotlin 中,外部類不能夠訪問內部類的 private 變量:

    🏝️
    class Outter {
        fun method() {
            val inner = Inner()
                                👇
            val result = inner.number * 2 // compile-error: Cannot access 'number': it is private in 'Inner'
        }
        
        class Inner {
            private val number = 1
        }
    }
    複製代碼
  • 能夠修飾類和接口

    • Java 中一個文件只容許一個外部類,因此 classinterface 不容許設置爲 private,由於聲明 private 後沒法被外部使用,這樣就沒有意義了。

    • Kotlin 容許同一個文件聲明多個 class 和 top-level 的函數和屬性,因此 Kotlin 中容許類和接口聲明爲 private,由於同個文件中的其它成員能夠訪問:

      🏝️                   
      private class Sample {
          val number = 1
          fun method() {
              println("Sample method()")
          }
      }
                  // 👇 在同一個文件中,因此能夠訪問
      val sample = Sample()
      複製代碼

練習題

  1. 建立一個 Kotlin 類,這個類須要禁止外部經過構造器建立實例,並提供至少一種實例化方式。
  2. 分別用 Array、IntArray、List 實現 「保存 1-100_000 的數字,並求出這些數字的平均值」,打印出這三種數據結構的執行時間。

做者介紹

視頻做者

扔物線(朱凱)
  • 碼上開學創始人、項目管理人、內容模塊規劃者和視頻內容做者。
  • Android GDE( Google 認證 Android 開發專家),前 Flipboard Android 工程師。
  • GitHub 全球 Java 排名第 92 位,在 GitHub 上有 6.6k followers 和 9.9k stars。
  • 我的的 Android 開源庫 MaterialEditText 被全球多個項目引用,其中包括在全球擁有 5 億用戶的新聞閱讀軟件 Flipboard 。
  • 曾屢次在 Google Developer Group Beijing 線下分享會中擔任 Android 部分的講師。
  • 我的技術文章《給 Android 開發者的 RxJava 詳解》發佈後,在國內多個公司和團隊內部被轉發分享和做爲團隊技術會議的主要資料來源,以及逆向傳播到了美國一些如 Google 、 Uber 等公司的部分華人團隊。
  • 創辦的 Android 高級進階教學網站 HenCoder 在全球華人 Android 開發社區享有至關的影響力。
  • 以後創辦 Android 高級開發教學課程 HenCoder Plus ,學員遍及全球,有來自阿里、頭條、華爲、騰訊等知名一線互聯網公司,也有來自中國臺灣、日本、美國等地區的資深軟件工程師。

文章做者

Walker(張磊)

Walker(張磊) ,即刻 Android 高級工程師。2015 年加入即刻,參與了即刻 2.0 到 6.0 版本的架構設計和產品迭代。多年 Android 開發經驗,曾就任於 OPPO,專一於客戶端用戶體驗、音視頻開發和性能優化。

相關文章
相關標籤/搜索