创建一个类

  1. class Runoob() {
  2. fun foo() { print("Foo") } // 成员函数
  3. }

构造函数及初始化

一个类可以有一个主构造函数及多个次构造函数,主构造器是类头部的一部分,位于类名称之后:

class Girl constructor(old:Int,name:String){    
    var old = old    
    var name = name    
    fun printMsg(){        
        Log.i("Girl","女孩的名字为 $name,年龄为$old")    
     }
}

注:如果主构造器没有任何注解,也没有任何可见度修饰符,那么constructor关键字可以省略。
又注:该类的主构造函数用Java理解即为

public class Girl{
        public Girl(int old,String name){
        }
}

如果类有一个主构造函数,每个次构造函数需要委托给主构造函数,可以直接委托或者通过别的次构造 函数间接委托。委托到同一个类的另一个构造函数用 this 关键字即可:

class Girl constructor(old:Int,name:String) {    
    var old = old    
    var name = name    

    constructor(name: String) : this(12, name) {                print("${name}的年龄为$old")    
    }
}

注:实际上上叙主构造函数中进行赋值的操作,在Kotlin中有更方便的操作:

class Girl constructor(var old:Int,var name:String) {  //直接进行初始化赋值,这种方法只能用在主构造函数中,次构造函数不能用

    fun show() {        
        print("${name}的年龄为$old")    
    }
}

fun main(args: Array<String>) {    
    var girl = Girl(12,"小")    
    girl.name = "大"    
    girl.show()
}

附注:一个类中可以没有主构造函数,只有次构造函数


  • 私有构造函数(不开放构造函数)

如果一个抽象类没有声明任何(主或次)构造函数,它会有一个生成的不带参数的主构造函数。构造函 数的可见性是 public。如果你不希望你的类有一个公有构造函数,你需要声明一个带有非默认可见性的空的主构造函数:

class DontCreateMe private constructor () { ... }

主构造函数不能包含任何的代码。初始化的代码可以放到以 init 关键字作为前缀的初始化块 (initializer blocks)中。

在实例初始化期间,初始化块按照它们出现在类体中的顺序执行,与属性初始化器交织在⼀起:

class InitOrderDemo(name: String) { 
    val firstProperty = "First property:$name".also(::println) 
    init { 
        println("First initializer block that prints ${name}") 
    } 

    val secondProperty = "Second property: ${name.length}".also(::println) 

    init { 
        println("Second initializer block that prints ${name.length}") 
    }
}

类的属性(var、val)

类的属性可以用关键字 var 声明为可变的,否则使用只读关键字 val 声明为不可变,默认加上private。

  var name: String = "小明"//相当于private String name
  val url: String =“www.baidu.com”//不可更改,相当于private final String url

我们可以将对象的set方法设为私有:将对象设为var,又不希望外界改

class Person {    
    var name : String? = null    
    private set    

    var age : Int = 0    
    private set
}

幕后属性(field)

在 Kotlin 类中不能直接声明字段。然而,当一个属性需要一个幕后字段时,Kotlin 会自动提供。这个幕后 字段可以使用 field 标识符在访问器中引用:

var counter = 0 // 注意:这个初始器直接为幕后字段赋值 
set(value) { 
    if (value >= 0) field = value 
}

注:幕后属性只能在访问器中引用
注:为什么会有幕后属性的设计呢,这是为了解决一个问题,,如果我们改成如下:

var counter = 0 // 注意:这个初始器直接为幕后字段赋值 
set(value) { 
   counter = value 
}

如上,我们似乎也可以没有幕后属性,但实际上并非如此:当我们调用counter = value的时候,实际上是调用了counter的set(value)方法,在这种情况下,如上图的代码,我们将再一次执行counter = value,调用set(value)然后陷入死循环。


属性的完整声明(getter,setter)

在上边的例子已经说过,当我们调用counter = value时,实际上是调用了count的set(value)方法,但是当我们会进行声明时,编译器会为var的属性生成默认get、set方法,为val的属性生成get方法

var <propertyName>[: <PropertyType>] [= <property_initializer>]
      [<getter>]
      [<setter>]
var stringRepresentation: String 
    get() = this.toString() 
    set(value) { 
        setDataFromString(value) // 解析字符串并赋值给其他属性 
    }

注:我们重写get、set方法时完全可以按照我们的目的来执行,不一定要返回原来值或是对值进行修改,可以只是打印日志,也可以设置其他值,比如:

val isEmpty:Boolean
    get() = this.size == 0

注 :上边的例子并不会生成幕后字段,因为是val,只有get方法,如果为var的话,则会生成,但是如果在将set方法进行实现,且依旧不用到field,则依旧不会生成

