Kotlin -- Kotlin初探
最近Kotlin從之前的默默無聞,到現在着實火了一把。跟着潮流,也從中文站上下了一份文檔來看了一下。之後感覺現在Kotlin系統性的學習資料還比較缺乏,就照着那份指導文檔,大致看了下Kotlin的語法等基礎內容。現把該過程中的一些示例代碼記錄下來,供後續有機會再深入學習時回顧。
示例代碼:
package xzm.kotlin.main
/**
* Created by Fassbender on 2017/6/28.
*/
import com.xzm.test.SingleFun
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.lang.IllegalArgumentException
import javax.swing.JComponent
import kotlin.reflect.KProperty
fun main(args: Array<String>) {
println("hello kotlin!")
println("add1: " + add1(1, 2))
println("add2: " + add2(2, 2))
println("add3: " + add3(3, 4))
add4(5, 6)
add5(7, 8)
varIntPrint()
varStrPrint()
var s1 = "I'm s1"
var s2 = "I'm s2"
println("handleString: " + handleString(s1, s2))
val str = "abcdefg"
val check = 1
typeAutoCheck(str)
typeAutoCheck(check)
loopCheck()
describe(1)
defaultParam()
defaultParam(1)
defaultParam(2, "aaa")
handleStringWithNull(null, "bb")
objCallMethods()
handleClassPart()
handleInheritPart()
}
/**
*兩個Int類型的參數,返回值爲Int;普通形式
*關鍵字:fun
*fun methodName(varName : Type, varName : Type) : returnType
*
*/
fun add1(a: Int, b: Int): Int {
return a + b
}
/**
*兩個Int類型的參數,返回值爲Int
*關鍵字:fun
*fun methodName(var1 : Type, var2 : Type) : returnType = var1 + var2
*
*fun methodName(var1 : Type, var2 : Type) = var1 + var2
*
*將一個表達式作爲函數體,可以自動推斷返回類型;可略去{}
*/
fun add2(a: Int, b: Int): Int = a + b
fun add3(a: Int, b: Int) = a + b
/**
*函數返回無意義的值:Unit(Java -> void)
*
*$可以獲取變量值
*
*/
fun add4(a: Int, b: Int): Unit {
println("here $a and $b is ${a + b}")
}
/**
*返回無意義的值時,返回類型可以不寫
*
*/
fun add5(a: Int, b: Int) {
println("here $a and $b is ${a + b}")
}
/**
* val 是不可變賦值,過後不能改變它的值;本質上是隻聲明瞭getter()方法
* var 是可變賦值,過後可以再改變變量的值;本質上同時聲明瞭getter()/setter()方法
* 聲明一個屬性的完整語法是:
* var <propertyName>[: <PropertyType] [=<property_initializer]
* [<getter>]
* [<setter>]
* 其中initializer/getter/setter都是可選的.如果屬性類型可以從initializer或者getter推斷出,也PropertyType聲明可省略
*/
fun varIntPrint() /*: Unit*/ {
val a: Int = 1 //有明確類型、初始值
val b = 2 //自動推斷類型爲Int,並賦初值2
val c: Int //如果沒有明確初始值, Int不能省略
c = 3 //此時初始化,沒有初始化的變量都無法使用
var d: Int
d = 4
var e: Int //必須有初始值,不然無法使用
e = 5 //注掉初始化, $e讀參失敗
println("here a is $a ,b is $b, c is $c, d is $d, e is $e")
}
/**
*使用字符串模板,即可以包含一小段代碼
*/
fun varStrPrint() /*: Unit*/ {
var a = 1
var s1 = "a is $a"
val s2 = "but it does't matter"
var s3 = "${s1.replace("is", "was")}, $s2"
var s4 = "${s3.isNotBlank()}"
var bool = s3.isNullOrBlank()
println("$s3, is Blank : $s4, bool: $bool")
}
/**
*條件表達式
*/
fun maxOf(a: Int, b: Int) = if (a > b) a else b
fun maxOf1(a: Int, b: Int): Int {
if (a > b)
return a
else
return b
}
/**
*當函數中某個變量的值可能爲null的時候,需要在它的聲明類型後加入'?'標識,否則調用的時候不能傳入null作爲參數;
*/
fun handleString(str1: String?, str2: String): String? {
if (str1 == null)
return "arg is null, return directly"
else
return "str1 is $str1, str2 is $str2"
}
/**
*對象調用時,?會確保不出現空指針調用
*expression ?: **** -> expression爲null時,執行****
*/
fun handleStringWithNull(str1: String?, str2: String): String? {
val s1 = str1?.toUpperCase() ?: "str1 is null, this is default vavlue"//這樣聲明如果去掉了此時的?,編譯會報錯;kotlin是空安全的語言,因爲str1可能爲null,這裏必須加上?,表明str1不爲null時,纔會調用到該函數.
val s2 = str2.toUpperCase() //str2不需要這樣,因爲str2參數本來就不能爲null,如果函數調用處傳入null,編譯也會出錯;當然加上?,也沒有問題,只不過多此一舉
println("str1: $s1, str2: $s2")
return null;
}
/**
*類型檢測/自動類型轉換
*is運算符用於檢測一個表達式是否是某個類型的一個實例;如果一個不可變的局部變量或屬性已經判斷出爲某類型,那麼檢測後的分支中可以直接將表達式當做該類型使用,而無需顯示轉換
*/
fun typeAutoCheck(obj: Any = "") /*: Unit*/ {
if (obj is String) { // is 關鍵字
println("the str length: ${obj.length}")
} else
println("type error, length is invalid")
}
/**
*使用for or while
*/
fun loopCheck() {
val items = listOf("aa", "bb", 1) //創建通用list時,此處看出不要求類型一致
for (it in items) { // in 關鍵字
println("for list content: $it")
}
for (index in items.indices) {//庫字段:0...length-1
println("for use indices list content:${items[index]}")
}
for ((index, value) in items.withIndex()) {//庫函數withIndex(),使用(index, value) -> (下標,值)的形式遍歷
println("for use withIndex list content current index: $index, the value: $value")
}
var index: Int = 0
while (index < items.size) {
println("while list content: ${items[index]}")
index++
}
var maps = mapOf(1 to "a", "b" to 2, 3 to "3") //創建通用maps時,此處看出不要求類型一致
for ((k, v) in maps)
println("maps loop current key: $k, value: $v")
val arrays = arrayOf(1, 2, 3, "aa") //創建通用數組時,可以看出對數組元素類型不要求一致
val a = Array(5, { i -> (i * i).toString() }) //接受數組大小和一個函數參數的工廠函數來創建數組,函數參數給定每個索引的值
val b = intArrayOf(1, 2, 3) //創建特定類型,如Int時,則必須保持類型一致;對應數組的還有Byte/Short等
for (value in b) {
println("loop intArrayOf value: $value")
}
for (value in arrays)
println("array loop current value: $value")
for (value in a)
println("constructor array current value: $value")
val str = "abcdefg" //字符串值是不可變的,可以使用索引來訪問字符串中的某個字符值:str[i]
"bbb".toUpperCase()
for (c in str)
println("get value use index c: $c")
val escapestr = "Hello World! \n" //轉義字符串
val rawstr = """for (c in str) {
|println("value: $\n")
}""" //原始字符串
println(escapestr)
println(rawstr)
//$打印
val escapestr2 = "$9.99"
val rawstr2 = """$9.99"""
val rawstr3 = """${'$'}9.99"""
println(escapestr2)
println(rawstr2)
println(rawstr3)
val aa: Byte? = 1
val bb: Int? = aa?.toInt() //aa不能直接轉爲爲Int,需要顯示提升類型;Kotlin中,較小類型並不是較大類型的子類型
}
/**
*使用when代替傳統的switch...case語句
*when既可以當表達式使用,也是當語句使用.如果單做表達式使用,則符合條件的分支的值即是表達式的值;如果當做語句使用,則忽略個別表達式的值
*when作爲表達式使用時,必須有else分支,除非編譯器能檢測出所有的可能情況都已經覆蓋了;
*我們可以使用任意表達式作爲分支條件
*
*另外,變種的when語句可以起到跟某些if()結構類似的效果,比如if - else/if - else if - else
*/
fun describe(obj: Any) {
val items = listOf("aa", "bb", "cc", 1)
when (obj) { //條件順序比較,直到找到合適的分支匹配;否則,進入else分支
"Hello" -> println("current value is $obj")
is String -> println("current type is String: $obj")
in items -> println("current obj $obj in the items") //該項內容是不是在某個集合中
2, 3 -> println("match this branch, current value is: $obj") //多個分支需要使用相同的處理
in 8..10 -> println("current value is inside Range") //可以判斷某個值是否在一個區間內
!in 20..22 -> println("current value is out of Range") //可以判斷某個值是否在一個區間內
else
-> println("Not support all branch") //else 分支相當於 Java中的default
}
}
/**作爲表達式使用,when()寫成單表達式*/
fun describeExpression(obj: Any) = when (obj) {
"aa" -> println("match aa")
"bb" -> println("match bb")
else
-> {
println("Not match any of them")
}
}
fun describeExpressionReturnInt(obj: Any): Int = when (obj) {
1 -> 1
"Red" -> 2
obj is Test -> 3
else
-> throw IllegalArgumentException("Arguments is error") //throw表達式的類型是特殊類型 Nothing。該類型沒有值,而是用於標記永遠不能達到的代碼位置
}
/**
*缺省參數函數
*/
fun defaultParam(a: Int = 0, str: String = "defaultParam") {
println("defaultParam a: $a, str: $str")
}
/**
*單表達式函數
*/
fun theAnswer() = 42
fun theAnswer1(): Int {
return 42
}
/*
*單表達式函數變形
*/
fun theAnswer2(value: Any) = when (value) {
1 -> println("current value is $value")
2 -> println("current value is $value")
else
-> println("None of support value")
}
/**
*with()
*一個對象調用多個函數
*使用可空Boolean
*/
class Test {
fun penDown() {
println("call penDown")
}
fun penUp() {
println("call pendUp")
}
fun turn() {
println("call turn")
}
fun forward() {
println("call forward")
}
fun isNull() {
println("call obj is null")
}
}
fun objCallMethods() {
val test = Test()
with(test) {
penDown()
turn()
forward()
penUp()
}
var t: Test? = null
println(t?.penDown() ?: "call obj is null")
val bool: Boolean? = null
if (bool == true)
println("bool is true")
else
println("bool in false or null")
}
/**
*kotlin中的類默認是final的(java語義),不可繼承;要設計可繼承的類,在class前添加open關鍵字,標識該類可繼承
*kotlin沒有new關鍵字
*主構造函數和次構造函數
*次構造函數可以委託給主構造函數
*如果主構造函數的所有參數都有默認值,在JVM上,Kotlin會生成一個額外的無參構造函數,此時所有的屬性將採用默認值;
*但如果存在有限個參數全有默認值的次構造函數,該無參構造含會被覆蓋(或者說我們相當於顯示聲明瞭無參構造和函數)
*
* 一般地,屬性聲明爲飛控類型必須在構造函數中初始化;lateinit關鍵字在這種情景下有些特殊
*/
class ClassA /*private*/ constructor(var a: Int = -1, var str: String = "default") {
init {
this.a = a
this.str = str
}
constructor(a: Int) : this(a, "a pararm")
constructor(str: String) : this(3, str)
}
fun handleClassPart(): Unit {
val classA = ClassA(2)
val classB = ClassA("cc")
val classC = ClassA()
println("classA a: ${classA.a}, str: ${classA.str} -- classB a: ${classB.a}, str: ${classB.str} -- classC a: ${classC.a}, str: ${classC.str}")
}
/**
*kotlin中的類默認是不可繼承的,使用open(意爲開發)關鍵字可將類聲明爲可繼承類;這裏只是類可繼承,但不代表子類可以覆蓋它的成員
*kotlin需要使用open顯示聲明可覆蓋的成員(基類中),使用override顯示聲明覆蓋後的成員(子類中)
*使用final關鍵字可以再次禁止覆蓋成員(override),它不能與open關鍵字同時修飾
*屬性覆蓋與接口覆蓋類似.我們可以使用var/val屬性覆蓋一個val屬性,但不能用val屬性覆蓋var屬性.
* kotlin中類不能字段,但可以有屬性
*/
open class Base constructor(var aBase: Int = -1, var bBase: Int = -2) {
init { //主構造函數的初始化模塊
}
open var x: Int? = 5
open val y: Int = 23
open val z: Int = 3
open fun f(): String {
return "f() in Base"
}
open fun g(): String {
return "g() in Base"
}
open fun h() {
println("h() int Base")
}
open fun i() {
println("i() in Base")
}
open fun j() {
println("j() in Base")
}
open fun k() {
println("k() in Base")
}
}
//無需聲明成員爲open,因爲這是顯而易見的;接口的方法體是可選的,因爲子類必須覆蓋這些方法
interface Inter {
fun h() {
println("h() in Inter")
}
fun m()
}
/**
* 接口中定義的屬性要麼是抽象的,不能提供初始化;要麼提供訪問器的實現.
* 在接口中定義的屬性不能用有幕後字段(backing field),因此接口中的訪問器不能引用它們
*/
interface Inter1 {
var a: Int //抽象的屬性
//var a: Int // 編譯錯誤,var的變量get()會訪問幕後字段? val爲什麼可行
// get() = 2
val str: Int
get() = 1
val propertyWithImplementation: String
get() = "foo"
fun h() {
println("h() int Inter1")
}
fun g()
}
/**
* 沒有必要用open標註一個抽象類或抽象成員,因爲這是顯而易見的
* kotlin可以聲明抽象類,抽象類的成員覆蓋與普通類似;我們可以用一個abstract成員覆蓋一個非抽象類的open成員;抽象成員在本類中可以不用實現
*/
abstract class Abs : Base() {
override fun i() {
println("i() in Abs")
}
override abstract fun j()
}
/**
* 子類必須實現抽象類父類的抽象成員
*/
class AbsImpl : Abs() {
override fun j() {
println("j() override in BasImpl")
}
}
open class Sub constructor(var aSub: Int = 0, override var x: Int? = -1/*主構造函數中覆蓋屬性*/) : Base(aSub), Inter {
override val y: Int = 2 //也可以在主構造函數中覆蓋屬性
override val z: Int = 4
override fun f(): String {
return "f() in Sub"
}
final override fun g(): String { //使用final關鍵字可以再次禁止覆蓋,即Sub的子類無法覆蓋g()
return "g() in Sub"
}
override fun h() { //從基類和接口同時繼承了h()的兩份實現,這裏必須要覆蓋h()以消除歧義;同時,我們可以使用super<ClassName>.h()的方式來顯示錶明需要調用哪一份實現
super<Inter>.h()
}
override fun m() {
println("m() override in Sub")
}
/**
* 聲明一個屬性的完整語法是:
* var <propertyName>[: <PropertyType] [=<property_initializer]
* [<getter>]
* [<setter>]
* 其中initializer/getter/setter都是可選的.如果屬性類型可以從initializer或者getter推斷出,也可省略
*/
var s: Int = 3 //顯示聲明屬性的getter/setter
get() {
return field
}
set(value) { //value就是sub.s = xx的xx值
field = value
}
val m: Int get() = 3
val n get() = 6
//var j : Int get() = 3 //編譯錯誤,'Property must be initialized'
}
fun handleInheritPart() {
val sub = Sub()
sub.h()
println("call Inherit method ${sub.f()} -- ${sub.g()}")
var base = Base()
println("attribute override sub.y: ${sub.y},sub.x: ${sub.x},sub.z: ${sub.z},sub.m: ${sub.m},sub.n: ${sub.n} --> base.y: ${base.y}, base.x: ${base.x}, base.z: ${base.z}")
val impl: AbsImpl = AbsImpl()
impl.i()
impl.j()
impl.k()
println("const str: $str")
println("Sub s: ${sub.s}")
sub.s = 22
println("Sub s: ${sub.s}")
val exten = ExtensionClass()
exten.h()
exten.g()
printFoo(D())
val c = C()
c.b = 3
println("Extension property in C a: ${c.a}, b: ${c.b}")
F().caller(E())
test()
testAnon()
}
/**
* 已知值的屬性可以使用const修飾符聲明爲編譯常量,這些屬性需滿足以下條件
* 1、位於頂層或者Object的一個成員
* 2、用String或原生類型初始化
* 3、沒有自定義getter
*/
private const val str: String = "this is const str"
/**
* 頂層聲明情況:
* 1、如果不指定任何可見性標識符,默認爲public;此時該聲明將隨處可見
* 2、如果聲明爲private,則它將在聲明它的文件內可見
* 3、如果聲明爲internal,則它會在相同模塊內可見
* 4、protected不適用於頂層聲明
*
* 3中的模塊是指編譯在一起的一套Kotlin文件:
* --⼀個 IntelliJ IDEA 模塊;
* --⼀個 Maven 或者 Gradle 項⽬;
* --⼀次 <kotlinc> Ant 任務執⾏所編譯的⼀套⽂件。
*
* 類內部聲明情況:
* private意味着只在這個類內部(包含其所有可見成員)可見
* protected和private一樣 + 在子類中可見
* internal能見到類聲明的本模塊內的任何客戶端都可見其internal成員
* public能見到類聲明的任何客戶端都可見其public成員
* Kotlin中外部類沒法訪問內部類的private成員
* 如果你覆蓋一個protected成員而未顯示指定其可見性,該成員還將是protected可見性
*
* 局部聲明情況:
* 局部變量、函數和類不能有可見性修飾符
*
*/
//函數拓展
class ExtensionClass {
fun g() {
println("g() in ExtensionClass")
}
}
/**
* 聲明拓展函數,需要用它的接收者(被擴展的類型)類型作爲前綴,即類型名;特殊地,可以爲可空的接收者類型定義擴展,即使其值爲null
*
* 擴展並不能真正的修改它所擴展的類.通過擴展,你並沒有在類中插入一個新成員;僅僅是可以通過該類型的變量用點表達式去調用這個新函數
*/
fun ExtensionClass.h() {
if (this == null) return //this對象指向當前調用該擴展函數的實例對象
println("Extension fun h() for ExtensionClass")
}
/**
* 如果一個定義有一個擴展函數和成員函數,而這兩個函數有相同的接收者類型、函數名,並且都適用於給定的參數,這種情況總是取成員函數
*
* 當然擴展函數重載同樣名字但不同簽名的成員函數同樣可以,編譯器此時總是可以找到的合適的函數去調用
*/
fun ExtensionClass.g() {
println("Extension fun g() for ExtensionClass")
}
/**
* 擴展函數是靜態分發的
*
* 調用的擴展函數由函數調用所在的表達式的類型來決定,而不是由表達式運行時結果決定
*/
open class C
class D : C()
fun C.foo() = "C"
fun D.foo() = "D"
fun printFoo(c: C) {
println(c.foo()) //調用的擴展函數只取決於參數c的類型,該類型是C
}
// printFoo(D()) -> C
//由於擴展並沒有真正插入到類中,因此對擴展屬性來說幕後字段是無效的;因此擴展屬性不能有初始化器,它們的行爲只能由顯示提供的setter/getter定義
val C.a: Int
get() = 1
var C.b: Int
get() = 2
set(value) {
2 * value
}
/**
* 擴展聲明爲成員
*
* 在一個類內部,你可以爲另一個類聲明擴展.在這樣的擴展內部,有多個隱式接收者--其中的成員可以無需通過限定符訪問.
*
* 擴展聲明所在的類的實例稱爲分發接收者,擴展方法調用所在的接收者類型的實例稱爲擴展接收者
*
* 對於分發接收者和擴展接收者的成員名字衝突的情況,擴展接收者優先.引用分發接收者的成員你可以使用限定的this語法(eg: this@F)
*/
class E {
fun bar() {
println("bar() in E")
}
override fun toString(): String {
return "toString() in E"
}
}
class F {
fun baz() {
println("baz() in F")
}
fun E.foo() {
bar() //調用E.bar
baz() //調用F.baz
println(toString()) //調用E.toString
println([email protected]()) //調用F.toString
}
override fun toString(): String {
return "toString() in F"
}
fun caller(e: E) {
e.foo()
}
}
/**
* 聲明爲成員的擴展可以聲明爲open並在子類中覆蓋.這意味着這些函數的分發對於分發接收者類型是虛擬的(動態分發/多態),但對於擴展接收者類型是靜態的.
*/
open class G {
}
class G1 : G() {
}
open class H {
open fun G.foo() {
println("G.foo in H")
}
open fun G1.foo() {
println("G1.foo in H")
}
fun caller(g: G) {
g.foo() // 調⽤擴展函數
}
}
class H1 : H() {
override fun G.foo() {
println("G.foo in H1")
}
override fun G1.foo() {
println("G1.foo in H1")
}
}
fun test() {
//函數擴展
H().caller(G()) // 輸出 "G.foo in H"
H1().caller(G()) // 輸出 "G.foo in H1" —— 分發接收者虛擬解析
H().caller(G1()) // 輸出 "G.foo in H" —— 擴展接收者靜態解析
H1().caller(G1()) // 輸出 "G.foo in H1"
//數據類測試
println("Parcel name: ${p2.name}, age: ${p2.age}")
val (name, age) = p1 //解構
println("Parcel name: $name, age: $age")
}
/**
* 我們經常需要創建一些只保存數據的類.一些標準函數往往是從數據機械推導而來的.kotlin中,這叫做數據類,標記爲data
*
* 編譯器自動從主構造函數中聲明的所有屬性導出以下成員:
* equals()/hashCode()對
* toString() 格式是'User(name = aa,age = 42)'
* componentN()解構函數按聲明順序對應於所有屬性
* copy()函數
*
* 上述的函數如果類中有顯示定義或有從基類繼承,則不會生成該函數
*
*
* 爲了確保生成代碼的一致性和行爲有意義,數據類必須滿足如下條件:
* 主構造函數至少有一個參數
* 主構造函數的所有參數必需聲明爲val或var
* 數據類不能使抽象、開放、密封或者內部的
* 1.1版本之後,kotlin不僅可以實現接口,也可以擴展其他類
*
* 如果數據類需要生成一個無參的默認構造函數,需要保證主構造函數的所有參數都有默認值
*/
data class Paracel(val name: String, val age: Int)
val p1 = Paracel("aa", 12)
val p2 = p1.copy(age = 8) //在很多情況下,我們需要複製⼀個對象改變它的⼀些屬性,但其餘部分保持不變。copy()函數就是爲此⽽⽣成
//'out T' 類似於Java '? extends T' -> 協變類型
//'in T' 類似於Java '? super T' -> 逆變類型
//'out T(類型爲T或者T的子類)'標註確保它僅從Source<T>成員中返回(作爲返回值類型),不能把它當做函數參數使用;
// in與out相反,'in T(類型爲T或者T的父類)'表明類型T只能作爲函數參數使用,而不能在成員中返回
abstract class source<out T> {
abstract fun next(): T
}
//冒號之後指定的類型是上界:只有Comparable<T>的子類型可以代替T;如果沒有這樣顯示聲明類型上界,則默認是Any.
fun <T : Comparable<T>> sort(list: List<T>) {
}
//如果需要指定多個上界,則需要一個單獨的where語句
fun <T> cloneWhenGrater(list: List<T>, threshold: T): List<T> where T : Comparable<T>, T : Cloneable {
return list
}
/**
* 星投影
*
*有時你想說,你對類型參數一無所知,但仍然希望以安全的方式使用它。
*這裏的安全方式是定義泛型類型的這種投影,該泛型類型的每個具體實例化將是該投影的子類型。
*
*Kotlin 爲此提供了所謂的星投影語法:
*
*對於 Foo <out T>,其中 T 是一個具有上界 TUpper 的協變類型參數,Foo <*> 等價於 Foo <out TUpper>。 這意味着當 T 未知時,你可以安全地從 Foo <*> 讀取 TUpper 的值。
*對於 Foo <in T>,其中 T 是一個逆變類型參數,Foo <*> 等價於 Foo <in Nothing>。 這意味着當 T 未知時,沒有什麼可以以安全的方式寫入 Foo <*>。
*對於 Foo <T>,其中 T 是一個具有上界 TUpper 的不型變類型參數,Foo<*> 對於讀取值時等價於 Foo<out TUpper> 而對於寫值時等價於 Foo<in Nothing>。
*
*如果泛型類型具有多個類型參數,則每個類型參數都可以單獨投影。
*例如,如果類型被聲明爲 interface Function <in T, out U>,我們可以想象以下星投影:
*
*Function<*, String> 表示 Function<in Nothing, String>;
*Function<Int, *> 表示 Function<Int, out Any?>;
*Function<*, *> 表示 Function<in Nothing, out Any?>。
*
*注意:星投影非常像 Java 的原始類型,但是安全。
*/
/**
* 嵌套類/內部類/匿名內部類
*/
interface Anonymous {
fun foo()
fun goo()
}
open class Tt constructor(val age: Int) {
open val y: Int = 15
open fun f() {
println("f() int Tt")
}
}
interface Ts {
fun h()
}
class Outer {
val bar: Int = 2
class Nested { //嵌套類,不能訪問外部類成員
fun foo() = 2
}
inner class Inner { //內部類,inner標註,可以訪問外部類成員;內部類會帶有一個對外部類對象的引用
fun foo() = 3
fun goo() = [email protected]
}
}
class Listener {
fun addListener(obj: Anonymous) {
obj.foo()
obj.goo()
}
fun addListener(obj: SingleFun) {
obj.hoo()
}
}
/**
* 匿名對象可以用作只在本地或私有作用域中聲明的類型。如果你使用匿名對象作爲公有函數的返回值類型或用作公有屬性的類型,
* 那麼該函數或屬性的實際類型將會是匿名對象聲明的超類型,如果你沒有聲明任何超類型,就會是Any;在匿名對象中添加的成員將無法訪問。
*/
class Cc {
//私有函數,所以其返回類型是匿名對象類型
private fun foo() = object {
val x: String = "x"
}
//公有函數,所以其返回類型是Any
fun publicFoo() = object {
val x: String = "x"
}
fun bar() {
val x1 = foo().x //沒問題
//val x2 = publicFoo().x //錯誤:未能解析的引用“x”
}
}
/**
* 就像Java匿名內部類一樣,對象表達式中的代碼可以訪問來自包含它的作用域的變量。(與Java不同的是,這不僅限於final變量。)
*/
fun countClicks(window: JComponent) {
var clickCount = 0
var enterCount = 0
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
clickCount++
}
override fun mouseEntered(e: MouseEvent) {
enterCount++
}
})
// ……
}
/**
* 單例模式是一種非常有用的模式,而Kotlin(繼Scala之後)使單例聲明變得很容易
* 這稱爲對象聲明。並且它總是在object關鍵字後跟一個名稱。就像變量聲明一樣,對象聲
* 明不是一個表達式,不能用在賦值語句的右邊。
*/
class DataProvider
object DataProviderManager {
val list: Collection<DataProvider>? = null
fun registerDataProvider(provider: DataProvider) {
// ……
}
val allDataProviders: Collection<DataProvider>?
get() = list// ……
}
/**
* 要引用該對象,我們直接使用其名稱即可:
* DataProviderManager.registerDataProvider(……)
* 這些對象可以有超類型
*/
object DefaultListener : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ……
}
override fun mouseEntered(e: MouseEvent) {
// ……
}
}
/**
* 注意:對象聲明不能在局部作用域(即直接嵌套在函數內部),但是它們可以嵌套到其他對
* 象聲明或非內部類中。
*
* 對象表達式和對象聲明有一個重要的語義差異:
*
* 1、對象表達式是在使用它們的地方立即執行(及初始化)的
*
* 2、對象聲明是在第一次被訪問到時延遲初始化的
*
* 3、伴生對象的初始化是在相應的類被加載(解析)時,與java靜態初始化器的語義相匹配
*
*/
fun testAnon() {
//嵌套類
val netsted = Outer.Nested()
println("netsted.foo(): " + netsted.foo())
//內部類
val iner = Outer().Inner()
println("inner.foo(): " + iner.foo() + " goo(): " + iner.goo())
//單基類實現的匿名內部類
val listener = Listener()
listener.addListener(object : Anonymous { //必須是'object',應該是關鍵字
override fun foo() {
println("匿名內部類實例 foo()")
}
override fun goo() {
println("匿名內部類實例 goo()")
}
})
//如果對象時函數式Java接口,即只包含一個函數的Java接口;可以使用帶接口類型前綴的lambda表達式創建它
val anonymousSingleFun = SingleFun { println("hoo() in 函數式Java接口(只擁有單個方法的java接口)") }
listener.addListener(anonymousSingleFun)
//多基類實現的匿名內部類實例 -> 創建一個繼承自某個/某些類型的匿名類的對象
val useBin = object : Tt(12), Ts { //對象表達式
override val y: Int = 5
//多個超類由跟在':'後面的','隔開的列表指定;如果超類有構造函數,則必須傳遞一個合適的參數
override fun h() {
println("h() in 匿名類實例")
}
override fun f() {
println("f() in 匿名類實例")
}
}
useBin.f()
println("useBin y: ${useBin.y}")
useBin.h()
//任何時候,如果我們只是需要一個“對象而已”,並不需要特殊超類型,可以這樣寫
val objNormal = object { //對象表達式
val x = 2
val y = 3
fun g() {
println("對象聲明之方法調用")
}
}
println("objNormal x: ${objNormal.x}, y:${objNormal.y}")
objNormal.g()
for (protocol in ProtocolState.values())
println("ProtocolState: $protocol, protocol: ${protocol.protocol}, name:${protocol.name}, ordinal:${protocol.ordinal}")
printAllValues<ProtocolState>()
println("value by Name: " + getEnumObjByName<ProtocolState>("UDP"))
testAll()
}
/**
* 每個枚舉常量都是一個對象;枚舉常量之間用','隔開
*
* 每個枚舉常量都具有在枚舉類聲明中獲取其名稱和位置的屬性:
* val name: String
* val ordinal: Int
* 枚舉常量還實現了Comparable接口,其中自然順序是它們在枚舉類中定義的順序
*/
enum class Direction {
SOUTH, WEST, EAST, NORTH
}
/**
* 因爲每個枚舉常量都是枚舉類的實例,所以它們可以進行初始化
*/
enum class Color constructor(val rgb: Int = 0x0001) { //枚舉類型構造函數默認是private,也只能是private
RED(0x0003),
WHITE(0x0006),
BLACK(0x0009)
}
/**
* 枚舉常量也可以聲明自己的匿名類及相應的方法、以及覆蓋基類的方法。注意,如果枚舉類定義任何成員,要使用分號將成員與定義中的枚舉常量定義分隔開,就像Java中一樣
*
* EnumClass.valueOf(value: String): EnumClass 根據名稱獲取枚舉常量
* EnumClass.values(): Array<EnumClass> 獲取枚舉常量的集合
*/
enum class ProtocolState(val protocol: String = "TFTP") {
UDP("udp") {
override fun protocol(): ProtocolState {
return UDP
}
},
TCP("tcp") {
override fun protocol(): ProtocolState {
return TCP
}
},
HTTP("http") {
override fun protocol(): ProtocolState {
return HTTP
}
};
abstract fun protocol(): ProtocolState
}
/**
* 自Kotlin 1.1 起,可以使用enumValues<T>()和enumValueOf<T>()庫函數以泛型的方式訪問枚舉類中的常量
*
* reified類型
*/
inline fun <reified T : Enum<T>> printAllValues() {
println("printAllValues: " + enumValues<T>().joinToString { it.name })
}
inline fun <reified T : Enum<T>> getEnumObjByName(name: String): T {
return enumValueOf(name)
}
/**
* 類委託,Kotlin中的委託相當於Java中的Proxy設計模式;Kotlin中以關鍵字by實現
*/
interface ISubjetc { //抽象主題接口
fun g()
}
class SubjetcImpl : ISubjetc { //實際主題實現類,委託類
override fun g() {
println("g() in SubjetcImpl")
}
}
/**
* Derived相當於ProxySubject,代理類
*
* by 子句表示sub將在Derived中內部存儲,並且編譯器會生成轉發給sub中的所有實現自ISubject接口的覆寫方法
* (從Java角度來看,就是它內部會覆寫ISubject接口的所有的方法,內部會通過保存的sub對象轉而調用到SubjectImpl中的真實實現中)
*
*特殊地,覆蓋會以我們期望的方式進行;如果在Derived中覆蓋了抽象主題類的接口,則編譯器會使用Derived的實現代替委託對象中的實現
*
* 在這裏,如果Derived中覆寫了g(),則會輸出"g() int Derived",而不是"g() in SubjectImpl"
*
*/
class Derived(sub: ISubjetc) : ISubjetc by sub { //主題實現代理類
//override fun g() {
// println("g() int Derived")
//}
}
/**
* 屬性委託,就是對其屬性值的操作不再依賴於其自身的getter()/setter()方法,而是將其託付給一個代理類,從而每個使用類中的該屬性
* 可以通過代理類統一管理,再也不用在每個類中,對其聲明重複的getter()/setter()操作方法。
*
* 當我們使用屬性的get或者set的時候,屬性委託的getValue和setValue就會被調用。
*/
class Example {
var y: String by Delegate()
}
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name} in $thisRef.'")
}
}
fun testAll() {
val sub = SubjetcImpl()
Derived(sub).g()
var example = Example()
println(example.y)
example.y = "Aaa"
}
package com.xzm.test;
/**
* Created by Fassbender on 2017/6/30.
*/
public interface SingleFun {
void hoo();
}