Kotlin——面向对象(上),封装、继承、多态
类和对象
类是一种自定义类型,可以使用类来定义变量,这种类型的变量统称为引用变量,所有类都是引用类型
定义类
类(class)和对象(object)其中类是某一批对象的抽象。对象才是一个具体存在的实体
kotlin定义类的语法
[修饰符]class 类名 [constructor 主构造器]{
零个到多个次构造器定义...
零个到多个属性...
零个到多个方法...
}
- 修饰符:可以是public|internal|private(只能出现其中之一)、final|open|abstract(也只能出现其中之一)。
- 构造器:构造器是一个类创建对象的根本途径,如果一个类没有构造器,这个类就无法创建对象。一个Kotlin类可以有01个主构造器和0N个次构造器。主构造器是类头的一部分,它跟在类名(和泛型声明)后,使用constructor关键字定义一个无执行体的构造器
class User constructor(firstName:String){}
如果主构造器没有任何注解或修饰符,则可以省略constructor关键字
class User(firstName:String){}
定义构造器的语法格式
[修饰符] constructor (形参列表){
//零条到多条可执行语句组成的构造器执行体
}
- 属性 定义属性的语法
[修饰符] var|val 属性名:类型 [=默认值]
[<getter>]
[<setter>]
- 修饰符 修饰符可以省略 也可以是 public|protected|internal|private|final|open|abstruct
对象的产生和使用
调用某个类的构造器即可创建这个类的对象,并且无须使用new 关键字
fun main() {
var p:Person =Person()
p.say("hello")
//访问对象属性
p.name="asd"
}
class Person{
var name:String? = null
var age:Int = 0
fun say(content: String) {
println(content)
}
}
对象的this引用
Kotlin中this指向调用该方法的对象,根据this出现位置的不同,this作为对象的默认引用有两种情形
- 在构造器中引用该构造器正在初始化的对象
- 在方法中引用调用该方法的对象 谁调用方法 this就代表谁
- 在构造方法中代表构造器正在初始化的对象
class Dog{
fun jump(){
println("正在执行jump方法")
}
fun run(){
//this表示 调用run方法的对象
this.jump()
println("正在执行run方法")
}
}
Kotlin允许对象的一个成员直接调用另一个成员,可以省略this前缀
方法详解
方法是类或对象的行为特征的抽象,方法是类或对象最重要的组成部分。
中缀表示法
Kotlin的方法还可以用infix修饰,infix方法只能有一个参数,这样方法就能像双目运算符一样使用
fun main() {
var orign = Apple(3.4)
var appleAnd = orign add Apple(1.6)
println(appleAnd)
}
class Apple (weight:Double){
var weight = weight
//infix
infix fun add(other:Apple):Apple{
return Apple(weight+other.weight)
}
override fun toString(): String {
return "Apple(weight=$weight)"
}
}
属性和字段
读写属性和只读属性
Kotlin使用val定义只读属性,使用var定义读写属性。系统会为只读属性生成getter方法,会为读写属性生成getter和setter方法
在定义Kotlin的普通属性时,需要显式指定初始值:要么在定义时指定初始值,要么在构造器中指定初始值
自定义getter和setter
在定义属性时可指定自定义的getter和setter方法,这些方法可加入自定义的逻辑。其中getter是一个形如get(){}的方法,getter应该是无参数、带一个返回值的方法。setter是一个形如set(value){}的方法,带一个参数、无返回值
定义getter、setter方法时无须使用fun关键字
class User(first:String,last:String){
var first = first
var last = last
//自定义get方法
get() {
println("get方法执行")
return "${first}.${last}"
}
}
幕后字段
Kotlin中定义一个普通属性时,Kotlin会为该属性生成一个field、getter和setter方法。Kotlin为该属性所生成的field就被称为幕后字段
如果Kotlin类的属性有幕后字段,则Kotlin要求为该属性显式指定初始值,要么在定义时指定,要么在构造器中指定
如果Kotlin类的属性没有幕后字段,则Kotlin不允许为该属性指定初始值
幕后属性
幕后属性就是用Private修饰的属性,Kotlin不会为幕后属性生成任何getter、setter方法,因此不能直接访问幕后属性,必须由开发者为幕后属性提供getter、setter方法
延迟初始化属性
Kotlin提供了lateinit修饰符来实现属性的延迟初始化,使用lateinit修饰的属性,可以在定义该属性时和在构造器中都不指定初始值
有如下限制:
- lateinit只能修饰在类体中声明的可变属性
- lateinit修饰的属性不能有自定义的getter或setter方法
- lateinit修饰的属性必须是非空类型
- lateinit修饰的属性不能是原生类型
封装
包和导包
Kotlin的包与Java的包相同,Kotlin导包也需要如下格式
package packagename
默许导包
正如前面所看到 的 ,前面程序使用 Kotlin 定义的 List、 Map 等类时, 程序都没有为这些类
导包 , 其原因都是因为 Kotlin 的默认导入 。
Kotlin 默认会导入如下包。
~ kotlin.*
~ kotlin.annotation.*
~ kotlin.collections.*
~ kotlin.comparisons. * (自 Kotlin 1 . 1 起)
~ kotlin.io. *
kotlin.ranges.*
~ kotlin.sequences. *
kotlin.text. *
此外, 对于 川巾f 平台 , 还会自动导入如下两个包。
java.lang.*
~ kotlin.jvm. *
对于 JavaScript 平台 , 则额外导入如下包。
~ kotlin.js.*
使用访问控制符
- private 与Java的private类似,private成员只能在该类的内部或文件的内部被访问
- internal:internal成员可以在该类的内部或文件的内部或者同一个模块内被访问
- protected:protected成员可以在该类的内部或文件的内部或其子类中被访问
- public:public成员可以在任意地方被访问
Kotlin的默认访问控制修饰符是public
位于包内的顶层成员
顶层类、接口、函数、属性,只能使用private、internal和public其中之一,不能使用protected修饰符
使用private 修饰,只能在当前文件中被访问
使用internal修饰 只能在当前文件中或当前模块中被访问
不加修饰符或使用public修饰,可以在任意地方被访问
位于类、接口之内的成员
- 使用private修饰,这些成员只能在类中被访问
- internal:这些成员能在该类或当前模块中被访问
- protected:这些成员能在该类或该类的子类中被访问
- 不加修饰符或使用public修饰,可以在任意地方被访问
深入构造器
主构造器和初始化块
Kotlin的主构造器并不是传统意义上的构造器,更像Java的初始化块,或者说对初始化块的增强——Java的初始化块不能传入参数,Kotlin通过主构造器的设计,允许为初始化块传入参数
初始化块的语法格式
init{
//初始化块中的可执行代码,可以使用主构造器定义的参数
}
当程序通过主构造器创建对象时,系统其实就是调用该类里定义的初始化块。如果一个类里定义了两个普通初始化块,则前面的初始化块先执行,后面定义的初始化块后执行
从上面可知,初始化块就是主构造器的执行体,如果没有为Kotlin提供任何构造器,则系统会为这个类提供一个无参数的主构造器,这个构造器的执行体为空,Kotlin类至少包含一个构造器
一旦提供了自定义的构造器,系统就不再提供默认的构造器
次构造器和构造器重载
Kotlin允许使用constructor关键字定义N个次构造器,次构造器类似于Java传统的构造器。
Kotlin的主构造器其实属于初始化块,因此Kotlin要求所有的次构造器必须委托调用主构造器,其实就是先调用主构造器,然后才执行次构造器代码
无主构造器:
fun main(args: Array<String>) {
//通过无参构造器创建UserA
var userA = UserA()
//通过有参构造器创建
var userA1 = UserA("asda", 12)
}
private class UserA{
var name:String?
var count:Int
init {
println("初始化块")
}
constructor(){
name = null
count = 0
}
constructor(name: String, age: Int){
this.name = name
this.count = age
}
}
带主构造器:
fun main(args: Array<String>) {
var useB = UseB("asd")
//次构造器
var useB2 = UseB("a", 22)
var useB3 = UseB("a", "b", 21)
}
private class UseB(name:String){
var name:String?
var count:Int
init {
println("UserB初始化块")
this.name = name
this.count = 0
}
//次构造器
constructor(name:String,age:Int):this(name){
this.count = age
}
constructor(a:String,b:String,c:Int):this(a,c){
this.name = b
this.count = c
}
}
主构造器声明属性
Kotlin允许在主构造器上声明属性,直接在参数之前使用var或val即可声明属性,当程序调用这种方式声明的主构造器创建对象时,传给该构造器的参数将会赋值给对象的属性
fun main(args: Array<String>) {
var item = Item("123", 1231231)
println(item.code)
println(item.price)
}
private class Item(var code:String,var price:Int){
}
如果主构造器的所有参数都有默认值,程序能以构造参数的默认值来调用构造器(不需要为构造参数传入值),则看上去就想调用无参的构造器
fun main(args: Array<String>) {
val item = Item()
println(item.code)
println(item.price)
}
private class Item(var code:String = "",var price:Int = 222){
}
类的继承
Kotlin的继承同样的单继承:每个类最多只有一个直接父类
继承的语法
修饰符 class SubClass:SuperClass{
//类定义部分
}
- 如果一个Kotlin类定义时并未显式指定这个类的直接父类,则这个类默认扩展Any类。因此Any类是所有类的父类,要么是其直接父类,要么是其间接父类
- Any类只有equals()、hashCode()和toString()
- Kotlin的类默认就有final修饰,因此Kotlin的类默认是不能派生子类的,为了让一个类能派生子类,需要使用open修饰该类
- Kotlin的子类必须调用父类构造器
- 子类的主构造器
如果子类定义了主构造器,由于主构造器属于类头,如果没有定义初始化块,那么主构造器是没有执行体的,为了让主构造器能调用父类构造器,因此主构造器必须在继承父类的同时委托调用父类构造器
open class BaseClass{
var name:String
constructor(name:String){
this.name = name
}
}
//子类没有声明主构造器,因此要在声明继承时委托调用父类构造器
class SubClass1 : BaseClass("name"){}
//子类声明主构造器 必须在声明继承时委托调用父类构造器
class SubClass2(name:String):BaseClass(name){}
- 子类的刺构造器
子类的次构造器同样需要委托调用父类构造器
如果子类定义了主构造器,由于子类的次构造器总会委托调用子类的主构造器,而主构造器一定会委托调用父类构造器,因此子类的所有次构造器最终也调用了父类构造器
如果子类没有定义主构造器,则此时次构造器委托调用父类构造器可分为 3 种方式。
子类构造器显式使用:this(参数)显式调用本类中重载的构造器,系统将根据 this(参数)调用中传入的实参列表调用本类中的另一个构造器。调用本类中的另一个构造器最终还是要调用父类构造器。
子类构造器显式使用:super(参数)委托调用父类构造器,系统将根据 super(参数)调用中传入的实参列表调用父类对应的构造器。
子类构造器既没有: super(参数)调用,也没有:this(参数)调用,系统将会在执行子类构造器之前,隐式调用父类无参数的构造器。
open class BaseClass{
var name:String = ""
constructor(name:String){
this.name = name
}
constructor(){}
}
class SubClass:BaseClass{
//构造器没有显式委托 隐式调用父类的无参构造器
constructor() : super() {
println("sub的无参构造")
}
constructor(name:String):super(name){
}
constructor(name: String, age: Int) : this(name){
}
}
重写父类方法
子类继承父类,将可以获得父类的全部属性和方法
open class Fruit(var weight:Int){
fun info(){
println("weight${weight}")
}
}
class AppleA: Fruit(12)
fun main(args: Array<String>) {
var appleA = AppleA()
appleA.info()
//访问apple本身没有 父类中有的weight属性
println(appleA.weight)
//Apple对象的info方法
appleA.info()
}
重写方法
open class Fruit(var weight:Int){
fun info(){
println("weight${weight}")
}
open fun subInfo(){
println("it's subinfo fun")
}
}
class AppleA: Fruit(12){
override fun subInfo() {
println("Apple Info fun")
}
}
fun main(args: Array<String>) {
var appleA = AppleA()
appleA.info()
//访问apple本身没有 父类中有的weight属性
println(appleA.weight)
//Apple对象的info方法
appleA.info()
//访问重写的方法
appleA.subInfo()
}
subInfo方法使用open修饰,Kotlin默认为所有方法添加final修饰符,阻止该方法被重写,添加open关键字用于阻止Kotlin自动添加final修饰符
Kotlin类重写父类的方法必须添加override修饰符
方法的重写要遵循"两同两小一大"规则:方法名相同、形参列表相同:子类方法的返回值类型应比父类方法的返回值类型更小或相等。子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等。“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等
子类覆盖父类方法后,子类的对象无法访问父类中被覆盖的方法,但可以在子类方法中调用父类被覆盖的方法,使用super作为调用者来调用父类中被覆盖的方法
子类无法访问父类中具有private权限的方法
重写父类的属性
- 父类被重写的属性必须使用open修饰,子类重写的属性必须使用override修饰
- 重写的子类属性类型与父类属性类型要兼容
- 重写的子类属性要提供更大的访问权限
super限定
如果需要在子类方法中调用父类中被覆盖的方法或属性,则可使用super限定
super是Kotlin提供的一个关键字,用于限定该对象调用它从父类继承得到的属性或方法
多态
与Java相同,Kotlin的变量也有两个类型:编译时类型,运行时类型。编译时类型由声明该变量时使用的类型决定。运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就表现出所谓的多态
is检查类型
Kotlin提供了类型检查运算符:is和!is
is运算符前一个操作数通常是一个变量,后一个操作数通常是一个类。用于判断前面的变量是否引用后面的类,或者其子类
as运算符转型
- as:不安全的强制转型运算符
- as?:安全的强制转型运算符
- 当把子类实例赋给父类变量时,被称为向上转型。这种转型总是可以成功的。所以说子类是一种特殊的父类
- 当把一个父类变量赋给子类变量时,就需要进行向下转型,向下转型必须使用as或as?运算符
使用as执行的强制转换类型是不安全的转换,如果转换类型失败则会引发异常
使用as?执行的强制转换是安全的转换,如果转换失败则会返回Null,使用as?转换返回的是可空的值,因此程序需要对as?转换的结果进行null判断
//定义水果类
open class Fruit{
var name:String
var price:Int
constructor(name: String, price: Int){
this.name = name
this.price = price
}
}
class Apple:Fruit{
var color:String
constructor(name:String,price:Int,color:String):super(name,price){
this.color = color
}
}
class Grape:Fruit{
var sugarRate:String
constructor(name:String,price:Int,sugarRate:String):super(name,price){
this.sugarRate = sugarRate
}
}
fun main(args: Array<String>) {
//使用集合保存4个水果
var fruits = mutableListOf<Fruit>()
fruits.add(0,Apple("a1",1,"c1"))
fruits.add(1,Apple("a2",2,"c2"))
fruits.add(2,Grape("g1",3,"s1"))
fruits.add(2,Grape("g2",4,"s2"))
//遍历集合,使用父类调用
for (f in fruits) {
var ap = f as? Apple
println("${ap?.name}${ap?.color}")
}
}