《Kotlin从零到精通Android开发》欧阳燊(一)

1.开发环境,2.数据类型,3.控制语句,4.函数运用,5.类和对象

第一章 搭建Kotlin开发环境

1.5.1 Kotlin代码和Java代码PK

可以把Kotlin看做是Java的升级版,不但完全兼容Java,而且极大的精简了语法,让开发者专注于业务逻辑代码,无须关心代码框架,若想充分运用Kotlin的优异特性,除了导入Kotlin的核心库,还得导入Kotlin的扩展库和anko库
Kotlin的优势
1.控件

tv_test.setText("张飞");可以简化为tv_test.text="张飞"

2.监听

btn_toast.setOnClickListener {
      toast("点击事件") }
btn_toast_long.setOnLongClickListener {
      longToast(" 长按事件并longtoast"); true }

1.5.2 Anko库

1.5.3 Lambda表达式

Lambda表达式其实是一个匿名函数
Java是从1.8开始支持Lambda表达式,所以Java代码只能从1.8开始使用。但是Kotlin一诞生就支持Lambda表达式,所以不在乎JDK版本是1.7还是1.8




第二章 数据类型

2.1 简单变量之间的转换

toInt,toLong toString

2.2 数组变量的声明

分配一个常量数组:

var long_array:LongArray = longArrayOf(1, 2, 3)
var float_array:FloatArray = floatArrayOf(1.0f, 2.0f, 3.0f)
var double_array:DoubleArray = doubleArrayOf(1.0, 2.0, 3.0)
var boolean_array:BooleanArray = booleanArrayOf(true, false, true)
var char_array:CharArray = charArrayOf('a', 'b', 'c')var ins:IntArray = intArrayof(1,2,3

String数组的话比较特殊,如下:

var string_array:Array<String> = arrayOf("How", "Are", "You")

其他的数组类型也可以这样写

var int_array:Array<Int> = arrayOf(1, 2, 3)
var long_array:Array<Long> = arrayOf(1, 2, 3)
var float_array:Array<Float> = arrayOf(1.0f, 2.0f, 3.0f)
var double_array:Array<Double> = arrayOf(1.0, 2.0, 3.0)
var boolean_array:Array<Boolean> = arrayOf(true, false, true)
var char_array:Array<Char> = arrayOf('a', 'b', 'c')

数组元素的操作
string_array[i]和 string_array.get(i) 都可以获取元素

2.3 字符串

字符串转换为其他类型:toInt,toDouble
获取字符串指定位置的元素:

tv_convert.text = origin[number].toString()
tv_convert.text = origin.get(number).toString()

字符串模板:${origin.length}

2.4 容器

容器默认是只读容器,如果需要允许修改容器,需要加上Mutable前缀,如MutableSet,MutableList,MutableMap,只有可变容器才能进行增删改
《Kotlin从零到精通Android开发》欧阳燊(一)_第1张图片

Kotlin中的3个循环方式:for in,迭代器,forEach
无论是for in还是迭代器,写法都不够简洁,所以Kotlin创造了forEach采用匿名函数的形式,it代表每个元素

Set/MutableSet(集合)

集合是一种最简单的容器
1.容器内部的元素按顺序排列,因此无法按照下标访问
2.容器内部元素不重复,通过hash值校验是否存在相同的元素,若存在则覆盖它

Set/MutableSet有以下不足:
1.集合不允许修改内部元素的值
2.集合无法删除指定位置的元素
3.不能通过下标获取指定位置的元素
所以实际开发基本用不到集合,而是用队列(List)和映射(Map)

List/MutableList(队列)

有序排列的容器,
1.可以通过下标和get方法获取元素
2.add方法默认在队列末尾,也可以指定位置
3.set方法替换或修改指定位置元素
4.removeAt删除指定位置元素
5.除3种遍历方式外多了一种元素下标遍历的方式
sortBy和sortByDescending可以升序和降序排列

Map/MutableMap(映射)

1.constainsKey和constainsValue判断是否存在相同的key和value
2.put方法添加或修改元素
3.remove通过key删除元素

//to方式初始化
var goodsMap: Map<String, String> = mapOf(" key " to "value")
//Pair方式初始化
var goodsMutMap: MutableMap<String, String> = mutableMapOf(Pair(" key", "value"))

4.3种遍历方式都支持




第三章 控制语句

3.1条件分支

简单分支

tv_answer.text = if (is_odd==true) “打他” else “不打他”
仿佛Java中的三元运算符:变量名= 条件语句?A:B,可是Kotlin并不提供这个三元运算符,使用上面的if else语句就实现了相同的功能,所以三元就取消了。

多路分支

when/else

when/else和switch/case的区别:
1.switch被when替代
2.case 常量值被 常量值->替代
3.break取消了,因为kotlin默认一个分支处理完了自动跳出语句
4.default被else替代

简化写法:

btn_when_value.setOnClickListener {
     
tv_answer.text = when (count) {
     
0 -> "张飞"
1 -> "刘备"
else -> "默认"
}

Java中的switch/case有个限制,case后面只能是常量,不能是变量,Kotlin的when/case则可以使用变量。
拓展多个相同条件

btn_when_region.setOnClickListener {
     
tv_answer.text = when (count) {
     
1,3,5,7,9 -> "张飞"
in 13..19 -> "刘备"
!in 6..10 -> "许褚"
else -> "默认"
}

类型判断

Java中判断变量是否是String类型
if(a instanceof String)
Kotlin 中则使用
if(a is String)代替
when/else也支持类型判断

tv_answer.text = when (countType) {
     
is Long -> "是Long类型"
is Double -> "是浮点类型"
else -> "默认"
}

3.2循环处理

遍历循环

for (int i=0; i

条件循环

// 左闭右开区间,合法值包括11不包括66
for (i in 11 until 66) { … }
// 默认递增1这里默认递增4
for (i in 23…89 step 4) { … }
// for循环默认递增downTo代表递减
for (i in 50 downTo 7) { … }

可是这些方法并不完美,业务需求是千变万化的,并非几种固定模式可以解决,所以while循环和Java处理是一致的

btn_repeat_begin.setOnClickListener {
     
var poem:String=""
var i:Int = 0
while (i < poemArray.size) {
     
if (i%2 ==0) {
     
poem = "$poem${
       poemArray[i]}?\n"
} else {
     
poem = "$poem${
       poemArray[i]}?\n"
}
i++
}
poem = "${
       poem}??????${
       i}??"
tv_poem_content.text = poem
}

跳出多重循环

while+continue+break
通过@标记直接跳出多重循环

btn_repeat_break.setOnClickListener {
     
var i:Int = 0
var is_found = false
//给外层循环加个outside的标记
outside@ while (i < poemArray.size) {
     
var j:Int = 0
var item = poemArray[i];
while ( j < item.length) {
     
if (item[j] == '?') {
     
is_found = true
//直接跳出outside循环
break@outside
}
j++
}
// 如果内层循环直接跳出两层循环,那么下面的判断就不需要了
// if (is_found)
// break
i++
}
tv_poem_content.text = if (is_found) "找到了" else "没找到"}

总结:
对于循环,Kotlin仍然保留for和while两种循环,主要区别是Kotlin取消了for(int i =0;i++;i<10)的规则,同时新增了跳出多重循环的支持:break@标记位

3.3空安全

字符串的有效性判断

Kotlin校验字符串空值的几个方法:
isNullOrEmpty : 为空指针或者字串长度为0时返回true,非空串与可空串均可调用。

isNullOrBlank : 为空指针或者字串长度为0或者全为空格时返回true,非空串与可空串均可调用。

isEmpty : 字串长度为0时返回true,只有非空串可调用。

isBlank : 字串长度为0或者全为空格时返回true,只有非空串可调用。

isNotEmpty : 字串长度大于0时返回true,只有非空串可调用。

isNotBlank : 字串长度大于0且不是全空格串时返回true,只有非空串可调用。

声明可空变量

Kotlin中默认声明的变量默认都是非空,可空则需要加?
非空和可空字符串
var strNotNull:String = “”
var strCanNull:String?

strNotNull允许调用全部6个方法
strCanNull只允许调用isNullOrEmpty和isNullOrBlank两个方法
所以Kotlin对可空串进行了编译检查,一旦发现可空串调用了非空方法,会提示语法错误

下面获取字符串的长度的判断

val strA:String = “非空”
val strB:String? = null
val strC:String? = “可空串”
strA可以直接调用:strA.length,对于strB和strC,必须进行非空判断,否则编译不通过

校验空值的运算符

?: 类似Java中的三元运算符
!!表示强行把该变量从可空类型转为非空类型,从而避免变量是否非空的校验,!!强行放弃了非空判断,开发者就得自己判断了。//!!表示 不做非空判断,如果变量为空,就是抛出异常,只有确保为非空时才能使用!!

btn_exclamation_two.setOnClickListener {
     
try {
     
length = strB!!.length
tv_check_result.text = "$length"
} catch(e:Exception) {
     
tv_check_result.text = "空指针异常"
}
}

总结

Kotlin引入了空安全的概念,并在编译时开展对象是否为空的校验。相关的操作符说明概括如下:

1、声明对象实例时,在类型名称后面加问号,表示该对象可以为空;

2、调用对象方法时,在实例名称后面加问号,表示一旦实例为空就返回null;

3、新引入运算符“?:”,一旦实例为空就返回该运算符右边的表达式;

4、新引入运算符“!!”,通知编译器不做非空校验,运行时一旦发现实例为空就扔出异常;

3.4等式判断

结构相等

Kotlin把字符串当做跟整型一样的基本数据类型,统一运算符
image.png
这种不比较存储地址,只比较变量结构内部值的行为,Kotlin称之为结构相等

引用相等

有时候结构相等并不是真的相等,判断相等需要一种由内而外的全部相等判断,该准则叫引用相等,意思是除了值相等,引用的地址也必须相等,=和!
引用相等校验的是变量的唯一性,结构相等校验的是变量的等值性

s和in

除了判断是否相等,还可以判断是否为某种类型,数组是否存在某个元素
运算符is和!is代替instansof
运算符in和!in ,变量名in数组名,判断该数组中是否存在此变量

3.5小结

本章可以学到
1.Kotlin的简单分支和多路分支
2.Kotlin的遍历循环和条件循环
3.可空变量,可空变量的处理
4.结构相等和引用相等,类型判断,数组存在判断




第四章 函数运用

4.1 函数的基本用法

kotlin默认函数就是public,省略了public
空安全机制,如果变量允许为空,需要在变量类型后面加?

Kotlin中的函数和变量的声明写法类似

var i:Int   
fun main():Int

功能定义var对fun,参数类型Int对Int,唯一的区别就是函数定义多了()以及()内部的入参,Kotlin设计师的初衷正是想把函数作为一个特殊的变量

Java使用void表示不存在返回参数,Kotlin的返回参数一定存在,即使不返回,也会默认返回一个Unit对象
//Unit类型表示没有返回参数,也可以直接省略Unit声明

fun getDinnerUnit():Unit {
     
tv_process.text = "张飞"
tv_result.text = ""
}

4.2 输入参数的变化

入参的默认值

fun getFourBigDefault(general:String, first:String=" 第一", second:String=" 第二 "):String {
     
var answer:String = "$general$first,$second"
return answer
}

命名参数

修改默认值

getFourBigDefault("第一个") 只修改第一个参数的值
getFourBigDefault(second="第二个")只修改第二个参数的值

使用了命名参数表达式:second=“第二个”,该表达式实现了给指定参数赋值的功能。

可变参数

Java中的可变参数是String… strs,在Kotlin中新增了vararg关键字代替

fun getFourBigVararg(general:String, first:String=" 第一", second:String=" 第二", vararg otherArray: String?):String {
     
var answer:String = "$general$first$second"
//循环取出可变参数数组
for (item in otherArray) {
     
answer = "$answer$item"
}
return answer
}

可变数组也是可以的 :vararg otherArray: Array

4.3几种特殊函数

泛型函数

定义泛型函数时,要在函数名称前面添加 <T>,入参也肯定包含<T>
fun <T> appendString(tag:String, vararg otherInfo: T?):String {
     
var str:String = "$tag:"

for (item in otherInfo) {
     
str = "$str${
       item.toString()},"
}
return str
}
调用
appendString<Int>("小于10的素数",2,3,5,7)
appendString<Double>("花钱的日子",5.20,6.18,11.11,12.12)

