1.类的构造

1.1类的简单定义

//java
public class MainActivity extends AppCompatActivity {
    ... 
}

//Kotlin
class MainActivity : AppCompatActivity() {
    ... 
}

Kotlin对类的写法有以下特点:

<1>省略了关键字public,缘于它默认就是开放的;

<2>用冒号“:”代替extends,表示继承关系;

<3>进行继承时,父类后面多了括号()

class Animal {
    //类的初始化函数init
    init {
        //Kotlin使用println代替Java的System.out.println
        println("Animal:这是个动物类")
    }
}

var animal = Animal()//类的实例创建,忽略new关键字

1.2 类的构造函数

//如果主构函数没有带@符号的注解说明,constructor就可以省略
//class AnimalMain (context:Context, name:String) {
class AnimalMain constructor(context:Context, name:String) {
    init {
        context.toast("这是只$name")
    }
}
//含二级构造函数
class AnimalMain constructor(context:Context, name:String) {
    init {
        context.toast("这是只$name")
    }
    constructor(context:Context, name:String, sex:Int) : this(context,
    name) {
    var sexName:String = if(sex==0) "公" else "母"
    context.toast("这只${name}是${sexName}的")
    }
}

<1>二级构造函数没有函数名称,只用关键字constructor表示这是一个构造函数;

<2>二级构造函数需要调用主构函数。

//构造函数调用
btn_class_main.setOnClickListener {
    setAnimalInfo()
    when (count%2) {
        0 -> { var animal = AnimalMain(this, animalName) }
        else -> { var animal = AnimalMain(this, animalName, animalSex) }
    }
}

上述二级构造函数存在一个问题,会调用到主构函数的toast,为了只调用对应的构造函数,改写成如下形式:

class AnimalSeparate {
    constructor(context:Context, name:String) {
        context.toast("这是只$name")
    }
    constructor(context: Context, name:String, sex:Int) {
        var sexName:String = if(sex==0) "公" else "母"
        context.toast("这只${name}是${sexName}的")
    }
}

1.3 带默认参数的构造函数

class AnimalDefault (context: Context, name:String, sex:Int = 0) {
    init {
        var sexName:String = if(sex==0) "公" else "母"
        context.toast("这只${name}是${sexName}的")
    }
}

var animal = AnimalDefault(this, animalName)//调用,默认sex为“公”

注:上述类如果想要Java类识别默认参数,需要加入“@JvmOverloads”注解:

//因为加入了注解标志@,需要补上constructior关键字
class AnimalDefault @JvmOverloads constructor(context: Context, name:St
ring, sex:Int = 0) {
    init {
        var sexName:String = if(sex==0) "公" else "母"
        context.toast("这只${name}是${sexName}的")
    }
}

AnimalDefault animal = new AnimalDefault(this, animalName);//java 代码调用

2.类的成员

2.1成员属性

class WildAnimal (var name:String, val sex) {
}

animal.name //相当于Java的get方法

成员变量需要var或val声明,调用只需对象.成员变量即可。

2.2 成员方法

class WildAnimalFunction (var name:String, val sex:Int = 0) {
    var sexName:String
    init {
        sexName = if(sex==0) "公" else "母"
    }

    //成员方法
    fun getDesc(tag:String):String {
        return "欢迎来到$tag:这只${name}是${sexName}的"
    }
}

animal.getDesc("动物园")//调用

2.3 伴生对象

class WildAnimalCompanion (var name:String, val sex:Int = 0) {
    ...
    //在类加载时就运行伴生对象的代码块,其作用相当于Java里面的static{...}代码块
    //在关键字companion表示伴随,object表示对象
    companion object WildAnimal{
        fun judgeSex(sexName:String):Int {
            var sex:Int = when (sexName) {
                "公","雄" -> 0
                "母","雌" -> 1
                else -> -1
            }
            return sex
        }
    }
}

WildAnimalCompanion.WildAnimal.judgeSex(sexName)//调用
WildAnimalCompanion.judgeSex(sexName)//WildAnimal可省略

