第一次知道kotlin這個(gè)語(yǔ)言是在JakeWharton的這個(gè)dex-method-list 項(xiàng)目里,本來(lái)這個(gè)項(xiàng)目用的是java后來(lái)大神又用Kotlin實(shí)現(xiàn)了一下娜亿,前兩天1.0正式版發(fā)布了拳球,被各種新聞刷了一下后有必要學(xué)習(xí)一下横浑。
花了半天看完了《Kotlin for Android》這份文檔丘薛,之前很多人說(shuō)像Swift,看完這份文檔后才發(fā)現(xiàn)它更像C#渐尿,比如帶問(wèn)號(hào)的可null類型,委托矾瑰,lambda和的Linq類似砖茸,還有 explicit,implicit cast (協(xié)變逆變),sealed(密封類),里面還提供了和Linq To Sql 很像的Anko框架,以及getter殴穴,setter渔彰。
一. Android下的Kotlin
按照慣例我們做一個(gè)"Helloworld"項(xiàng)目來(lái)嘗嘗鮮
環(huán)境配置
使用Kotlin開發(fā)Android應(yīng)用需要安裝Android Studio 和其配套的插件
需要安裝Kotlin插件嵌屎,是使Android Studio支持Kotlin的基礎(chǔ)插件,在《Kotlin for Android》一書中說(shuō)還需要 Kotlin Extensions For Android 插件恍涂,這個(gè)插件在最近的版本中已經(jīng)obsolote宝惰,這個(gè)插件已經(jīng)被集成到Kotlin插件里,所以不需要安裝這個(gè)插件了再沧。
1. 使用AndroidStudio創(chuàng)建一個(gè)新項(xiàng)目
我是用的Android Studio是最新的2.0 Beta5尼夺,Gradle是2.10
2. 將MainActivity類轉(zhuǎn)換成Kotlin代碼
這是一個(gè)很給力的特性,可以把Java代碼無(wú)縫的轉(zhuǎn)換成Kotlin代碼炒瘸。這種轉(zhuǎn)換并不是最優(yōu)化的淤堵,但是在學(xué)習(xí)Kotlin的時(shí)候這個(gè)方法很有用,可以對(duì)比之前java代碼和Kotlin代碼語(yǔ)法的轉(zhuǎn)換顷扩。
先打開MainActivity類拐邪,然后有三種方式進(jìn)行轉(zhuǎn)換:
- 選擇
code
->Convert File To Kotlin File
- 選擇
Help
->Find Action
或者使用快捷鍵 在彈出的對(duì)話框里輸入convert
就會(huì)有提示,如下圖 - 直接使用快捷鍵
shift+alt+command
可以直接觸發(fā)轉(zhuǎn)換工具
3. 配置Gradle
代碼轉(zhuǎn)換完成后會(huì)提示配置Kotlin隘截,可以手動(dòng)配置也可以使用Android Studio自帶的配置工具進(jìn)行配置扎阶。
暫不介紹手動(dòng)配置,自動(dòng)配置會(huì)在第一次代碼轉(zhuǎn)換完成后有個(gè)configure
提示(如下圖)婶芭,選擇ok即可自動(dòng)更新Gradle配置文件东臀,非常方便
配置完成后Gradle配置文件里會(huì)自動(dòng)加上kotlin gradle的插件配置
之后選擇sync項(xiàng)目,將gradle插件下載下來(lái)即可運(yùn)行項(xiàng)目了犀农。
4. 運(yùn)行項(xiàng)目
Kotlin里有個(gè)對(duì)Android開發(fā)來(lái)說(shuō)很好用的特性就是它剔除了findViewById()方法的調(diào)用惰赋。
配置gralde
使用這個(gè)特性需要在app的build.gradle文件配置里增加下面這個(gè)plugin引用。
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
//增加這句
apply plugin: 'kotlin-android-extensions'
修改代碼
將activity_main.xml
修改如下
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="dodola.kotlinandroid1.MainActivity">
<TextView
android:id="@+id/helloText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
</RelativeLayout>
修改MainActivity.kt代碼如下:
package dodola.kotlinandroid1
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
helloText.text="hello dodola"
}
}
我們需要import進(jìn)一個(gè)特殊的包呵哨,import kotlinx.android.synthetic.main.activity_main.*
這個(gè)包有個(gè)命名規(guī)則赁濒,后面再介紹。
從helloText.text="hello dodola"
代碼中可以看出不需要調(diào)用findViewById孟害,也沒有調(diào)用setText方法流部,而是直接使用text來(lái)進(jìn)行賦值,這個(gè)使用的是Kotlin的一個(gè)叫做Getter Setter
技術(shù)纹坐,這種機(jī)制類似C#里的Accessors(訪問(wèn)器)
枝冀,后面再詳細(xì)介紹,會(huì)發(fā)現(xiàn)很多特性都可以在C#里找到相應(yīng)的技術(shù)耘子,我之前是做.Net開發(fā)的果漾,所以對(duì)C#相對(duì)敏感,后面會(huì)拿C#和Swift和Kotlin做對(duì)比谷誓,這樣也有助于理解Kotlin里的一些技術(shù)绒障。
二. Kotlin 介紹
下面介紹一下Kotlin語(yǔ)言的一些細(xì)節(jié)。內(nèi)容是從官方文檔和其他文章里總結(jié)得來(lái)的捍歪,目標(biāo)是可以快速的學(xué)習(xí)Kotlin語(yǔ)言户辱。
1. 基礎(chǔ)
1.1 基本類型
Kotlin中沒有像Java中的原始基本類型鸵钝,基本類型都被當(dāng)做對(duì)象形式存在。
數(shù)值類型
Kotlin提供的數(shù)值類型如下:
Type | Bitwidth |
---|---|
Double | 64 |
Float | 32 |
Long | 64 |
Int | 32 |
Short | 16 |
Byte | 8 |
需注意Char在Kotlin中不屬于數(shù)值類型
字面值常量
整型:123
長(zhǎng)整型:123L //需要增加L后綴
16進(jìn)制:0x0f
二進(jìn)制:0b00001011
Double:123.5庐镐,123.5e10//默認(rèn)是Double類型
Float:123.5f //Float類型需要增加F或f后綴
注:不支持8進(jìn)制字面值常量
val i = 12 // An Int
val iHex = 0x0f // 一個(gè)十六進(jìn)制的Int類型
val l = 3L // A Long
val d = 3.5 // A Double
val f = 3.5F // A Float
顯式轉(zhuǎn)換
數(shù)值類型不會(huì)自動(dòng)轉(zhuǎn)型恩商,必須要顯示的進(jìn)行類型轉(zhuǎn)換,例子
//Byte -> Int
val b:Byte =1
val i:Int=b.toInt()
比如下面這種情況:
fun pow(a:Int):Double{
return Math.pow(a.toDouble(),2);//此處會(huì)出現(xiàn)Kotlin: The integer literal does not conform to the expected type kotlin.Double 的編譯錯(cuò)誤
}
上面例子pow(double a, double b)
傳入的是兩個(gè)Double類型必逆,但是直接寫入Int類型并不會(huì)進(jìn)行隱式轉(zhuǎn)換怠堪。需要做一次顯示轉(zhuǎn)換
fun pow(a:Int):Double{
return Math.pow(a.toDouble(),2.toDouble());
}
每個(gè)數(shù)值類型都支持下面轉(zhuǎn)換:
toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char
隱式轉(zhuǎn)換,基于運(yùn)算符重載技術(shù)名眉,例子:
val l=1.toLong() + 1 //Long+Int=>Long
字符類型Char
字符類型使用Char表示粟矿,不能當(dāng)成數(shù)值使用。
和Java一樣损拢,字符類型值是用單引號(hào)包起來(lái)的1
,'\n'陌粹,可以顯示轉(zhuǎn)換成Int
類型
val c:Char='c'
val i: Int = c.toInt()
布爾值
val booltest=true
字符串
使用String
聲明,Kotlin中有兩種樣式的String
一種是和Java類似的聲明:
val s="Hello world"
一種是多行形式的表示
val text = """
for (c in "foo")
print(c)
"""
運(yùn)行結(jié)果如下圖:
可以像數(shù)組那樣訪問(wèn)福压,并且可以被迭代:
val s = "Example"
val c = s[2] // 這是一個(gè)字符'a'
// 迭代String
val s = "Example"
for(c in s){
print(c)
}
模板
字符串可以包含模板表達(dá)式掏秩,模板表達(dá)式由一個(gè) $ 開始并包含另一個(gè)簡(jiǎn)單的名稱:
val i = 10
val s = "i = $i"
//結(jié)果為 i = 10
或者是一個(gè)帶大括號(hào)的表達(dá)式:+
val s = "abc"
val str = "$s.length is ${s.length}"
//結(jié)果為 abc.length is 3
1.2 數(shù)組
創(chuàng)建數(shù)組
指定長(zhǎng)度
val sizeArray=arrayOfNulls<Int>(5)//sizeArray: Integer[5]{null}
val sizeArray1=IntArray(5)//sizeArray1: {0,0,0,0,0}
//同樣的類型還有:BooleanArray,ByteArray,IntArray,CharArray,DoubleArray,FloatArray,LongArray,ShortArray,
使用值創(chuàng)建
//使用裝箱操作
val sizeArray2=arrayOf(1,2,3,4,5)
/**
sizeArray2: Integer[]
0 = {Integer@447} "1"
1 = {Integer@448} "2"
2 = {Integer@449} "3"
3 = {Integer@450} "4"
4 = {Integer@451} "5"
**/
//下面方法創(chuàng)建的數(shù)組是未裝箱的
val sizeArray3=intArrayOf(1,2,3,4,5)//sizeArray3:int[5] {1,2,3,4,5}
//同類的方法還有
//booeanArrayOf,byteArrayOf,doubleArrayOf,floatArrayOf,intArrayOf,longArrayOf,shortArrayOf
空數(shù)組
val emptyArray=emptyArray<Int>()//emptyArray: Integer[0]
訪問(wèn)數(shù)組
val arr = arrayOf(1, 2, 3)
println(asc[1]) // 1
println(asc.get(1)) // 1
遍歷數(shù)組
for(i in arr){
println(i)
}
//1 2 3
for(j in asc.indices){
println(j)
}
//0 1 2
1.3 基本語(yǔ)法
定義包名
package my.demo //在源文件的最開頭
import java.util.
包名不必和文件夾路徑一致
定義類
class MainActivity{
//包含一個(gè)默認(rèn)的構(gòu)造器
}
構(gòu)造方法
class Datas{
}
定義方法
//第一種形態(tài)
fun pow(a:Int):Double{
return Math.pow(a.toDouble(),2.toDouble());
}
//第二種形態(tài),一個(gè)表達(dá)式函數(shù)體和一個(gè)可推斷類型
fun pow(a:Int)= Math.pow(a.toDouble(),2.toDouble());
定義局部變量
用val
聲明只讀變量:
//-------in kotlin---------
val a:Int=1
val b = 1
val c:Int //聲明
c=1
c=2//ERROR: val 只能賦值一次隧膏,這里會(huì)造成編譯錯(cuò)誤
//--------in java---------
final int a=1;
使用var
聲明可變變量:
//--------in kotlin-------
var x=5
x+=1
//--------in java ---------
int x=5;
字符串模板
Kotlin允許在字符串中嵌入變量和表達(dá)式:
val name = "Bob"
println("My name is ${name}") //打印"My name is Bob"
val a = 10
val b = 20
println("The sum is ${a+b}") //打印"The sum is 30"
if 表達(dá)式
fun max(a: Int, b: Int): Int {
if (a > b)
return a
else
return b
}
fun max(a: Int, b: Int) = if (a > b) a else b
when表達(dá)式
用于替代switch
哗讥,但功能更強(qiáng)大
val age = 17
val typeOfPerson = when(age){
0 -> "New born"
in 1..12 -> "Child"
in 13..19 -> "Teenager"
else -> "Adult"
}
fun cases(obj: Any) {
when (obj) {
1 -> print("one")
"hello" -> print("Greeting")
is Long -> print("Long")
!is Long -> print("Not a string")
else -> print("Unknown")
}
}
注:else是必須的嚷那,相當(dāng)于switch里的default
for 循環(huán)
fun sum(a: IntArray): Int {
var sumValue=0;
for(i in a){
sumValue+=i
}
return sumValue
}
//或者
fun sum(a: IntArray): Int {
var sumValue=0;
for(i in a.indices){
sumValue+=a[i]
}
return sumValue
}
while do ..while 循環(huán)
while和do..while循環(huán)的語(yǔ)法與Java完全相同胞枕。
fun sum(a: IntArray): Int {
var sumValue=0;
var i=0
while(i<a.size){
sumValue+=a[i++]
}
return sumValue
}
范圍表達(dá)式
在Kotlin中,范圍創(chuàng)建只需要..
操作符魏宽,例如:
val r1 = 1..5
//該范圍包含數(shù)值1,2,3,4,5
如果創(chuàng)建一個(gè)降序范圍腐泻,則需要使用downTo函數(shù)
val r2 = 5 downTo 1
//該范圍包含數(shù)值5,4,3,2,1
默認(rèn)范圍的步長(zhǎng)是1,可使用step方法修改步長(zhǎng):
val r3 = 5 downTo 1 step 2
//該范圍包含數(shù)值5,3,1
可使用in
操作符檢查數(shù)值是否在某個(gè)范圍內(nèi)
if (x in 1..y-1)
print("OK")
范圍外:
if (x !in 0..array.lastIndex)
print("Out")
Lambda
val sumLambda: (Int, Int) -> Int = {x,y -> x+y}
1.4 包
聲明包
package foo.bar
如果沒有指定包名队询,那這個(gè)文件的內(nèi)容就從屬于沒有名字的 "default" 包派桩。
import
import foo.Bar //Bar 現(xiàn)在可以不用條件就可以使用
//或者范圍內(nèi)的所有可用的內(nèi)容 (包,類蚌斩,對(duì)象铆惑,等等):
import foo.*/ /foo 中的所有都可以使用
//如果命名有沖突,我們可以使用 as 關(guān)鍵字局部重命名解決沖突
import foo.Bar // Bar 可以使用
import bar.Bar as bBar // bBar 代表 'bar.Bar'
1.5 控制流
if表達(dá)式
//傳統(tǒng)用法
var max = a
if (a < b)
max = b
//帶 else
var max: Int
if (a > b)
max = a
else
max = b
//作為表達(dá)式
val max = if (a > b) a else b
//if分之可以作為塊送膳,最后一個(gè)表達(dá)式是該塊的值
val max = if (a > b){
print("Choose a")
a
}
else{
print("Choose b")
b
}
when 表達(dá)式
一般用法,用來(lái)替代 switch,需注意的是里面的條件可以重復(fù),如果條件重復(fù)的話則按照最先匹配的條件處理
val x=2
when(x){
1->print("1")
2->print("2")
else->print("else")//文檔里寫else 是mandatory的,但是去掉也可以
}
如果多個(gè)條件的處理方式一樣,可以寫作下面方式:
when (x) {
0,1 -> print("x == 0 or x == 1")
else -> print("otherwise")
}
可以用任意表達(dá)式作為分支的條件
when (x) {
parseInt(s) -> print("s encode x")
else -> print("s does not encode x")
}
可以用 in 或者 !in 檢查值是否值在一個(gè)集合中:
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
可以使用 is 判斷是否是某個(gè)類型:
//官方的這個(gè)例子寫的很奇怪,感覺為了使用 when 來(lái)故意這么寫的
val hasPrefix = when (x) {
is String -> x.startsWith("prefix")
else -> false
}
//上面的例子可以直接寫成
val hasPrefix=x.startsWith("prefix")
//下面的例子可能更好一些
var a=B()
when(a){
is C->print("A")
is B->print("B")
is A->print("C")
}
open class A{
}
open class B:A(){
}
class C:B(){
}
when 也可以用來(lái)代替 if-else, 如果沒有提供任何參數(shù)則分支的條件就是表達(dá)式
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}
測(cè)試?yán)?
fun main(args: Array<String>) {
var x=3
when(x){
1->println("1->1")
2->{
x=3
println("2->${x}")
}
3,4,5->println("3,4,5->")
test()->println("test()->${test()}")
in 7..10->println("6..10->${x}")
}
var a=B()
when(a){
is C->print("A")
is B->print("B")
is A->print("C")
}
}
open class A{
}
open class B:A(){
}
class C:B(){
}
fun test():Int{
println("=====test====")
return 6
}
for
for (item in collection)
print(item)
for (i in array.indices)
print(array[i])
while
//和 Java 一樣
返回與跳轉(zhuǎn)
標(biāo)簽
標(biāo)簽相當(dāng)于 C語(yǔ)言 中的 label,通過(guò)@結(jié)尾來(lái)表示,比如 abc@
loop@ for(i in 1..100){
//
}
聲明了標(biāo)簽之后可以使用 break 或者 continue 進(jìn)行跳轉(zhuǎn):
loop@ for (i in 1..10) {
for (j in 1..10) {
if (i == 5) {
continue@loop
}
}
}
2. 類
2.1 類
定義類
使用 class
關(guān)鍵字聲明類
class Invoice{
}
如果沒有類體可以省略大括號(hào)
class Empty
主構(gòu)造方法(primary constructor)
Kotlin 可以包含一個(gè)主要構(gòu)造方法和多個(gè)次要構(gòu)造方法,主構(gòu)造方法是class header 的一部分:跟在類名的后面(可選類型參數(shù))
class Person constructor(firstName: String) {
}
如果主構(gòu)造哦函數(shù)沒有注解或可見性說(shuō)明员魏,則 constructor 關(guān)鍵字是可以省略:
class Person(firstName: String){
}
主構(gòu)造不能包含任何代碼。初始化代碼可以放在初始化塊(initializer blocks)
叠聋,初始化塊前綴使用init
關(guān)鍵字:
class Customer(name: String) {
init {
logger.info("Customer initialized with value ${name}")
}
}
注意撕阎,主構(gòu)造方法的參數(shù)可以在初始化塊中使用,也可以用在類的屬性初始化聲明處:
class Customer(name: String) {
val customerKey = name.toUpperCase()
}
屬性的聲明可以寫在主構(gòu)造方法中:
class Person(val firstName: String, val lastName: String, var age: Int){
}
主構(gòu)造方法中的屬性可以是val
和var
如果構(gòu)造函數(shù)有注解或可見性聲明,則 constructor
關(guān)鍵字是不可少的碌补,并且注解應(yīng)該在前:
class Customer public inject constructor (name: String) {...}
次要構(gòu)造方法(secondary constructors)
使用 constructor 關(guān)鍵字聲明次構(gòu)造方法
calss Person {
constructor(parent: Person) {
parent.children.add(this)
}
}
如果顯示定義了主構(gòu)造方法則次構(gòu)造方法需要使用 this 關(guān)鍵字代理構(gòu)造方法
class Person(val name: String) {
constructor (name: String, parent: Person) : this(name) {//此處需注意 次構(gòu)造方法里的 name 和 this(name)的 name 是同一個(gè),就是說(shuō)必須調(diào)用主構(gòu)造方法
parent.children.add(this)
}
}
如果一個(gè)非抽象類沒有聲明構(gòu)造方法,它會(huì)產(chǎn)生一個(gè)默認(rèn)的構(gòu)造方法,默認(rèn)是 public 的,如果不想有公共構(gòu)造方法,需要顯式聲明一個(gè)private構(gòu)造方法:
class DontCreateMe private constructor () {
}
屬性和字段
使用var聲明可變屬性,使用val聲明只讀屬性,屬性必須初始化,若定義的時(shí)候沒有初始化則必須在構(gòu)造方法里進(jìn)行初始化
Getters Setters
語(yǔ)法:
var <propertyName>: <PropertyType> [ = <property_initializer> ]//
<getter>
<setter>
一般用法:
var allByDefault: Int? // 錯(cuò)誤: 需要一個(gè)初始化語(yǔ)句, 默認(rèn)實(shí)現(xiàn)了 getter 和 setter 方法
var initialized = 1 // 類型為 Int, 默認(rèn)實(shí)現(xiàn)了 getter 和 setter
val simple: Int? // 類型為 Int 虏束,默認(rèn)實(shí)現(xiàn) getter 棉饶,但必須在構(gòu)造函數(shù)中初始化
val inferredType = 1 // 類型為 Int 類型,默認(rèn)實(shí)現(xiàn) getter
可以自定義getter和setter方法
var stringRepresentation: String
get() = this.toString()
set (value) {
setDataFormString(value) // 格式化字符串,并且將值重新賦值給其他元素
}
///自定義getter方法,
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null)
_table = HashMap() //參數(shù)類型是推導(dǎo)出來(lái)的
return _table ?: throw AssertionError("Set to null by another thread")
}
注:setter方法里的參數(shù)可以自定義名稱
Backing Fileds
在Kotlin里沒有字段,但提供了Backing Fields
機(jī)制,備用字段使用field
關(guān)鍵字聲明,field
關(guān)鍵詞只能用于屬性的訪問(wèn)器
var counter = 0 //在初始化中直接寫入備用字段
set(value) {
if (value >= 0)
field = value
}
//如果counter賦值給負(fù)數(shù)則counter值為0
屬性延遲初始化
非空屬性必須在定義的時(shí)候初始化,kotlin提供了一種可以延遲初始化的方案,使用lateinit
關(guān)鍵字描述屬性
class LazyProperty(val initializer: () -> Int) {
var value: Int? = null
val lazy: Int
get() {
if (value == null) {
value = initializer()
}
return value!!
}
}
方法
方法聲明
使用關(guān)鍵字fun
來(lái)聲明成員方法:
fun double(x: Int): Int {
}
方法調(diào)用
//通過(guò)傳統(tǒng)的方法調(diào)用函數(shù)
val result = double(2)
//通過(guò).調(diào)用成員函數(shù)
Sample().foo() // 創(chuàng)建Sample類的實(shí)例,調(diào)用foo方法
中綴符號(hào)
成員方法或者擴(kuò)展方法,只有一個(gè)參數(shù)使用infix
關(guān)鍵詞描述
/給 Int 定義一個(gè)擴(kuò)展方法
infix fun Int.shl(x: Int): Int {
...
}
println(1 shl 2) //用中綴注解調(diào)用擴(kuò)展函數(shù)
輸出4
參數(shù)
參數(shù)基本格式name:type,參數(shù)之間使用逗號(hào)隔開,每個(gè)參數(shù)必須指明類型
fun powerOf(number: Int, exponent: Int) {
...
}
默認(rèn)參數(shù)
參數(shù)可以設(shè)置默認(rèn)值,參數(shù)被忽略時(shí)候會(huì)使用默認(rèn)值,這樣可以減少重載方法的定義
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size() ) {
...
}
參數(shù)命名
調(diào)用參數(shù)的時(shí)候可以對(duì)參數(shù)命名
fun reformat(str: String, normalizeCase: Boolean = true,upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' ') {
...
}
reformat(str,
normalizeCase = true,
uppercaseFirstLetter = true,
divideByCamelHumps = false,
wordSeparator = '_'
)
注意:命名參數(shù)語(yǔ)法不能用在調(diào)用Java方法中,因?yàn)镴ava字節(jié)碼不能確保方法參數(shù)命名的不變性.
單表達(dá)式函數(shù)
當(dāng)函數(shù)只包含單個(gè)表達(dá)式時(shí),大括號(hào)可以省略并在=
后面定義函數(shù)
fun double(x:Int):Int=x*2
在編譯器可以推斷出返回值類型的時(shí)候,返回值的類型可以省略:
fun double(x: Int) = x * 2
變長(zhǎng)參數(shù)
函數(shù)的變長(zhǎng)參數(shù)可以用vararg
關(guān)鍵字進(jìn)行標(biāo)識(shí)
fun vars(vararg v:Int){
for(vt in v){
print(vt)
}
}
//調(diào)用方法:
vars(1,2,3,4,5)//輸出12345
注意 只有一個(gè)參數(shù)可以被標(biāo)注為vararg
,如果可變長(zhǎng)度參數(shù)不是最后一個(gè)參數(shù),則后面的參數(shù)需要通過(guò)命名參數(shù)語(yǔ)法進(jìn)行傳值,如果參數(shù)是函數(shù)類型,則需要通過(guò)lambda傳遞.
*前綴操作符
調(diào)用變長(zhǎng)參數(shù)的函數(shù)時(shí),可以將傳入一個(gè)同參數(shù)類型一致的數(shù)組來(lái)引用數(shù)組內(nèi)容位可變參數(shù)內(nèi)容
val a = array(1, 2, 3)
val list = asList(-1, 0, *a, 4)
局部方法
kotlin支持在一個(gè)函數(shù)中包含另一個(gè)函數(shù)(同JS)
fun dfs(graph: Graph) {
fun dfs(current: Vertex, visited: Set<Vertex>) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v, visited)
}
dfs(graph.vertices[0], HashSet())
}
局部方法可以訪問(wèn)外部方法的局部變量(類似java匿名內(nèi)部類)
fun dfs(graph: Graph) {
val visited = HashSet<Vertex>()
fun dfs(current: Vertex) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v)
}
dfs(graph.vertices[0])
}
成員函數(shù)
成員函數(shù)是定義在一個(gè)類或者對(duì)象里的
class Sample() {
fun foo() { print("Foo") }
}
泛型函數(shù)
具體內(nèi)容請(qǐng)看 泛型
部分
尾遞歸函數(shù)
這個(gè)概念在ES6里有同樣定義.一種避免棧溢出的方法,當(dāng)函數(shù)被標(biāo)記為tailRecursive
時(shí),編譯器會(huì)優(yōu)化遞歸,用循環(huán)代替
tailRecursive fun findFixPoint(x: Double = 1.0): Double
= if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
//等效于下面的代碼:
private fun findFixPoint(): Double {
var x = 1.0
while (true) {
val y = Math.cos(x)
if (x == y) return y
x = y
}
}
使用 tailrec
修飾符必須在最后一個(gè)操作中調(diào)用自己。在遞歸調(diào)用代碼后面是不允許有其它代碼的镇匀,并且也不可以在 try/catch/finall 塊中進(jìn)行使用照藻。當(dāng)前的尾遞歸只在 JVM 的后端中可以用
接口定義
接口使用 interface 關(guān)鍵字聲明,可以包含抽象方法和方法的實(shí)現(xiàn),這點(diǎn)類似于 Java 8的 Default interface
interface MyInterface {
fun bar()
fun foo() {
// optional body
}
}
泛型
接口繼承
class Child : MyInterface {
override fun bar() {
// body
}
}
接口中的屬性
可以在接口中定義屬性,接口中的屬性可以是抽象的也可以實(shí)現(xiàn)訪問(wèn)器,但是不能定義 backing field.
interface MyInterface {
val property: Int // abstract
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(property)
}
}
class Child : MyInterface {
override val property: Int = 29
}
抽象類
使用abstract關(guān)鍵字來(lái)描述一個(gè)抽象類,抽象類里的方法如果不提供實(shí)現(xiàn)則需要用abstract關(guān)鍵字來(lái)描述抽象方法.抽象的方法默認(rèn)是open的
abstract class A {
abstract fun f()
}
interface B {
open fun f() { print("B") }
}
class C() : A() , B {
//我們是必須重寫 f() 方法
}
密封類(Sealed Classes)
概念比較難理解,和 C#中的概念不一樣,詳細(xì)的內(nèi)容可以看這篇文檔
聲明一個(gè)密封類,使用sealed修飾類坑律,密封類可以有子類岩梳,但是所有的子類都必須要內(nèi)嵌在密封類中。
sealed 不能修飾 interface ,abstract class(會(huì)報(bào) warning,但是不會(huì)出現(xiàn)編譯錯(cuò)誤)
官方例子:
//PS 這例子...
sealed class Expr {
class Const(val number: Double) : Expr()
class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
}
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// the `else` clause is not required because we've covered all the cases
}
繼承
Kotlin里所有類都繼承于父類Any
,相當(dāng)于Object
class Example
Kotlin 里類默認(rèn)都是final的,如果聲明的類需要被繼承則需要使用open 關(guān)鍵字來(lái)描述類
open class Base(p: Ont)
class Derived(p: Int) : Base(p)
如果類沒有主構(gòu)造函數(shù)
class MyView : View {
constructor(ctx: Context) : super(ctx) {
}
constructor(ctx: Context, attrs: AttributeSet) : super(ctx,attrs) {
}
}
如果父類只存在默認(rèn)構(gòu)造方法
open class A{
}
//形式1
class B:A(){
}
//形式2
class C:A{
constructor():super();
}
重寫方法
在子類里重寫父類的方法需要使用override關(guān)鍵字描述方法
open class A{
open fun me(){}
fun me1(){}
}
class B:A(){
//override fun me1(){} Error: 'me' in 'A' is final and cannot be overridden
override fun me(){}
}
如果一個(gè)類從它的直接父類繼承了同一個(gè)成員的多個(gè)實(shí)現(xiàn)晃择,那么它必須重寫這個(gè)成員并且提供自己的實(shí)現(xiàn)
open class A {
open fun f () { print("A") }
fun a() { print("a") }
}
interface B {
fun f() { print("B") } //接口的成員變量默認(rèn)是 open 的
fun b() { print("b") }
}
class C() : A() , B{
override fun f() {
super<A>.f()//調(diào)用 A.f()
super<B>.f()//調(diào)用 B.f()
}
}
如果類C中沒有定義f() 編譯器會(huì)報(bào)如下錯(cuò)誤:
Error:(29, 0) Class 'C' must override public open fun f(): kotlin.Unit defined in A because it inherits many implementations of it
作用域修飾符
對(duì)象表達(dá)式
F#和JS都里有這個(gè)類似的定義
對(duì)象表達(dá)式是一個(gè)表達(dá)式冀值,可用于創(chuàng)建基于現(xiàn)有的基類型、接口或接口集動(dòng)態(tài)創(chuàng)建的匿名對(duì)象類型的新實(shí)例宫屠。
對(duì)象表達(dá)式
我們通過(guò)下面這樣的方式創(chuàng)建繼承自某種(或某些)匿名類的對(duì)象:
window.addMouseListener(object: MouseAdapter () {
override fun mouseClicked(e: MouseEvent) {
//...
}
})
如果父類有構(gòu)造函數(shù)列疗,則必須傳遞相應(yīng)的構(gòu)造函數(shù)。多個(gè)父類可以用逗號(hào)隔開浪蹂,跟在冒號(hào)后面
open class A(x: Int) {
public open val y: Int = x
}
interface B { ... }
val ab = object : A(1), B {
override val y = 14
}
有時(shí)候我們只是需要一個(gè)沒有父類的對(duì)象抵栈,我們可以這樣寫:
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
像 java 的匿名內(nèi)部類一樣,對(duì)象表達(dá)式可以訪問(wèn)閉合范圍內(nèi)的變量 (和 java 不一樣的是坤次,這些不用聲明為 final)
fun countClicks(windows: JComponent) {
var clickCount = 0
var enterCount = 0
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
clickCount++//此處需注意,java里匿名內(nèi)部類是無(wú)法修改方法棧里的變量
}
override fun mouseEntered(e: MouseEvent){
enterCount++
}
})
}
對(duì)象聲明
聲明單例模式:
object DataProviderManager {
fun registerDataProvider(provider: Dataprovider) {
//...
}
val allDataProviders : Collection<DataProvider>
get() = //...
}
Extensions
Kotlin可以在不繼承父類的情況下對(duì)一個(gè)類增加一個(gè)新的方法,這種方法叫做Extensions.
Kotlin支持?jǐn)U展方法和擴(kuò)展屬性
擴(kuò)展方法
為了聲明一個(gè)擴(kuò)展函數(shù)古劲,我們需要在函數(shù)名使用接收者類型作為前綴
fun MutableList<Int>.swap(x: Int, y: Int) {
val temp = this[x] // this 對(duì)應(yīng) list
this[x] = this[y]
this[y] = tmp
}
擴(kuò)展方法中的this關(guān)鍵字對(duì)應(yīng)被擴(kuò)展對(duì)象
val l = mutableListOf(1, 2, 3)
l.swap(0, 2)
靜態(tài)解析擴(kuò)展
擴(kuò)展方法是靜態(tài)分發(fā)的,不會(huì)動(dòng)態(tài)根據(jù)類型來(lái)進(jìn)行方法調(diào)用.
open class C
class D: C()
fun C.foo() = "c"
fun D.foo() = "d"
fun printFoo(c: C) {
println(c.foo())
}
printFoo(D())//此處的值為c
/////對(duì)比一下多態(tài)下的方法調(diào)用
open class A{
open fun foo():String{
return "a"
}
}
class B:A() {
override fun foo():String{
return "b"
}
}
fun printClass(a:A){
println(a.foo())
}
printClass(B())//此處輸出b
如果擴(kuò)展的方法和成員方法名稱和參數(shù)一樣,則按照成員方法調(diào)用,不會(huì)調(diào)用擴(kuò)展方法
class C {
fun foo() { println("member") }
}
fun C.foo() { println("extension") }
擴(kuò)展屬性
語(yǔ)法結(jié)構(gòu)如下 : val 類名.屬性名[:屬性類型] get() set()
擴(kuò)展屬性只能通過(guò)getter和setter方法來(lái)進(jìn)行定義:
val <T> List<T>.lastIndex: Int
get() = size-1
///
val Foo.bar = 1 //error: initializers are not allowed for extension properties
創(chuàng)建類實(shí)例
val invoice = Invoice()
val customer = Customer("Joe Smith")
注意Kotlin沒有new
關(guān)鍵字