搜索
首页系统教程Windows系列第4章 类与面向对象编程第4章 类与面向对象编程

第4章 类与面向对象编程第4章 类与面向对象编程

May 07, 2025 pm 04:42 PM
windows电脑access工具ai面向对象编程区别spring mvcjava框架排列标准库

第4章 类与面向对象编程

在前面的章节中,我们学习了Kotlin的语言基础知识、类型系统等相关的知识。在本章节以及下一章中,我们将一起来学习Kotlin对面向对象编程以及函数式编程的支持。

本章我们介绍Kotlin的面向对象编程。

4.1 面向对象编程简史

50年代后期,在用FORTRAN语言编写大型程序时,由于没有封装机制,那个时候的变量都是“全局变量”,那么就会不可避免的经常出现变量名冲突问题。在ALGOL60中采用了以 Begin - End 为标识的程序块,使块内变量名是局部的,以避免它们与程序中块外的同名变量相冲突。在编程语言中首次提供了封装(保护)的机制。此后,程序块结构广泛用于Pascal 、Ada、C等高级语言之中。

60年代中后期,Simula语言在ALGOL基础上研制开发,它将ALGOL的块结构概念向前发展一步,提出了对象的概念,并使用了类,也支持类继承。其后的发展简史如下图所示:

第4章 类与面向对象编程第4章 类与面向对象编程

面向对象发展简史

阿伦·凯(Alan Kay)是Smalltalk面向对象编程语言的发明人之一,也是面向对象编程思想的创始人之一,同时,他还是笔记本电脑最早的构想者和现代Windows GUI的建筑师。最早提出PC概念和互联网的也是阿伦·凯,所以人们都尊称他为“预言大师”。他是当今IT界屈指可数的技术天才级人物。

面向对象编程思想主要是复用性和灵活性(弹性)。复用性是面向对象编程的一个主要机制。灵活性主要是应对变化的特性,因为客户的需求是不断改变的,怎样适应客户需求的变化,这是软件设计灵活性或者说是弹性的问题。

Java是一种面向对象编程语言,它基于Smalltalk语言,作为OOP语言,它具有以下五个基本特性:

1.万物皆对象,每一个对象都会存储数据,并且可以对自身执行操作。因此,每一个对象包含两部分:成员变量和成员方法。在成员方法中可以改变成员变量的值。

2.程序是对象的集合,他们通过发送消息来告知彼此所要做的事情,也就是调用相应的成员函数。

3.每一个对象都有自己的由其他对象所构成的存储,也就是说在创建新对象的时候可以在成员变量中使用已存在的对象。

4.每个对象都拥有其类型,每个对象都是某个类的一个实例,每一个类区别于其它类的特性就是可以向它发送什么类型的消息,也就是它定义了哪些成员函数。

5.某一个特定类型的所有对象都可以接受同样的消息。另一种对对象的描述为:对象具有状态(数据,成员变量)、行为(操作,成员方法)和标识(成员名,内存地址)。

面向对象语言其实是对现实生活中的实物的抽象。

每个对象能够接受的请求(消息)由对象的接口所定义,而在程序中必须由满足这些请求的代码,这段代码称之为这个接口的实现。当向某个对象发送消息(请求)时,这个对象便知道该消息的目的(该方法的实现已定义),然后执行相应的代码。

我们经常说一些代码片段是优雅的或美观的,实际上意味着它们更容易被人类有限的思维所处理。

对于程序的复合而言,好的代码是它的表面积要比体积增长的慢。

代码块的“表面积”是是我们复合代码块时所需要的信息(接口API协议定义)。代码块的“体积”就是接口内部的实现逻辑(API背后的实现代码)。

在面向对象编程中,一个理想的对象应该是只暴露它的抽象接口(纯表面, 无体积),其方法则扮演箭头的角色。如果为了理解一个对象如何与其他对象进行复合,当你发现不得不深入挖掘对象的实现之时,此时你所用的编程范式的原本优势就荡然无存了。

面向对象编程是一种编程思想,相比于早期的结构化程序设计,抽象层次更高,思考解决问题的方式上也更加贴近人类的思维方式。现代编程语言基本都支持面向对象编程范式。

