Kotlin类与继承、抽象类、嵌套类、内部类、匿名内部类、伴生对象、对象声明、对象表达式

举报
yd_221104950 发表于 2020/11/28 22:52:39 2020/11/28
【摘要】 Kotlin类与继承、抽象类、嵌套类、内部类、匿名内部类、伴生对象、对象声明、对象表达式 定义属性主构造器次构造函数Kotlin 中没有 new 关键字抽象类内部类嵌套类匿名内部类类的修饰符继承重写方法重写属性派生类的初始化顺序调用超类实现重写的规则伴生对象对象声明对象表达式对象表达式和对象声明之间的语义差异 定义 与Java一样,使用class关键...

定义

与Java一样,使用class关键字来定义。Kotlin 类声明可以包含:构造函数初始化代码块函数属性内部类对象声明

类名为 Example

class Human {  // 类名为 Example // 大括号内是类体构成
}

  
 
  • 1
  • 2
  • 3

空类:如果没有类体,花括号可以省略。

// 空类
class Empty

  
 
  • 1
  • 2

属性

类的属性可以用 var 声明为可变的,使用只读关键字 val 声明为不可变

// 类的属性可以用关键字 var 声明为可变的,否则使用只读关键字 val 声明为不可变。
class Human { var name: String = "Tom" val url: String = "http://www.baidu.com" ...
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

属性声明的完整语法:

var <propertyName>[: <PropertyType>] [= <property_initializer>] [<getter>] [<setter>]

  
 
  • 1
  • 2
  • 3

getter 和 setter 都是可选。如果属性类型可以从初始化语句或者类的成员函数中推断出来,那就可以省去类型,val不允许设置setter函数,因为它是只读的

var allByDefault: Int? // 错误: 需要一个初始化语句, 默认实现了 getter 和 setter 方法
var initialized = 1 // 类型为 Int, 默认实现了 getter 和 setter
val simple: Int? // 类型为 Int ,默认实现 getter ,但必须在构造函数中初始化
val inferredType = 1   // 类型为 Int 类型,默认实现 getter
var lastName: String = "zhang" get() = field.toUpperCase()   // 将变量赋值后转换为大写 set

var no: Int = 100 get() = field // 后端变量 set(value) { if (value < 10) { // 如果传入的值小于 10 返回该值 field = value } else { field = -1 // 如果传入的值大于等于 10 返回 -1 } }
var heiht: Float = 145.4f private set // set方法是私有的,也就是说只能在类中使用

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

Kotlin 中类不能有字段。提供了 Backing Fields(后端变量) 机制,备用字段使用field关键字声明,field 关键词只能用于属性的访问器:

var no: Int = 100 get() = field // 后端变量 set(value) { if (value < 10) { // 如果传入的值小于 10 返回该值 field = value } else { field = -1 // 如果传入的值大于等于 10 返回 -1 } }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Kotlin提供了属性延迟加载方案。这个对于那些对象变量特别有用,只需要在声明时使用lateinit 关键字描述,

lateinit var subject: MySubject

  
 
