前言
类和继承在Kotlin中是比较常见的,可以对比之前java来学习Kotlin中的类和继承。
类
Kotlin中定义一个类class
// 类名 类头 类体
class Apple(var name:String){}
类头和类体是可选的,如果一个类没有类体,可省略花括号
class Apple
构造函数
分为主构造函数和次构造函数。一个类中可以有多个次构造函数和一个主构造函数,主构造函数是类头的一部分,仅跟在类名后,使用constructor关键字
class Apple constructor(var name:String)
主构造函数没有任何注解或可见修饰符,可省略这个constructor关键字
class Apple(var name:String){}
一个类有注解或访问修饰符,constructor关键字不可以省略,注解或访问修饰符要放到constructor关键字的前面。
//私有化主构造器
class Apple private constructor(var name:String){}
// 给构造器函数添加注解
class Apple @Inject constructor(var name:String){}
// 构造器函数注解和私有化构造器
class Apple @Inject private constructor(var name:String){}
init初始化块
主构造函数不能包含任何代码(次构造函数可以),初始化的代码需要放到init关键字作为i前缀的初始化块中
class Apple(var name:String){
init{
println("init")
}
}
已经有了类体的花括号{},如果再加上一个主构造函数的花括号{}就不协调,放到init块中。init块是主构造函数的一部分,它将和类属性的初始化按照他们在类体中出现的顺序执行
class Person(var name:String){
var height = 171.also{ println("height=$it name=$name") }
init {
println("person init one $height $name")
}
var age = 18.also { println("age=$it name=$name") }
init {
println("class init two $age $name")
}
}
fun test() {
val person = Person("ming")
}
主构造器中声明属性
构造函数中的参数可以在类属性的初始化器和init块中访问。将参数声明成属性(var val)可以在类的方法中访问该参数。
class Person(name:String){
private fun info(){
println(name)// 无法访问name,报红。构造函数中的参数无法在类中的方法中访问
}
}
class Person(var name:String){
private fun info(){
println(name)// name已经通过var进行了属性声明
}
}
参数声明成类属性可以在类的方法中访问该属性。通常声明一个类的属性是放到类体中声明的,Kotlin提供了便捷语法,可以在类的主构造函数中声明一个类属性,也是主构造函数的参数。
次构造函数
一个类可以用多个次构造函数
class Person{
constructor(name:String){
println("name=$name")
}
constructor(name:String,age:Int){
println("name:$name age:$age")
}
}
一个类既有主构造函数又有次构造函数,每个次构造函数需要委托给主构造函数,可以委托或别的次构造函数委托。委托到同一个类的另一个次构造函数用关键字this
class Person(name:String){
// 直接委托
constructor(name:String,age:Int):this(name){
println("name:$name age:$age")
}
// 间接委托
constructor(name:String,age:Int,sex:Int):this(name,age){
println("name:$name age:$age sex:$sex")
}
}
初始化块代码会作为主构造函数的一部分,委托给主构造函数init块中的代码会作为次构造函数的第一条语句,初始化块与属性初始化器中的代码都会在次构造函数体之前执行。即使没有主构造函数,委托仍会隐式发生
fun test() {
val person = Person("xiake",18)
}
class Person(name:String){
val info = name.apply { println("info init name:$name") }
init {
println("class init name:$name")
}
constructor(name:String,age:Int):this(name){
println("name:$name age:$age")
}
constructor(name:String,age:Int,sex:Int):this(name,age){
println("name:$name age:$age sex:$sex")
}
}
输出:
info init name:xiake
class init name:xiake
name:xiake age:18
创建类的实例
创建对象实例省略了new关键字
val person = Person("xiake",18)
继承
java中共同的超类Object,Kotlin中超类Any,没有类型声明的类默认超类Any。Any有三个fangf:equals hashcode toString。所有的类都定义了这些方法。
public open class Any {
public open operator fun equals(other: Any?): Boolean
public open fun hashCode(): Int
public open fun toString(): String
}
默认Kotlin的类都不可被继承,即final。用关键字open修饰一个类可被继承,放在关键字class前。
open class Person // Person类可被继承
Java遵循单继承(extends)多实现(implements)原则。Kotlin也遵循这个原则,继承和实现都用冒号:表示,在类头中把超类型放在冒号之后,多个超类用冒号隔开
open class Person(name:String){}
class Student(name:String):Person(name){}
interface Callback{}
class Student1(name:String):Person(name),Callback{}
子类有一个主构造函数,父类使用子类主构造函数中的参数就地初始化。
class MainActivity :AppcompatActivity(){}
在java中不需要加小括号(),子类的构造函数必须访问父类的构造函数。当类只有次构造函数,在次构造函数去访问,可省略括号()
class Person:Any{
constructor():super()
}
public class MyView extends View {
public MyView(Context context) {
super(context);
}
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
AndroidStudio右键,Convert Java File to Kotlin File
class MyView : View {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
}
MyView中没有主构造函数,只有三个次构造函数。在次构造函数中去访问父类的构造函数,不需要小括号了()
覆盖方法
使一个方法被覆盖,依然用open关键字,在fun前面加open。
open class Person constructor(name:String){
open fun eat(){
println("eat")
}
}
class Student(var name:String):Person(name){
override fun eat() {
super.eat()
}
}
覆盖一个方法必须用override关键字修饰。标记为override成员本身是开放的,可以在子类中覆盖它。如果禁止覆盖,使用final关键字
class Student(var name:String):Person(name){
final override fun eat() {
super.eat()
}
}
覆盖属性
在父类中声明的属性在子类中重新声明必须以override开头
open class Person{
open val name:String = "Any"
}
class Student:Person(){
override var name:String = "xiake"
}
可以用var属性覆盖val属性。反之不行。因为val声明了get方法,覆盖为var子类额外声明了一个set方法。可以将一个需要覆盖的属性声明到主构造函数中
open class Person{
open val name:String = "Any"
}
class Student constructor(override var name:String):Person(){}
子类的初始化顺序
先完成父类的初始化,再初始化子类。遵循面向对象继承原则,子类的构造函数必须访问父类的构造函数
fun test() {
val person = Student("xiake")
}
open class Person{
init {
println("person init")
}
open val name:String ="Any".also { println("person name:$it") }
}
class Student constructor(override var name:String):Person(){
init {
println("student init")
}
private val age = 20.also { println("student age:$it") }
}
输出:
person init
person name:Any
student init
student age:20
嵌套类和内部类
- 嵌套类:在一个类或一个接口内部声明一个嵌套类
fun test() {
val a = Outer.A()
val b = CallBack.B()
}
class Outer {
init {
println("outer init")
}
class A {
init {
println("A init")
}
}
}
interface CallBack{
class B{
init {
println("B init")
}
}
}
嵌套类中无法访问外部类的属性和方法
- 内部类:使用
inner关键字定义内部类,内部类的创建需要先创建外部类的实例,内部类可以访问外部类的属性和方法
fun test() {
val a = Outer().A()
}
class Outer {
init {
println("outer init")
}
val name = "outer"
fun getInfo() = "info"
inner class A {
init {
println("A init")
}
val info = "$name ${getInfo()}".also { println("info=$it") }
}
}
输出:
outer init
A init
info=outer info
访问父类中的方法和属性
子类可以使用super关键字调用父类的函数和属性访问器
open class Person{
open fun eat(){
println("eat")
}
open val work get() = "work"
val sleep = 5*60*1000L
}
class Student :Person(){
override fun eat() {
super.eat()
}
override val work = super.work
val mySleep = super.sleep
}
在内部类中访问外部类中的属性和方法,使用@标签限制的super
open class Person{
open fun eat(){
println("eat")
}
open val work get() = "work"
val sleep = 5*60*1000L
}
class Student :Person(){
override fun eat() {
super.eat()
}
override val work = super.work
val mySleep = super.sleep
inner class A{
val work = super@Student.work
fun getInfo(){
super@Student.eat()
}
}
}
覆盖多个父类中的同名方法
一个类它有多个父类,多个父类有同名方法。重写该方法,该方法内访问父类中的同名方法,使用尖括号中父类型名限定的super
open class Person{
open fun eat(){
println("eat")
}
}
interface CallBack{
fun eat(){}// kotlin接口中的方法可以有默认实现
}
class Student:Person(),CallBack{
override fun eat() {
super<Person>.eat()
super<CallBack>.eat()
}
}
抽象类
定义抽象类abstract,抽象类默认可继承的。将abstract放在class前面
abstract class BaseFragment(){}
抽象类和接口区别在于抽象类即可以定义抽象方法和属性,也可以定义非抽象方法和属性
abstract class Person{
abstract fun eat()
abstract val name:String
fun work(){
println("work")
}
val info = "person info"
}
class Student:Person(){
override fun eat() {
println("student eat")
}
override val name: String="name"
}
Class和File区别
Kotlin中可以不用依靠类的作用域,创建一个属性和方法。将顶层属性 扩展函数 顶层函数放在一个File文件中,可以在任意需要的地方访问它们。或在一个文件中定义多个类或接口,直接定义成File。普通类直接定义成Class文件。在Kotlin类的作用域外部,添加任意的属性 方法 接口 类 Kotlin编译器会自动识别将Class文件转换成.kt的File文件
总结
类与继承对高阶函数和Lambda表达式是更容易理解一点。在平时开发中我们必然遇到并切运用都项目中去。