计算机领域中的所有问题,都可以通过向上一层进行抽象封装来解决.这里的封装的本质概念,其实就是“映射”。从面向过程到面向对象,再到设计模式,架构设计,面向服务,Sass/Pass/Iass等等的思想,各种软件理论思想五花八门,但万变不离其宗——

你要解决一个怎样的问题?你的问题领域是怎样的?你的模型(数据结构)是什么?你的算法是什么?你对这个世界的本质认知是怎样的?你的业务领域的逻辑问题,流程是什么? 等等。

面向对象编程的以现实世界中的事物(对象)为中心来思考, 认识问题, 并根据这些事物的本质特征, 把它们抽象表示为系统中的类。其核心思想可以用下图简要说明:

第4章 类与面向对象编程第4章 类与面向对象编程

面向对象编程

面向对象编程基于类编程,更加贴近人类解决问题的习惯方法。让软件世界更像现实世界。面向对象编程通过抽象出关键的问题域来分解系统。对象不仅能表示具体的事物,还能表示抽象的规则、计划或事件。关于面向对象编程的核心的概念如下图所示

第4章 类与面向对象编程第4章 类与面向对象编程

面向对象编程的核心的概念

4.2 声明类

本节介绍Kotlin中类和构造函数的声明。

4.2.1 空类

使用class关键字声明类。我们可以声明一个什么都不干的类

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">class AnEmptyClassfun main(args: Array<String>) {    val anEmptyClass = AnEmptyClass() // Kotlin中不需要使用new    println(anEmptyClass)    println(anEmptyClass is AnEmptyClass) // 对象实例是AnEmptyClass类型    println(anEmptyClass::class)}</code>

输出

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">com.easy.kotlin.AnEmptyClass@2626b418trueclass com.easy.kotlin.AnEmptyClass (Kotlin reflection is not available)</code>
4.2.2 声明类和构造函数

在Kotlin中, 我们可以在声明类的时候同时声明构造函数,语法格式是在类的后面使用括号包含构造函数的参数列表

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">class Person(var name: String, var age: Int, var sex: String) { // 声明类和构造函数    override fun toString(): String { // override关键字,重写toString()        return "Person(name='$name', age=$age, sex='$sex')"    }}</code>

使用这样的简洁语法,可以通过主构造器来定义属性并初始化属性值(这里的属性值可以是var或val)。

在代码中这样使用Person类

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">val person = Person("Jack", 29, "M")println("person = ${person}")</code>

输出

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">person = Person(name='Jack', age=29, sex='M')</code>

另外,我们也可以先声明属性,等到构造实例对象的时候再去初始化属性值,那么我们的Person类可以声明如下

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">class Person1 {    lateinit var name: String // lateinit 关键字表示该属性延迟初始化    var age: Int = 0  // lateinit 关键字不能修饰 primitive 类型    lateinit var sex: String    override fun toString(): String {        return "Person1(name='$name', age=$age, sex='$sex')"    }}</code>

我们可以在代码中这样创建Person1的实例对象

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">    val person1 = Person1()    person1.name = "Jack"    person1.age = 29    person1.sex = "M"    println("person1 = ${person1}")</code>

输出

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">person1 = Person1(name='Jack', age=29, sex='M')</code>

如果我们想声明一个具有多种构造方式的类,可以使用 constructor 关键字声明构造函数,示例代码如下

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">class Person2() { // 无参的主构造函数    lateinit var name: String    var age: Int = 0    lateinit var sex: String    constructor(name: String) : this() { // this 关键字指向当前类对象实例        this.name = name    }    constructor(name: String, age: Int) : this(name) {        this.name = name        this.age = age    }    constructor(name: String, age: Int, sex: String) : this(name, age) {        this.name = name        this.age = age        this.sex = sex    }    override fun toString(): String {        return "Person1(name='$name', age=$age, sex='$sex')"    }}</code>

上面的写法,总体来看也有些样板代码,其实在IDEA中,我们写上面的代码,只需要写下面的3行,剩下的就交给IDEA自动生成了

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">class Person2 {    lateinit var name: String    var age: Int = 0    lateinit var sex: String}</code>

