关键词:
类
Kotlin 中定义类有些地方不同于 java。
不需要 public
一个类默认就是 public 的,所以不用显示地声明一个类为 public。
不需要花括号
如果一个类是空实现,可以不需要
class MyClass
主构造方法
Kotlin 规定每个类允许有一个主构造方法和多个次要构造方法。
主构造方法是类名的一部分
主构造方法定义类名之后,用 constructor 关键字声明:
class MyClass contructor(username: String)
...
这种形式定义的构造方法就是主构造方法。它的声明属于类头的一部分。
constructor 可以省略
如果主构造方法没有任何注解修饰或者访问修饰符,则 constructor 可以省略,这种情况下更像是在定义一个函数而不是类,比如:
class MyClass(username:String)
...
主构造器中不包含代码
你没看错,kotlin 中主构造方法中没有代码(没有方法体)。那么初始化成员变量怎么办?答案是放在 init 代码块:
class MyClass(username: String)
init
println("init MyClass")
username = ""
init 块中的代码会在主构造器被调用时调用,相当于将主构造方法的方法头和方法体分开定义,方法头放在类头后面,方法体放在类体内(init 块)。
构造参数可以用于 init 和属性初始化
如上面的代码中所示,在 init 块中,可以直接使用主构造方法中定义的参数。
此外,主构造方法参数也可以用于给属性赋初值:
class MyClass(username: String)
private val username = username
没有 new 关键字
当实例化对象时,直接调用构造方法即可,没有new 关键字(同 swift)。当你创建 MyClass 实例时,你会发现 init 中的代码被执行,控制台中会打印 init MyClass字样:
var myClass = MyClass("Chris") // 这里会打印 init MyClass
默认构造方法(无参构造方法)
如果一个非抽象类,没有定义任何构造方法,编译器会自动提供一个默认的主构造方法(无参),访问级别 public。
此外,如果主构造方法的所有参数都提供了默认值,比如:
class Person(val username:String = "xxx")
那么编译器会自动创建一个无参构造方法,同时这个无参构造方法会使用所提供的默认值初始化。这样做的目的是为了和一些 Java 框架(比如 Spring)兼容(IoC框架通常使用无参构造方法创建实例)。
次要构造方法
次要构造方法不是必须的,如果有,那么可以有多个。次要构造方法是放在类体内定义的(而非类头)。次要构造方法没有方法名,只有关键字 constructor:
constructor(username: String, age: Int)
...
kotlin 中的次要构造方法必须直接或间接地调用主构造方法,这点同 swift 一样(参考 swift 中指定构造方法
的概念):
constructor(username: String, age: Int):this(username)
println(username+","+age)
this.age = age
注意this(username)
的写法,这里调用了主构造方法。注意,主构造方法先调用,然后才是次要构造方法体内的代码。
间接调用主构造方法
constructor(username: String, age: Int, address: String):this(username,age)
this.address = address
这个次要构造方法没有直接调用主构造方法,而是调用了另外一个次要构造方法,这样就相当于间接地调用了主构造方法。
私有的主构造方法
某些情况下,你可能想让一个类不能够从外部实例化,那么你可以让主构造函数变成 private(这时 constructor 不可省略) :
class Person private constructor(username: String)
在定义主构造方法的同时定义属性
Kotlin 提供一种简化的构造方法定义形式,在定义构造参数的同时就对属性进行初始化:
class Person (private val username: String, private val age:Int)
如你所见,类的成员变量或属性现在直接在主构造方法中定义,代替了构造参数。它们和构造参数的区别在于,作用域不同。在主构造方法定义的属性,其中整个类的作用域内有效,而构造参数仅在 init 中有效,或者在定义属性初始化值时有效。
属性
非 optional 属性必须初始化
这样声明一个属性:
private var username: String
IDE 提示属性必须被初始化,要么是一个 abstract 属性。如果你使用 abstract 修饰,则带来两个问题:一,abstract 和 pirvate 不兼容,你必须将 private 去掉,二,类必须是一个 abstract 类。
如果你要对 username 进行初始化,那么必须注意,对于非 optional 属性,你不能赋值为 null(或者你将它改成 optional 的)。
在 init 块中初始化
不需要在声明变量时就初始化,你也可以选择在 init 块中初始化。Kotlin 运行时会自动检测你在 init 中的初始化动作并判定为属性已经初始化:
init
println(username)
this.username = username
这样,属性 username 上的警告就会消失。注意,init 块中username
引用的是构造参数的 username
, this.username
引用的才是 username
属性。
get 方法
Kotlin 中 get 方法的定义是极度简化的:
val age
get() = 20
age 是一个只读属性所以用 val,同时其 get 方法只是一个返回整型的表达式,因此函数使用了简化的赋值形式。age 的类型可以通过 get 的返回值推断,因此无需明确指定。
自动提供的 get/set 方法
以下是一个读写属性的例子:
var address: String = address
在不明确定义 get/set 的情况下,kotlin 编译器自动生成对应的 get/set 方法。
内置变量 field 和 value
如果想在 get 方法中直接访问变量所对应的私有变量的值(backing field),可以用 field 关键字(类似于 O-C 中以 _ 开头的私有变量):
get()
return field
在 set 方法中,也可以直接对 field 赋值:
set(value)
field = value
这里,value 并不是 kotlin 的关键字,它只是一个参数名,你可以修改为其它。这点不同于 swift ,swift 中通过 newValue 关键字来表示即将赋给属性的新值。
实际上,默认的 get/set 实现就是上面的样子,同时 IDE 会提示冗余的实现,意思是它们与默认实现相同,建议你删除。
修改可见性
可以仅仅修改 get/set 的访问级别,而不修改默认实现:
private var name:String
private set
private get
注意,get 方法的访问级别必须和属性的访问级别相同。
延迟初始化
与 java 不同,kotlin 的属性要求必须明确提供初值,或者修改为 optional。这带来了一些不便。为了解决这个问题,kotlin提供了延迟初始化的概念。
lateinit var name:String
lateinit 关键字告诉 kotlin,不检测该属性的初初始化情况,这个值会在实例化之后进行初始化。但是需要程序员遵守以下规则:
- 非空(非optional)类型的属性必须中构造方法中初始化。
- 如果是依赖注入或者UnitTest,可以使用 lateinit 关键字修饰该属性。但存在如下限制:
- 只能用于类体中声明的属性,不能用于主构造器中声明的属性。
- 该属性不能定义 set/get 方法。
- 只能用于非空(非 optional)属性,且类型不是原生数据类型(如 Int、Double)。
继承
类默认不可继承
在 java 中除了 Final 修饰的类外,都是可继承的。但是中 kotlin 中,默认所有类都 final 的、不可继承的。如果需要继承某个类,那么需要显式地将这个类标记为 open。
open class Parent(name:String, age: Int)
必须调用父类的构造方法
要继承某个类,使用:
关键字(kotlin 中取消了 extend 关键字)。同时需要显式地调用父类构造方法。
父类的构造方法可能有参数,也可能没有参数。
如果没有参数很简单,这样就可以了:
class Child:Parent()
如果父类有参数,那么继承时必须传递参数给父类的构造方法。这有两种传递方法:利用主构造器传参,或者利用次要构造器传参。
利用主构造器传参
定义子类主构造器,利用子类主构造器中的参数,来调用父类的构造方法,。
class Child(name:String, age:Int): Parent(name, age)
利用次要构造器传参
如果一个类没有主构造方法时怎么办?在次要构造器中调用。
如果一类没有主构造方法,同时他又要继承某个类,那么他必须在次要构造方法中调用父类构造方法:
class Child: Parent
constructor(name:String, age:Int): super(name, age)
这里,:super(name, age)
表示调用父类的构造方法。
重写
必须使用 override
kotlin 中,重写时必须使用 override 关键字。这与 java 不同。
默认不可重写
父类中的方法默认是 final 的,不可以被重写。必须标记为 open 才能被子类重写。
这对于属性重写也是一样的。无论方法还是属性的重写,都必须中父类中明确标记为 open。
open 可以被 final 中断
父类的 open 方法或属性被子类重写之后仍然是 open 的,子类的子类仍然可以重写这个方法/属性,但是如果你想终止这种“可重写”的关系延续,那么你可以在子类中,将该方法修饰为 final,这样子类的子类将不能重写该方法/属性:
open class Child:Person()
final override func name()
在主构造器中 override
在主构造器中重写属性也是可以的:
class Child(override val name: String): Parent()
调用父类实现
通过 super 调用父类的实现。
override fun method()
super.method()
...
重写 getter 方法
override val name: String
get() = super.name+" and child"
var 不能重写为 val
Kotlin 中,属性的可变性可以被子类重写。但是只能是将 val 属性重写为 var 属性(不可变->可变),但反过来不行。换句话说,只能将只读
属性重写为读写
属性。
深入kotlin-数据类和密封类(代码片段)
Dataclass定义Kotlin中对java中的pojo类型进行了专门的支持和简化,叫做数据类(dataclass)。一个数据类的定义非常简单,仅有一个主构造方法即可:dataclassPerson(valname:String,varage:Int,varaddress:String)//使用dataclassvarpers 查看详情
深入kotlin-接口和抽象类(代码片段)
接口接口可以拥有实现jdk8开始,接口中的方法可以拥有方法实现(default关键字修饰的接口方法)。Kotlin也是同样的:interfaceA funmethod() println("A") 如果一个类B继承了接口A,因为A中的方法已经有默认实现... 查看详情
深入kotlin-委托(代码片段)
委托委托不是java中的概念。它仅仅是一种设计模式,但是kotlin从语言层级上,通过by关键字提供了对委托模式的支持。类委托interfaceMyInteface//1 funmyPrint()classMyInterfaceImpl(valname:String):MyInterface//2 overridefunmyPring() println(name) 查看详情
深入kotlin-反射的应用(代码片段)
到目前为止,我们都是通过反射获取类的信息,接下来我们能利用这些反射信息干什么呢?调用成员函数classMyClass funprintSomething(name:String) println("something:$name") fun 查看详情
深入kotlin-反射的应用(代码片段)
到目前为止,我们都是通过反射获取类的信息,接下来我们能利用这些反射信息干什么呢?调用成员函数classMyClass funprintSomething(name:String) println("something:$name") fun 查看详情
深入kotlin-类(代码片段)
类Kotlin中定义类有些地方不同于java。不需要public一个类默认就是public的,所以不用显示地声明一个类为public。不需要花括号如果一个类是空实现,可以不需要classMyClass主构造方法Kotlin规定每个类允许有一个主构造方法和... 查看详情
深入kotlin-与java互操作:kotlin调用java(代码片段)
Kotlin调用Java比如如下Java类:publicclassPerson privateStringname; privatebooleanmarried; privateintage; ......在kotlin中调用Person:funmain(args:Array<String>) vallist=ArrayList<String>//调用 查看详情
深入kotlin-与java互操作:kotlin调用java(代码片段)
Kotlin调用Java比如如下Java类:publicclassPerson privateStringname; privatebooleanmarried; privateintage; ......在kotlin中调用Person:funmain(args:Array<String>) vallist=ArrayList<String>//调用 查看详情
深入kotlin-与java互操作:kotlin调用java(代码片段)
Kotlin调用Java比如如下Java类:publicclassPerson privateStringname; privatebooleanmarried; privateintage; ......在kotlin中调用Person:funmain(args:Array<String>) vallist=ArrayList<String>//调用 查看详情
深入kotlin-kclass特性深入研究(代码片段)
KClass特性深入研究kotlin反射vs.Java反射其实可以通过KClass获取属性的java特性,比如获得get/set方法和field的引用:classT(valx:Int)funmain(args:Array<String>) println(T::x)//打印:valcom.kotlin.T.x:kotlin.Intprintln(T::x.javaGetter)//打印ÿ... 查看详情
深入kotlin-伴生对象和扩展(代码片段)
伴生对象在kotlin中,类没有static方法的概念,这与java不同。kotlin用package级别的函数来取代静态方法(在字节码层级,这就是静态方法)。所谓伴生对象其实就是位于class中的object,使用companionobject关键字... 查看详情
深入kotlin-数据类和密封类(代码片段)
Dataclass定义Kotlin中对java中的pojo类型进行了专门的支持和简化,叫做数据类(dataclass)。一个数据类的定义非常简单,仅有一个主构造方法即可:dataclassPerson(valname:String,varage:Int,varaddress:String)//使用dataclassvarpers... 查看详情
深入kotlin-与java互操作:kotlin调用java(代码片段)
Kotlin调用Java比如如下Java类:publicclassPerson privateStringname; privatebooleanmarried; privateintage; ......在kotlin中调用Person:funmain(args:Array<String>) vallist=ArrayList<String>//调用Java类arraylistlist.add("hello")list.add("world")for(item... 查看详情
从java角度深入理解kotlin(代码片段)
前言前几个月,在组内分享了关于Kotlin相关的内容。但由于PPT篇幅的原因,有些内容讲的也不是很详细。所以通过一篇文字来详解介绍Kotlin的特性,为了方便大家对本文有一个大概的了解,文本主要讲如下内容... 查看详情
深入kotlin-方法引用和属性引用(代码片段)
反射kotlin通过kotlin-reflect.jar提供对反射的支持。KClass通过类引用KClassKClass引用了kotlin类(具有内省能力)。类似于Java的class。要获取一个类的KClass,通过类型名::class获得,而对应的Javaclass则通过类型名::class.java获得:fun... 查看详情
深入kotlin-伴生对象和扩展(代码片段)
伴生对象在kotlin中,类没有static方法的概念,这与java不同。kotlin用package级别的函数来取代静态方法(在字节码层级,这就是静态方法)。所谓伴生对象其实就是位于class中的object,使用companionobject关键字... 查看详情
深入kotlin-协程(代码片段)
协程是轻量级的funmain()=runBlocking repeat(100)//2 launch() delay(1000) println 查看详情
深入kotlin-接口和抽象类(代码片段)
接口接口可以拥有实现jdk8开始,接口中的方法可以拥有方法实现(default关键字修饰的接口方法)。Kotlin也是同样的:interfaceA funmethod() println("A") 如果一个类B继承了接口A,因为A中的方法已经有默认实现... 查看详情