首發於公衆號: DSGtalk1989html
構造器java
kotlin中一個類只能有一個主構造器和一個或多個次構造器。主構造器能夠直接跟在class
定義的類名後面可是沒有方法體,以下:json
class Person constructor(s : String) {
}
//也能夠寫成這樣,記得,沒有空格
class Person(s : String){
}
//一旦構造函數存在修飾符或者是註解的狀況下,咱們就不能省去constructor的方法名
class Child public constructor(s : String){
}
複製代碼
以上的都是主構造函數,次構造函數定義在類中,而且kotlin強制規定,次構造函數必需要調用主構造函數,以下:app
class Person constructor(s : String) {
constructor(i : Int) : this("123"){
}
}
//也能夠沒有主構造器,以下的方式就是直接起的次構造器
//只有這種狀況下,次構造器不須要再調用主構造器,因此通常以下這種方式跟咱們的java習慣比較像
class Person{
constructor(){
}
}
複製代碼
private,public,protected,internalide
前面三個你們比較熟悉了,在java中都有,可是internal
是kotlin中才引入的,叫作模塊內可見,即同一個module
中可見。函數
咱們分別來看下,用這四個修飾符來描述屬性所帶來的編譯區別。ui
//kt
private var a = "a"
public var b = "b"
protected var c = "c"
internal var d = "d"
//decompiled
private String a;
@NotNull
private String b;
@NotNull
private String c;
@NotNull
private String d;
@NotNull
public final String getB() {
return this.b;
}
public final void setB(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.b = var1;
}
@NotNull
protected final String getC() {
return this.c;
}
protected final void setC(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.c = var1;
}
@NotNull
public final String getD$app_debug() {
return this.d;
}
public final void setD$app_debug(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.d = var1;
}
複製代碼
總結一下,就是不論是哪個修飾符,最終通過編譯以後生成的在java中的參數描述都是private
的,修飾符真正形成的區別是在編譯了自後的get
和set
的方法不一樣。this
private
的話,就不會生成get
和set
方法,由於對於這個參數來講,是外部不可訪問的。public
和protected
就是相應的set
和get
。而internal
則是public
的setD$app_debug
和getD$app_debug
方法。咱們能夠認爲這兩個方法,在model
中都是能夠被訪問的。spa
init關鍵字debug
上面說到主構造器直接寫在類名以後是沒有方法體的,所以一旦咱們想要在構造函數中作一些初始化的操做,就須要挪到init中實現了。
class Person constructor(firstName: String) {
init {
println("FirstName is $firstName")
}
}
複製代碼
getter和setter
跟java差的有點多,首先屬性定義前面說過了,kotlin中getter和setter直接定義在屬性下方,因爲kotlin的自己屬性的直接訪問性,只要你建立的是public的屬性,均可以直接獲取到屬性值即get方法和修改屬性值即set方法。
因此免去了爲了fastjson
而專門去寫的setter
和getter
,可是一旦你須要在getter
和setter
時作一些其餘的操做,咱們就須要去顯示的寫出get
和set
了
var sex = "boy"
get() {
return "girl"
}
set(value) {
when {
value.contains("girl") -> field = "boy"
}
}
var age = 16
get() = field + 10
private set(value) = action(value)
fun action(int: Int) {
}
複製代碼
get
和set
能夠直接包含方法體,也能夠直接經過等號的方式鏈到單行表達式或者方法。 即咱們認爲,一旦觸發取值和賦值的時候會作相應的操做。其中field
就是指的他本身。
>>注:field的重要性<<
在kotlin中,咱們定義了一個參數name
而後,只要去調用他,好比parent.name
或者說去對他進行賦值name = "Tony"
最終都會被編譯成使用了get
和set
方法,以下
//Observer.kt
fun main(args : Array<String>){
var observer = Observer()
observer.name = "Tony"
var newName = observer.name
}
//Observer.decompiled.java
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
Observer observer = new Observer();
//調用了set
observer.setName("Tony");
//調用了get
String newName = observer.getName();
}
複製代碼
因此咱們在類中定義屬性的setter
和getter
的時候,若是直接操做屬性自己,就會出現死循環。這就是field
的用途
//kt
var no: Int = 100
get() = no
set(value) {
if (value < 10) { // 若是傳入的值小於 10 返回該值
no = value
} else {
no = -1 // 若是傳入的值大於等於 10 返回 -1
}
}
//decompiled
int no = 100;
public int getNo() {
return getNo();// Kotlin中的get() = no語句中出來了變量no,直接被編譯器理解成「調用getter方法」
}
public void setNo(int value) {
if (value < 10) {
setNo(value);// Kotlin中出現「no =」這樣的字樣,直接被編譯器理解成「這裏要調用setter方法」
} else {
setNo(-1);// 在setter方法中調用setter方法,這是不正確的
}
}
複製代碼
很顯然,形成了死循環。
lateinit關鍵字
咱們都知道kotlin中,在方法中定義屬性時,咱們必須進行初始化的操做。而類中自己咱們一開始並不知道他究竟是什麼,因此會有但願晚一點再初始化的需求,這裏就可使用lateinit
關鍵字來描述,那咱們就能夠不用給出具體的初始化值,可是kotlin會要求你必須給出屬性的類型。
lateinit var game : String
複製代碼
那麼這個game
咱們能夠以後再對其賦值。
從kotlin 1.2開始已經支持全局和局部變量都是用lateinit, 而且咱們能夠經過isInitialized來判斷是否已經初始化過
抽象類
咱們默認定義的class
都是final
的,沒法被繼承的。因此一旦須要這個class
可以被繼承,咱們須要加上open
關鍵字。若是這是個抽象類,那咱們須要添加abstract
關鍵字。一旦被abstract
描述,就無需再加上open
了。
open class Person(){
}
abstract class Parent{
}
複製代碼
緊接着,另外幾種場景
方法是否能夠被重寫
默認方法都是final
的,若是須要讓方法能夠被重寫,須要在方法前再加上open
全部咱們平時在java中寫的一個單純的類實際上轉換成kotlin是以下這個樣子的:
open class Person constructor(s: String) {
open fun getName(){}
}
複製代碼
一樣的abstract
也是這個意思,加載class
前面只是形容類,跟方法和屬性什麼的一點關係都沒有
抽象屬性
這是一個比較新的東西,由於java不支持抽象屬性。就是說,你要是繼承我,你就必需要初始化我所要求初始化的屬性。
abstract class Parent(ame : String){
abstract var ame : String
}
//兩種集成方式,一種是直接在構造函數中對抽象屬性進行復寫
//因爲父類構造須要傳一個字符串,因此在繼承時也須要直接傳入,此處傳入的是Child1本身的構造參數s
class Child1 constructor(s: String, override var ame: String) : Parent(s) {
}
//一種是在類中對屬性進行復寫
class Child2 constructor(s: String) : Parent(s) {
override lateinit var ame: String
}
//若是子類沒有主構造函數,也能夠經過次構造函數調用`super`方法實現
class Child: Parent {
constructor() : super("s")
override lateinit var ame: String
}
複製代碼
嵌套類
直接在class
內部定義class
,基本和java差很少
class Outer { // 外部類
private val bar: Int = 1
class Nested { // 嵌套類
fun foo() = 2
}
}
fun main(args: Array<String>) {
val demo = Outer.Nested().foo() // 調用格式:外部類.嵌套類.嵌套類方法/屬性
println(demo) // == 2
}
複製代碼
內部類
在剛纔嵌套類的基礎上加上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)
}
}
}
複製代碼
惟一的區別在於內部類持有了外部類的引用,能夠經過@外部類名
的方式,來訪問外部類的成員變量。
內部類和嵌套類的區別
咱們來看以下兩個嵌套類和內部類的以及讓門各自編譯成class文件的例子
//Out.kt
class Out{
class Inner{
}
}
//Out.decompiled.java
public final class Out {
public static final class Inner {
}
}
//Out.kt
class Out{
inner class Inner{
}
}
//Out.decompiled.java
public final class Out {
public final class Inner {
}
}
複製代碼
已經很明顯了,inner
之因此持有外部的引用,是由於他不是static
的。也就是說kotlin的class
默認就是static final
的。
調用嵌套類的方式與調用內部類的方式差異也只是一個括號而已
fun main(args : Array<String>){
//內部類調用
Out().Inner().method()
//嵌套類的調用
Out1.Inner().method()
}
複製代碼
其實比較容易理解,嵌套類是static
的直接能夠經過類名來進行訪問嵌套類。
匿名內部類
通常用在接口層面的不少,咱們一般知道的是傳參是個接口,方法中調用了接口方法的形式,以下:
class Observer{
fun getIt(listener: Listener){
listener.onClick()
}
}
interface Listener{
fun onClick()
}
fun main(args : Array<String>){
var observer = Observer()
//注意,此處的object是kotlin獨有的關鍵字
//不是隨便寫寫的,匿名內部類必須經過這個關鍵字來申明
observer.getIt(object : Listener{
override fun onClick() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
})
}
複製代碼
一般咱們也能夠直接使用接口名
+ lambda表達式
的方式來生成匿名內部類,但條件是這個接口必須是函數式java接口,即只有一個抽象方法的java文件中定義的接口。
好比咱們基本碰到的全部的什麼OnclickListener
等等
tv_case_id.setOnClickListener { View.OnClickListener{
} }
複製代碼
不過kotlin中定義的接口,咱們就必須經過object
的方式去實現了
同時匿名內部類咱們能夠單獨的拿出來進行定義,實際上咱們能夠把object :
理解成一個匿名的內部類實現了一個接口,也就是說咱們還能夠實現多個接口,好比:
open class A(x: Int) {
public open val y: Int = x
}
interface B { …… }
val ab: A = object : A(1), B {
override val y = 15
}
複製代碼
一般咱們在java中是沒法作到匿名內部類實現多個接口的,由於咱們只能new
一個接口出來。
更甚者說,咱們不少時候甚至不須要這個object
去實現或者是繼承什麼,咱們能夠直接搞一個object
出來
fun foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
}
複製代碼
匿名對象最爲函數的返回類型
咱們上面是將匿名對象賦值給了對象,咱們還能夠吧匿名對象直接賦值給方法,好比下面這個樣子。
fun publicFoo() = object {
val x: String = "x"
}
複製代碼
這裏涉及到公有仍是私有的問題。
匿名對象咱們通常只能用在私有域和本地。白話的說就是一旦變成了公有,那就說誰均可以去調用,因爲匿名對象只在生命的本地和私有域起做用,致使公有調用拿到的對象只能是匿名對象的超類(即父類,好比上面的object : Listener
就是Listener
,若是沒有顯式的定義超類就是Any
)那麼這樣一來,就會致使匿名內部類中定義的屬性是拿不到的,好比上面的x
,由於上面的object
並無顯式的定義超類,因此他返回的是Any
,而Any
是沒有x
屬性的.
匿名對象訪問變量
在java中匿名內部類想要訪問相應的屬性變量必需要final
才行,可是在kotlin中,咱們直接能夠訪問包含匿名對象做用域中的全部變量。
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++
}
})
}
複製代碼