自动生成构造函数的操作示意图

1.在当前类中“右击”鼠标操作,选择Generate (在Mac上的快捷键是 Command N)

第4章 类与面向对象编程第4章 类与面向对象编程

右击鼠标操作

点击之后,跳出对话框:生成次级构造函数
第4章 类与面向对象编程第4章 类与面向对象编程

选择Generate

选择构造函数的参数
第4章 类与面向对象编程第4章 类与面向对象编程

生成次级构造函数

选中相应的属性,点击OK,即可生成。

一个属性都不选,生成

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">constructor()</code>

选择一个 name 属性,生成

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">    constructor(name: String) {        this.name = name    }</code>

选择name,age属性生成

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">    constructor(name: String, age: Int) : this(name) {        this.name = name        this.age = age    }</code>

3个属性都选择,生成

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">    constructor(name: String, age: Int, sex: String) : this(name, age) {        this.name = name        this.age = age        this.sex = sex    }</code>

最后,我们可以在代码中这样创建Person2的实例对象

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">    val person21 = Person2()    person21.name = "Jack"    person21.age = 29    person21.sex = "M"    println("person21 = ${person21}")    val person22 = Person2("Jack", 29)    person22.sex = "M"    println("person22 = ${person22}")    val person23 = Person2("Jack", 29, "M")    println("person23 = ${person23}")</code>

实际上,我们在编程实践中用到最多的构造函数,还是这个

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">class Person(var name: String, var age: Int, var sex: String)</code>

而当确实需要通过比较复杂的逻辑来构建一个对象的时候,可采用构建者(Builder)模式来实现。

4.3 抽象类与接口

抽象类表示“is-a”的关系,而接口所代表的是“has-a”的关系。

抽象类用来表征问题领域的抽象概念。所有编程语言都提供抽象机制。机器语言是对机器的模仿抽象,汇编语言是对机器语言的高层次抽象,高级语言(Fortran,C,Basic等)是对汇编的高层次抽象。而我们这里所说的面向对象编程语言是对过程函数的高层次封装。这个过程如下图所示

第4章 类与面向对象编程第4章 类与面向对象编程

编程语言的抽象机制

抽象类和接口是Kotlin语言中两种不同的抽象概念,他们的存在对多态提供了非常好的支持。这个机制跟Java相同。

4.3.1 抽象类与抽象成员

抽象是相对于具象而言。例如设计一个图形编辑软件,问题领域中存在着长方形(Rectangle)、圆形(Circle)、三角形(Triangle)等这样一些具体概念,它们是具象。但是它们又都属于形状(Shape)这样一个抽象的概念。它们的关系如下图所示

第4章 类与面向对象编程第4章 类与面向对象编程

形状Shape的抽象继承关系

对应的Kotlin代码如下

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">package com.easy.kotlinabstract class Shapeclass Rectangle : Shape() // 继承类的语法是使用冒号 : , 父类需要在这里使用构造函数初始化class Circle : Shape()class Triangle : Shape()</code>

因为抽象的概念在问题领域中没有对应的具体概念,所以抽象类是不能够实例化的。下面的代码编译器会报错

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">val s = Shape() // 编译不通过!不能实例化抽象类</code>

我们只能实例化它的继承子类。代码示例如下

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">val r = Rectangle()println(r is Shape) // true</code>

现在我们有了抽象类,但是没有成员。通常一个类的成员有属性和函数。抽象类的成员也必须是抽象的,需要使用abstract 关键字修饰。下面我们声明一个抽象类Shape,并带有width ,heigth,radius属性和 area() 函数, 代码如下

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">abstract class Shape {    abstract var width: Double    abstract var heigth: Double    abstract var radius: Double    abstract fun area(): Double}</code>

这个时候,继承抽象类Shape的方法如下

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">class Rectangle(override var width: Double, override var heigth: Double, override var radius: Double) : Shape() { // 声明类的同时也声明了构造函数    override fun area(): Double {        return heigth * width    }}class Circle(override var width: Double, override var heigth: Double, override var radius: Double) : Shape() {    override fun area(): Double {        return 3.14 * radius * radius    }}</code>

其中,override 是覆盖写父类属性和函数的关键字。