内联函数

//该函数既不接收Array也不接收Array
fun setArrayNumber(array:Array<Number>) {
     
var str:String = "数组排列"
for (item in array) {
     
str = str + item.toString() + ", "
}
tv_function_result.text = str
}
//只有内联函数才可以被具体化
inline fun <reified T : Number> setArrayStr(array:Array<T>) {
     
var str:String = "数组排列"
for (item in array) {
     
str = str + item.toString() + ", "
}
tv_function_result.text = str
}



var int_array:Array<Int> = arrayOf(1, 2, 3)
var float_array:Array<Float> = arrayOf(1.0f, 2.0f, 3.0f)
var double_array:Array<Double> = arrayOf(11.11, 22.22, 33.33)
//Kotlin要求参数类型完全匹配,所以Int继承Number也不能调用 setArrayNumber传送Int类型
//btn_generic_number.setOnClickListener { setArrayNumber(int_array) }
btn_generic_number.setOnClickListener {
     
when (count%3) {
     
0 -> setArrayStr<Int>(int_array)
1 -> setArrayStr<Float>(float_array)
else -> setArrayStr<Double>(double_array)
}
count++
}

简化函数

函数其实是一种特殊的变量,既然是变量那么就可以用=赋值

fun factorial(n:Int):Int {
     
if (n <= 1) n
else n*factorial(n-1)
}
可以简写为:
fun factorial(n:Int):Int = if (n <= 1) n else n*factorial(n-1)

