Kotlin从入门到放弃之基础篇(三)
类和继承
类
和Java相同,Kotlin中声明类用class关键字:
class DemoClass{
}
从上面的例子可以看出,类的声明包括类名、类头(包括类型参数和主构造函数等)、类主体,类主体用大括号包裹。其中,类头和类主体是可选的,如果没有类体可以省略大括号:
class DemoClass;
构造函数
在Kotlin中可以有一个主构造函数(包含于类头中,跟在类名的后面可以有可选的类型参数)和多个二级构造函数:
class DemoClass @Inject constructor(num : Int);
如果主构造函数没有注解或者可见的说明,则constructor关键字可以省略:
class DemoClass (var num :Int)
♣ 在主函数中声明的参数可以作为全局变量使用
♣ 主函数中声明的全局变量和在类主体中声明的全局变量是一样的,只是主函数简化了其写法。
注意:主函数中不能包含任何的代码,如果有需要可以将初始化代码放入以init做前缀的初始化块中:
class DemoClass( num : Int){
init{//以init作为前缀的初始化块
val firstStr :String ="这是一个String".//在初始化块中声明
print("the num is $num")
print("the firstStr is $firstStr")
}
val length=firstStr.length //属性声明
//下方操作不符合规范,为错误代码
fun test(){
val length = firstStr.length //不能使用
}
}
♣ 从上面的例子中可以看出声明在初始化块中的属性只能在初始话块或者属性声明中使用,不能在其他的地方使用
关于二级构造函数:
♣ 首先二级构造函数不能存在val 或者var的声明
♣ 声明二级构造函数需要加constructor前缀
♣ 如果类有主构造函数,那么声明二级构造函数的过程中需要直接或者间接通过另一个二级构造函数代理主构造函数。使用this关键字。
♣ 如果类没有构造函数(包括主构造函数和二级构造函数),则其会产生一个没有参数的构造函数,为public.所以如果你不想自己的构造函数为public则需要声明一个空的主构造函数。
class PersonDemo(var name :String){//存在主构造函数
constructor(name : String, sex :String) : this(name){
//...
}
constructor(name : String,sex : String,age : Int) :this(name,sex){
//...
}
}
class SecondPersonDeom{//不存在主构造函数
constructor(name : String){//不能使用 "this()"
}
constructor(name : String, sex : String) : this(name){
}
}
注意:在无主构造函数时不能使用constructor(name : String) : this(),这种操作是不被允许的。
另一个注意: Kotlin中没有new关键字:
val XiaoMing=Person("小明","男");
类成员
和Java中相同的是,Kotlin中包含:构造函数和初始化代码块、函数、属性、内部类、对象声明。
继承
Kotlin中所有的类都有共同的父类Any(地位类似于Java中的Object)。Any只有equals(),hashCode()以及toString()三个成员。
class Demo //不存在父类时的声明,隐式继承于Any
显式的声明一个父类:
open class Person(name : String)
class Student(name : String) : Person(name)
当子类有构造函数时,父类必须在主构造函数中进行初始化:
open class Person(var name : String)
class Stuent(name : String) : Person(name)//父类必须进行初始化
当子类没有构造函数时,则必须在子类的每一个二级构造函数中用super关键字初始化父类,或者代理另一个二级构造函数。在此过程中子类中不同的二级构造函数可以调用父类不同的构造方法:
open class Person(name : String){
constructor(name : String, sex : String) :this(name){
}
}
class Student :Person{
constructor(name : Stirng,sex : String) : super(name){
}
constructor(name : String, sex : String ) : super(name,sex){
}
}
♣ 注意,Kotlin中的所有的类默认为final,只有标注了open的类才能被继承。
关于重写
在Kotlin中的函数和类都默认是final的,也就是说默认不能被子类重写,如果想用子类重写该函数,则需要用open关键字修饰。
open class Person{
open fun doSomething(){
}
open fun doRun(){
}
}
class Stuent : Person{
override fun doSomething(){
print("this is child")
}
final override fun doRun(){
print("this is child run")
}
}
♣ 在final类(默认为final)中,open类型的成员是不被允许的
♣ override关键字修饰的成员是open的,它可以在子类中被重写,如果不想被重写则需要加final关键字
关于继承的一些规则:如果一个类从他的直接父类中继承了同一个函数的多个实现(即继承了不同父类中名称相同的函数),那么该类必须重写这个函数并提供自己的实现(或者只是继承两者并不添加自己的实现),我们用super关键字表示父类提供的方法。
open class A{
open fun m(){
print("this is a")
}
}
interface B{
fun m()
fun n()
}
class C :A , B{
override fun m (){//只是继承两者并不添加自己的实现
super<A>.m()//调用父类A中的m
super<B>.m()//调用父类B中的m
}
}
关于子类继承父类的成员变量:
♣ 能够继承父类中的public和protected修饰的成员变量,但是不能继承private成员变量
♣ 对于父类中的包访问权限的成员变量,如果子类和父类在同一包下则能够访问,反之不能。
♣ 关于子类继承父类中的成员变量,如果子类中存在相同名称的成员变量,则会覆盖父类中的该成员变量。如果想继续使用父类中的成员变量则需要使用super关键字来进行引用。
抽象类和接口
在Kotlin中通过抽象类和接口来完成抽象。Kotlin通过abstract关键字进行修饰。
抽象方法:只有声明而没有具体实现的方法:
abstract fun demoFun()
含有抽象方法的类为抽象类,必须用abstract关键字修饰
抽象属性:抽象属性同样使用abstract关键字进行修饰
abstract val age : Int
abstract var name : String
♣ 抽象属性在抽象类中不能被初始化
♣ 在子类中,如果没有构造函数必须手动初始化抽象属性,反之则可在主构造函数中进行初始化。
♣ 抽象属性只能在抽象类中被声明。
抽象类:含有抽象方法的类称之为抽象类用abstract关键字修饰,抽象类中不止可以有抽象方法还可以有具体实现的方法(参考Java)。
♣ 抽象方法必须为public或者protected(如果为private则不能被继承),默认为public
♣ 不能用抽象类来创建实例
♣ 当一个抽象类被继承时,除非子类也是抽象方法,否则必须实现父类的抽象方法和初始化其抽象属性
接口:
Kotlin中的接口和Java 8中的类似,他们可以同时包含抽象方法和方法的实现。与抽象类不同的是,接口中不能保存状态,所以他的属性都是抽象的:
interface InterfaceDemo{
var name: String
fun doSomething()
fun doRun(){
//可以包含方法体
}
}
♣ 一个类可以实现多个接口
抽象类和接口的区别:
1、接口不能保存状态,所以接口中的属性都是抽象的,但是抽象类中可以存在非抽象的属性
2、一个类只能继承一个抽象类,但是一个类可以实现多个接口
3、抽象类是对事物的抽象,即对类抽象。而接口是对行为的抽象。抽象类是对整个类整体进行抽象,而接口是对类的局部行为进行抽象。
4、设计层面的不同。抽象类作为很多子类的父类,它更提供了一种模版式的设计。而接口则是一种方法的签名,是一种行为规范,提供的是辐射式的设计。
伴随对象
Kotlin中不支持静态方法,虽然官方推荐我们使用包级别的方法,但是依然不能满足我们的需求。这时候需要用到伴随对象,用companion关键字修饰:
fun main(args : Array<String>){
Stuent.Utils.doSomething()
}
class Stuent(name : String){
companion object Utils {//被companion修饰的伴随对象
fun doSomething(){
print("this is Utils")
}
}
}
通过上面的例子我们可以看出伴随对象的调用不用创建包含伴随对象的实例。
♣ 伴随对象所在的类被加载,则伴随对象被初始化(和Java静态成员一样)。
密封类
Kotlin中的密封类是Java中没有的新概念。密封类允许你表达约束层次结构,其中对象只能是给定类型之一。这相当于一个枚举类的扩展:枚举值集合的类型是严格限制的,每个枚举常量只有一个实例,密封类的子类可以包含不同状态的多个实例:
sealed class Person{//通过sealed关键字进行修饰
class Stuent(var name : String) : Person()
class Salesman(var name : String,sex : String) : Person()
}
♣ 密封类可以有子类,但必须嵌套在密封类声明内部。
♣ 密封类的扩展可以在任何地方,不必在密封类声明内部进行