在代码中这样调用具体实现的类的函数

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">fun main(args: Array<String>) {    val r = Rectangle(3.0, 4.0, 0.0)    println(r.area()) // 12.0    val c = Circle(0.0, 0.0, 4.0)    println(c.area()) // 50.24}</code>

抽象类中可以有带实现的函数,例如我们在抽象类Shape中添加一个函数onClick()

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">abstract class Shape {    ...    fun onClick() { // 默认是final的,不可被覆盖重写        println("I am Clicked!")    }}</code>

那么,我们在所有的子类中都可以直接调用这个onClick()函数

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">    val r = Rectangle(3.0, 4.0, 0.0)    r.onClick() // I am Clicked!    val c = Circle(0.0, 0.0, 4.0)    c.onClick() // I am Clicked!</code>

父类Shape中的onClick()函数默认是final的,不可被覆盖重写。如果想要开放给子类重新实现这个函数,我们可以在前面加上open 关键字

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">abstract class Shape {    ...    open fun onClick() {        println("I am Clicked!")    }}</code>

在子类中这样覆盖重写

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">class Rectangle(override var width: Double, override var heigth: Double, override var radius: Double) : Shape() {    override fun area(): Double {        return heigth * width    }    override fun onClick(){        println("${this::class.simpleName} is Clicked!")    }}fun main(args: Array<String>) {    val r = Rectangle(3.0, 4.0, 0.0)    println(r.area())    r.onClick()}</code>

其中,this::class.simpleName 是Kotlin中的反射的API,在Gradle工程的build.gradle中需要添加依赖 compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" ,我们将在后面的章节中详细介绍。

上面的代码运行输出

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">12.0Rectangle is Clicked!</code>

当子类继承了某个类之后,便可以使用父类中的成员变量,但是并不是完全继承父类的所有成员变量。具体的原则如下:

1.能够继承父类的public和protected成员变量;不能够继承父类的private成员变量;

2.对于父类的包访问权限成员变量,如果子类和父类在同一个包下,则子类能够继承;否则,子类不能够继承;

3.对于子类可以继承的父类成员变量,如果在子类中出现了同名称的成员变量,则会发生隐藏现象,即子类的成员变量会屏蔽掉父类的同名成员变量。如果要在子类中访问父类中同名成员变量,需要使用super关键字来进行引用。

4.3.2 接口

接口是一种比抽象类更加抽象的“类”。接口本身代表的是一种“类型”的概念。但在语法层面,接口本身不是类,不能实例化接口,我们只能实例化它的实现类。

接口是用来建立类与类之间的协议。实现该接口的实现类必须要实现该接口的所有方法。在Java 8 和Kotlin中,接口可以实现一些通用的方法。

接口是抽象类的延伸,Kotlin跟Java一样,不支持同时继承多个父类,也就是说继承只能存在一个父类(单继承)。但是接口不同,一个类可以同时实现多个接口(多组合),不管这些接口之间有没有关系。这样可以实现多重继承。

和Java类似,Kotlin使用interface作为接口的关键词:

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">interface ProjectService</code>

Kotlin 的接口与 Java 8 的接口类似。与抽象类相比,他们都可以包含抽象的方法以及方法的实现:

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">interface ProjectService {    val name: String    val owner: String    fun save(project: Project)    fun print() {        println("I am project")    }}</code>

接口是没有构造函数的。我们使用冒号: 语法来实现一个接口,如果有多个用逗号隔开:

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">class ProjectServiceImpl : ProjectService // 跟继承抽象类语法一样,使用冒号class ProjectMilestoneServiceImpl : ProjectService, MilestoneService // 实现多个接口使用逗号( ,) 隔开</code>

在重写print()函数时,因为我们实现的ProjectService、MilestoneService都有一个print()函数,当我们直接使用super.print()时,编译器是无法知道我们想要调用的是那个里面的print函数的,这个我们叫做覆盖冲突,如下图所示

第4章 类与面向对象编程第4章 类与面向对象编程

覆盖冲突

这个时候,我们可以使用下面的语法来调用:

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">super<ProjectService>.print()super<MilestoneService>.print()</code>
4.4 object对象

