原文地址:[Kotlin 里那些「不是那么寫的」](https://kaixue.io/kotlin-basic-2/)
[TOC]
上一篇我們講了 Kotlin 上手最基礎的三個點:變量、函數和類型。大家都聽說過,**Kotlin 完全兼容 Java,這個意思是用 Java 寫出來的代碼和 Kotlin 可以完美交互,而不是說你用 Java 的寫法去寫 Kotlin 就完全沒問題,這個是不行的**。這期內容我們就講一下,Kotlin 里那些「不 Java」的寫法。
## Constructor
上一篇中簡單介紹了 Kotlin 的構造器,這一節具體看看 Kotlin 的構造器和 Java 有什么不一樣的地方:
* Java
~~~java
??
public class User {
int id;
String name;
?? ??
public User(int id, String name) {
this.id = id;
this.name = name;
}
}
~~~
* Kotlin
~~~kotlin
???
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
~~~java
??
public class User {
??
{
// 初始化代碼塊,先于下面的構造器執行
}
public User() {
}
}
~~~
* Kotlin
~~~kotlin
???
class User {
??
init {
// 初始化代碼塊,先于下面的構造器執行
}
constructor() {
}
}
~~~
正如上面標注的那樣,Kotlin 的 init 代碼塊和 Java 一樣,都在實例化時執行,并且執行順序都在構造器之前。
上一篇提到,Java 的類如果不加 final 關鍵字,默認是可以被繼承的,而** Kotlin 的類默認就是 final 的**。在 Java 里 final 還可以用來修飾變量,接下來讓我們看看 Kotlin 是如何實現類似功能的。
## final
Kotlin 中的`val`和 Java 中的`final`類似,表示只讀變量,不能修改。這里分別從成員變量、參數和局部變量來和 Java 做對比:
* Java
~~~java
??
??
final int final1 = 1;
??
void method(final String final2) {
??
final String final3 = "The parameter is " + final2;
}
~~~
* Kotlin
~~~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`麻煩一點:我需要多寫一個單詞。雖然只麻煩這一點點,但就導致很多人不寫。
這就是一件很有意思的事:從`final`到`val`,只是方便了一點點,但卻讓它的使用頻率有了巨大的改變。這種改變是會影響到代碼質量的:在該加限制的地方加上限制,就可以減少代碼出錯的概率。
### `val`自定義 getter
不過`val`和`final`還是有一點區別的,**雖然`val`修飾的變量不能二次賦值,但可以通過自定義變量的 getter 函數,讓變量每次被訪問時,返回動態獲取的值**:
~~~kotlin
???
??
val size: Int
get() { // ?? 每次獲取 size 值時都會執行 items.size
return items.size
}
~~~
不過這個屬于`val`的另外一種用法,大部分情況下`val`還是對應于 Java 中的`final`使用的。
## static property / function
剛才說到大家都不喜歡寫`final`對吧?但有一種場景,大家是最喜歡用`final`的:常量。
~~~java
??
public static final String CONST_STRING = "A String";
~~~
在 Java 里面寫常量,我們用的是`static`+`final`。而在 Kotlin 里面,除了`final`的寫法不一樣,`static`的寫法也不一樣,而且是更不一樣。確切地說:**在`Kotlin`里,靜態變量和靜態方法這兩個概念被去除了**。
那如果想在 Kotlin 中像 Java 一樣通過類直接引用該怎么辦呢?Kotlin 的答案是`companion object`(伴生對象):
~~~kotlin
???
class Sample {
...
??
companion object {
val anotherString = "Another String"
}
}
~~~
為啥 Kotlin 越改越復雜了?不著急,我們先看看`object`是個什么東西。
### `object`
Kotlin 里的`object`——首字母小寫的,不是大寫,Java 里的`Object`在 Kotlin 里不用了。
>[info] Java 中的`Object`在 Kotlin 中變成了`Any`,和`Object`作用一樣:作為所有類的基類。
**而`object`不是類,像`class`一樣在 Kotlin 中屬于關鍵字**:
~~~kotlin
???
object Sample {
val name = "A name"
}
~~~
它的意思很直接:**創建一個類,并且創建一個這個類的對象**。這個就是`object`的意思:對象。
在代碼中如果要使用這個對象,直接通過它的類名就可以訪問:
~~~kotlin
???
Sample.name
~~~
**當你給一個類使用了object關鍵字,這個類的所有方法全都相當于靜態方法**。因為它本質上相當于一個單例對象,所以沒得選,全部都是直接用類名訪問的。
這不就是單例么,所以**在 Kotlin 中創建單例不用像 Java 中那么復雜,只需要把`class`換成`object`就可以了**。
* 單例類
我們看一個單例的例子,分別用 Java 和 Kotlin 實現:
* Java 中實現單例類(非線程安全):
~~~java
??
public class A {
private static A sInstance;
public static A getInstance() {
if (sInstance == null) {
sInstance = new A();
}
return sInstance;
}
// ??還有很多模板代碼
...
}
~~~
可以看到 Java 中為了實現單例類寫了大量的模版代碼,稍顯繁瑣。
* Kotlin 中實現單例類:
~~~kotlin
???
// ?? class 替換成了 object
object A {
val number: Int = 1
fun method() {
println("A.method()")
}
}
~~~
和 Java 相比的不同點有:
* 和類的定義類似,但是把`class`換成了`object`。
* 不需要額外維護一個實例變量`sInstance`。
* 不需要「保證實例只創建一次」的`getInstance()`方法。
相比 Java 的實現簡單多了。
>[info] 這種通過`object`實現的單例是一個餓漢式的單例,并且實現了線程安全。
* 繼承類和實現接口
Kotlin 中不僅類可以繼承別的類,可以實現接口,`object`也可以:
~~~kotlin
???
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:
~~~java
?? ??
ViewPager.SimpleOnPageChangeListener listener = new ViewPager.SimpleOnPageChangeListener() {
@Override // ??
public void onPageSelected(int position) {
// override
}
};
~~~
* Kotlin:
~~~kotlin
???
val listener = object: ViewPager.SimpleOnPageChangeListener() {
override fun onPageSelected(position: Int) {
// override
}
}
~~~
和 Java 創建匿名類的方式很相似,只不過把`new`換成了`object:`:
* Java 中`new`用來創建一個匿名類的對象
* **Kotlin 中`object:`也可以用來創建匿名類的對象**
這里的`new` 和`object:`修飾的都是接口或者抽象類。
### `companion object`(伴生對象)
**用`object`修飾的對象中的變量和函數都是靜態的,但有時候,我們只想讓類中的一部分函數和變量是靜態的該怎么做呢**:
~~~kotlin
???
class A {
??
object B {
var c: Int = 0
}
}
~~~
如上,可以在類中創建一個對象,把需要靜態的變量或函數放在內部對象 B 中,外部可以通過如下的方式調用該靜態變量:
~~~kotlin
???
A.B.c
??
~~~
**類中嵌套的對象可以用`companion`修飾**:
~~~kotlin
???
class A {
??
companion object B {
var c: Int = 0
}
}
~~~
**`companion`可以理解為伴隨、伴生,表示修飾的對象和外部類綁定**。
但這里有一個小限制:**一個類中最多只可以有一個伴生對象,但可以有多個嵌套對象**。就像皇帝后宮佳麗三千,但皇后只有一個。
這樣的好處是調用的時候可以省掉對象名:
~~~kotlin
???
A.c // ?? B 沒了
~~~
所以,當有`companion`修飾時,對象的名字也可以省略掉:
~~~kotlin
???
class A {
// ?? B 沒了
companion object {
var c: Int = 0
}
}
~~~
這就是這節最開始講到的,**Java 靜態變量和方法的等價寫法:`companion object`變量和函數**。
* 靜態初始化
Java 中的靜態變量和方法,在 Kotlin 中都放在了`companion object`中。因此 **Java 中的靜態初始化在 Kotlin 中自然也是放在`companion object`中的,像類的初始化代碼一樣,由`init`和一對大括號表示**:
~~~kotlin
???
class Sample {
??
companion object {
??
init {
...
}
}
}
~~~
### top-level property / function 聲明
**除了靜態函數這種簡便的調用方式,Kotlin 還有更方便的東西:「`top-level declaration`頂層聲明」。其實就是把屬性和函數的聲明不寫在`class`里面,這個在 Kotlin 里是允許的**:
~~~kotlin
???
package com.hencoder.plus
// ?? 屬于 package,不在 class/object 內
fun topLevelFuncion() {
}
~~~
**這樣寫的屬性和函數,不屬于任何`class`,而是直接屬于`package`,它和靜態變量、靜態函數一樣是全局的,但用起來更方便:你在其它地方用的時候,就連類名都不用寫**:
~~~kotlin
???
import com.hencoder.plus.topLevelFunction // ?? 直接 import 函數
topLevelFunction()
~~~
寫在頂級的函數或者變量有個好處:在 Android Studio 中寫代碼時,IDE 很容易根據你寫的函數前幾個字母自動聯想出相應的函數。這樣提高了寫代碼的效率,而且可以減少項目中的重復代碼。
* **命名相同的頂級函數**
頂級函數不寫在類中可能有一個問題:**如果在不同文件中聲明命名相同的函數,使用的時候會不會混淆**?來看一個例子:
* 在`org.kotlinmaster.library`包下有一個函數 method:
~~~kotlin
???
package org.kotlinmaster.library1
??
fun method() {
println("library1 method()")
}
~~~
* 在`org.kotlinmaster.library2`包下有一個同名函數:
~~~kotlin
???
package org.kotlinmaster.library2
??
fun method() {
println("library2 method()")
}
~~~
**在使用的時候如果同時調用這兩個同名函數會怎么樣**:
~~~kotlin
???
import org.kotlinmaster.library1.method
??
fun test() {
method()
??
org.kotlinmaster.library2.method()
}
~~~
**可以看到當出現兩個同名頂級函數時,IDE 會自動加上包前綴來區分,這也印證了「頂級函數屬于包」的特性**。
### 對比
那在實際使用中,在`object`(靜態)、`companion object`(局部靜態)和 top-level 中該選擇哪一個呢?簡單來說按照下面這幾個原則判斷:
* **如果想寫工具類的功能,直接創建文件,寫 top-level「頂層」函數**。
* 像TAG這種只是針對類的,而不是針對外部使用者的屬性或者方法,就用`object`或`companion object`。
* 如果需要繼承別的類或者實現接口,就用`object`或`companion object`。
**總結**:簡單地判斷原則:能寫在 top-level「頂層」函數就用 top-level,而`object`或`companion object`看情況按需使用。
## 常量
Java 中,除了上面講到的的靜態變量和方法會用到`static`,聲明常量時也會用到,那 Kotlin 中聲明常量會有什么變化呢?
* Java 中聲明常量:
~~~java
??
public class Sample {
?? ??
public static final int CONST_NUMBER = 1;
}
~~~
* Kotlin 中聲明常量:
~~~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 類:
~~~java
??
public class User {
int id; // ?? 可修改
String name; // ?? 可修改
public User(int id, String name) {
this.id = id;
this.name = name;
}
}
~~~
在使用的地方聲明一個`static final`的 User 實例`user`,它是不能二次賦值的:
~~~java
??
static final User user = new User(123, "Zhangsan");
?? ??
~~~
但是可以通過訪問這個`user`實例的成員變量改變它的值:
~~~java
??
user.name = "Lisi";
??
~~~
所以 **Java 中的常量可以認為是「偽常量」,因為可以通過上面這種方式改變它內部的值。而 Kotlin 的常量因為限制類型必須是基本類型,所以不存在這種問題,更符合常量的定義**。
前面講的`val`「只讀變量」和靜態變量都是針對單個變量來說的,接下來我們看看編程中另外一個常見的主題:數組和集合。
## 數組和集合
### 數組
聲明一個 String 數組:
* Java 中的寫法:
~~~java
??
String[] strs = {"a", "b", "c"};
?? ??
~~~
* Kotlin 中的寫法:
~~~kotlin
???
val strs: Array<String> = arrayOf("a", "b", "c")
?? ??
~~~
可以看到 Kotlin 中的數組是一個擁有泛型的類,創建函數也是泛型函數,和集合數據類型一樣。
>[info] 針對泛型的知識點,我們在后面的文章會講,這里就先按照 Java 泛型來理解。
**將數組泛型化有什么好處呢?對數組的操作可以像集合一樣功能更強大,由于泛型化,Kotlin 可以給數組增加很多有用的工具函數**:
* `get() / set()`
* `contains()`
* `first()`
* `find()`
這樣數組的實用性就大大增加了。
* 取值和修改
Kotlin 中獲取或者設置數組元素和 Java 一樣,可以使用方括號加下標的方式索引:
~~~kotlin
???
println(strs[0])
?? ??
strs[1] = "B"
~~~
* **不支持協變**
Kotlin 的數組編譯成字節碼時使用的仍然是 Java 的數組,但在語言層面是泛型實現,這樣會失去協變 (covariance) 特性,就是**子類數組對象不能賦值給父類的數組變量**:
* Kotlin
~~~kotlin
???
val strs: Array<String> = arrayOf("a", "b", "c")
??
val anys: Array<Any> = strs // compile-error: Type mismatch
??
~~~
* 而這在 Java 中是可以的:
~~~java
??
String[] strs = {"a", "b", "c"};
??
Object[] objs = strs; // success
??
~~~
關于協變的問題,這里就先不展開了,后面講泛型的時候會提到。
### 集合
Kotlin 和 Java 一樣有三種集合類型:List、Set 和 Map,它們的含義分別如下:
* `List`以固定順序存儲一組元素,元素可以重復。(**有序,可重復**)
* `Set`存儲一組互不相等的元素,通常沒有固定順序。(**無序,不可重復**)
* `Map`存儲 鍵-值 對的數據集合,鍵互不相等,但不同的鍵可以對應相同的值。
從 Java 到 Kotlin,這三種集合類型的使用有哪些變化呢?我們依次看看。
* List
* Java 中創建一個列表:
~~~java
??
List<String> strList = new ArrayList<>();
strList.add("a");
strList.add("b");
strList.add("c"); // ?? 添加元素繁瑣
~~~
* Kotlin 中創建一個列表:
~~~kotlin
???
val strList = listOf("a", "b", "c")
~~~
首先能看到的是 Kotlin 中創建一個`List`特別的簡單,有點像創建數組的代碼。而且**Kotlin 中的`List`多了一個特性:支持 covariant(協變)。也就是說,可以把子類的`List`賦值給父類的`List`變量**:
* Kotlin:
~~~kotlin
???
val strs: List<String> = listOf("a", "b", "c")
??
val anys: List<Any> = strs // success
??
~~~
* 而這在 Java 中是會報錯的:
~~~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`:
~~~java
??
Set<String> strSet = new HashSet<>();
strSet.add("a");
strSet.add("b");
strSet.add("c");
~~~
* Kotlin 中創建相同的`Set`:
~~~kotlin
???
val strSet = setOf("a", "b", "c")
~~~
和`List`類似,**`Set`同樣具有 covariant(協變)特性**。
* Map
* Java 中創建一個`Map`:
~~~java
??
Map<String, Integer> map = new HashMap<>();
map.put("key1", 1);
map.put("key2", 2);
map.put("key3", 3);
map.put("key4", 3);
~~~
* Kotlin 中創建一個`Map`:
~~~kotlin
???
val map = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 3)
~~~
和上面兩種集合類型相似創建代碼很簡潔。`mapOf`的每個參數表示一個鍵值對,**`to`表示將「鍵」和「值」關聯,這個叫做「中綴表達式」**,這里先不展開,后面的文章會做介紹。
* 取值和修改
* Kotlin 中的 Map 除了和 Java 一樣可以使用`get()`根據鍵獲取對應的值,還可以使用方括號的方式獲取:
~~~kotlin
???
??
val value1 = map.get("key1")
??
val value2 = map["key2"]
~~~
* 類似的,Kotlin 中也可以用方括號的方式改變`Map`中鍵對應的值:
~~~kotlin
???
??
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()`創建不可變的`List`,`mutableListOf()`創建可變的`List`。
* `setOf()`創建不可變的`Set`,`mutableSetOf()`創建可變的`Set`。
* `mapOf()`創建不可變的`Map`,`mutableMapOf()`創建可變的`Map`。
可以看到,有`mutable`前綴的函數創建的是可變的集合,沒有`mutbale`前綴的創建的是不可變的集合,不過不**可變的可以通過`toMutable*()`系函數轉換成可變的集合**:
~~~kotlin
???
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()
~~~
然后就可以對集合進行修改了,這里有一點需要注意下:
>[success]**`toMutable*()`返回的是一個新建的集合,原有的集合還是不可變的,所以只能對函數返回的集合修改**。
### `Sequence`
**除了集合 Kotlin 還引入了一個新的容器類型`Sequence`,它和`Iterable`一樣用來遍歷一組數據并可以對每個元素進行特定的處理**,先來看看如何創建一個`Sequence`。
* 創建
* 1、類似`listOf()`,使用一組元素創建:
~~~kotlin
???
sequenceOf("a", "b", "c")
~~~
* 2、使用`Iterable`創建:
~~~kotlin
???
val list = listOf("a", "b", "c")
list.asSequence()
~~~
這里的`List`實現了`Iterable`接口。
* 3、使用 lambda 表達式創建:
~~~kotlin
??? // ?? 第一個元素
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`內可以引用:
~~~java
?? ??
package org.kotlinmaster.library;
// 沒有可見性修飾符
class User {
}
~~~
~~~java
?? // ?? 和上面同一個 package
package org.kotlinmaster.library;
public class Example {
void method() {
new User(); // success
}
}
~~~
~~~java
??
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就可以運行)**。在 Kotlin 中`public`修飾符「可以加,但沒必要」。
### `@hide`
**在 Android 的官方 sdk 中,有一些方法只想對 sdk 內可見,不想開放給用戶使用**(因為這些方法不太穩定,在后續版本中很有可能會修改或刪掉)。為了實現這個特性,會在方法的注釋中添加一個 Javadoc 方法`@hide`,用來限制客戶端訪問:
~~~java
??
/**
* @hide ??
*/
public void hideMethod() {
...
}
~~~
但這種限制不太嚴格,可以通過反射訪問到限制的方法。針對這個情況,**Kotlin 引進了一個更為嚴格的可見性修飾符:`internal`。**
### `internal`
`internal`表示修飾的類、函數僅對 module 內可見,**這里的 module 具體指的是一組共同編譯的 kotlin 文件**,常見的形式有:
* Android Studio 里的 module
* Maven project
>[info] 我們常見的是 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`修飾的變量「類中可見」和 「文件中可見」:
~~~kotlin
???
class Sample {
private val propertyInClass = 1 // ?? 僅 Sample 類中可見
}
private val propertyInFile = "A string." // ?? 范圍更大,整個文件可見
~~~
`private`修飾內部類的變量時,在 Java 和 Kotlin 中的區別
* 在 Java 中,外部類可以訪問內部類的`private`變量:
~~~java
??
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`變量**:
~~~kotlin
???
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 中一個文件只允許一個外部類,所以`class`和`interface`不允許設置為`private`,因為聲明`private`后無法被外部使用,這樣就沒有意義了。
* **Kotlin 允許同一個文件聲明多個`class`和 top-level 的函數和屬性,所以 Kotlin 中允許類和接口聲明為`private`,因為同個文件中的其它成員可以訪問**:
~~~kotlin
???
private class Sample {
val number = 1
fun method() {
println("Sample method()")
}
}
// ?? 在同一個文件中,所以可以訪問
val sample = Sample()
~~~
* * *
## 練習題
1. 創建一個 Kotlin 類,這個類需要禁止外部通過構造器創建實例,并提供至少一種實例化方式。
2. 分別用 Array、IntArray、List 實現 「保存 1-100\_000 的數字,并求出這些數字的平均值」,打印出這三種數據結構的執行時間。
- 前言
- Kotlin簡介
- IntelliJ IDEA技巧總結
- idea設置類注釋和方法注釋模板
- 像Android Studion一樣創建工程
- Gradle
- Gradle入門
- Gradle進階
- 使用Gradle創建一個Kotlin工程
- 環境搭建
- Androidstudio平臺搭建
- Eclipse的Kotlin環境配置
- 使用IntelliJ IDEA
- Kotlin學習路線
- Kotlin官方中文版文檔教程
- 概述
- kotlin用于服務器端開發
- kotlin用于Android開發
- kotlin用于JavaScript開發
- kotlin用于原生開發
- Kotlin 用于數據科學
- 協程
- 多平臺
- 新特性
- 1.1的新特性
- 1.2的新特性
- 1.3的新特性
- 開始
- 基本語法
- 習慣用法
- 編碼規范
- 基礎
- 基本類型
- 包與導入
- 控制流
- 返回與跳轉
- 類與對象
- 類與繼承
- 屬性與字段
- 接口
- 可見性修飾符
- 擴展
- 數據類
- 密封類
- 泛型
- 嵌套類
- 枚舉類
- 對象
- 類型別名
- 內嵌類
- 委托
- 委托屬性
- 函數與Lambda表達式
- 函數
- Lambda表達式
- 內聯函數
- 集合
- 集合概述
- 構造集合
- 迭代器
- 區間與數列
- 序列
- 操作概述
- 轉換
- 過濾
- 加減操作符
- 分組
- 取集合的一部分
- 取單個元素
- 排序
- 聚合操作
- 集合寫操作
- List相關操作
- Set相關操作
- Map相關操作
- 多平臺程序設計
- 平臺相關聲明
- 以Gradle創建
- 更多語言結構
- 解構聲明
- 類型檢測與轉換
- This表達式
- 相等性
- 操作符重載
- 空安全
- 異常
- 注解
- 反射
- 作用域函數
- 類型安全的構造器
- Opt-in Requirements
- 核心庫
- 標準庫
- kotlin.test
- 參考
- 關鍵字與操作符
- 語法
- 編碼風格約定
- Java互操作
- Kotlin中調用Java
- Java中調用Kotlin
- JavaScript
- 動態類型
- kotlin中調用JavaScript
- JavaScript中調用kotlin
- JavaScript模塊
- JavaScript反射
- JavaScript DCE
- 原生
- 并發
- 不可變性
- kotlin庫
- 平臺庫
- 與C語言互操作
- 與Object-C及Swift互操作
- CocoaPods集成
- Gradle插件
- 調試
- FAQ
- 協程
- 協程指南
- 基礎
- 取消與超時
- 組合掛起函數
- 協程上下文與調度器
- 異步流
- 通道
- 異常處理與監督
- 共享的可變狀態與并發
- Select表達式(實驗性)
- 工具
- 編寫kotlin代碼文檔
- 使用Kapt
- 使用Gradle
- 使用Maven
- 使用Ant
- Kotlin與OSGI
- 編譯器插件
- 編碼規范
- 演進
- kotlin語言演進
- 不同組件的穩定性
- kotlin1.3的兼容性指南
- 常見問題
- FAQ
- 與Java比較
- 與Scala比較(官方已刪除)
- Google開發者官網簡介
- Kotlin and Android
- Get Started with Kotlin on Android
- Kotlin on Android FAQ
- Android KTX
- Resources to Learn Kotlin
- Kotlin樣品
- Kotlin零基礎到進階
- 第一階段興趣入門
- kotlin簡介和學習方法
- 數據類型和類型系統
- 入門
- 分類
- val和var
- 二進制基礎
- 基礎
- 基本語法
- 包
- 示例
- 編碼規范
- 代碼注釋
- 異常
- 根類型“Any”
- Any? 可空類型
- 可空性的實現原理
- kotlin.Unit類型
- kotlin.Nothing類型
- 基本數據類型
- 數值類型
- 布爾類型
- 字符型
- 位運算符
- 變量和常量
- 語法和運算符
- 關鍵字
- 硬關鍵字
- 軟關鍵字
- 修飾符關鍵字
- 特殊標識符
- 操作符和特殊符號
- 算術運算符
- 賦值運算符
- 比較運算符
- 邏輯運算符
- this關鍵字
- super關鍵字
- 操作符重載
- 一元操作符
- 二元操作符
- 字符串
- 字符串介紹和屬性
- 字符串常見方法操作
- 字符串模板
- 數組
- 數組介紹創建及遍歷
- 數組常見方法和屬性
- 數組變化以及下標越界問題
- 原生數組類型
- 區間
- 正向區間
- 逆向區間
- 步長
- 類型檢測與類型轉換
- is、!is、as、as-運算符
- 空安全
- 可空類型變量
- 安全調用符
- 非空斷言
- Elvis操作符
- 可空性深入
- 可空性和Java
- 函數
- 函數式編程概述
- OOP和FOP
- 函數式編程基本特性
- 組合與范疇
- 在Kotlin中使用函數式編程
- 函數入門
- 函數作用域
- 函數加強
- 命名參數
- 默認參數
- 可變參數
- 表達式函數體
- 頂層、嵌套、中綴函數
- 尾遞歸函數優化
- 函數重載
- 控制流
- if表達式
- when表達式
- for循環
- while循環
- 循環中的 Break 與 continue
- return返回
- 標簽處返回
- 集合
- list集合
- list集合介紹和操作
- list常見方法和屬性
- list集合變化和下標越界
- set集合
- set集合介紹和常見操作
- set集合常見方法和屬性
- set集合變換和下標越界
- map集合
- map集合介紹和常見操作
- map集合常見方法和屬性
- map集合變換
- 集合的函數式API
- map函數
- filter函數
- “ all ”“ any ”“ count ”和“ find ”:對集合應用判斷式
- 別樣的求和方式:sumBy、sum、fold、reduce
- 根據人的性別進行分組:groupBy
- 扁平化——處理嵌套集合:flatMap、flatten
- 惰性集合操作:序列
- 區間、數組、集合之間轉換
- 面向對象
- 面向對象-封裝
- 類的創建及屬性方法訪問
- 類屬性和字段
- 構造器
- 嵌套類(內部類)
- 枚舉類
- 枚舉類遍歷&枚舉常量常用屬性
- 數據類
- 密封類
- 印章類(密封類)
- 面向對象-繼承
- 類的繼承
- 面向對象-多態
- 抽象類
- 接口
- 接口和抽象類的區別
- 面向對象-深入
- 擴展
- 擴展:為別的類添加方法、屬性
- Android中的擴展應用
- 優化Snackbar
- 用擴展函數封裝Utils
- 解決煩人的findViewById
- 擴展不是萬能的
- 調度方式對擴展函數的影響
- 被濫用的擴展函數
- 委托
- 委托類
- 委托屬性
- Kotlin5大內置委托
- Kotlin-Object關鍵字
- 單例模式
- 匿名類對象
- 伴生對象
- 作用域函數
- let函數
- run函數
- with函數
- apply函數
- also函數
- 標準庫函數
- takeIf 與 takeUnless
- 第二階段重點深入
- Lambda編程
- Lambda成員引用高階函數
- 高階函數
- 內聯函數
- 泛型
- 泛型的分類
- 泛型約束
- 子類和子類型
- 協變與逆變
- 泛型擦除與實化類型
- 泛型類型參數
- 泛型的背后:類型擦除
- Java為什么無法聲明一個泛型數組
- 向后兼容的罪
- 類型擦除的矛盾
- 使用內聯函數獲取泛型
- 打破泛型不變
- 一個支持協變的List
- 一個支持逆變的Comparator
- 協變和逆變
- 第三階段難點突破
- 注解和反射
- 聲明并應用注解
- DSL
- 協程
- 協程簡介
- 協程的基本操作
- 協程取消
- 管道
- 慕課霍丙乾協程筆記
- Kotlin與Java互操作
- 在Kotlin中調用Java
- 在Java中調用Kotlin
- Kotlin與Java中的操作對比
- 第四階段專題練習
- 朱凱Kotlin知識點總結
- Kotlin 基礎
- Kotlin 的變量、函數和類型
- Kotlin 里那些「不是那么寫的」
- Kotlin 里那些「更方便的」
- Kotlin 進階
- Kotlin 的泛型
- Kotlin 的高階函數、匿名函數和 Lambda 表達式
- Kotlin協程
- 初識
- 進階
- 深入
- Kotlin 擴展
- 會寫「18.dp」只是個入門——Kotlin 的擴展函數和擴展屬性(Extension Functions / Properties)
- Kotlin實戰-開發Android