尾递归函数tailrec

如果函数尾部递归调用自身,加上tailrec关键字表示是一个尾递归函数,此时编译器会优化递归,用循环代替递归,从而避免栈溢出的情况。

tailrec fun findFixPoint(x: Double = 1.0): Double
= if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))

高阶函数

A函数作为B函数的入参,那么B函数称为高阶函数,A函数称为函数变量

fun <T> maxCustom(array: Array<T>, greater: (T, T) -> Boolean): T? {
     
var max: T? = null
for (item in array)
if (max == null || greater(item, max))
max = item
return max
}

greater就是函数变量

调用

因为高阶函数maxCustom是泛型函数,所以要在函数名称后面加<String>var string_array:Array<String> = arrayOf("How", "do", "you", "do")

maxCustom<String>(string_array, {
      a, b -> a.length > b.length })

第二个参数被大括号包了起来,这是Lambda表达式的匿名函数写法,中间的->把匿名函数分为2部分,前半部分表示输入函数,后半部分表示函数体。{ a, b -> a.length > b.length }按照规范的写法是下面这样的,

fun anonymous(a:String, b:String):Boolean {
     
var result:Boolean = a.length > b.length
return result
}

4.4 增强系统函数

扩展函数

开发者给系统类补写新的方法,比如扩展系统的Array类,可以在你定义的函数名称前加上Array,Array