单例模式很常用。它是一种常用的软件设计模式。例如,Spring中的Bean默认就是单例。通过单例模式可以保证系统中一个类只有一个实例。即一个类只有一个对象实例。

Kotlin中没有 静态属性和方法,但是可以使用关键字 object 声明一个object 单例对象:

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">package com.easy.kotlinobject User {    val username: String = "admin"    val password: String = "admin"    fun hello() {        println("Hello, object !")    }}fun main(args: Array<String>) {    println(User.username) // 跟Java的静态类一样的调用形式    println(User.password)    User.hello()}</code>

Kotlin中还提供了 伴生对象 ,用companion object关键字声明:

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">class DataProcessor {    companion object DataProcessor {        fun process() {            println("I am processing data ...")        }    }}fun main(args: Array<String>) {    DataProcessor.process() // I am processing data ...}</code>

一个类只能有1个伴生对象。

4.5 数据类

顾名思义,数据类就是只存储数据,不包含操作行为的类。Kotlin的数据类可以为我们节省大量样板代码(Java 中强制我们要去写一堆getter、setter,而实际上这些方法都是“不言自明”的),这样最终代码更易于理解和便于维护。

使用关键字为 data class 创建一个只包含数据的类:

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">data class LoginUser(val username: String, val password: String)</code>

在IDEA中提供了方便的Kotlin工具箱,我们可以把上面的代码反编译成等价的Java代码。步骤如下

1.菜单栏选择:Tools -> Kotlin -> Show Kotlin Bytecode

第4章 类与面向对象编程第4章 类与面向对象编程

菜单栏选择:Tools -> Kotlin -> Show Kotlin Bytecode

点击Decompile
第4章 类与面向对象编程第4章 类与面向对象编程

点击Decompile

反编译之后的Java代码
第4章 类与面向对象编程第4章 类与面向对象编程

反编译之后的Java代码

上面这段反编译之后的完整的Java代码是

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">public final class LoginUser {   @NotNull   private final String username;   @NotNull   private final String password;   @NotNull   public final String getUsername() {      return this.username;   }   @NotNull   public final String getPassword() {      return this.password;   }   public LoginUser(@NotNull String username, @NotNull String password) {      Intrinsics.checkParameterIsNotNull(username, "username");      Intrinsics.checkParameterIsNotNull(password, "password");      super();      this.username = username;      this.password = password;   }   @NotNull   public final String component1() {      return this.username;   }   @NotNull   public final String component2() {      return this.password;   }   @NotNull   public final LoginUser copy(@NotNull String username, @NotNull String password) {      Intrinsics.checkParameterIsNotNull(username, "username");      Intrinsics.checkParameterIsNotNull(password, "password");      return new LoginUser(username, password);   }   // $FF: synthetic method   // $FF: bridge method   @NotNull   public static LoginUser copy$default(LoginUser var0, String var1, String var2, int var3, Object var4) {      if ((var3 & 1) != 0) {         var1 = var0.username;      }      if ((var3 & 2) != 0) {         var2 = var0.password;      }      return var0.copy(var1, var2);   }   public String toString() {      return "LoginUser(username="   this.username   ", password="   this.password   ")";   }   public int hashCode() {      return (this.username != null ? this.username.hashCode() : 0) * 31   (this.password != null ? this.password.hashCode() : 0);   }   public boolean equals(Object var1) {      if (this != var1) {         if (var1 instanceof LoginUser) {            LoginUser var2 = (LoginUser)var1;            if (Intrinsics.areEqual(this.username, var2.username) && Intrinsics.areEqual(this.password, var2.password)) {               return true;            }         }         return false;      } else {         return true;      }   }}</code>

编译器会从主构造函数中声明的属性,自动创建以下函数:

equals() / hashCode() 函数toString() 格式为"LoginUser(username=" this.username ", password=" this.password ")"component1(),component2() 函数返回对应下标的属性值,按声明顺序排列copy() 函数: 根据旧对象属性重新 new LoginUser(username, password) 一个对象出来

如果这些函数在类中已经被明确定义了,或者从超类中继承而来,编译器就不再生成。

数据类有如下限制:

主构造函数至少包含一个参数参数必须标识为val 或者 var不能为 abstract, open, sealed 或者 inner不能继承其它类 (但可以实现接口)

另外,数据类可以在解构声明中使用:

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">package com.easy.kotlindata class LoginUser(val username: String, val password: String)fun main(args: Array<String>) {    val loginUser = LoginUser("admin", "admin")    val (username, password) = loginUser    println("username = ${username}, password = ${password}") // username = admin, password = admin}</code>

Kotlin 标准库提供了 Pair 和 Triple数据类 。

4.6 注解

注解是将元数据附加到代码中。元数据信息由注解 kotlin.Metadata定义。

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">@Retention(AnnotationRetention.RUNTIME)@Target(AnnotationTarget.CLASS)internal annotation class Metadata</code>

这个@Metadata信息存在于由 Kotlin 编译器生成的所有类文件中, 并由编译器和反射读取。例如,我们使用Kotlin声明一个注解

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">annotation class Suspendable // Java中使用的是@interface Suspendable</code>

那么,编译器会生成对应的元数据信息

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">@Retention(RetentionPolicy.RUNTIME)@Metadata(   mv = {1, 1, 7},   bv = {1, 0, 2},   k = 1,   d1 = {"\u0000\n\n\u0002\u0018\u0002\n\u0002\u0010\u001b\n\u0000\b\u0086\u0002\u0018\u00002\u00020\u0001B\u0000¨\u0006\u0002"},   d2 = {"Lcom/easy/kotlin/Suspendable;", "", "production sources for module kotlin_tutorials_main"})public @interface Suspendable {}</code>

Kotlin 的注解完全兼容 Java 的注解。例如,我们在Kotlin中使用Spring Data Jpa

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">interface ImageRepository : PagingAndSortingRepository<Image, Long> {       @Query("SELECT a from #{#entityName} a where a.isDeleted=0 and a.isFavorite=1 and a.category like %:searchText% order by a.gmtModified desc")    fun searchFavorite(@Param("searchText") searchText: String, pageable: Pageable): Page<Image>    @Throws(Exception::class)    @Modifying    @Transactional    @Query("update #{#entityName} a set a.isFavorite=1,a.gmtModified=now() where a.id=?1")    fun addFavorite(id: Long)}</code>

用起来跟Java的注解基本一样。再举个Kotlin使用Spring MVC注解的代码实例

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">@Controllerclass MeituController {    @Autowired    lateinit var imageRepository: ImageRepository    @RequestMapping(value = *arrayOf("/", "meituView"), method = arrayOf(RequestMethod.GET))    fun meituView(model: Model, request: HttpServletRequest): ModelAndView {        model.addAttribute("requestURI", request.requestURI)        return ModelAndView("meituView")    }}</code>

从上面的例子,我们可以看出Kotlin使用Java框架非常简单方便。

4.7 枚举

Kotlin中使用 enum class 关键字来声明一个枚举类。例如

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">enum class Direction {    NORTH, SOUTH, WEST, EAST // 每个枚举常量都是一个对象, 用逗号分隔}</code>

相比于字符串常量,使用枚举能够实现类型安全。枚举类有两个内置的属性:

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">    public final val name: String    public final val ordinal: Int</code>

分别表示的是枚举对象的值跟下标位置。例如上面的Direction枚举类,它的枚举对象的信息如下

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">>>> val north = Direction.NORTH>>> north.nameNORTH>>> north.ordinal0>>> north is Directiontrue</code>

每一个枚举都是枚举类的实例,它们可以被初始化:

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">enum class Color(val rgb: Int) {    RED(0xFF0000),    GREEN(0x00FF00),    BLUE(0x0000FF)}</code>

枚举Color的枚举对象的信息如下

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">>>> val c = Color.GREEN>>> cGREEN>>> c.rgb65280>>> c.ordinal1>>> c.nameGREEN</code>
4.8 内部类4.8.1 普通嵌套类

Kotlin中,类可以嵌套。一个类可以嵌套在其他类中,而且可以嵌套多层。

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">class NestedClassesDemo {    class Outer {        private val zero: Int = 0        val one: Int = 1        class Nested {            fun getTwo() = 2            class Nested1 {                val three = 3                fun getFour() = 4            }        }    }}</code>