注:官方文档:

如果属性至少一个访问器使用默认实现,或者自定义访问器通过 field 引用幕后字段,将会为该属性生成一个幕后字段,反之,则不生成。


创建类实例

class Runoob() {
    fun foo() { print("Foo") } // 成员函数
}

我们可以像使用普通函数那样使用构造函数创建类实例:

val runoob = Runoob()//创建了Runoob的实例,在Kotlin中是没有new这个关键字的
runoob.foo()

继承(open)

  • 在Kotlin中,每一个类都默认是final,不可被继承,如果要想要被继承,需要加上open关键字
  • 在Kotlin中,每个类都会隐性继承一个类:Any(就像Java里的类继承Object,但这里的Any不等于Object。

官方文档:Any 并不是 java.lang.Object;尤其是,它除了 equals()、hashCode() 与 toString() 外没有任何成员

要声明一个显式的超类型,我们把类型放到类头的冒号之后: 创建类的实例 类成员

open class Base(p: Int) //父类,如果一个类要被继承,则必须标注为open
class Derived(p: Int) : Base(p)//派生类,继承并初始化父类
  • 如果派生类有一个主构造函数,其基类型可以(并且必须)用基类的主构造函数参数就地初始化。(如上)
  • 如果类没有主构造函数,那么每个次构造函数必须使用 super 关键字初始化其基类型,或委托给另一个构造函数做到这一点。
    注意,在这种情况下,不同的次构造函数可以调用基类型的不同的构造函数:
class MyView : View { 
    constructor(ctx: Context) : super(ctx) 

    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) }

注:上叙例子为Andriod中自定义View

覆盖父类方法及属性

  • 覆盖函数
open class Base { 
    open fun v() { ... } //可以被子类重写
    fun nv() { ... } //这个函数无法被子类重写
} 

class Derived() : Base() {
    override fun v() { ... } 
}

  • 覆盖属性
open class Foo { 
    open val x: Int get() { …… } 
}

class Bar1 : Foo() { 
    override val x: Int = …… //覆盖父类的属性
}

注:如果子类要覆盖父类的属性,则父类的被覆盖属性必须标注为open,而子类的重写属性必须注明为override
如果属性为标注为open,则默认为final,那么子类中不允许定义相同签名的函数,不论加不加 override。将 open 修饰符添加 到 final 类(即没有 open 的类)的成员上不起作⽤。

标记为 override 的成员本身是开放的,也就是说,它可以在子类中覆盖。如果你想禁止再次覆盖,使用final 关键字:

也可以用一个 var 属性覆盖一个 val 属性,但反之则不行。这是允许的,因为一个 val 属性本质 上声明了一个getter方法,而将其覆盖为 var 只是在子类中额外声明一个 setter方法

open class AnotherDerived() : Base() { 
    final override fun v() { ... } 
}

派生类初始化顺序

注意

在构造派生类的新实例的过程中,第一步完成其基类的初始化(在之前只有对基类构造函数参数的求值),因此发生在派生类的初始化逻辑运行之前。

这意味着,基类构造函数执⾏时,派生类中声明或覆盖的属性都还没有初始化。如果在基类初始化逻辑 中(直接或通过另⼀个覆盖的 open 成员的实现间接)使用了任何一个这种属性,那么都可能导致不正确 的行为或运行时故障。设计一个基类时,应该避免在构造函数、属性初始化器以及 init 块中使用open 成员

内部类

内部类调用外部类的父类方法

class Bar : Foo() { 
    override fun f() { /* …… */ } 
    override val x: Int get() = 0 

    inner class Baz { 
        fun g() { 
            super@Bar.f() // 调用 Foo 实现的 f() 
            println(super@Bar.x) // 使用 Foo 实现的 x 的 getter 
        }
    }
}

嵌套类

class Outer { 
    private val bar:Int = 1 
    class Nested { 
        fun foo() = 2
    } 
    val demo = Outer.Nested().foo() // == 2
}

接口类(当父类和接口类中出现同名的函数)

open class A { 
    open fun f() {
        print("A")
    }

    fun a() {
        print("a")
    } 
} 

interface B { 
    fun f() {
        print("B")
    } // 接口成员默认就是“open”的 

    fun b() { 
        print("b")
    } 
} 