拓展函数结合泛型函数
fun <T> Array<T>.swap(pos1: Int, pos2: Int) {
     
val tmp = this[pos1] //this表示数组变量自身
this[pos1] = this[pos2]
this[pos2] = tmp
}

有了拓展函数,数组变量可以直接调用,像是系统自带的方法一样

拓展高阶函数

maxCustom是求数组元素的最大值,可以把该方法拓展到Array类中,数组变量就可以直接调用了

fun <T> Array<T>.maxCustomize(greater: (T, T) -> Boolean): T? {
     
var max: T? = null
for (item in this)
if (max == null || greater(item, max))
max = item
return max
}

日期时间函数

除了数组,日期和时间的函数还是很常见的,一般我们都封装一个工具类,在Kotlin中我们拓展一下Date类

//只返回日期字符串
fun Date.getNowDate(): String {
     
val sdf = SimpleDateFormat("yyyy-MM-dd")
return sdf.format(this)
}
//只返回时间字符串
fun Date.getNowTime(): String {
     
val sdf = SimpleDateFormat("HH:mm:ss")
return sdf.format(this)
}
//返回详细时间字符串,精确到毫秒
fun Date.getNowTimeDetail(): String {
     
val sdf = SimpleDateFormat("HH:mm:ss.SSS")
return sdf.format(this)
}