2.4 静态属性

companion object WildAnimal{
    
    //静态常量的值是不可变的,所以要使用关键字val修饰
    val MALE = 0
    val FEMALE = 1
    val UNKNOWN = -1
    fun judgeSex(sexName:String):Int {
        var sex:Int = when (sexName) {
            "公","雄" -> MALE
            "母","雌" -> FEMALE
            else -> UNKNOWN
        }
    return sex
    }
}

WildAnimalCompanion.MALE//调用

3 类的继承

3.1开放性修饰词

开放性修饰词

说明

public

对所有人开放。Kotlin的类、函数、变量不加开放性修饰词的话,默认就是public类型

internal

只对本模块开放。这是Kotlin新增的关键字,对于app开发来说,本模块便指app自身

protected

只对自己和子类开放

privated

只对自己开放,即私有

 

3.2类的继承

Kotlin的类默认是不能继承的(即final类型),如果需要继承某类,该父类就应当声明为open类型。

open class Bird (var name:String, val sex:Int = MALE) {
    
    //需要被继承的方法也需要加上open
    open protected fun getSexName(sex:Int):String {
        return if(sex==MALE) "公" else "母"
    }
    
    fun getDesc(tag:String):String {
        return "欢迎来到$tag这只${name}是${sexName}的"
    }

    companion object BirdStatic{
        val MALE = 0
        val FEMALE = 1
        val UNKNOWN = -1
        fun judgeSex(sexName:String):Int {
            var sex:Int = when (sexName) {
                "公","雄" -> MALE
                "母","雌" -> FEMALE
                else -> UNKNOWN
            }
        return sex
        }
    }
}


//继承Bird的子类Duck
class Duck(name:String="鸭子", sex:Int = Bird.MALE) : Bird(name, sex) {
}

//activity调用
btn_class_duck.setOnClickListener {
    var sexBird = if (count++%3==0) Bird.MALE else Bird.FEMALE
    var duck = Duck(sex=sexBird)
    tv_class_inherit.text = duck.getDesc("鸟语林")
}

 继承父类protected方法,标准写法是“override protected”,然而protected可忽略。示例

class Ostrich(name:String="鸵鸟", sex:Int = Bird.MALE) : Bird(name, sex) {
    
    //protected的方法继承后可见性可升级为public,但不能降级为private
    override public fun getSexName(sex:Int):String {
        return if(sex==MALE) "雌" else "雄"
    }
    
}

3.3 抽象类

abstract class Chicken(name:String, sex:Int, var voice:String) : Bird(n
ame, sex) {
    ...
    abstract fun callOut(times:Int):String
}

子类的构造函数,原来的输入参数不用加var或val,新增的输入参数必须加入var或val,因为抽象类不能直接使用,所以构造函数不必给默认参数赋值,抽象方法必须在子类重写。

class Cock(name:String="鸡", sex:Int = Bird.MALE, voice:String="喔喔喔") : Chicken(name, sex, voice) {
    override fun callOut(times: Int): String {
        var count = when {
            times<=0 -> 0
            times>=10 -> 9
            else -> times
        } 
        return "$sexName$name${voice}叫了${numberArray[count]}声,原来它在报晓。"   
    }
}

tv_class_inherit.text = Cock().callOut(count++%10)//activity调用

3.4 接口

//接口不能带构造函数
interface Behavior {
    
    //接口内部的方法默认就是抽象的,open和abstract关键字可忽略
    open abstract fun fly():String
    fun swim():String
    
    //kotlin接口与Java接口不一样,允许实现方法
    //该方法默认是open类型,接口的方法默认都是open类型
    fun run():String {
        return "大多数鸟儿跑得并不像样"
    }

     //Kotlin的接口允许声明抽象属性,实现该接口的类必须重载该属性
     //与接口内部方法一样,抽象属性前面的open和abstract也可以忽略
    var skilledSports:String
}
class Goose(name:String="鹅", sex:Int = Bird.MALE) : Bird(name, sex), Behavior {