测试代码:

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">    val one = NestedClassesDemo.Outer().one    val two = NestedClassesDemo.Outer.Nested().getTwo()    val three = NestedClassesDemo.Outer.Nested.Nested1().three    val four = NestedClassesDemo.Outer.Nested.Nested1().getFour()</code>

我们可以看出,代码中 NestedClassesDemo.Outer.Nested().getTwo() 访问嵌套类的方式是直接使用 类名.来访问, 有多少层嵌套,就用多少层类名来访问。

普通的嵌套类,没有持有外部类的引用,所以是无法访问外部类的变量的:

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">class NestedClassesDemo {class Outer {        private val zero: Int = 0        val one: Int = 1        class Nested {            fun getTwo() = 2            fun accessOuter() = {                println(zero) // error, cannot access outer class                println(one)  // error, cannot access outer class            }        }}}</code>
4.8.2 嵌套内部类

如果一个类Inner想要访问外部类Outer的成员,可以在这个类前面添加修饰符 inner。内部类会带有一个对外部类的对象的引用:

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">package com.easy.kotlinclass NestedClassesDemo {    class Outer {        private val zero: Int = 0        val one: Int = 1        inner class Inner {            fun accessOuter() = {                println(zero) // works                println(one) // works            }        }    }}fun main(args: Array<String>) {    val innerClass = NestedClassesDemo.Outer().Inner().accessOuter()}</code>

我们可以看到,当访问inner class Inner的时候,我们使用的是Outer().Inner(), 这是持有了Outer的对象引用。跟普通嵌套类直接使用类名访问的方式区分。

4.8.3 匿名内部类

匿名内部类,就是没有名字的内部类。既然是内部类,那么它自然也是可以访问外部类的变量的。

我们使用对象表达式创建一个匿名内部类实例:

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">class NestedClassesDemo {    class AnonymousInnerClassDemo {        var isRunning = false        fun doRun() {            Thread(object : Runnable { // 匿名内部类                override fun run() {                    isRunning = true                    println("doRun : i am running, isRunning = $isRunning")                }            }).start()        }    }}</code>

如果对象是函数式 Java 接口,即具有单个抽象方法的 Java 接口的实例,例如上面的例子中的Runnable接口:

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">@FunctionalInterfacepublic interface Runnable {    public abstract void run();}</code>

我们可以使用lambda表达式创建它,下面的几种写法都是可以的:

代码语言:javascript代码运行次数:0运行复制
<code class="language-javascript">            fun doStop() {                var isRunning = true                Thread({                    isRunning = false                    println("doStop: i am not running, isRunning = $isRunning")                }).start()            }            fun doWait() {                var isRunning = true                val wait = Runnable {                    isRunning = false                    println("doWait: i am waiting, isRunning = $isRunning")                }                Thread(wait).start()            }            fun doNotify() {                var isRunning = true                val wait = {                    isRunning = false                    println("doNotify: i notify, isRunning = $isRunning")                }                Thread(wait).start()            }</code>

更多关于Lambda表达式以及函数式编程相关内容,我们将在下一章节中介绍。

本章小结

本章我们介绍了Kotlin面向对象编程的特性: 类与构造函数、抽象类与接口、继承与组合等知识,同时介绍了Kotlin中的注解类、枚举类、数据类、嵌套类、内部类、匿名内部类、单例object对象等特性类。

总的来说,在面向对象编程范式的支持上,Kotlin相比于Java增加不少有趣的功能与特性支持,这使得我们代码写起来更加方便快捷了。

我们知道,在Java 8 中,引进了对函数式编程的支持:Lambda表达式、Function接口、stream API等,而在Kotlin中,对函数式编程的支持更加全面丰富,代码写起来也更加简洁优雅。下一章中,我们来一起学习Kotlin的函数式编程。

以上是第4章 类与面向对象编程第4章 类与面向对象编程的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
如何回滚Windows 11更新如何回滚Windows 11更新May 12, 2025 pm 08:01 PM

