Kotlin語言基礎筆記
Kotlin流程控制語句筆記
Kotlin操作符重載與中綴表示法筆記
Kotlin擴展函數和擴展屬性筆記
Kotlin空指針安全(null-safety)筆記
Kotlin類型系統筆記
Kotlin面向對象編程筆記
Kotlin委托(Delegation)筆記
Kotlin泛型型筆記
Kotlin函數式編程筆記
Kotlin與Java互操作筆記
Kotlin協程筆記
Kotlin是基于Java的語言,所以Kotlin也是一種面向對象的語言,如果你熟悉Java,你應該很快可以上手,這篇文章假設你已經熟悉Java語言了。
1. 類與構造函數
Kotlin中的類與接口和Java中的有些區別。
- Kotlin中接口可以包含屬性申明。
- Kotlin的類申明,默認是final和public的。
- Kotlin的嵌套類并不是默認在內部的,他們不包含外部類的隱私引用。
- Kotlin的構造函數,分為主構造函數和次構造函數。
- Kotlin中可以使用
data
關鍵字來申明一個數據類。 - Kotlin中可以使用
object
關鍵字來表示單例對象、伴生對象等。
Kotlin的類由下面幾部分組成:
- 構造函數和初始化塊。
- 屬性
- 函數
- 嵌套類和內部類
- 對象申明
1.1 類的聲明
使用class
關鍵字聲明,這個跟Java是一樣的,如果你申明的類不包含任何東西的話,可以自己class后面直接寫上類名。
class Person
1.2 構造函數
在Kotlin中可以有一個主構造函數,一個或者多個次構造函數。
主構造函數
主構造函數直接跟在類名后面,如下:
open class Person constructor(var name: String, var age: Int) : Any() {
...
}
主構造函數中申明的屬性可以是可變的(var)也可以是不變的(val)。如果主構造函數沒有任何注解或者可見性修飾符,可以省略constructor關鍵字,而且Koltin中的類默認就是繼承Any的,也可以省略。所以可以簡化成如下:
open class Person(var name: String, var age: Int) {
...
}
如果這個類是有注解或者可見性修飾符,那么constructor關鍵字不可少,如下:
class Student @MyAutoware public constructor(name: String, age: Int) : Person(name, age) {
...
}
主構造函數不能包括任何代碼。初始化代碼可以放到以init
關鍵字作為前綴的初始化塊中:
class Student @MyAutoware public constructor(name: String, age: Int) : Person(name, age) {
init {
println("Student(name = $name, age = $age) created")
}
}
主構造函數的參數可以在初始化塊中使用,也可以在類體內申明的屬性初始化器中使用。
次構造函數
我們也可以在類體中使用constructor
申明次構造函數,次構造函數的參數不能使用val或者var申明。
annotation class MyAutoware
class Student @MyAutoware public constructor(name: String, age: Int) : Person(name, age) {
var grade: Int = 1
init {
println("Student(name = $name, age = $age) created")
}
constructor(name: String, age: Int, grade: Int) : this(name, age){
this.grade = grade
}
}
如果一個類有主構造函數,那么每個次構造函數都需要委托給主構造函數,委托到同一個類的另一個構造函數可以使用this關鍵字,如上面這個例子this(name, age)
如果一個非抽象類沒有申明任何構造函數(包括主或者次),它會生成一個不帶參數的主構造函數。構造函數的可見性是public。
私有的主構造函數
如果我們不希望這個類被實例化,我們可以申明如下:
class CantCreateMe private constructor()
當然我們可以通過次構造函數來創建。
class CantCreateMe private constructor(){
var name: String = ""
constructor(name: String) : this() {
this.name = name
}
}
1.3 屬性
class Point{
var x:Int = 0
var y:Int = 0
}
fun main(args: Array<String>) {
val p = Point()
p.x = 8
p.y = 10
println("Point(x = ${p.x}, y = ${p.y})")
}
可以看出Kotlin中的類的字段自動帶有getter和setter方法,比Java會簡潔很多。
1.3 方法(函數)
class Point{
var x:Int = 0
var y:Int = 0
operator fun plus(p: Point) {
x += p.x
y += p.y
}
}
fun main(args: Array<String>) {
val p = Point()
p.x = 8
p.y = 10
val p1 = Point()
p1.x = 2
p1.y = 3
val p2 = p + p1
println("Point(x = ${p.x}, y = ${p.y})")
}
2. 抽象類
下面就是一個抽象類,類需要用abstract修飾,其中也有抽象方法,跟Java有區別的是Kotlin的抽象類可以包含抽象屬性。
abstract class Person(var name: String, var age: Int){
abstract var addr: String
abstract val weight: Float
abstract fun doEat()
abstract fun doWalk()
fun doSwim() {
println("I am Swimming ... ")
}
open fun doSleep() {
println("I am Sleeping ... ")
}
}
2.1 抽象函數(方法)
在上面這個抽象類中,有doEat和doWalk抽象函數,同時還有具體的實現函數doSwim,在Kotlin中如果類和方法沒有修飾符的化,默認就是final的。這個跟Java是不一樣的。所以doSwim其實是final的,也就是說Person的子類不能覆蓋這個方法。如果一個類或者類的方法想要設計成被覆蓋(override)的,那么就需要加上open修飾符。下面是一個Person的子類:
class Teacher(name: String, age: Int) : Person(name, age) {
override var addr:String = "Guangzhou"
override val weight: Float = 100.0F
override fun doEat() {
println("Teacher is Eating ... ")
}
override fun doWalk() {
println("Teacher is Walking ... ")
}
override fun doSleep() {
super.doSleep()
println("Teacher is Sleeping ... ")
}
//編譯錯誤
// override fun doSwim() {
// println("Teacher is Swimming ... ")
// }
}
如果子類覆蓋了父類的方法或者屬性,需要使用override關鍵字修飾。如果子類沒有實現父類的抽象方法,則必須把子類也定義成
抽象函數的特征有以下幾點:
- 抽象函數、抽象屬性必須使用abstract關鍵字修飾。
- 抽象函數或者抽象類不用手動添加open關鍵字,默認就是open類型。
- 抽象函數沒有具體的實現,抽象屬性不用賦值。
- 含有抽象函數或者抽象屬性的類,必須要使用abstract關鍵字修飾。抽象類可以有具體實現的函數,這樣的函數默認是final的(不能被覆蓋)。如果想要被覆蓋,需要手工加上open關鍵字。
3. 接口
Kotlin中的接口跟Java8的接口類似。他們都可以包含抽象方法以及實現的方法。
interface UserService{
var version: String
fun save(user: User)
fun log(){
println("UserService log...")
}
}
3.1 接口的實現
接口是沒有構造函數的,和繼承一樣,我們也是使用冒號:
來實現一個接口,如果要實現多個接口,使用逗號,
分開,如下:
data class User(var name: String, var password: String)
data class Product(var pid: String, var name: String)
interface UserService{
val version: String
val defaultPassword: String
get() = "123456"
fun save(user: User)
fun log(){
println("UserService log...")
}
}
interface ProductService{
val version: String
fun save(product : Product)
fun log(){
println("ProductService log...")
}
}
class UserProductServiceImpl : UserService, ProductService{
override val version: String = "1.0"
override val defaultPassword: String
get() = "@WSX1qaz"
override fun save(user: User) {
println("save user...")
}
override fun save(product: Product) {
println("save product")
}
override fun log() {
super<UserService>.log()
super<ProductService>.log()
}
}
3.2 覆蓋沖突
在上面的例子中,UserService和ProductService都有log()這個方法的實現,我們在重寫UserProductServiceImpl的log()方法時,如果我們調用super.log()
。編譯器是不知道我們要調用那個父類的log()方法的,這樣就產生了覆蓋沖突。
解決的辦法也很簡單,我們使用下面的這種語法來指定調用那個父類的log()方法。
super<UserService>.log()
super<ProductService>.log()
3.3 接口中的屬性
接口中的屬性,可以是抽象的,或者是提供訪問器的實現。因為接口沒有狀態,所以接口的屬性是無狀態的。請看上面的例子。
4. 繼承
在Kotlin中,所有的類都默認繼承Any這個類,Any并不是跟Java的java.lang.Object一樣,因為它只有equals(),hashCode()和toString()這三個方法。
除了抽象類和接口默認是可被繼承外,其他類默認是不可以被繼承的(相當于默認都帶有final修飾符)。而類中的方法也是默認不可以被繼承的。
- 如果你要繼承一個類,你需要使用
open
關鍵字修飾這個類。 - 如果你要繼承一個類的某個方法,這個方法也需要使用
open
關鍵字修飾。
open class Base
class SubClass :Base()
如果Base有構造函數,那么子類的主構造函數必須繼承。如下:
open class Base(type: String){
open fun canBeOverride() {
println("I can be override.")
}
fun cannotBeOverride() {
println("I can't be override.")
}
}
class SubClass(type: String) :Base(type){
override fun canBeOverride() {
super.canBeOverride()
println("Override!!!")
}
// override fun cannotBeOverride() { 編譯錯誤。
// super.cannotBeOverride()
// println("Override!!!")
// }
}
override重寫的函數也是open的,如果不希望被重寫,可以加上final:
open class Base(type: String){
open fun canBeOverride() {
println("I can be override.")
}
fun cannotBeOverride() {
println("I can't be override.")
}
}
open class SubClass(type: String) :Base(type){
//如果某個類繼承了SubClass,那么這個類將不可以重寫canBeOverride方法。
final override fun canBeOverride() {
super.canBeOverride()
println("Override!!!")
}
}
繼承實現接口:
abstract class Animal {
fun doEat() {
println("Animal Eating")
}
}
abstract class Plant {
fun doEat() {
println("Plant Eating")
}
}
interface Runnable {
fun doRun()
}
interface Flyable {
fun doFly()
}
class Dog : Animal(), Runnable {
override fun doRun() {
println("Dog Running")
}
}
class Eagle : Animal(), Flyable {
override fun doFly() {
println("Eagle Flying")
}
}
// 始祖鳥, 能飛也能跑
class Archaeopteryx : Animal(), Runnable, Flyable {
override fun doRun() {
println("Archaeopteryx Running")
}
override fun doFly() {
println("Archaeopteryx Flying")
}
}
fun main(args: Array<String>) {
val d = Dog()
d.doEat()
d.doRun()
val e = Eagle()
e.doEat()
e.doFly()
val a = Archaeopteryx()
a.doEat()
a.doFly()
a.doRun()
}
5. 枚舉類
Kotlin的枚舉類基本上跟Java的差不多。
enum class Direction{
NORTH, SOUTH, WEST, EAST
}
fun main(args: Array<String>) {
println(Direction.NORTH.name) //打印NORTH
println(Direction.NORTH.ordinal) //打印0
println(Direction.NORTH is Direction) //打印true
println(Direction.valueOf("NORTH")) 打印NORTH
println(Direction.values().joinToString(prefix = "[", postfix = "]")) //打印[NORTH, SOUTH, WEST, EAST]
}
枚舉類也可以有構造函數,如下:
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
fun main(args: Array<String>) {
println(Color.RED.rgb) //打印16711680
}
枚舉常量也可以聲明自己的匿名類:
enum class ActivtyLifeState {
onCreate {
override fun signal() = onStart
},
onStart {
override fun signal() = onStop
},
onStop {
override fun signal() = onStart
},
onDestroy {
override fun signal() = onDestroy
};
abstract fun signal(): ActivtyLifeState
}
fun main(args: Array<String>) {
println(ActivtyLifeState.onCreate) //打印onCreate
}
我們也可以使用enumValues ()
函數來列舉所有的值:
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
fun main(args: Array<String>) {
println(enumValues<Color>().joinToString { "${it.rgb} : ${it.name} : ${it.ordinal} " }) //打印16711680 : RED : 0 , 65280 : GREEN : 1 , 255 : BLUE : 2
}
6. 注解類
Kotlin中的注解與Java的的注解完全兼容。下面示例如何聲明一個注解:
annotation class 注解名
代碼示例:
@Target(AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.EXPRESSION,
AnnotationTarget.FIELD,
AnnotationTarget.LOCAL_VARIABLE,
AnnotationTarget.TYPE,
AnnotationTarget.TYPEALIAS,
AnnotationTarget.TYPE_PARAMETER,
AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
@Repeatable
annotation class MagicClass
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
@Repeatable
annotation class MagicFunction
@Target(AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
@Repeatable
annotation class MagicConstructor
在上面的代碼中,我們通過向注解類添加元注解(meta-annotation)的方法來指定其他屬性:
- @Target :指定這個注解可被用于哪些元素(類, 函數, 屬性, 表達式, 等等.);
- @Retention :指定這個注解的信息是否被保存到編譯后的 class 文件中, 以及在運行時是否可以通過反 射訪問到它;
- @Repeatable:允許在單個元素上多次使用同一個注解;
- @MustBeDocumented : 表示這個注解是公開 API 的一部分, 在自動產生的 API 文檔的類或者函數簽名中, 應該包含這個注解的信息。
注解可以用在類、函數、參數、變量(成員變量、局部變量)、表達式、類型上等。這個由該注解的元注解@Target定義。
@MagicClass class Foo @MagicConstructor constructor() {
constructor(index: Int) : this() {
this.index = index
}
@MagicClass var index: Int = 0
@MagicFunction fun magic(@MagicClass name: String) {
}
}
7. 單例模式(Singleton)與伴生對象(companion object)
7.1 單例模式
Kotlin中沒有靜態屬性和方法,但是也提供了實現類似單例的功能,使用object
關鍵字聲明一個object對象。
object StringUtils{
val separator: String = """\"""
fun isDigit(value: String): Boolean{
for (c in value) {
if (!c.isDigit()) {
return false
}
}
return true
}
}
fun main(args: Array<String>) {
println("C:${StringUtils.separator}Users${StringUtils.separator}Denny") //打印c:\Users\Denny
println(StringUtils.isDigit("12321231231")) //打印true
}
我們反編譯后可以知道StringUtils轉成了Java代碼:
public final class StringUtils {
@NotNull
private static final String separator = "\\";
public static final StringUtils INSTANCE;
@NotNull
public final String getSeparator() {
return separator;
}
public final boolean isDigit(@NotNull String value) {
Intrinsics.checkParameterIsNotNull(value, "value");
String var4 = value;
int var5 = value.length();
for(int var3 = 0; var3 < var5; ++var3) {
char c = var4.charAt(var3);
if (!Character.isDigit(c)) {
return false;
}
}
return true;
}
static {
StringUtils var0 = new StringUtils();
INSTANCE = var0;
separator = "\\";
}
}
7.2 伴生對象
使用companion object
來聲明:
class Utils{
object StringUtils{
val separator: String = """\"""
fun isDigit(value: String): Boolean{
for (c in value) {
if (!c.isDigit()) {
return false
}
}
return true
}
}
companion object MiscUtils{
fun isEmpty(value: String): Boolean {
return value.isEmpty()
}
}
}
class Class{
companion object {
fun isEmpty(value: String): Boolean {
return value.isEmpty()
}
}
}
fun main(args: Array<String>) {
println(Utils.isEmpty("xx"))
println(Utils.MiscUtils.isEmpty("xx"))
println(Class.isEmpty("xx"))
}
一個類只能有一個伴生對象。
class Utils{
val index:Int = 100
object StringUtils{
val separator: String = """\"""
fun isDigit(value: String): Boolean{
for (c in value) {
if (!c.isDigit()) {
return false
}
}
return true
}
}
companion object MiscUtils{
fun isEmpty(value: String): Boolean {
return value.isEmpty()
}
}
}
class Class{
companion object {
val index:Int = 100
fun isEmpty(value: String): Boolean {
return value.isEmpty()
}
}
}
fun main(args: Array<String>) {
println(Utils.MiscUtils.isEmpty("xx"))
println(Utils.isEmpty("xx")) //因為只能有一個伴生對象,所以可以省略到伴生對象的類名
println(Class.Companion.isEmpty("xx")) //可以使用默認的Companion來訪問伴生對象。
println(Class.Companion.index)
println(Class.isEmpty("xx")) //當然也可以省略
}
伴生對象的初始化是在相應的類被加載解析時,即使伴生對象的成員看起來像其他語言的靜態成員,在運行時他們仍然是真實的對象的實例成員。另外:
@JvmField注解,會在生成的Java類中使用靜態字段。
class ClassA{
@JvmField val separator: String = """\"""
}
上面這段會被轉成下面的Java代碼:
class ClassA{
@JvmField val separator: String = """\"""
}
@JvmStatic注解,會讓你在單例對象或者伴生對象中生成對應的靜態方法。
8. 密封類(sealed)
聲明一個密封類,需要在類名前面添加sealed
修飾符,密封類的所有子類必須與密封類在同一個文件中聲明:
sealed class Expression
class Unit : Expression()
data class Const(val number: Double) : Expression()
data class Sum(val e1: Expression, val e2: Expression) : Expression()
data class Multiply(val e1: Expression, val e2: Expression) : Expression()
object NaN : Expression()
密封類的主要使用場景其實更像是枚舉類的擴展,比如使用when表達式時。
fun eval(expr: Expression): Double = when (expr) {
is Unit -> 1.0
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
is Multiply -> eval(expr.e1) * eval(expr.e2)
NaN -> Double.NaN
// 不再需要 `else` 子句,因為我們已經覆蓋了所有的情況
}
9. 數據類(data class)
在Kotlin中從語言層面上支持創建一個領域實體類,也叫做數據類:
data class Book(val name: String)
data class Fook(var name: String)
data class User(val name: String, val gender: String, val age: Int) {
fun validate(): Boolean {
return true
}
}
定義好數據類后,會自動生成以下函數:
- equals()、hashCode()和toString()
- componentN() 函數 : 按聲明順序對應于所有屬性component1()、component2() ...
- copy()函數
如果我們自己實現了以上函數,那么Kotlin就不會生成,會使用我們實現的方法。
數據類有以下限制:
- 主構造函數必須至少有一個參數。
- 主構造函數所有參數必須要標記為val或者var。
- 數據類不能是抽象、開放、密封或者內部類,
9.1 數據類的解構
因為數據類自動生成componentN()函數,所以我們可以使用解構功能:
val helen = User("Helen", "Female", 15)
val (name, gender, age) = helen
println("$name, $gender, $age years of age")
輸出:
Helen, Female, 15 years of age
10. 嵌套類(Nested Class)
class NestedClassesDemo {
class Outer {
private val zero: Int = 0
val one: Int = 1
class Nested {
fun getTwo() = 2
class Nested1 {
val three = 3
fun getFour() = 4
}
}
}
}
我們可以這樣來訪問內部類:
val one = NestedClassesDemo.Outer().one
val two = NestedClassesDemo.Outer.Nested().getTwo()
val three = NestedClassesDemo.Outer.Nested.Nested1().three
val four = NestedClassesDemo.Outer.Nested.Nested1().getFour()
但是普通的嵌套類,并不會持有外部類的引用:
class NestedClassesDemo {
class Outer {
private val zero: Int = 0
val one: Int = 1
class Nested {
fun getTwo() = 2
fun accessOuter() = {
println(zero) // error, cannot access outer class
println(one) // error, cannot access outer class
}
}
}
}
如果要訪問Outer類的變量,那么我們需要把Nested類標記為內部類。
class NestedClassesDemo {
class Outer {
private val zero: Int = 0
val one: Int = 1
inner class Inner {
fun accessOuter() = {
println(zero) // works
println(one) // works
}
}
}