调用 :

Date().getNowDate()

单例对象

虽然拓展函数已经实现日期的获取,但是稍显繁琐,比如:Date().getNowDate(),一共占了4个括号。而且这些方法是依赖系统类来拓展的,如果没有系统类,那么也就无法拓展了。所以这个时候,Java中的Uitls工具类反而更灵活,更广泛,那么Kotlin有没有工具类的写法呢?

Java中一般采用static方法作为工具类,表示静态,无需对类进行构造即可直接调用。
既然是工具,那么一旦制定了规格就不会改变了,不能构造也不能修改,所以Kotlin使用object修饰,称为单例对象,相当于Java中的工具类。

我们用单例对象改造后

object DateUtil {
     

//返回日期
val nowDate: String
get() {
     
val sdf = SimpleDateFormat("yyyy-MM-dd")
return sdf.format(Date())
}
//返回时间
val nowTime: String
get() {
     
val sdf = SimpleDateFormat("HH:mm:ss")
return sdf.format(Date())
}
//返回具体时间
val nowTimeDetail: String
get() {
     
val sdf = SimpleDateFormat("HH:mm:ss.SSS")
return sdf.format(Date())
}
}

调用:

DateUtil.nowDateTime

4.5小结

1.定义输入和输出的完整函数
2.输入参数的几种特殊定义,包括默认参数,命名参数,可变参数,以及调用
3.特殊函数的使用,包括泛型函数,单例函数,简化函数,尾递归函数,高阶函数
4.合理利用拓展函数,单例对象对系统函数进行功能增强




第五章 类和对象

5.1类的构造

类的简单定义

Kotlin省略了pubic,默认就是public
:代替extends
Kotlin进行继承时,后面多了()
Kotlin对类初始化的方法是init,Java则为构造函数
Kotlin创建实例省略new

类的构造函数

Kotlin把函数看成特殊变量,那么类就能看成特殊函数,所以构造方法可以直接加在类名后面,而init方法是实例化该类的初始化动作。
Kotlin有主构造函数和二级构造函数的概念,来满足多个构造函数的情况

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}?")
}
}

二级构造函数没有函数名称,只用关键字constructor表示
二级构造函数需要调用主构造函数

但是测试中,发现会调用2次toast,能否不强制调用主构造函数呢?
Kotlin设定了主构造函数不是必须的,可以去掉主构造函数,这样两个构造函数是互相独立的,就不会去调用主构造函数了

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

带默认参数的构造参数

如此折腾一番,貌似跟Java一样了,没有简化,别急,Kotlin还有大招
可以把它们合并成一个带默认参数的主构造函数,既能输入2个参数,也能输入3个参数

//类的主构造函数使用了默认参数
class AnimalDefault (context: Context, name:String, sex:Int = 0) {
init {
var sexName:String = if(sex==0) "?" else "?"
context.toast("??${name}?${sexName}?")
}
}

但是这个默认参数只支持Kotlin代码调用,若想让Java也识别默认参数,得往该类的构造函数添加注解@JvmOverloads,告知编译器这个类是给Java重载用的

//加上@JvmOverloads的目的是让Java代码也能识别默认参数
//因为添加了注解,所以必须加上constructor
class AnimalDefault @JvmOverloads constructor(context: Context, name:St
ring, sex:Int = 0) {
init {
var sexName:String = if(sex==0) "?" else "?"
context.toast("??${name}?${sexName}?")
}
}

现在Java也能像Kotlin一样调用多构造方法了。
总结:
主构造函数的入参在类名后面声明,函数体则位于init方法中,二级构造函数必定先调用主构造函数,Kotlin构造函数也支持默认参数,避免了冗余的构造函数定义

5.2类的成员

成员属性

Java方式保存入参的值,太啰嗦了

