Kotlin学习指南—类和对象

类和对象

1. 类的构造

类及初始化举例:

主构造函数、次构造函数、init代码块

class AnimalMainSecConstructor constructor(val context: Context, val name: String){
    init {
        println("init")
        context.toast("$name")
    }
	//调用次级构造函数声明类的实例时,必须调用主构造函数
    constructor(context: Context, name: String, sex: Int):this(context, name){
        println("second constructor")
        val sexName:String = if(sex==0) "male" else "female"
        context.toast("this $name is $sexName")
    }
}

只有次级构造函数

class AnimalMainOnlySecConstructor {
   //含有默认参数
    constructor(context: Context, name:String = "AnimalMain"){
        println("sub instructor 1")
        context.toast("anmimal name $name")
    }
    //含有默认参数
    constructor(context: Context, name:String = "AnimalMain", sex:Int = 0){
        println("sub instructor 2")
        val sexName:String = if(sex==0) "male" else "female"
        context.toast("animal name $name, sex is $sexName")
    }
}

说明:

  1. Kotlin可以有主次构造函数, 主构造函数不是必须的。
  2. 如果既有主构造函数,又有次构造函数,可以使用主构造函数声明该类的实例,也可以使用次构造函数声明该类的实例, 使用次构造函数声明的话必须先调用主构造函数,再调用次构造函数代码块。
  3. 主构造函数初始化在init代码块中进行,类中如果init代码块同声明的变量交替出现,则都可以使用主构造函数传递的参数,初始化按照顺序执行。
  4. 如果存在主构造函数,主构造函数前没有注解(如 @JvmOverloads),那么constructor关键字可以省略。
  5. 构造函数的参数可以有默认参数,这样可以极大简化类的构造, 注意Java不支持默认构造函数,如果需要在Java中调用该构造函数,则需要在该构造函数前面(constructor前面)添加@JvmOverloads注解, 否则只能调用不含默认参数的完整构造函数进行实例声明。

2. 类的成员

2.1 成员属性

class AnimalMainSimple constructor(var context: Context, val name:String = "AnimalMain"val sex:Int = 0){
//非空的成员属性必须在声明时赋值或者在构造函数中赋值
//否则编译器会报错"Property must be initialized be abstract"
var sexName:String
    init {
        context.toast("AnimalMain Init, name $name")
        sexName = if(sex==0) "公" else "母"
    }
}

调用:

var animal =  WildAnimalMember(animalName, animalSex)
 println( "这只${animal.name}${animal.sexName}的")

说明:

  1. 输入参数使用valvar 关键字,可以在编译时自动在类中声明对应的变量。
  2. 在类中声明声明的变量必须在初始化时赋值,否则编译会报错。

2.2 成员方法

class AnimalMainSimple constructor(var context: Context, val name:String = "AnimalMain"val sex:Int = 0){
var sexName:String
    init {
        context.toast("AnimalMain Init, name $name")
        sexName = if(sex==0) "公" else "母"
    }
fun getDesc(tag:String):String {
 return "欢迎来到$tag: 这只${name}${sexName}的。"
 }
    
}

调用:

var animal =  WildAnimalMember(animalName, animalSex)
 println( "描述信息是${animal.getDesc("动物园")}")

2.3 伴生对象

/静态变量/静态方法
伴生对象是的引入是为了Kotlin用于声明静态成员, 可以在伴生对象中声明静态变量和静态方法

class WildAnimalCompanion(var name:String, val sex:Int = 0) {
    var sexName:String
    init {
        sexName = if (sex==0) "male" else "female"
    }
    
    fun getDesc(tag:String):String {
        return "欢迎来到$tag: 这是${name}${sexName}的。"
    }
    
    //在类加载时就运行伴生对象的代码块,其作用相当与Java中的static{}代码块
    //关键字companion表示伴随,object表示对象, WildAnimal表示伴生对象的名称
    companion object WildAnimal{
       //静态常量的值不可变的,所以要使用关键字val修饰
        val MALE = 0
        val FEMALE = 1
        val UNKNOWN = -1
        
        fun judgeSex(sexName:String): Int{
            var sex:Int = when(sexName){
                "male" -> MALE//0
                "female" -> FEMALE//1
                else -> UNKNOWN//-1
            }
            return sex
        }
    }
}

调用:

WildAnimalCompanion.WildAnimal.judgeSex("male") //正常调用
WildAnimalCompanion.judgeSex("male") //伴生对象名称可以省略,有点静态调用的意思

2.4 总结

Kotlin类成员分为实例成员和静态成员两种。实例成员包括成员属性和成员方法,其中与入参同名的成员属性可以在构造函数中直接声明,外部必须通过类的实例才能访问类的成员属性和成员方法。类的静态成员包括静态属性与静态方法,它们在类的伴生对象中定义,外部可以通过类名直接访问该类的静态成员。