Windows 11 更新导致系统问题?别慌!本文提供三种回滚更新的方法,助您恢复系统稳定性。 方法一:通过 Windows 设置回滚更新 此方法适用于更新时间在 10 天以内的用户。 步骤 1: 点击“开始”菜单,进入“设置”。您也可以按键盘上的 Windows 键 I。 步骤 2: 在“设置”中,选择“系统”,然后点击“恢复”。 步骤 3: 在“恢复选项”下,找到“以前的 Windows 版本”。如果“返回”按钮可点击,则可将系统回滚到之前的版本。 步骤 4: 系统会询问您回滚的原因

13个Windows键盘快捷键13个Windows键盘快捷键May 12, 2025 am 03:02 AM

掌握Windows键盘快捷键不仅仅是效率;它简化了您的整个计算体验。 Windows的界面可能不如直观,隐藏在菜单层中的关键设置。 幸运的是,存在无数捷径

如何加快PC(Windows 11)如何加快PC(Windows 11)May 11, 2025 pm 06:01 PM

您的Windows 11 PC的运行速度比平常慢吗? 打开应用程序和加载网站占据永恒?你并不孤单! 本指南提供了三个简单的无下载解决方案,可以在没有复杂设置调整的情况下提高计算机的性能

这款迷你PC兼作不太好的平板电脑这款迷你PC兼作不太好的平板电脑May 11, 2025 am 06:01 AM

这台迷你PC伪装成平板电脑,还有很多不足之处。 7英寸,1290x800分辨率的屏幕令人难以置信。虽然有些人可能将其用于媒体消费(类似于7英寸的亚马逊消防片),但不太可能是主要选择

Razer的新型Basilisk Mobile&Joro用于旅途游戏Razer的新型Basilisk Mobile&Joro用于旅途游戏May 11, 2025 am 03:02 AM

Razer 推出全新 Basilisk Mobile 和 Joro 游戏键盘,专为移动游戏玩家打造。Joro 键盘虽小巧,却配备完整功能键排和全尺寸方向键,提供熟悉舒适的布局。低矮的按键设计有助于保持人体工学手部姿势,减少长时间游戏带来的疲劳。Joro 使用游戏级按键,确保快速精准的按键响应和令人满意的反馈,并采用耐用的 UV 涂层 ABS 键帽增强耐用性。 对于竞技玩家而言,Joro 还配备了 Snap Tap 模式,通过允许在两个按键之间进行更快的输入而无需释放第一个按键来提高第一人称射击游

联想Lenovo的Legion 9i游戏笔记本电脑具有巨大的18英寸屏幕联想Lenovo的Legion 9i游戏笔记本电脑具有巨大的18英寸屏幕May 10, 2025 pm 09:04 PM

联想最新的Legion 9i游戏笔记本电脑是一家强大的力量,但可移植性可能是一个问题。 这款第10代模型具有突破性的18英寸显示屏,这是Legion 9i系列的首个。 屏幕提供令人惊叹的视觉效果,最多4K Res

您可以购买MSI的最新小型PC您可以购买MSI的最新小型PCMay 10, 2025 am 03:01 AM

MSI揭露紧凑型,无风扇嵌入式PC:MS-C927 对于那些欣赏小型计算机的人来说,MSI的最新产品MS-C927是一个值得注意的补充,尽管其美学可能并非屡获殊荣。 这款无风扇的嵌入式PC是Desig

我最喜欢的坐立玩具是机械开关我最喜欢的坐立玩具是机械开关May 10, 2025 am 01:04 AM

机械键盘的清脆声令人愉悦,但只有使用键盘时才能听到。这些小巧廉价的减压玩具解决了这个问题。 玩弄咔哒作响的东西能让我平静下来——声音很悦耳,我的双手在专注于其他事情的同时也能有所活动。我过去常拿伸缩笔这样做(在需要手写论文的年代),这让我的同学们很苦恼。 但现在,机械键盘和机械轴带来了同样的效果。机械轴发出的咔哒声和声音令人愉悦,不同机械轴的不同手感和“咔哒声”更增加了满足感。 相关 ##### 我沉迷于机械轴和键帽,而不是收藏键盘 好吧,也许我只是沉迷于收集任何咔哒作响的东西。 文章 1

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具