class C() : A(), B { // 编译器要求覆盖 f(): 

override fun f() { 
    super<A>.f() // 调用 A.f() 
    super<B>.f() // 调用 B.f() } }

注:当出现同名函数时,为了表示采用从哪个超类型继承的实现,我们使用由尖括号中超类型名限定的 super,如 super


interface Foo { 
    val count: Int 
} 

class Bar1(override val count: Int) : Foo //我们可以直接在构造函数里进行重写


class Bar2 : Foo { 
    override var count: Int = 0 
}

接口类的实例化
方式一:类继承

class 类名:IListener{
    //重写抽象方法
    override fun buildResult(isOK:Boolean) {    

    }
}

方式二:

class 类名{

    //创建一个对象,并继承
    val mListener = object:IListener{
          //重写抽象方法
        override fun buildResult(isOK:Boolean) {    

        }
    }

}

抽象类

类以及其中的某些成员可以声明为 abstract。抽象成员在本类中可以不用实现。需要注意的是,我们并不需要用open 标注一个抽象类或者函数,因为这不言而喻。

我们可以用一个抽象成员覆盖一个非抽象的开放成员:

open class Base { 
    open fun f() {} 
} 

abstract class Derived : Base() {
    override abstract fun f() 
}

注:基本用法和Java一致

伴生对象

和Java不同,Kotlin中没有静态方法(也没有static关键字)
但:在 JVM 平台,如果使用 @JvmStatic 注解,你可以将伴生对象的成员生成为真正的静态方法和字段。

class People { 
    companion object Name { //这里的Name是名字,也可以忽略不写
        fun speak(){"你好"}
        var name:String = "李银河"
    } 
}

调用伴生对象的方式和Java调用静态方法、静态对象的方式相似

People.speak();
People.name = "王小波"

请注意,即使伴生对象的成员看起来像其他语言的静态成员,在运行时他们仍然是真实对象的实例成员,甚至还可以继承接口。


创建一个结构式的对象(常常用来存储读取的数据)

任何时候,如果我们只需要“一个对象而已”,并不需要特殊超类型,那么我们可以简单地写:

fun foo() { 
    val adHoc = object { 
        var x: Int = 0 
        var y: Int = 0 
    } 

    print(adHoc.x + adHoc.y)
}

可见性

对于类内部声明的成员:

  • private 意味着只在这个类内部(包含其所有成员)可见;
  • protected:和 private 一样 + 在子类中可见。
  • internal :能见到类声明的 本模块内 的任何客户端都可见其 internal 成员;
  • public :能见到类声明的任何客户端都可见其 public 成员。

注意 对于Java用户:Kotlin 中外部类不能访问内部类的 private 成员。

拓展函数

我们可以在不继承且不改动原类的情况下,为一个类添加一个函数。
称之为:拓展函数

fun main(args: Array<String>) {    

    //为BadMan这个类添加一个函数:quite()
    fun BadMan.quite(){        
         println("哈哈哈哈哈哈哈${this.name}")    
    }    

    var bad = BadMan("张三",23)    
    bad.quite()
}
  • 如果一个类定义有一个成员函数与一个扩展函数,且这两个函数都有相同的接收者类型、相同的名字, 都适用给定的参数,这种情况总是取成员函数。例如:
class C { 
    fun foo() { 
        println("member") 
    } 
} 

fun C.foo() { 
    println("extension")
}

如果我们调用C.foo(),那么只会输出“member”

将方法作为参数进行传参(双冒号的用法)

实际上这种用法更多用在替代接口回调上

用法如下:

fun main() {
    val downloadService = DownLoadService()

    val url = "www.download.image"

    downloadService.start(url){result,path->
        if (result){
            println("已下载图片,保存地址$path")
        }else{
            println("下载失败")
        }
    }

}
class DownLoadService{

    fun start(url:String,listener:(Boolean,String)->Unit){

        val path = "本地保存地址"
        val result  = doDownload(url)
        listener(result,path) //进行回调
    }

    private fun doDownload(url: String): Boolean {
        //假装执行,并成功下载图片
        return true
    }
}

注:当然,如果要用回Java时的Interface,来回调数据,也是可以的啦。

以上的优点在于,避免了频繁创建Interface类来传递数据,及由于接口造成相关冗长代码

我们也直接修改为:

fun main() {
    val downloadService = DownLoadService()
    val url = "www.download.image"
    downloadService.start(url,::callBack)
    //如果callBack不是全局方法,而是DownLoadService的方法,那就要改为downloadService::callBack
}

fun callBack(result:Boolean,path:String){
    if (result){
        println("已下载图片,保存地址$path")
    }else{
        println("下载失败")
    }
}
class DownLoadService{
    fun start(url:String, listener: (result:Boolean,path:String)->Unit){
        val path = "本地保存地址"
        val result= doDownload(url)
        listener(result,path) //进行回调
    }
    private fun doDownload(url: String): Boolean {
        //假装执行,并成功下载图片
        return true
    }
}