3. 类的继承

3.1 开放性修饰符

Kotlin类默认是final的,不能被继承, 方法默认也是final,默认不能Override, 如果希望类被继承或者方法可以被覆写,则必须使用open修饰符。

open class Bird (var name:String, val sex:Int = 0) {
 //
}

Kotlin开放修饰符取值说明

开放修饰符说明
public对所有人开放。Kotlin的类、函数、变量不加开放性修饰符的话,默认是public类型
internal只对本模块内部开放,这是Kotlin新增关键字,对于APP开发来说,本模块便是App本身
protected只对自己和子类开放
private只对自己开放,即私有

说明:

  1. open只限制该类能否继承或者方法能否覆写,而开放性修饰符用于控制该成员的访问权限
  2. private和open修饰符不能并存

3.2 普通类继承

按照上一节说明,我们来声明一个类及其子类

下面声明Bird类

//Kotlin的类默认是final,如果需要继承,父类必须声明为open
//否则编译器报错“The type is final, so it cannot be inherited from”
open class Bird (var name:String, val sex:Int = MALE) {
 //变量、方法、类默认是public,可以省略
 //public var sexName:String
 var sexName:String
 init {
 sexName = getSexName(sex)
 }
 //私有的方法既不能被外部访问,也不能被子类继承,因此open和private不能共存
 //否则编译器会报错:Modifier 'open' is incompatiable with ‘private’
 //open private fun getSexName(sex:Int):String {
 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类


//注意父类Bird已经在构造函数声明了属性,故而子类Duck无须重复声明属性
//也就是说,子类的构造函数在输入参数前面不需要再加val和var
class Duck(name:String="Duck", sex:Int = Bird.MALE) : Bird(name, sex) {
}

定义鸵鸟类

class Ostrich(name:String="鸵鸟", sex:Int = Bird.MALE) : Bird(name, sex) {
 //继承protected方法,标准写法是“override protected”
 //override protected fun getSexName(sex:Int):String {
 //protected默认继承过来默认是protected, 可以省略protected
 //override fun getSexName(sex:Int):String {
 //protected方法继承过来之后只允许将可见性升级为public,但不能降级为private
 override public fun getSexName(sex:Int):String {
 return if(sex==MALE) "雄" else "雌"
 }
}

3.3 抽象类

说明:

  1. 使用abstract修饰,内部有abstract修饰的抽象方法, 无法实例化, 需要子类继承时重写抽象方法,方可使用子类实例化对象。
  2. abstract修饰的方法默认是open类型的

抽象类代码示例:

//定义抽象鸡类
abstract class Chicken(name:String, sex:Int, var voice:String):Bird(name, sex) {
    val numberArr:Array<String> = arrayOf("1", "2", "3","4","5", "6","7","8","9","10", "11")

    //抽象方法必须在子类进行重写,所以可以省略关键字open,因为abstract方法默认是open类型
    //open abstract fun callOut(time:Int):String
    abstract fun callOut(time:Int):String
}
//定义公鸡类
class Cock(name: String = "鸡",sex: Int = Bird.MALE, voice: String = "wowowo"):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 叫了${numberArr[count]}声,原来它是在报晓。"
    }
}
//定义母鸡类
class Hen(name: String = "鸡",sex: Int = Bird.FEMALE, voice: String = "gegege"):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 叫了${numberArr[count]}声,原来它是在下蛋。"
    }
}

fun myChickenTest(){
	//公鸡叫
    Cock().callOut(15)
    //母鸡叫
    Hen().callOut(13)
}

3.4 接口

说明:

  1. 间接实现多重继承;
  2. 接口不能定义构造函数;
  3. 接口内部方法通常要被实现它的类进行重写,所以这些方法默认是抽象的;
  4. kotlin接口内部允许实现某个方法;
  5. 可以声明没有初始化的变量,但是接口被实现时必须对该变量进行覆写赋值。

代码举例:
定义 Behavior 接口

interface Behavior {

    //Kotlin与Java一样不允许多多重继承,即不能同时继承两个及两个以上类
//否则编译器报错"Only one class may appear in a supertype list"
//所以仍然需要接口interface来间接实现多重继承功能
// 接口不能带构造函数, 否则编译器报错 "An interface may not have a constructor"
    //interface Behavior(val action:String) {
        //接口内部方法默认是抽象的,可以不加abstract和open
        open abstract fun fly():String
        //比如下面这个swim方法没加关键字abstract,也无需在此处实现方法
        fun swim():String
        //Kotlin接口和Java区别在于,Kotlin接口内部允许实现方法
        //此时该方法不是抽象方法,就不能加abstract
        //不过该方法依然是open类型,接口内部所有方法默认是open类型
        fun run():String {
            return "大多数鸟类不擅长跑,只有鸵鸟才擅长跑。"
        }
        //Kotlin的接口允许声明抽象属性,实现该接口的类必须重载该属性
        //与接口内部方法一样,抽象属性前面的open和abstract也可以省略
        //open abstract var skilledSports:String
        var skilledSports:String
}