    override fun fly():String {
        return "鹅能飞一点点,但飞不高,飞不远"
    }    
    
    override fun swim():String {
        return "鹅,鹅,鹅,曲项向天歌,白毛浮绿水,红掌拨清波"
    }
    
    //由于接口实现了run方法,所以此处可以不用实现该方法,当然也可以实现
    override fun run():String {
        //super用来调用父类的属性或方法,由于kotlin的接口允许实现方法,因此super所指的对象也可以是interface
        return super.run()
    }
    
    //重载接口的抽象属性
    override var skilledSports:String = "游泳"
}

其他类实现接口时,跟类继承一样把接口名称放在冒号后面,如果存在2个以上的接口或者既有父类也有接口,此时中间用逗号隔开。

btn_interface_behavior.setOnClickListener {
    tv_class_inherit.text = when (count++%3) {
        0 -> Goose().fly()
        1 -> Goose().swim()
        else -> Goose().run()
    }
}

3.5 接口代理

一个类先声明继承自某个接口,但并不直接实现该接口的方法,而是把已经实现该接口的代理类作为参数传给前面的类,相当于告诉前面的类:“该接口的方法我已经代替你实现,你直接拿去用便是。” 这样做的好处是,输入参数可以按照具体的业务场景传送相应的代理类。

示例:

//飞禽的行为类
class BehaviorFly : Behavior {
    override fun fly():String {
        return "翱翔天空"
    }
    override fun swim():String {
        return "落水凤凰不如鸡"
    }
    override fun run():String {
        return "能飞干嘛还要走"
    }
    override var skilledSports:String = "飞翔"
}

//水禽的行为类
class BehaviorSwim : Behavior {

    override fun fly():String {
        return "看情况,大雁能展翅高飞,企鹅却欲飞还休"
    }

    override fun swim():String {
        return "怡然戏水"
    }

    override fun run():String {
        return "赶鸭子上树"
    }

    override var skilledSports:String = "游泳"
}

//走禽的行为类
class BehaviorRun : Behavior {

    override fun fly():String {
        return "飞不起来"
    }

    override fun swim():String {
        return "望洋兴叹"
    }

    override fun run():String {
        return super.run()
    }

    override var skilledSports:String = "奔跑"
}

接着定义一个引用了代理类的野禽基类,通过关键字by表示该接口将由入参中的代理类实现。

//如果by的对象是个类,将编译报错
class WildFowl(name:String, sex:Int=MALE, behavior:Behavior) : Bird(name, sex), Behavior by behavior {
}
btn_delegate_behavior.setOnClickListener {
    var fowl = when (count++%6) {
        0 -> WildFowl("老鹰", Bird.MALE, BehaviorFly())
        1 -> WildFowl("凤凰", behavior=BehaviorFly())
        2 -> WildFowl("大雁", Bird.FEMALE, BehaviorSwim())
        3 -> WildFowl("企鹅", behavior=BehaviorSwim())
        4 -> WildFowl("鸵鸟", Bird.MALE, BehaviorRun())
        else -> WildFowl("燕子", behavior=BehaviorRun())
    }

    var action = when (count%11) {
        in 0..3 -> fowl.fly()
        4,7,10 -> fowl.swim()
        else -> fowl.run()
    }

    tv_class_inherit.text = "${fowl.name}$action"
}

4 几种特殊类

4.1嵌套类

在类的内部定义新类,这个新类叫作内部类。

class Tree(var treeName:String) {
    class Flower (var flowerName:String) {
        fun getName():String {
            return "这是一朵$flowerName"
            //普通的嵌套类不能访问外部类的成员,如treeName
        }
       
    }
}

btn_class_nest.setOnClickListener {

    val peachBlossom = Tree.Flower("桃花");
    tv_class_secret.text = peachBlossom.getName()

}

4.2 内部类

嵌套类加上inner前缀,就成为内部类

class Tree(var treeName:String) {

    ...