class WildAnimal (name:String, sex:Int = 0) {
     
var name:String 
val sex:Int 
init {
     
this.name = name
this.sex = sex
}
}

可以简化为:

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

只有2个改动之处:
增加关键字var,表示声明与该参数同名的可变属性并自动赋值
增加关键字val,表示声明与该参数同名的不可变属性并自动赋值
外部调用,就可以直接调用name:

var animal = WildAnimal("小狗", "公")
${
     animal.name}

所以Kotlin精简了太多:
1.冗余的同名属性声明语句
2.冗余的同名属性赋值语句
3.冗余的属性的set和get方法
如果某个字段并非入参的同名属性,就需要在内部显式的声明

class WildAnimalMember (var name:String, val sex:Int = 0) {
     
//非空的属性必须在声明是赋值或者在构造函数中赋值(本例是在构造函数中)
//否则编译器会报“Property must be initialized or be abstract”
var sexName:String
init {
     
sexName = if(sex==0) "?" else "?"
}
}

外部当然可以这样访问了:${animal.sexName}

成员方法

class WildAnimalFunction (var name:String, val sex:Int = 0) {
     
var sexName:String
init {
     
sexName = if(sex==0) "公" else "母"
}
//成员方法和普通方法一样
fun getDesc(tag:String):String {
     
return "${
       sexName}"
}
}

调用:

var animal = WildAnimalFunction(animalName, animalSex)
animal.getDesc("动物园")

伴生对象

Java中有个static静态方法,Kotlin去掉了static,无法直接声明静态成员,用伴生对象代替

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

外部调用:

WildAnimalCompanion.WildAnimal.judgeSex("公")
和简化的
WildAnimalCompanion.judgeSex("公") (更像Java中的static调用)

静态属性

class WildAnimalConstant(var name:String, val sex:Int = MALE) {
     
var sexName:String
init {
     
sexName = if(sex==MALE) "?" else "?"
}
fun getDesc(tag:String):String {
     
return "????$tag???${
       name}?${
       sexName}??"
}
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
}
}
}

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

5.3类的继承

开放性修饰符

Kotlin默认每个类都不能被继承,如果要让某个类成为基类,就需把该类开放出来,也就是添加关键字open作为修饰符
《Kotlin从零到精通Android开发》欧阳燊(一)_第2张图片

普通类的继承

open class Bird (var name:String, val sex:Int = MALE) {
     
//变量,方法,类默认都是public,一般把public省略了
//public var sexName:String
var sexName:String
init {
     
sexName = getSexName(sex)
}

//open和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 "${
       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不用重复声明
//即子类的构造函数在入参签名无需增加val和var
class Duck(name:String="??", sex:Int = Bird.MALE) : Bird(name, sex) {
     
}

子类也可以定义新的成员属性和成员方法,或者重写父类的open方法

抽象类

//子类的构造函数,原来的入参不用加var和val,新增的入参必须加var或val
//因为抽象类不能直接使用,所以构造函数不必给默认参数赋值
abstract class Chicken(name:String, sex:Int, var voice:String) : Bird(n
ame, sex) {
     
val numberArray:Array<String> = arrayOf("?","?","?","?","?","?"
,"?","?","?","?");
//抽象方法必须在子类进行重写,所以可以省略关键字open,因为abstract方法默认就是open类型的
open??
//open abstract fun callOut(times:Int):String
abstract fun callOut(times:Int):String
}

下面创建个实体类继承 抽象方法:

class Cock(name:String=" 鸡 ", sex:Int = Bird.MALE, voice:String=" 嘎嘎嘎") : Chicken(name, sex, voice) {
     
override fun callOut(times: Int): String {
     
var count = when {
     
//when判断大于小于时,要把完整的判断条件写到每个分支中
times<=0 -> 0
times>=10 -> 9
else -> times
}
return "$sexName$name${
       voice}${
       numberArray[count]}"
}
}

接口

Kotlin和Java一样也只能继承一个类,所以只能通过接口定义几个抽象方法,然后在实现类中重写,从而间接实现C++中的多重继承功能。
注意点:
1.接口不能定义构造函数
2.接口的内部方法要被实现类重写,这些方法默认为抽象方法
3.于Java不同的是,Kotlin允许接口内实现非抽象方法,而Java接口中必须全部是抽象方法