  • 1

但是注意,在使用前这个变量前一定要先初始化它再使用:

class AoC { lateinit var objec: MySubject fun setSubject() { objec = MySubject() }

}

class MySubject

@Test
fun testAoC(){ var obj = AoC() obj.setSubject() obj.objec.toString()
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

实际上,还有一种lazy委托,更方便,不过它只能用在只读属性上:

val objec: MySubject by lazy { MySubject() }

  
 
  • 1

主构造器

类可以有一个 主构造器,以及一个或多个次构造器,主构造器是类头部的一部分,位于类名称之后。

主构造器中不能包含任何代码,初始化代码可以放在初始化代码段中,初始化代码段使用 init 关键字作为前缀。类体的初始化过程是从上到下按出现的顺序依次执行属性的初始化和init代码块

class Human constructor(firstName: String) {
	init { println("FirstName is $firstName") }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

如果主构造器没有任何注解,也没有任何可见度修饰符,那么constructor关键字可以省略:

class Human(firstName: String) {}

  
 
  • 1

注意:如果构造器有注解,或者有可见度修饰符,这时constructor关键字是必须的,注解和修饰符要放在它之前。

class Customer public @Inject constructor(name: String) { /*...*/ }

  
 
  • 1

主构造器的参数不仅可以在初始化代码块中使用,还可以在类主体定义的属性初始化代码中使用。语法糖:可以通过主构造器来定义属性并初始化属性值(可以是var或val)

class People(val firstName: String, val lastName: String) { //...
}

  
 
  • 1
  • 2
  • 3

次构造函数

类除了有一个 主构造函数外还可以有一个或多个次构造函数。次构造函数需要加前缀 constructor来定义。

class Human { // 主构造函数是一个默认的空主构造函数 constructor(parent: Person) {// 次构造函数 parent.children.add(this) }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

如果类有主构造函数,每个次构造函数必须直接或间接通过另一个次构造函数调用主构造函数。在同一个类中调用另一个构造函数使用 this 关键字

class Human(val name: String) { constructor (name: String, age:Int) : this(name) { // 初始化... }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

如果一个非抽象类没有声明构造函数(主构造函数或次构造函数),它会产生一个没有参数的public构造函数。如果不想类有公共的构造函数,就得声明一个空的private主构造函数:

class DontCreateMe private constructor () {}

  
 
  • 1

在 JVM 虚拟机中,如果主构造函数的所有参数都有默认值,编译器会生成一个附加的无参的构造函数,这个构造函数会直接使用默认值。这使得 Kotlin 可以更简单的使用像 Jackson 或者 JPA 这样使用无参构造函数来创建类实例的库

class Customer(val customerName: String = "")

  
 
  • 1

Kotlin 中没有 new 关键字

val human = Human() // Kotlin 中没有 new 关键字

  
 
  • 1

抽象类

类本身,或类中的部分成员,都可以声明为abstract的。抽象成员在类中不存在具体的实现。无需对抽象类或抽象成员标注open注解,因为这是默认的。

open class Base { open fun f() {}
}

abstract class Derived : Base() { override abstract fun f() 
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

内部类

内部类使用 inner 关键字来表示。内部类会带有一个对外部类的对象的引用,所以内部类可以访问外部类成员属性和成员函数。


class Outer { private val bar: Int = 1 var v = "成员属性" fun hello(){ print("Hello world") } /**嵌套内部类**/ inner class Inner { fun foo() = bar  // 访问外部类成员 fun innerTest() { hello(); var o = this@Outer //获取外部类的成员变量 println("内部类可以引用外部类的成员,例如:" + o.v) } }
}

fun main(args: Array<String>) { val demo = Outer().Inner().innerTest() // 调用格式 ...
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

为了消除歧义,要访问来自外部作用域的 this,我们使用this@label,其中 @label 是一个 代指 this 来源的标签,就是类的名称。

嵌套类

没有inner关键字修饰的内部类就是嵌套类,它们的区别就是嵌套类没有外部类的引用,因此它不能使用外部类的属性和方法:

class Outer { // 外部类 private val bar: Int = 1 class Nested { // 嵌套类 fun foo() = 2 }
}
fun main(args: Array<String>) { val demo = Outer.Nested().foo() // 调用格式:外部类.嵌套类.嵌套类方法/属性 ...
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

匿名内部类

使用对象表达式来创建匿名内部类:

// 定义接口
interface TestInterFace { fun test()
}
class Test { fun setInterFace(test: TestInterFace) {// 使用接口定义变量 test.test() }
} fun main(args: Array<String>) { var test = Test() // 使用对象表达式来创建接口对象,即匿名内部类的实例 test.setInterFace(object : TestInterFace { override fun test() { println("对象表达式创建匿名内部类的实例") } })
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

类的修饰符

类属性修饰符,标示类本身特性:

abstract // 抽象类  
final // 类不可继承,默认属性
enum // 枚举类
open // 类可继承,类默认是final的
annotation  // 注解类

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

访问权限修饰符:

private // 仅在同一个文件中可见
protected  // 同一个文件中或子类可见
public // 所有调用的地方都可见
internal   // 同一个模块中可见

  
 
  • 1
  • 2
  • 3
  • 4

继承

所有类都继承 Any 类,就像Java中的所有类都继承Object一样。Any 默认提供了三个函数:

equals()

hashCode()

toString()

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

如果一个类要被继承,可以使用 open 关键字进行修饰,因为类默认是final,禁止被继承的,所以必须用open打开被继承功能。

open class Base(p: Int) // 定义基类
class Derived(p: Int) : Base(p)

  
 
  • 1
  • 2

如果子类有主构造函数, 则基类必须在主构造函数中立即初始化。

open class Person(var name : String, var age : Int){// 基类
}
class Student(name : String, age : Int, var no : String, var score : Int) : Person(name, age) {}

  
 
  • 1
  • 2
  • 3

如果子类没有主构造函数,则必须在每一个次级构造函数中用 super 关键字初始化基类,或者在代理另一个构造函数。初始化基类时,可以调用基类的不同构造方法。

class MyView : View {
	constructor(ctx: Context) : super(ctx)
	constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

  
 
  • 1
  • 2
  • 3
  • 4

重写方法

在基类中,使用fun声明函数时,此函数默认为final修饰,不能被子类重写。如果允许子类重写该函数,那么就要手动添加 open 修饰它, 子类重写方法使用 override 关键词:

open class Shape { open fun draw() {} fun fill() {}  // 这个默认是final的,不能被重写
}
class Circle: Shape() { override fun draw() { } // 必须要用关键字override
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

一个标记了override的成员本身是open的,它可以在子类中被重写,如果你想禁止重写,可以使用final

open class Rectangle() : Shape() {
	final override fun draw() { /*...*/ }
}

  
 
  • 1
  • 2
  • 3

那么Rectangle的子类就不能再重写draw()这个方法了。

如果有多个相同的方法(继承或者实现自其他类,如A、B类),则必须要重写该方法,使用super范型去选择性地调用父类的实现:

open class A { open fun f () { print("A") } fun a() { print("a") }
}

interface B { fun f() { print("B") } //接口的成员变量默认是 open 的 fun b() { print("b") }
}

class C() : A() , B{ override fun f() { super<A>.f()//调用 A.f() super<B>.f()//调用 B.f() }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

重写属性

属性重写使用 override 关键字,属性必须具有兼容类型,每一个声明的属性都可以通过初始化程序或者getter方法被重写:

open class Foo { open val x: Int get() {return 99}
}

class Bar1 : Foo() { override var x: Int = 88 set(value) { field = value + 8}
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

可以用一个var属性重写一个val属性,但是反过来不行。因为val属性本身定义了getter方法,重写为var属性会在衍生类中额外声明一个setter方法。
可以在主构造函数中使用 override 关键字作为属性声明的一部分:

interface Foo { val count: Int
}

class Bar1(override val count: Int) : Foo

class Bar2 : Foo { override var count: Int = 0
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

派生类的初始化顺序

先对超类的构造函数中的参数进行求值,再完成超类的初始化。最后才是派生类的初始化。

open class Base(val name: String) {
	init { println("Initializing Base") }
	open val size: Int = name.length.also { println("Initializing size in Base: $it") }
}

class Derived(name: String,val lastName: String) : Base(name.capitalize().also { println("Argument for Base: $it") }) { init { println("Initializing Derived") }
	override val size: Int = (super.size + lastName.length).also { println("Initializing size in Derived:
$it") }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在超类构造函数执行时,派生类里定义的或重写的属性还没有被初始化。如果这些属性中的任何一个被 用在基类的初始化逻辑(直接或间接通过另一个重写open成员实现),那么将会导致不正确的行为或运行失败。设计一个基类的时候,因此你应该避免在构造函数中、属性初始化器、init块中使用open成员。

调用超类实现

在派生类中的代码可以使用super关键字调用它的超类函数和属性访问器实现。

open class Rectangle {
		open fun draw() { println("Drawing a rectangle") }
	val borderColor: String get() = "black"
}
class FilledRectangle : Rectangle() {
	override fun draw() { super.draw() println("Filling the rectangle")
	}
	val fillColor: String get() = super.borderColor
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

一个内部类里面,使用用外部类名限定的super关键字访问外部类的超类:super@Outer

class FilledRectangle: Rectangle() {
	fun draw() { /* ... */ }
	val borderColor: String get() = "black"
	inner class Filler {
		fun fill() { /* ... */ }
		fun drawAndFill() { super@FilledRectangle.draw() // Calls Rectangle's implementation of draw() fill() println("Drawn a filled rectangle with color ${super@FilledRectangle.borderColor}") // Uses Rectangle's implementation of borderColor's get()
		}
	}
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

重写的规则

在Kotlin,实现继承受以下规则约束:

  1. 如果一个类从它的直接超类继承了同一成员的多个实现,它必须重写这个成员,并提供它自己的实现。

  2. 为了表示继承的实现所在的父类型,我们使用通过在尖括号中的父类型名称限定的super,如super<Base>

   open class Rectangle { open fun draw() { /* ... */ } } interface Polygon { fun draw() { /* ... */ } // 接口成员,默认都是open的 } class Square() : Rectangle(), Polygon { // 编译器要求重写draw() override fun draw() { super<Rectangle>.draw() // 调用Rectangle.draw() super<Polygon>.draw() // 调用Polygon.draw() } }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Square继承RectanglePolygon,它们都有draw()方法的实现,所以在Square中必须重写draw()方法,并提供它自己的实现,这有助于消除歧义。

伴生对象

类内部的对象声明可以用 companion 关键字标记,这样它就与外部类关联在一起,我们就可以直接通过外部类访问到对象的内部元素。
注意:每一个类只能有一个伴生对象,即一个类里面只能声明一个内部关联对象,即关键字 companion 只能使用一次。

class MyClass {
	companion object Factory {
		fun create(): MyClass = MyClass()
	}
}
val instance = MyClass.create()   // 访问到对象的内部元素

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

伴生对象的成员可以通过仅使用类名作为限定词来调用:

val instance = MyClass.create()

  
 
  • 1

伴生对象的名称可以省略,在这种情况下,Companion这个名字将会被使用:

class MyClass {
	companion object { }
}
val x = MyClass.Companion

  
 
  • 1
  • 2
  • 3
  • 4

类的名字作为类的伴生对象的引用,无论这个伴生对象是否命名:

class MyClass1 {
	companion object Named { }
}
val x = MyClass1
class MyClass2 {
	companion object { }
}
val y = MyClass2

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

注意:虽然伴生对象的成员看起来像其他语言中的静态成员,在运行时它们仍然是真实对象的实例成员,可以实现接口:

interface Factory<T> {
	fun create(): T
}
class MyClass {
	companion object : Factory<MyClass> {
		override fun create(): MyClass = MyClass()
	}
}
val f: Factory<MyClass> = MyClass

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

然而,在JVM中,如果你使用@JvmStatic注解,你可以有伴生对象生成的静态方法和静态字段的成员。

对象声明

对象声明是用来实现单例的最好方式。Kotlin 使用 object 关键字来声明一个对象。Kotlin 中我们可以方便的通过对象声明来获得一个单例。

object DataManager { fun registerDataProvider(provider: DataProvider) { } const val count: Int = 88
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

直接通过名称来使用这个单例:

DataManager.count

  
 
  • 1

对象可以有超类型:

object DefaultListener : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { // …… } override fun mouseEntered(e: MouseEvent) { // …… }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

当对象声明在另一个类的内部时,这个对象并不能通过外部类的实例访问到该对象,而只能通过类名来访问,同样该对象也不能直接访问到外部类的方法和变量。

class Site { var name = "菜鸟教程" object DeskTop{ var url = "www.runoob.com" fun showName(){ print{"desk legs $name"} // 错误,不能访问到外部类的方法和变量 } }
}
fun main(args: Array<String>) { var site = Site() site.DeskTop.url // 错误,不能通过外部类的实例访问到该对象 Site.DeskTop.url // 正确,只能通过类名来访问到该以对象
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

对象表达式

通过对象表达式实现一个匿名内部类的对象用于方法的参数中:

// 定义接口
interface TestInterFace { fun test()
}
class Test { fun setInterFace(test: TestInterFace) {// 使用接口定义变量 test.test() }
}
fun main(args: Array<String>) { var test = Test() // 使用对象表达式来创建接口对象,即匿名内部类的实例 test.setInterFace(object : TestInterFace { override fun test() { println("对象表达式创建匿名内部类的实例") } })
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

对象可以继承于某个基类,或者实现其他接口:

open class A(x: Int) { public open val y: Int = x
}

interface B {}

val ab: A = object : A(1), B { override val y = 15
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

通过对象表达式可以越过类的定义直接得到一个对象:

fun main(args: Array<String>) { val site = object { var name: String = "菜鸟教程" var url: String = "www.runoob.com" } println(site.name) println(site.url)
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

注意,匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数的返回类型或者用作公有属性的类型,那么该函数或属性的实际类型会是匿名对象声明的超类型,如果你没有声明任何超类型,就会是 Any。在匿名对象中添加的成员将无法访问。

class C { // 私有函数,所以其返回类型是匿名对象类型 private fun foo() = object { val x: String = "x" } // 公有函数,所以其返回类型是 Any fun publicFoo() = object { val x: String = "x" } fun bar() { val x1 = foo().x // 没问题 val x2 = publicFoo().x  // 错误:未能解析的引用“x” }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

在对象表达中可以方便的访问到作用域中的其他变量:

fun countClicks(window: JComponent) { var clickCount = 0 var enterCount = 0 window.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { clickCount++ } override fun mouseEntered(e: MouseEvent) { enterCount++ } }) // ……
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

对象表达式和对象声明之间的语义差异

  • 对象表达式是在使用他们的地方立即执行的
  • 对象声明是在第一次被访问到时延迟初始化的
  • 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配

文章来源: blog.csdn.net,作者:WongKyunban,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/weixin_40763897/article/details/107395951

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。