定义Goose继承Bird类并实现Behavior接口

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 = "游泳"
}

Goose实例化

fun test(){
    var goose = Goose()
    goose.fly()
    goose.swim()
    goose.run()
}

4. 特殊类

4.1 嵌套类

类似于Java中的静态内部类, 嵌套类前面无修饰符,无法访问外部类
嵌套类举例:

class Tree(var treeName: String){
	class Flower(var flowerName: String) {
		 fun getName() :String {
		return "flowerName = $flowerName"
		//不能访问外部类成员,如treeName, 编译器报错“Unresolved reference:***”
		//return “this treename ${treeName} has a flower $flowerName”
	}
}
}

使用:

val peachBlossom = Tree.Flower("桃花")
val name = peachBlossom.getName()

4.2 内部类

内部类前面有修饰符inner

内部类举例:

class Tree(var treeName: String){
	inner class Flower(var flowerName: String) {
		 fun getName() :String {
		//只有声明为内部类(关键字inner),才能访问外部类的成员
		return "this treename $treeName has a flower $flowerName"
	}
}
}

使用:

val peach = Tree("Peach Tree").Flower("Peach flower")
val name = peach.getName()

4.3 枚举类

和Java枚举类很像
枚举类举例:

//无构造函数
enum class SeasonType {
    SPRING, SUMMER,AUTUMN, WINTER
}

//有构造函数
enum class SeasonName(val seasonName: String) {
	SPRING("春天"), 
	SUMMER("夏天"),
	AUTUMN("秋天"), 
	WINTER("冬天")
}

枚举类使用

btn_class_name.setOnClickListener {
	var seasonName: String;
	if(count%2 == 0) {
	    seasonName = when (count++%4) {
			SeasonType.SPRING.ordinal -> SeasonType.SPRING.name
			SeasonType.SUMMER.ordinal -> SeasonType.SUMMER.name
			SeasonType.AUTUMN.ordinal -> SeasonType.AUTUMN.name
			SeasonType.WINTER.ordinal -> SeasonType.WINTER.name
	}
	} else {
		seasonName = when (count++%4) {
			SeasonName.SPRING.ordinal -> SeasonName.SPRING.seasonName
			SeasonName.SUMMER.ordinal -> SeasonName.SUMMER.seasonName
			SeasonName.AUTUMN.ordinal -> SeasonName.AUTUMN.seasonName
			SeasonName.WINTER.ordinal -> SeasonName.WINTER.seasonName
		}
	}

}

4.4 密封类

密封类是为解决枚举值判断多余分支的问题,像一种更加严格的枚举类,其内部有且仅有自身的实例对象,所以是一个有限的自身实例的集合。
密封类举例:

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()
}

密封类使用

btn_class_sealed.setOnClickListener {
   var season = when(count++%4) {
   	 0 -> SeasonSealed.Spring("Spring")
   	 1 -> SeasonSealed.Summer("Summer")
   	 2 -> SeasonSealed.Autumn("Autumn")
   	 else -> SeasonSealed.Winter("Winter")
}
tv_class_name.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 数据类

简化了Bean的创建,和Java相比省去许多模板代码, 数据类自带copy、toString、equals等方法
数据类举例:

data class Plant(var name:String, var stem:String, var leaf:String, var flower:String, var seed:String){
//如果类里面没有代码块,大括号可以省略
}

数据类对象创建及使用

var lotus = Plant("植物1""植物2""植物3""植物4""植物5")
var lotus2 = lotus.copy()

4.6模板类

如HashMap、ArrayList, 创建实例时才确定类型

泛型类举例:

//类定义
class Box<T>(t: T) {
        var value = t
    }
//实例化
  val box: Box<Int> = Box<Int>(1) // 或者
  val box1 = Box(1) // 编译器会进行类型推断,1 类型 Int,所以编译器知道我们说的是 Box<Int>  
  var boxString = Box<String>("Runoob")  

5.参考

官方文档翻译
https://hltj.gitbooks.io/kotlin-reference-chinese/content/txt/classes.html

Kotlin for android Developer
https://wangjiegulu.gitbooks.io/kotlin-for-android-developers-zh/content/lei_ji_cheng.html

Android官方的Kotlin页
https://developer.android.google.cn/kotlin

菜鸟教程-Kotlin
https://www.runoob.com/kotlin/kotlin-tutorial.html

使用Kotlin构建Android MVVM应用程序
https://www.jianshu.com/p/77e42aebd7bb

参考教材
《Kotlin从零到精通Android开发》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Calvin880828

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值