interface Behavior {
     
//接口内部默认是抽象open方法,所以可以省略abstract和open
open abstract fun fly():String
//比如这个方法,就省略了
fun swim():String

//kotlin接口内部允许实现方法,该方法不是抽象方法,不过依然是open类型,接口内部默认open
fun run():String {
     
return "跑步"
}
//Kotlin接口允许声明抽象属性,实现该接口的方法必须重载该属性
//和方法一样,open和abstract也可以省略
//open abstract var skilledSports:String
var skilledSports:String
}

我们创建一个实现类来实现接口:

class Goose(name:String="鹅", sex:Int = Bird.MALE) : Bird(name, sex), B
ehavior {
     
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 = "??"
}

接口代理

class BehaviorFly : Behavior {
     
override fun fly():String {
     
return "飞1"
}
override fun swim():String {
     
return "游泳1"
}
override fun run():String {
     
return "跑1"
}
override var skilledSports:String = "飞翔"
}

class BehaviorSwim : Behavior {
     
override fun fly():String {
     
return "飞2"
}
override fun swim():String {
     
return "游泳2"
}
override fun run():String {
     
return "跑2"
}
override var skilledSports:String = "游泳"
}
class BehaviorRun : Behavior {
     
override fun fly():String {
     
return "飞3"
}
override fun swim():String {
     
return "游泳3"
}
override fun run():String {
     
return "跑3"
}
override var skilledSports:String = "奔跑"
}

下面定义一个代理类:

//只有接口才能使用关键字by进行代理操作
class WildFowl(name:String, sex:Int=MALE, behavior:Behavior) : Bird(nam
e, sex), Behavior by behavior {
     
}

外部调用:代理类的入参要传入具体的行为对象

WildFowl("老鹰", Bird.MALE, BehaviorFly())
WildFowl("企鹅", behavior=BehaviorSwim())
WildFowl("鸵鸟", Bird.MALE, BehaviorRun())

总结:
1.Kotlin的类默认不可被继承,必须添加open才可以,而Java默认允许被继承,除了final类
2.Kotlin新增了修饰符internal,表示只对本模块(app自身)开放
3.:代替Java中的extends和implement
4.Kotlin接口中可以实现方法,而Java接口中全部必须是抽象方法
5.Kotlin引入接口代理的概念,而Java中不存在代理的写法

5.4几种特殊类

嵌套类

Java中的嵌套类允许访问外部类的成员,而Kotlin的嵌套类不允许访问外部类的成员

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

调用嵌套类:

//调用嵌套类时,只能引用外部类的类名,不能调用外部类的构造函数
val peachBlossom = Tree.Flower("桃花");
peachBlossom.getName()

内部类

普通嵌套类不能访问外部类的成员,那么如果想访问怎么办呢?
inner关键字,加载嵌套类class的前面即可,就可以访问外部类的成员了

class Tree(var treeName:String) {
     
// 加上inner 关键字就变成了内部类
inner class Flower (var flowerName:String) {
     
fun getName():String {
     
return "${
       treeName}${
       flowerName}"
    }
  }
}

调用内部类:

//调用内部类时,必须调用外部类的构造函数
val peach = Tree("桃树").Flower("桃花");
tv_class_secret.text = peach.getName()

总结:
内部类比嵌套类多了inner关键字
内部类可以访问外部类的成员
调用内部类必须调用外部类的构造函数,调用嵌套类只能调用外部类的类名

枚举类

Java中的枚举类:

enum Season {
      
SPRING, SUMMER, AUTUMN, WINTER
 }

Kotlin中的枚举类:

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

调用:

