三、面向对象
1、继承
1.1 open改变类的继承属性
在kotlin设计时默认所有的非抽象类是无法被继承的,如果想要使得一个非抽象类可以被继承,我们需要使用open关键字。
open class Person {
var name ="";
var age = 0;
fun eat() {
println(name + " is eating. He is " + age +" years old.")
}
}
同理如果类中的方法需要继承也需要使用open
关键字
1.2 主构造函数与次构造函数
主构造函数:
- 主构造函数的特点是没有函数体,直接定义在类名后面即可。
- 主构造函数只能有一个,而且通常用于初始化类的属性,以及执行与类的整体初始化相关的操作。
- 主构造函数的参数可以带有默认值,从而允许创建对象时省略某些参数。
我们使用init
关键字编写主构造函数的逻辑
class Person(val name: String, val age: Int) {
init {
println("Person对象已创建,姓名:$name,年龄:$age")
}
}
注意:
在主构造函数中声明val、var
的字段会自动成为该类的字段,这样就会导致和父类中同名的字段冲突。如果不使用val、var
关键字数据的作用域仅限定在主构造函数中。
副构造函数:
- 副构造函数是类中的额外构造函数,可以有多个,它们用
constructor
关键字声明。 - 副构造函数通常用于提供不同的构造方式,允许使用不同的参数组合来创建对象。
class Person(val name: String, val age: Int) {
// 主构造函数
init {
println("Person对象已创建,姓名:$name,年龄:$age")
}
// 副构造函数1,接受姓名参数
constructor(name: String) : this(name, 0) {
println("副构造函数1被调用")
}
// 副构造函数2,接受年龄参数
constructor(age: Int) : this("未知姓名", age) {
println("副构造函数2被调用")
}
}
1.3 继承的格式
格式:
class 新的类名(无/参数) : 被继承类(无/参数){}
重写类的继承方法需要关键字override
,并且被重写的方法需要有open
关键字修饰。
open class Person(val name:String,val age:Int) {
open fun eat() {
println(name + " is eating. He is " + age +" years old.")
}
}
class Student(val sno:String,val age1:Int) : Person(sno,age1){
override fun eat(){
println("$name Dont $age")
}
}
fun main(){
var i = Student("JAck",19)
i.eat()
}
kotlin规定,当一个类既有主构造函数又有次构造函数,所有的次构造函数都必须调用主构造函数。
open class Person(val name:String,val age:Int) {
fun eat() {
println(name + " is eating. He is " + age +" years old.")
}
}
class Student(val sno:String,val grade:Int,name: String,age: Int) : Person(name,age){
//通过次构造函数调用主构造函数
constructor(name: String,age: Int) : this("",0,name,age){
}
constructor() : this("",0)
}
fun main(){
var i = Student("JAck",19)
var p = Student("abs",1,"jaks",1)
i.eat()
}
在这里this
的用法是什么??
在这个类中,this
关键字的使用是为了调用不同构造函数的目的。
具体来说,这个Student
类具有以下构造函数:
- 主构造函数:
Student
类的主构造函数接受四个参数,分别是sno
、grade
、name
和age
。这些参数用于初始化类的属性,其中sno
和grade
是继承自Person
类的属性,而name
和age
是本类的属性。 - 第一个副构造函数:这个副构造函数接受两个参数,即
name
和age
,它通过使用this
关键字来**调用了主构造函数,**传递了空字符串""
和0
作为参数,这意味着它将调用主构造函数以初始化sno
和grade
属性,并且将传递的name
和age
参数用于初始化本类的属性。 - 第二个副构造函数:这个副构造函数不接受任何参数,它同样通过使用
this
关键字来**调用了第一个副构造函数,**传递了空字符串""
和零0
作为参数,从而也会触发调用主构造函数,并且初始化本类的属性。
2、接口
接口的基本用法和java一致,但是在kotlin中允许代码对接口中的方法进行默认实现。
interface Study{
fun readBooks()
fun doHomework(){
println("ANT")
}
}
3、数据类——Java中toString、equals、hashCode的省略
在kotlin中我们不必写这些繁琐的方法,直接使用关键字data即可,代码如下:
//使用data修饰
data class Cellphone(val brand: String,val price: Double) {
}
fun main(args: Array<String>) {
val cellphone1 = Cellphone("Samsung",1299.99)
val cellphone2 = Cellphone("Samsung",1299.99)
println(cellphone1)
println( "cellphone1 == cellphone2 ?? "+(cellphone1 == cellphone2))
}
Kotlin会根据主构造函数中的参数帮你将equals(),hash Code(),toString()等固定且无实际逻辑意义的方法自动生成。
4、单例类——object关键字
在kotlin中创建一个单例类的方式极其简单,只需要将class
关键字改为object
关键字即可。
object Singleton {
fun singletonTest(){
println("This is a OneInstance")
}
}
此时Singleton就是一个单例类了,我们可以在这个类中直接编写需要的函数。
可以看出在Kotlin中我们不需要私有化构造函数。
5、Lambda编程
5.1 集合的创建和遍历
5.11 List集合
5.111、listOf
一般情况下我们需要使用这种方式初始化:
val list = ArrayList<String>()
list.add("Apple")
list.add("Banana")
list.add("Orange")
在kotlin中提供了一个内置的listOf
函数来简化初始化集合的写法。
val list = listOf<String>("Apple","Banana","Orange")
当然我们也可以使用for-in遍历这个集合,并且Kotlin会自动补全fruit
的类型。
for(fruit in list){
println(fruit)
}
不过需要注意的是使用listOf
创建的集合是一个不可变集合
,就是该集合只能用于读取操作。
为什么会不可修改呢?
通过源码我们发现底层是数组的存储方式这样就能解释listOf`创建的集合只能用于读写操作了,同样他的查询效率也是比较高的。
5.112、mutableListOf
使用mutableListOf
创建的集合是一个可变集合,可以对集合进行增删改查的操作。
查看源码可以发现创建的是一个Arraylist类型的集合。使用链表的形式存储。
5.12 Set类型
Set集合的用法基本上和List集合的一样,只不过创建集合的方式变成了setOf()
和mutableSetof()
5.13 Map类型
Map类的的基本创建方法和Java中是一致的。
val map = HashMap<String, Int>()
map.put("Apple",1)
map.put("Banana",2)
map.put("Orange",3)
当然kotlint
在Map中同样提供了mapOf
和mutableMapOf
方法用于集合的创建。Kotlin 的 mapOf
和 mutableMapOf
在底层都使用哈希表(Hash Table)作为存储结构,mapOf
是数组存储,mutableMapOf
是LinkedHash
val map = mapOf("Apple" to 1,"Banana" to 2,"Orange" to 3)
for((fruit,number) in map){
println(fruit + number)
}
需要注意的时这里的to不是关键字而是一个infix函数。
Kotlin 中,infix函数允许你以更自然的方式使用中缀表示法调用函数。
5.2 集合函数式API
5.21 Lambda表达式的优化过程
{参数名:参数类型 -> 函数体}
val maxLengthFruit = list.maxBy({fruit:String -> fruit.length})
- Kotlin规定当Lambda参数是函数的最后一个参数时,可以将Lambda表达式移到括号外面
val maxLengthFruit = list.maxBy(){fruit:String -> fruit.length}
- 如果Lambda表达式是函数的唯一个参数,还可以将括号省略。而且kotlin具有出色的推到机制,我们还可以不声明参数类型。
val maxLengthFruit = list.maxBy{fruit -> fruit.length}
- 并且当Lambda表达式参数列表只有一个参数时,也不必要声明参数名。可以使用
it
代替
val maxLengthFruit = list.maxBy{it.length}
5.22 常用API
-
计算最大长度:maxBy{it.length}
-
小写转大写:map { it.uppercase() }
-
过滤集合中的数据:filter函数
val newList = list.filter { it.length > 5 }.map { it.uppercase() }
-
判断集合是否存在至少一个元素满足要求:any.{}
-
判断集合是否所有元素都满足要求:all.{}
val anyResult = list.any{it.length >= 5} val allResult = list.all { it.length >= 10 }
6、空指针检查机制
Kotlin将空指针检查机制提到了编译时期,它默认所有的参数和变量都是非空的。
那么我们应该如何表示一个可空的类型呢?
6.1 ?
很简单在类名后加一个?代表可以是非空整形,例如:
I
n
t
?
Int?
Int?
但是此时就可能出现潜在的空指针异常风险,那么我们应该如何解决呢?Kotlin提供了一个辅助判空工具——?.。
6.2 辅助判空工具
6.2.1 ?.
例如这段代码:
if(A != null){
A.do()
}
可以简化为这样的形式:
A?.do()
6.2.2 ?:
例如这段代码:
if(A != null){
A
}else{
B
}
可以简化为以下形式:
Val c = A ?: C
左边表达式不为空就返回左边的值,否则返回右边的值。
6.2.3 非空断言工具——!!
Kotlin的这种检查机制在某些情况下也会出现问题,例如:
此时是无法通过编译的,因为uppercase无法知道content不是空值。
此时就需要一种断言工具——!!
val upString = content!!.uppercase()
这是一种有风险的写法,意在告诉Kotlin,这里对象肯定不是空你不需要帮我检查空指针。
6.2.4 let辅助工具
这个函数提供了函数式API的编程接口,并将原始对象作为参数传递到Lambda表达式中。
obj.let{ obj1 ->
//业务逻辑
}
在这里,obj和obj1实际上是同一个对象。
如使用let修改这一段代码:
fun doStuday(study: Study?) {
study?.doHomework()
study?.readBooks()
}
修改结果如下:
fun doStuday(study: Study?) {
study?.let {
it.readBooks()
it.doHomework()
}
}
let
是可以处理全局变量的判空问题但是if
不行,因为if可以导致别的线程修改了这个全局变量。