面向对象的概念
-
封装性
封装性是面向对象的核心思想,将对象的属性和行为封装起来,不需要让外界知道具体实现细节,这就是封装思想。例如,用户使用电脑,只需要使用手指敲键盘就可以了,无须知道电脑内部是如何工作的,即使用户可能碰巧知道电脑的工作原理,但在使用时,并不完全依赖电脑工作原理这些细节。
-
继承性
继承性主要描述的是类与类之间的关系,通过继承,可以在无须重新编写原有类的情况下,对原有类的功能进行扩展。例如,有一个汽车的类,该类中描述了汽车的普通特性和功能,而轿车的类中不仅应该包含汽车的特性和功能,还应该增加轿车特有的功能,这时,可以让轿车类继承汽车类,在轿车类中单独添加轿车特性的方法就可以了。
-
多态性
多态性指的是在程序中允许出现重名现象,它指在一个类中定义的属性和方法被其他类继承后,它们可以具有不同的数据类型或表现出不同的行为,这使得同一个属性和方法在不同的类中具有不同的语义。
class类:对象的抽象
创建类
类是对象的抽象,它用于描述一组对象的共同特征和行为。类中可以定义成员变量和成员函数,其中成员变量用于描述对象的特征,也被称作属性,成员函数用于描述对象的行为,可简称为函数或方法。
class Person{
var name = "X"
var age = 18
fun sayHello() {
println("name:$name\nage:$age")
}
}
创建对象
var p=Person()
创建了一个Person类的实例对象。
实际应用:
class Person {
// 成员属性
var name = "X"
var age = 18
// 成员方法
fun sayHello() {
println("Name:$name\nAge:$age")
}
}
fun main() {
val p = Person()
p.sayHello()
// Name:X
// Age:18
println("Name:${p.name}")
// Name:X
p.name="Z"
println("Name:${p.name}")
// Name:Z
}
类的封装
类的封装是指在定义一个类时,将类中的属性私有化,即使用private关键字来修饰,私有属性只能在它所在的类中被访问。为了让外界访问这些私有属性,需要提供一些使用public修饰的公有方法。(这句话目前还没明白)。
class Student {
var name:String = ""
private var age:Int = 0
fun setAge(age:Int) {
if (age>=0) {
this.age = age
println("Age:$age")
}else {
println("WRONG.")
}
}
fun sayHello() {
println("Name:$name\nAge:$age")
}
}
fun main() {
val stu = Student()
stu.name = "Z"
stu.setAge(-4)
stu.sayHello()
stu.setAge(3)
}
其中,this关键字指的是该类的当前对象,意在与Student类中的私有属性age区分开。
构造函数
构造函数是类的一个特殊成员,它会在类实例化对象时被自动调用。Kotlin中的构造函数分为两种——主构函数和次构函数。
简单来说,构造函数的意图是在初步实例化类时,就可以简单地传入参数,进行使用(跟函数一样)。
主构函数:constructor
一个类可以有一个主构造函数和多个次构造函数。主构函数位于类头跟在类名之后,如果主构造函数没有任何注解或可见性修饰符(如public),constructor关键字可省略。
主构函数的语法如下:
class ClassName constructor([args1,args2,args3]) {}
-
当在定义一个类时,如果没有显示指定主构函数,则Kotlin编译器会默认为其生成一个无参主构函数,这点和Java是一样的。
class ClassName constructor() {} //第一种写法 class ClassName() {} //第二种写法 -
有参的主构函数:通过有参的构造函数为属性赋值。主构造器中不能包含任何代码,初始化代码可以放在初始化代码段中,初始化代码段使用
init关键字作为前缀。class Clerk constructor(username:String) { init{ println("I am $username") } } fun main() { var clerk=Clerk("Z") }// I am Z在《Kotlin从基础到实战》一书中,对主构函数的举例十分多此一举:
class Clerk constructor(username:String) { var name:String init{ name=username println("I am $name") } }多此一举之处在于,
var name:String这一行以及下面的赋值是完全没有必要的。往下看之后,作者是为了刻意介绍this关键字。也就是说,他试图将构造函数中的username和类中的成员变量name都变成name,然后为了区分,便故意在init中加入this.name。因为之前已经研究过this,这里不再详说。class Clerk constructor(name:String) { var name:String init{ //这里this.name指的是类中的成员变量name this.name = name println("I am $name") } }
次构函数:constructor和this
在Kotlin中,可以定义多个次构函数,次构函数同样使用
constructor关键字定义,只不过次构函数位于类体中。
次构函数应用于以下的场景中,即假如主构函数只有一个name属性,而尚存在同时给name属性和age属性赋值的情况,这就需要再添加一个函数,也就是次构函数。次构造函数必须调用主构造函数或其他次构造函数,其调用方式为次构函数:this(参数列表)。
class Workers constructor(name:String) {
init {
println("I am $name.")
}
constructor (name:String,age:Int):this(name) {
println("I am $name,\nI am $age years old.")
}
constructor (name:String,age:Int,sex:String):this(name,age) {
println("I am $name,\nI am $age years old,\nI am a $sex.")
}
}
fun main() {
var person = Workers("Z",18,"man")
}
从代码中可看出,第1个次构函数继承自主构函数,第2个次构函数继承自第1个次构函数。不仅第2个次构函数被调用,第1个次构函数和主构函数都会被调用。
类的继承
继承
在Kotlin中,类的继承是指在一个现有类的基础上去构建一个新类,构建出来的新类被称作子类,现有类被称作父类,子类会自动拥有父类所有可继承的属性和方法。
这个概念与python一致。在具体的语法上,父类最前端应当加上open关键字(原因是Kotlin中的所有类都默认使用final关键字修饰,不能被继承),子类需要在声明时加上:FatherClass(),举例如下:
open class Father() {
fun sayHello() {
println("Hello")
}
}
class Son:Father() {}
fun main() {
var son = Son()
son.sayHello() // "Hello"
}
- 一个子类只能继承一个父类。
- 多个子类可以继承一个父类。
- 多层继承是可以的,如A继承B,B继承C,这时,A也可算是C的子类。
继承的重写
指的是有些时候,子类并不会完全继承父类,对于所继承的某些方法或属性需要进行修改,这种修改叫做方法或属性的重写。
- 父类中,需要被重写的方法或属性需要在前面加上
open关键字。 - 子类中,重写的方法或属性前需要使用
override关键字。
open class Father() {
open var name = "Z"
open var age = 25
fun sayHello() {
println("Hello,I am $name,I am $age years old.")
}
}
class Son():Father() {
override var name = "X"
override var age = 15
}
fun main() {
val father = Father()
val son = Son()
father.sayHello()
son.sayHello()
//Hello,I am Z,I am 25 years old.
//Hello,I am X,I am 15 years old.
}
super:重写后再次调用父类
如前所述,重写后因为有了新的方法或属性,但这时,假如还想访问父类的属性或方法,则需要super.func。
A BORING example:
open class Father() {
open var name = "Z"
open var age = 25
open fun sayHello() {
println("Hello,I am $name,I am $age years old.")
}
}
class Son():Father() {
override var name = "X"
override var age = 15
override fun sayHello() {
super.sayHello()
println("Hello,I am ${super.name}'s son,I am $name,I am $age years old.")
}
}
fun main() {
val son = Son()
son.sayHello()
}
一个没看懂的小知识,Any类
Kotlin中所有类都继承Any类,它是所有类的父类,如果一个类在声明时没有指定父类,则默认父类为Any类,在程序运行时,Any类会自动映射为Java中的java.lang.Object类。
在Kotlin中,所有类型都是引用类型,这些引用类型统一继承父类Any,Any类中默认提供了3个方法,分别是equals()、hashCode()和toString()。
抽象类与接口
抽象类 abstract
当定义一个类时,通常需要定义一些方法来描述该类的行为特征,但有时这些方法的实现方式是无法确定的。因此,可以将其定义为抽象方法,抽象方法使用abstract关键字修饰,该方法没有方法体,在使用时需要实现其方法体,当一个类中包含了抽象方法,该类必须使用abstract关键字定义为抽象类。
也就是说,实际需要是需要在类中定义一个方法,但是这个方法具体内容不知道,于是可以将方法先定义为抽象方法,有了抽象方法,方法所在的类就必须定义为抽象类。
但是,有抽象方法的类必须声明抽象类,但是抽象类中不必须有抽象方法。
抽象类不能被实例化,原因在于抽象方法不能被调用。如果想调用,则需要创建一个子类。
如下:
abstract class Animal() {
abstract fun eat()
}
class Monkey(var food:String):Animal() {
// var food = food
override fun eat() {
println("Monkeys are eating $food.")
}
}
fun main() {
var monkey = Monkey("banana")
monkey.eat()
// Monkeys are eating banana.
}
接口 interface
如果一个抽象类中的所有方法都是抽象的,则可以将这个类用另外一种方式来定义,即接口。接口可以说是一个特殊的抽象类,在定义接口时,需要使用interface关键字来声明:
interface Animal {
fun eat()
}
eat()并没有abstract关键字修饰,因为接口默认带有了Animal没有括号- 与抽象类一样,接口需要子类以实例化来调用方法
类继承接口
interface Animal {
fun eat()
}
class Monkey(var food:String):Animal {
override fun eat() {
println("Monkeys are eating ${food}.")
}
}
fun main() {
val monkey = Monkey("banana")
monkey.eat()
}
接口继承接口(其实是一样的)
interface Animal {
fun eat()
}
interface Monkey:Animal {
fun sleep()
}
class GoldenMonkey(var food:String):Monkey{
override fun eat() {
println("I am GoldenMonkey,I love $food.")
}
override fun sleep() {
println("I am GoldenMonkey,I love sleep.")
}
}
fun main() {
var g = GoldenMonkey("banana")
g.eat()
g.sleep()
}
当然,一个类可以接收多个接口或抽象类,用,隔开。
常见的类
嵌套类与内部类inner
嵌套类和内部类都是类中套一个类,二者的区别也仅仅在于里面的类前面是否有一个inner标记,有inner的是内部类。
二者实质上的区别在于,嵌套类没有inner,所以不能访问它本身以外的变量或方法,举例如下:
class A{
var name="A"
class B{
fun sayHello() {
println("Hello!$name!")
}
}
}
这段代码是错误的,因为class B本身作为嵌套类,是不可以访问class A中的变量的。
但是换成内部类就成立了:
class A{
var name="A"
inner class B {
fun sayHello(){
println("Hello $name.")
}
}
}
fun main() {
A().B().sayHello()
}
这就成立了。
枚举类enum
每个枚举常量都是一个对象,枚举常量用逗号分隔,枚举类的前面用
enum关键字来修饰。
enum class Week{
Monday,Tuesday,Wednesday,Tuesday,Friday,Saturday,Sunday
}
枚举类支持构造函数*(简单来说,构造函数的意图是在初步实例化类时,就可以简单地传入参数,进行使用(用法跟函数一样)。)*
enum class Week(val what:String, val doSomething:String) {
Monday("Mon","Work"),
Tuesday("Tue","Party"),
Wednesday("Wed","Work"),
Thursday("Thu","Work"),
Friday("Fri","Work"),
Saturday("Sat","Overtime"),
Sunday("Sun","Rest")
}
密封类 sealed
每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。
- 密封类必须用sealed关键字来修饰。
- 由于密封类的构造函数是私有的,因此密封类的子类只能定义在密封类的内部或者同一个文件中。
sealed class A {
class A1:A() {}
class A2:A() {}
class B() {}
}
class A3:A() {}
其中,密封类A的子类有A1 A2 A3,类B属于A的嵌套类。
==注意==:密封类适用于子类可数的情况,而枚举类适用于实例可数的情况。
数据类 data
数据类不可以用abstract、open、sealed或inner关键字来修饰。
在实际开发中,经常会用到数据类来存储一些数据信息,这些信息一般是通过该类的主构造函数来传递的。
data class Man(var name:String,var age:Int) {
init {
println("Name=$name\nAge:$age")
}
}
fun main() {
var man:Man = Man("Z",20)
}
单例模式 object
单例模式就是在程序运行期间针对该类只存在一个实例。就好比这个世界只有一个太阳一样,假设现在要设计一个太阳类,则该类就只能有一个实例对象,否则就违背了事实。
object SingleMode {
var s = "SingleMode"
fun sayHello() {
println("Hello,I am $s.")
}
}
fun main() {
SingleMode.s = "X"
SingleMode.sayHello()
SingleMode.s = "Z"
SingleMode.sayHello()
//Hello,I am X.
//Hello,I am Z.
}
可以看出两个特点:
- 单例类的最大特征是可以直接通过
类名.成员名的形式调用类中的属性与函数,不需要创建该类的实例对象,这是因为通过object关键字创建单例类时,默认创建了该类的单例对象,因此在调用该类中的属性和函数时,不需要重新创建该类的实例对象。 - 单例类的实例对象只有一个,但是可以重复调用、修改其属性,调用函数。
伴生对象 companion object
伴生对象是在类加载时初始化,生命周期与该类的生命周期一致。
定义伴生对象是通过companion关键字标识的,由于每个类中有且仅有一个伴生对象,因此也可以不指定伴生对象的名称,并且其他对象可以共享伴生对象。
class Company {
// Factory伴生对象名称可以写也可以省略,因为一个类只能由一个伴生对象
companion object Factory {
fun sayHello() {
println("I am a Companion Object.")
}
}
}
fun main() {
//两种调用方法
Company.Factory.sayHello()
Company.sayHello()
}