上一篇我们简单的看了一下关于kotlin的一些基本语法,接下来我们继续接着看,对kotlin的一些基础语法还不懂的同学,可以去看我上一篇文章Android之kotlin篇(一)—— kotlin的基本使用。现在,我们就接着上一篇继续往下看。
类和对象
Kotlin 类可以包含:构造函数和初始化代码块、函数、属性、内部类、对象声明。
Kotlin 中使用关键字 class 声明类,后面紧跟类名:
class Runoob { // 类名为 Runoob
// 大括号内是类体构成
// 函数
fun foo() { print("Foo") }
}
// 也可以定义一个空类
class Runoob
构造方法
主构造函数
Koltin 中的类可以有一个 主构造器,以及一个或多个次构造器,主构造器是类头部的一部分,位于类名称之后:
class Person constructor(firstName: String) {}
// 如果主构造器没有任何注解,也没有任何可见度修饰符,那么constructor关键字可以省略。
class Person(firstName: String) {
}
// 注意,主构造器中不能包含任何代码,初始化代码可以放在初始化代码段中,
// 初始化代码段使用 init 关键字作为前缀。
class Person constructor(firstName: String) {
init {
println("FirstName is $firstName")
}
}
// 当然,也可以忽略init方法直接在构造器里面去使用
class People(val firstName: String, val lastName: String) {
//...
}
次构造函数
类也可以有二级构造函数,需要加前缀 constructor
class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}
如果类有主构造函数,每个次构造函数都要,或直接或间接通过另一个次构造函数代理主构造函数。在同一个类中代理另一个构造函数使用 this 关键字。
class Person(val name: String) {
constructor (name: String, age:Int) : this(name) {
// 初始化...
}
}
如果一个非抽象类没有声明构造函数(主构造函数或次构造函数),它会产生一个没有参数的构造函数。构造函数是 public 。如果你不想你的类有公共的构造函数,你就得声明一个空的主构造函数。
class DontCreateMe private constructor () {
}
getter 和 setter
属性声明的完整语法:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
// 例如
var lastName: String = "zhang"
get() = field.toUpperCase() // 将变量赋值后转换为大写
set
var no: Int = 100
get() = field // 后端变量
set(value) {
if (value < 10) { // 如果传入的值小于 10 返回该值
field = value
} else {
field = -1 // 如果传入的值大于等于 10 返回 -1
}
}
var heiht: Float = 145.4f
private set
// 其中由于Kotlin 中类不能有字段,所以备用字段使用field关键字声明,field 关键词只能用于属性的访问器。
抽象类
抽象是面向对象编程的特征之一,类本身,或类中的部分成员,都可以声明为abstract的。抽象成员在类中不存在具体的实现。
注意:无需对抽象类或抽象成员标注open注解。
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
abstract override fun f()
}
嵌套类
我们可以把类嵌套在其他类中
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)
}
}
}
fun main(args: Array<String>) {
val demo = Outer().Inner().foo()
println(demo) // 1
val demo2 = Outer().Inner().innerTest()
println(demo2) // 内部类可以引用外部类的成员,例如:成员属性
}
匿名内部类
一般情况下匿名内部类主要是处理点击回调监听等情况,其实也可以理解为和Java的setOnClickListener类似。
class Test {
var v = "成员属性"
fun setInterFace(test: TestInterFace) {
test.test()
}
}
/**
* 定义接口
*/
interface TestInterFace {
fun test()
}
fun main(args: Array<String>) {
var test = Test()
/**
* 采用对象表达式来创建接口对象,即匿名内部类的实例。
*/
test.setInterFace(object : TestInterFace {
override fun test() {
println("对象表达式创建匿名内部类的实例")
}
})
}
类的修饰符
classModifier: 类属性修饰符,标示类本身特性。
abstract // 抽象类
final // 类不可继承,默认属性
enum // 枚举类
open // 类可继承,类默认是final的
annotation // 注解类
accessModifier: 访问权限修饰符
private // 仅在同一个文件中可见
protected // 同一个文件中或子类可见
public // 所有调用的地方都可见
internal // 同一个模块中可见
继承
Kotlin 中所有类都继承该 Any 类,它是所有类的基类,对于没有超类型声明的类是默认基类。
class Example // 从 Any 隐式继承
Any 默认提供了三个函数:
equals()
hashCode()
toString()
如果一个类要被继承,可以使用 open 关键字进行修饰。
open class Base(p: Int) // 定义基类
class Derived(p: Int) : Base(p)
构造函数继承
class Outer(context: Context?) : View(context) {
constructor(context: Context?, attributes: Attributes) : this(context)
constructor(context: Context?, attributes: Attributes, defStyleAttr: Int) : this(
context,
attributes
)
}
重写
在基类中,使用fun声明函数时,此函数默认为final修饰,不能被子类重写。如果允许子类重写该函数,那么就要手动添加 open 修饰它, 子类重写方法使用 override 关键词。
/**用户基类**/
open class Person{
open fun study(){ // 允许子类重写
println("我毕业了")
}
}
/**子类继承 Person 类**/
class Student : Person() {
override fun study(){ // 重写方法
println("我在读大学")
}
}
fun main(args: Array<String>) {
val s = Student()
s.study();
}
接口
Kotlin 接口与 Java 8 类似,使用 interface 关键字定义接口,允许方法有默认实现。
interface MyInterface {
fun bar() // 未实现
fun foo() { //已实现
// 可选的方法体
println("foo")
}
}
实现接口
class Child:MyInterface {
override fun bar() {
TODO("Not yet implemented")
}
}
接口中的属性
接口中的属性只能是抽象的,不允许初始化值,接口不会保存属性值,实现接口时,必须重写属性。
interface MyInterface{
var name:String //name 属性, 抽象的
}
class MyImpl:MyInterface{
override var name: String = "runoob" //重写属性
}
扩展
Kotlin 可以对一个类的属性和方法进行扩展,且不需要继承或使用 Decorator 模式。
扩展是一种静态行为,对被扩展的类代码本身不会造成任何影响。
class Person {
}
companion object {
private fun Person.student(name: String) {
print("用户名 $name")
}
@JvmStatic
fun main(args: Array<String>) {
val person = Person()
person.student("jack")
}
}
若扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数。
class C {
fun foo() { println("成员函数") }
}
fun C.foo() { println("扩展函数") }
fun main(arg:Array<String>){
var c = C()
c.foo()
}
// 结果为 : 成员函数
伴生对象的扩展
如果一个类定义有一个伴生对象 ,你也可以为伴生对象定义扩展函数和属性。
伴生对象通过"类名."形式调用伴生对象,伴生对象声明的扩展函数,通过用类名限定符来调用。
class Person {
companion object{}
}
companion object {
private fun Person.Companion.student(name: String) {
println(name)
}
val Person.Companion.age: Int
get() = 10
@JvmStatic
fun main(args: Array<String>) {
println(Person.student("jack"))
}
}
总结:
(1)对于扩展来说,是kotlin相对于Java的一大利器。设想一个场景:当我们将一个比较完整的框架封装成aar或者jar的时候,在使用它的过程中需要在里面某个方法里面添加一个函数对特殊情况进行处理,使用Java的话这个时候我们只能把这个方法重新添加进去重新生成aar或者jar,但是如果我们使用kotlin则完全不用那么麻烦,直接使用扩展添加函数就可以解决这个问题,这样的话是不是很Nice。
(2)对于伴生对象而言:伴生对象内的成员相当于 Java 中的静态成员,其生命周期伴随类始终,在伴生对象内部可以定义变量和函数,这些变量和函数可以直接用类名引用,对于伴生对象扩展函数,有两种形式,一种是在类内扩展,一种是在类外扩展,这两种形式扩展后的函数互不影响(甚至名称都可以相同),即使名称相同,它们也完全是两个不同的函数。
数据类
Kotlin 可以创建一个只包含数据的类,关键字为 data。
data class User(val name: String, val age: Int)
编译器会自动的从主构造函数中根据所有声明的属性提取以下函数:
equals() / hashCode()
toString() 格式如 "User(name=John, age=42)"
componentN() functions 对应于属性,按声明顺序排列
copy() 函数
生成数据类需要满足以下条件:
(1)主构造函数至少包含一个参数;
(2)所有的主构造函数的参数必须标识为val 或者 var ;
(3)数据类不可以声明为 abstract, open, sealed 或者 inner;
(4)数据类不能继承其他类 (但是可以实现接口)。
泛型
泛型,即 "参数化类型",将类型参数化,可以用在类,接口,方法上。
与 Java 一样,Kotlin 也提供泛型,为类型安全提供保证,消除类型强转的烦恼。
声明一个泛型
class Box<T>(t: T) {
var value = t
}
创建类的实例时我们需要指定类型参数
val box: Box<Int> = Box<Int>(1)
//或者
val box = Box(1) // 编译器会进行类型推断,1 类型 Int,所以编译器知道我们说的是 Box<Int>。
Kotlin 泛型函数的声明与 Java 相同,类型参数要放在函数名的前面:
fun <T> boxIn(value: T) = Box(value)
// 以下都是合法语句
val box4 = boxIn<Int>(1)
val box5 = boxIn(1) // 编译器会进行类型推断
协变、逆变与抗变
当我们在kotlin里中使用泛型的时候,一定会碰到out、in这些关键词,咋一看感觉这个词好陌生,在Java里面其实也有,不过Java里面叫的是通配符,而在kotlin中则采用数学的叫法为协变、逆变与抗变,那么,协变、逆变与抗变到底是什么东西呢?推荐大家看下这篇博客,看完就明白了。协变、逆变与抗变
枚举
枚举常量用逗号分隔,每个枚举常量都是一个对象
enum class Color{
RED,BLACK,BLUE,GREEN,WHITE
}
// 或者
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
对象声明
Kotlin 使用 object 关键字来声明一个对象。
Kotlin 中我们可以方便的通过对象声明来获得一个实例。
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ……
}
val allDataProviders: Collection<DataProvider>
get() = // ……
}
引用该对象
// 第一种方式:我们直接使用其名称即可
DataProviderManager.registerDataProvider(……)
// 第二种方式,定义一个变量来获取获取这个对象
var data1 = DataProviderManager
伴生对象
类内部的对象声明可以用 companion 关键字标记,这样它就与外部类关联在一起,我们就可以直接通过外部类访问到对象的内部元素
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
val instance = MyClass.create() // 访问到对象的内部元素
委托
类委托
类的委托即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。
// 创建接口
// 约束类
interface IGamePlayer {
// 打排位赛
fun rank()
// 升级
fun upgrade()
}
// 被委托对象,本场景中的游戏代练
class RealGamePlayer(private val name: String): IGamePlayer{
override fun rank() {
println("$name 开始排位赛")
}
override fun upgrade() {
println("$name 升级了")
}
}
// 委托对象
class DelegateGamePlayer(private val player: IGamePlayer): IGamePlayer by player
// Client 场景测试
fun main() {
val realGamePlayer = RealGamePlayer("张三")
val delegateGamePlayer = DelegateGamePlayer(realGamePlayer)
delegateGamePlayer.rank()
delegateGamePlayer.upgrade()
}
// 运行结果
张三 开始排位赛
张三 升级了
属性委托
在Kotlin 中,有一些常见的属性类型,虽然我们可以在每次需要的时候手动实现它们,但是很麻烦,各种样板代码存在,我们知道,Kotlin可是宣称要实现零样板代码的。为了解决这些问题呢?Kotlin标准为我们提供了委托属性。
属性委托语法格式:
val/var <属性名>: <类型> by <表达式>
import kotlin.reflect.KProperty
// 定义包含属性委托的类
class Test {
// 属性委托
var prop: String by Delegate()
}
前面讲的委托中,我们有个约束角色,里面定义了代理的业务逻辑。而委托属性呢?其实就是上面的简化,被代理的逻辑就是这个属性的get/set方法。get/set会委托给被委托对象的setValue/getValue方法,因此被委托类需要提供setValue/getValue这两个方法。如果是val 属性,只需提供getValue。如果是var 属性,则setValue/getValue都需要提供。
比如上面的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.")
}
}
上面我们讲了,要实现属性委托,就必须要提供getValue/setValue方法,对于比较懒的同学可能就要说了,这么复杂的参数,还要每次都要手写,真是麻烦,一不小心就写错了。确实是这样,为了解决这个问题, Kotlin 标准库中声明了2个含所需 operator方法的 ReadOnlyProperty / ReadWriteProperty 接口。
interface ReadOnlyProperty<in R, out T> {
operator fun getValue(thisRef: R, property: KProperty<*>): T
}
interface ReadWriteProperty<in R, T> {
operator fun getValue(thisRef: R, property: KProperty<*>): T
operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
被委托类 实现这两个接口其中之一就可以了,val 属性实现ReadOnlyProperty,var属性实现ReadOnlyProperty。
// val 属性委托实现
class Delegate1: ReadOnlyProperty<Any,String>{
override fun getValue(thisRef: Any, property: KProperty<*>): String {
return "通过实现ReadOnlyProperty实现,name:${property.name}"
}
}
// var 属性委托实现
class Delegate2: ReadWriteProperty<Any,Int>{
override fun getValue(thisRef: Any, property: KProperty<*>): Int {
return 20
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) {
println("委托属性为:${property.name} 委托值为:$value")
}
}
// 测试
class Test {
// 属性委托
val d1: String by Delegate1()
var d2: Int by Delegate2()
}
标准委托
延迟属性 Lazy
lazy() 是一个函数, 接受一个 Lambda 表达式作为参数, 返回一个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lamda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。
val lazyValue: String by lazy {
println("computed!") // 第一次调用输出,第二次调用不执行
"Hello"
}
fun main(args: Array<String>) {
println(lazyValue) // 第一次执行,执行两次输出表达式
println(lazyValue) // 第二次执行,只输出返回值
}
// 执行输出结果
computed!
Hello
Hello
可观察属性 Observable
observable 可以用于实现观察者模式。
Delegates.observable() 函数接受两个参数: 第一个是初始化值, 第二个是属性值变化事件的响应器(handler)。
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("初始值") {
prop, old, new ->
println("旧值:$old -> 新值:$new")
}
}
fun main(args: Array<String>) {
val user = User()
user.name = "第一次赋值"
user.name = "第二次赋值"
}
// 执行输出结果
旧值:初始值 -> 新值:第一次赋值
旧值:第一次赋值 -> 新值:第二次赋值
到这里,kotlin的基础篇就结束了,虽然内容比较简单,但是还是很有必要多看看,多练练巩固巩固,下一篇我们继续来看kotlin的高级用法。