自從實習結束後直到如今將近一年多的時間再也沒有用過kotlin, 在今年五月份I/O大會上,Google再次明確了Kotlin在Android開發中的地位,並毫無懸念的將這個「後生晚輩」定爲官方「首選」語言,這使得Kotlin不得不被「傳統java 開發者」重視,他們是時候改擁抱變化了,因此咱們項目主要技術負責人如今勉強接受你們使用Kotlin開發意願(實在不容易~)。java
對荒廢了一年的語言如今從新拾起感受又熟悉又陌生,但心裏仍是很興奮的,最近準備系統地整理一下Kotlin相關內容, 但願能在短期內從新掌握,今天主要從新梳理一下Kotlin基礎語法。編程
Kotlin安裝與配置這裏省略(網上不少教程,AndroidStudio3.x版本默認已支持Kotlin插件)數組
Kotlin 的基本數據類型包括 Byte、Short、Int、Float、Long、Double 、Boolean, 在java中基本數據類型一共有八種分別包括整形:byte、short、int、long,浮點型:float、double , 字符型 char 以及布爾型 boolean,不一樣於 Java 的是,Kotlin 字符不屬於數值類型,是一個獨立的數據類型, 你們能夠看出Kotlin的基本數據類型和java不同,Kotlin 中其實沒有基礎數據類型,只有封裝的對象類型,你每定義的一個變量,其實 Kotlin 幫你封裝了一個對象,這樣能夠保證不會出現空指針, 對應Java中八大基本數據類型的對象類型、java在使用基礎數據類型時候能夠經過裝箱操做封裝成對象;安全
當須要可空引用時,像數字、字符會被裝箱。裝箱操做不會保留同一性。bash
布爾用 Boolean 類型表示,它有兩個值:true 和 false。當須要可空引用布爾會被裝箱。閉包
數組用Array類實現,具備size屬性、get、setf方法,因爲使用 [] 重載了 get 和 set 方法,因此咱們能夠經過下標很方便的獲取或者設置數組對應位置的值。java中素組具備length屬性以及使用[]經過下標方式訪問屬性;app
數組的建立兩種方式:一種是使用函數arrayOf();另一種是使用工廠函數。以下所示,咱們分別是兩種方式建立了兩個數組:編程語言
fun main(args: Array<String>) {
//[1,2,3]
val a = arrayOf(1, 2, 3)
//讀取數組內容
println(a[0]) // 輸出結果:1
println(b[1]) // 輸出結果:2
}
val x: IntArray = intArrayOf(1, 2, 3
複製代碼
除了類Array,還有ByteArray, ShortArray, IntArray,LongArray等用來表示各個類型的數組,省去了裝箱操做,所以效率更高,其用法同Array同樣:ide
和java同樣,String屬於不可變的,Kotlin能夠經過[]很方便訪問對應下標字符,java中經過chatAt方法或者subString等方式獲取對應字符,Kotlin中String支持遍歷形式訪問其中的字符,這一點很使用;函數
for (c in str) {
println(c)
}複製代碼
另外Kotlin 支持三個引號 """ 擴起來的字符串,支持多行字符串,好比:
fun main(args: Array<String>) {
val text = """多行字符串 多行字符串"""
println(text) // 輸出
}複製代碼
String 能夠經過 trim(),trimEnd(),trimStart(),trimMargin() 等方法來刪除多餘的空白。
java中能夠經過隱式類型轉換,數值大的類型能夠轉換成數據小的類型,可是這樣每每會丟失精度,在kotlin中因爲不一樣的表示方式,較小類型並非較大類型的子類型,較小的類型不能隱式轉換爲較大的類型。 這意味着在不進行顯式轉換的狀況下咱們不能把 Byte 型值賦給一個 Int 變量,
val b: Byte = 1 // OK, 字面值是靜態檢測的
val i: Int = b.toInt() // OK複製代碼
每種數據類型都有下面的這些方法,能夠轉化爲其它的類型:
toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char複製代碼
val
定義,只能爲其賦值一次,val a: Int = 1 // 當即賦值 (非空屬性必須在定義時候初始化,)
val b = 2 // 自動推斷出 `Int` 類型 (非空屬性必須在定義時候初始化,)
複製代碼
var
關鍵字:var x = 5 // 自動推斷出 `Int` 類型 (非空屬性必須在定義時候初始化,)
x += 1
複製代碼
Kotlin語法支持類型自動推斷,在聲明變量或者常量的時候能夠不用指定其類型,編譯器在編譯時候會爲咱們指定其類型;
非空屬性必須在定義的時候初始化,kotlin提供了一種能夠延遲初始化的方案,使用 lateinit 關鍵字描述屬性:
var count:Int?=null //可空屬性能夠在後面賦值
count=5複製代碼
lateinit var name : String 非空屬性使用延遲初始化
函數定義使用fun關鍵字,參數格式爲 參數:類型 ,最後函數返回值類型,以下
fun sum( a:Int, b:Int):Int{return a+b}複製代碼
亦能夠函數表達式聲明函數
fun sum( a:Int, b:Int)= a+b // 自動類型推斷或者 fun sum(a:Int,b:Int):Int=a+b 複製代碼
無返回值的函數定義(相似Java中的void):
fun printSum(a: Int, b: Int): Unit {
print(a + b)
}
複製代碼
Unit
返回類型能夠省略:
public fun printSum(a: Int, b: Int) {
print(a + b)
}
複製代碼
可變長參數用vararg關鍵字進行修飾:
fun print(vararg v:Int){
for(a in v){
println("$a")
}
} 複製代碼
$ 表示一個變量名或者變量值
$varName 表示變量值
${varName.fun()} 表示變量的方法返回值:
var a = 1
// 模板中的簡單名稱:
val s1 = "a is $a"
a = 2
// 模板中的任意表達式:
val s2 = "${s1.replace("is", "was")}, but now is $a"
// 運行結果:a was 1, but now is 2複製代碼
看一個經常使用if表達式:
fun value(a:Int, b :Int):Int {
if(a>b) {
return a+b
}else{
return a-b
}
}複製代碼
經過條件表達式能夠:
fun value(a:Int,b:Int)=if(a>b) a+b else a-b
Kotlin的空安全設計對於聲明可爲空的參數,在使用時要進行空判斷處理,有兩種處理方式,字段後加!!像Java同樣拋出空異常,另外一種字段後加?可不作處理返回值爲 null或配合?:作空判斷處理
//類型後面加?表示可爲空
var age: String? = "23"
//拋出空指針異常
val ages = age!!.toInt()
//不作處理返回 null
val ages1 = age?.toInt()
//age爲空返回-1
val ages2 = age?.toInt() ?: -1複製代碼
當一個引用可能爲 null 值時, 對應的類型聲明必須明確地標記爲可爲 null。
Kotlin 的NULL機制旨在消除來自代碼空引用的危險,這在java語言中屬於最多見的陷阱之一,也就是訪問空引用的成員會致使空引用異常。常常會拋出 NullPointerException
或簡稱 NPE
。
在Kotlin中常常會有一些鏈式調用用法,安全調用在鏈式調用中頗有用,如:person?.class?.countName 若是調用鏈中任何一個屬性值出現null狀況,調用鏈會直接返回null,
後面屬性不會出現NPE異常。
若是相對非null值執行某個操做,能夠結合let操做符一塊兒使用:
val listWithNulls: List<String?> = listOf("Kotlin", null)
for (item in listWithNulls) {
item?.let {
println(it) // 輸出 Kotlin 並忽略 null
}
}複製代碼
一樣安全調用也能夠出如今賦值的左側,若是調用鏈中的任何一個接收者爲空都會跳過賦值,而右側的表達式根本不會求值:
person?.class?.mathTeacher = TeachManager.getTeacher()複製代碼
7. Elvis 操做符
當咱們有一個可空的引用 r
時,咱們能夠說「若是 r
非空,我使用它;不然使用某個非空的值 x
」:
val l: Int = if (b != null) b.length else -1
或者
fun value(a:Int,b:Int)=if(a>b) a+b else a-b
//條件表達式用法
複製代碼
除了完整的 if-表達式,這還能夠經過 Elvis 操做符表達,寫做 ?:
:
val l = b?.length ?: -1複製代碼
若是 ?:
左側表達式非空,elvis 操做符就返回其左側表達式,不然返回右側表達式。 請注意,當且僅當左側爲空時,纔會對右側表達式求值。
這種Elvis用法相似java語言中三元操做符;
public int value(int a,int b){return a>b?a+b :a-b}
等同於:
fun value(a:Int,b:Int)=if(a>b) a+b else a-b
複製代碼
val l = b?.length ?: -1
等同於:
final int l= b!=null? b.length : -1 複製代碼
若是對象不是目標類型,那麼常規類型轉換可能會致使 ClassCastException
,
val aInt: Int? = a as Int複製代碼
爲了不類型轉成異常,另外一個選擇是使用安全的類型轉換,若是嘗試轉換不成功則返回 null:
val aInt: Int? = a as? Int
或者
val sInt :Int?= if(a is Int) a as Int else null
或者
val sInt :Int?= if(a is Int) a else null複製代碼
is 運算符檢測一個表達式是否某類型的一個實例(相似於Java中的instanceof關鍵字), 若是一個不可變的局部變量或屬性已經判斷出爲某類型,那麼檢測後的分支中能夠直接看成該類型使用,無需顯式轉換:
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// 作過類型判斷之後,obj會被系統自動轉換爲String類型
return obj.length
}
// 這裏的obj仍然是Any類型的引用
return null
}
或者
fun getStringLength(obj: Any): Int? {
if (obj !is String)
return null
// 在這個分支中, `obj` 的類型會被自動轉換爲 `String`
return obj.length
}複製代碼
若是你有一個可空類型元素的集合,而且想要過濾掉空元素,你可使用 filterNotNull
來實現:
val listOf = listOf<Int?>(1, 2, null, 4)複製代碼
val intList: List<Int> = listOf.filterNotNull()複製代碼
區間表達式由具備操做符形式 .. 的 rangeTo 函數輔以 in 和 !in 造成。
使用 in 運算符來檢測某個數字是否在指定區間內:
for (i in 1..4) print(i) // 輸出「1234」
for (i in 4..1) print(i) // 什麼都不輸出
if (i in 1..10) { // 等同於 1 <= i && i <= 10
println(i)
}
// 使用 step 指定步長
for (i in 1..4 step 2) print(i) // 輸出「13」
for (i in 4 downTo 1 step 2) print(i) // 輸出「42」
// 使用 until 函數排除結束元素
for (i in 1 until 10) { // i in [1, 10) 排除了 10
println(i)
}
val list = listOf("a", "b", "c")
if (-1 !in 0..list.lastIndex) {
println("-1 is out of range")
}
if (list.size !in list.indices) {
println("list size is out of valid list indices range, too")
}
複製代碼
for 循環能夠對任何提供迭代器(iterator)的對象進行遍歷,語法以下:
一樣,kotlin的for循環中使用的也是in操做符,
val items = listOf("dog", "cat", "pig")
for (item in items) {
println(item)
}
複製代碼
或者經過索引
val items = listOf("apple", "banana", "kiwifruit")
for (index in items.indices) {
println("item at $index is ${items[index]}")
}
val array=arrayOf("a","b","c")
for (i in array.indices) {
print(array[i])
}複製代碼
when 將它的參數和全部的分支條件順序比較,直到某個分支知足條件,
when 既能夠被當作表達式使用也能夠被當作語句使用。若是它被當作表達式,符合條件的分支的值就是整個表達式的值,若是當作語句使用, 則忽略個別分支的值。
when 相似java語言的 switch 操做符。其最簡單的形式以下:
fun describe(obj: Any): String =
when (obj) {
1 -> "One"
"Hello" -> "Greeting"
is Long -> "Long"
!is String -> "Not a string"
else -> "Unknown"
}
複製代碼
在Kotlin中Any類是全部類的超類,相似java中的Object;
在 when 中,else 同 switch 的 default。若是其餘分支都不知足條件將會求值 else 分支。若是不少分支須要用相同的方式處理,則能夠把多個分支條件放在一塊兒,用逗號分隔。
when 也能夠用來取代 if-else if鏈。 若是不提供參數,全部的分支條件都是簡單的布爾表達式,而當一個分支的條件爲真時則執行該分支:
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}複製代碼
val fruits = listOf("banana", "avocado", "apple", "kiwifruit")
fruits.filter { it.startsWith("a") }
.sortedBy { it }
.map { it.toUpperCase() }
.forEach { println(it) }
結果:APPLE AVOCADO
複製代碼
類的修飾符包括 classModifier 和_accessModifier_:
classModifier: 類屬性修飾符,標示類自己特性。
abstract // 抽象類
final // 類不可繼承,默認屬性
enum // 枚舉類
open // 類可繼承,類默認是final的
annotation // 註解類複製代碼
accessModifier: 訪問權限修飾符
private // 僅在同一個文件中可見
protected // 同一個文件中或子類可見
public // 全部調用的地方均可見
internal // 同一個模塊中可見複製代碼
Koltin 中的類能夠有一個 主構造器,以及一個或多個次構造器,主構造器是類頭部的一部分,位於類名稱以後:
class Person constructor(firstName: String) {}複製代碼
若是主構造器沒有任何註解,也沒有任何可見度修飾符,那麼constructor關鍵字能夠省略。
class Person(firstName: String) {
}複製代碼
主構造器中不能包含任何代碼,初始化代碼能夠放在初始化代碼段中,初始化代碼段使用 init 關鍵字做爲前綴。
class Person constructor(firstName: String) {
init {
firstName="Scus" //主構造器的參數能夠在初始化代碼段中使用
}
}複製代碼
另外能夠經過主構造器來定義屬性並初始化屬性值(能夠是var或val):
class People(val firstName: String, val lastName: String) {
//...
}複製代碼
若是一個非抽象類沒有聲明構造函數(主構造函數或次構造函數),它會產生一個沒有參數的構造函數。構造函數是 public 。若是你不想你的類有公共的構造函數,你就得聲明一個空的主構造函數:
class DontCreateMe private constructor () {
}複製代碼
類也能夠有二級構造函數,須要加前綴 constructor:
class Person(val name: String) {
constructor (name: String, age:Int) : this(name) {
// 初始化...
}
}複製代碼
抽象是面向對象編程的特徵之一,類自己,或類中的部分紅員,均可以聲明爲abstract的。抽象成員在類中不存在具體的實現。
注意:無需對抽象類或抽象成員標註open註解。
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}複製代碼
咱們能夠把類嵌套在其餘類中,看如下實例:
class Outer { // 外部類
private val bar: Int = 1
class Nested { // 嵌套類
fun foo() = 2
}
}複製代碼
使用對象表達式來建立匿名內部類:
test.setInterFace(object : TestInterFace {
override fun test() {
println("對象表達式建立匿名內部類的實例")
}
})
複製代碼
內部類使用 inner 關鍵字來表示。
內部類會帶有一個對外部類的對象的引用,因此內部類能夠訪問外部類成員屬性和成員函數。
class Outer {
private val bar: Int = 1
var v = "成員屬性"
/**嵌套內部類**/
inner class Inner {
fun foo() = bar // 訪問外部類成員
fun innerTest() {
var o = this@Outer //獲取外部類的成員變量
println("內部類能夠引用外部類的成員,例如:" + o.v)
}
}
}
複製代碼
Kotlin 能夠建立一個只包含數據的類,關鍵字爲 data:
data class User(val name: String, val age: Int)複製代碼
密封類用來表示受限的類繼承結構:當一個值爲有限幾種的類型, 而不能有任何其餘類型時。在某種意義上,他們是枚舉類的擴展:枚舉類型的值集合 也是受限的,但每一個枚舉常量只存在一個實例,而密封類 的一個子類能夠有可包含狀態的多個實例。
聲明一個密封類,使用 sealed 修飾類,密封類能夠有子類,可是全部的子類都必需要內嵌在密封類中。
sealed class Expr
data class Const(val number: Double) : Expr()
data 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
}複製代碼
interface MyInterface {
fun bar() // 未實現
fun foo() {//已實現
println("foo")
}
}複製代碼
class Child : MyInterface {
override fun bar() {
// 方法體
}
override fun fool() {
// 方法體
}
}複製代碼
interface MyInterface{
var name:String //name 屬性, 抽象的
}
class MyImpl:MyInterface{
override var name: String = "runoob" //重寫屬性
}複製代碼
kotlin 中全部類都繼承該 Any 類,它是全部類的超類,對於沒有超類型聲明的類是默認超類:
若是一個類要被繼承,可使用 open 關鍵字進行修飾。
open class Base(p: Int) // 定義基類
class Derived(p: Int) : Base(p)複製代碼
注意:Any 不是 java.lang.Object。
若是子類有主構造函數, 則基類必須在主構造函數中當即初始化。
open class Person(var name : String, var age : Int){// 基類
}
class Student(name : String, age : Int, var no : String, var score : Int) : Person(name, age) {
}複製代碼
若是子類沒有主構造函數,則必須在每個二級構造函數中用 super 關鍵字初始化基類,或者在代理另外一個構造函數。初始化基類時,能夠調用基類的不一樣構造方法。
class Student : Person {
constructor(ctx: Context) : super(ctx) {
}
constructor(ctx: Context, attrs: AttributeSet) : super(ctx,attrs) {
}
}複製代碼
在基類中,使用fun聲明函數時,此函數默認爲final修飾,不能被子類重寫。若是容許子類重寫該函數,那麼就要手動添加 open 修飾它,。
Kotlin 使用 object 關鍵字來聲明一個對象。
Kotlin 中咱們能夠方便的經過對象聲明來得到一個單例。
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ……
}
val allDataProviders: Collection<DataProvider>
get() = // ……
}複製代碼
引用該對象,咱們直接使用其名稱便可:
DataProviderManager.registerDataProvider(……)複製代碼
固然你也能夠定義一個變量來獲取獲取這個對象,當你定義兩個不一樣的變量來獲取這個對象時,你會發現你並不能獲得兩個不一樣的變量,也就是說經過這種方式,咱們得到一個單例:如
object Site {
var url:String = ""
val name: String = "菜鳥教程"
}
fun main(args: Array<String>) {
var s1 = Site
var s2 = Site
s1.url = "www.runoob.com"
println(s1.url)
println(s2.url)
}
// 輸出結果都爲"www.runnoob.com"
複製代碼
類內部的對象聲明能夠用 companion 關鍵字標記,這樣它就與外部類關聯在一塊兒,咱們就能夠直接經過外部類訪問到對象的內部元素。
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
val instance = MyClass.create() // 訪問到對象的內部元素複製代碼
咱們能夠省略掉該對象的對象名,而後使用 Companion 替代須要聲明的對象名:
class MyClass {
companion object {
}
}
複製代碼
注意:一個類裏面只能聲明一個內部關聯對象,即關鍵字 companion 只能使用一次。
伴生對象的成員看起來像java的靜態成員,但在運行時他們仍然是真實對象的實例成員。