btn_class_enum.setOnClickListener {
     
if (count%2 == 0) {
     
//ordinal表示枚举类型的序号,name表示枚举类型的名称
tv_class_secret.text = 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 -> "未知"
}
} else {
     
tv_class_secret.text = when (count++%4) {
     
//使用自定义属性seasonName表示
SeasonName.SPRING.ordinal -
> SeasonName.SPRING.seasonName
SeasonName.SUMMER.ordinal -
> SeasonName.SUMMER.seasonName
SeasonName.AUTUMN.ordinal -
> SeasonName.AUTUMN.seasonName
SeasonName.WINTER.ordinal -
> SeasonName.WINTER.seasonName
else -> "未知"
枚举类的构造函数是给枚举类型使用的,外部不能直接调用枚举类的构造函数
//else -> SeasonName("??").name
}
}
}

密封类

当when语句判断枚举类的时候,末尾例行公事加了else分支,因为when语句不知道有4种枚举,因此以防万一,必须要有else分支。为了解决判断多余分支的问题,Kotlin提出了"密封类"的概念,密封类像是一种更严格的枚举类,它内部有且仅有自身的实例对象,密封类采用了嵌套类的手段,它的嵌套类全部由自身派生而来,仿佛一个家谱。

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

有了密封类,外部使用when就不需要else分支了
调用:

btn_class_sealed.setOnClickListener {
     
var season = when (count++%4) {
     
0 -> SeasonSealed.Spring("春天")
1 -> SeasonSealed.Summer("夏天")
2 -> SeasonSealed.Autumn("秋天")
else -> SeasonSealed.Winter("冬天")
}
//密封类是一种严格的枚举类,它的值是一个有限的集合
//密封类确保条件分支覆盖了所有的枚举类型,因此不再需要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
}
}

数据类

即Java中的Bean实体类,Java中的做法:
1.定义字段,构造函数
2.定义get /set方法
3.只想修改对象的某几个字段的值,其他字段也必须修改
4.调试时,得把每个字段的值都打印出来
这些任务都毫无技术含量可言,所以Kotlin有了数据类

只需要在class前面增加data关键字,并声明完整参数的构造函数,即可实现以下功能:
1.自动声明于构造函数入参同名的属性字段
2.自动实现每个属性字段的get/set方法
3.自动提供equals方法,用于比较两个数据对象是否相等
4.自动提供copy方法,允许完整负责数据对象,也可在复制后单独修改某几个字段的值
5.自动提供toString方法,便于打印数据对象中保存的值

下面我们马上定义一个:

//数据类必须有主构造函数,且至少有一个输入参数
//并且要声明与输入参数同名的属性,即输入参数前面添加var或val
//数据类不能是基类,也不能是子类,不能是抽象类,也不能是内部类,更不能是密封类
data class Man(var name:String, var age:Int, var sex:String) {
     
}

精简的前提是要有规范:
1.数据类必须有主构造函数,且至少有一个输入参数,因为它的属性字段要和输入参数一一对应,如果没有属性字段,那么也就不是数据类了
2.输入参数前面添加var或val,这保证每个入参都会自动声明同名的属性字段
3.只能是独立的类,不能是其他类型的类,否则不同规则之间会产生冲突

var man = Man("张飞", "25", "男")
//数据类的copy方法不带参数,表示复制一模一样的对象
var man2 = man.copy()
//数据类的copy方法带参数,表示指定参数另外赋值
var man3 = man.copy(name="诸葛亮")
//数据类自带equals方法和toString方法
man.equals(man2)
man.toString()

copy,equals,toString方法都是数据类自带的,提高了开发者的编码效率

模板类(泛型类)

常见的ArrayList,HashMap,AsyncTask都是模板类
举个例子:
计算小河的长度,如果输入数字就以m为单位,如果输入汉字就以米为单位

//在类名后面添加,表示这个是一个模板类
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)
//当然保守起见,还是按照规矩添加<参数类型>
2 -> River<Double>("??", 50.5)
//如果你已经是老手了,怎么方便怎么来,Kotlin的涉及初衷就是偷懒
else -> River("大河", "一千")
}

tv_class_secret.text = river.getInfo()
}

5.5小结

1.类的定义和主构造函数和二级构造函数
2.类内部定义的成员属性和成员方法,伴生对象的静态属性和静态方法
3.修饰符,继承抽象类,接口,接口代理
4.特殊类:嵌套类,内部类,枚举类,密封类,数据类,模板类

你可能感兴趣的:(Kotlin,android,kotlin)