    inner class Fruit (var fruitName:String) {
        fun getName():String {
            
            //只有声明了内部类,才能访问外部类的成员
            return "这是${treeName}长出来的$fruitName"
        }
    }

}

4.3 枚举类

将关键字enum 修饰class就变成枚举类

enum class SeasonType {
    SPRING, SUMMER, AUTUMN, WINTER
}

枚举类属性

ordinal属性用于获取该枚举值的序号,name属性用于获取该枚举值的名称。

枚举类如果存在构造函数,枚举变量也必须调用对应的构造函数。

enum class SeasonName (val seasonName:String) {
    SPRING("春天"),
    SUMMER("夏天"),
    AUTUMN("秋天"),
    WINTER("冬天")
}

//调用
SeasonType.SPRING.ordinal
SeasonType.SPRING.name

SeasonName.SPRING.seasonName//调用构造函数的名称

4.4 密封类

密封类像是一种更严格的枚举类,它内部有且仅有的实例对象,所以是一个有限的自身实例集合。需要在该类的class前面添加sealed作为标志

sealed class SeasonSealed {
    //密封类内部的每个嵌套类都必须继承该类
    class Spring (var name:String) : SeasonSealed()
    class Summer (var name:String) : SeasonSealed()
    class Autumn (var name:String) : SeasonSealed()
    class Winter (var name:String) : SeasonSealed()
}

SeasonSealed.Spring("春天")

密封类确保条件分支覆盖了所有的枚举类型,因此不再需要else分支

tv_class_secret.text = when (season) {
    is SeasonSealed.Spring -> season.name
    is SeasonSealed.Summer -> season.name
    is SeasonSealed.Autumn -> season.name
    is SeasonSealed.Winter -> season.name
}

4.5 数据类

在class面前添加修饰词data,并声明拥有完整输入参数的构造函数,那么该类称为数据类。数据类有以下功能:

<1>自动声明与构造函数入参同名的属性字段;
<2>自动实现每个属性字段的get/set方法;
<3>自动提供equals方法,用于比较两个数据对象是否相等;
<4>自动提供copy()方法,允许完整复制某个数据对象,也可在复制后单独修改某几个字段的值;
<5>自动提供toString()方法,用于打印数据对象中保存的所有字段值

示例:

//数据类必须有主构函数,且至少有一个输入参数
//输入参数前要添加关键字var或val
//数据类不能是基类也不能是子类,不能是抽象类,也不是内部类,更不是密封类
data class Plant(var name:String, var stem:String, var leaf:String, var flower:String, var fruit:String, var seed:String) {
}
var lotus = Plant("莲", "莲藕", "莲叶", "莲花", "莲蓬", "莲子")
//数据类的copy()方法不带参数,表示复制一模一样的对象
var lotus2 = lotus.copy()
btn_class_data.setOnClickListener {
    lotus2 = when (count++%2) {
        
        0 -> lotus.copy(flower="􁞚􁜰")
        else -> lotus.copy(flower="􁞰􁜰")
    }
    
    var result = if (lotus2.equals(lotus)) "相等" else "不等"
    
}

4.6 模板类

举例说明

//在类名后面添加“<T>”,表示这是一个模板类
class River<T> (var name:String, var length:T) {
    fun getInfo():String {
        var unit:String = when (length) {
        is String -> "􁔂"
        //Int,Long,Float,Double都是数字类型Number
        is Number -> "m"
        else -> ""
    }
    return "${name}的长度是$length$unit。"
    }
}
btn_class_generic.setOnClickListener {
    var river = when (count++%4) {
        
        //模板类(泛型类)声明对象时,要在模板类的类名后面加上“<参数类型>”
        0 -> River<Int>("小溪", 100)
        //如果编译器根据输入参数就能知晓参数类型,也可直接忽略“<参数类型>”
        1 -> River("瀑布", 99.9f)
        ....
    }
    ...
}

备注:第1到第4篇kotlin基础知识汇总全部摘自《Kotlin从零到精通(Android开发)》一书。