展开菜单
首页 精品内容 本月促销 装机必备 Windows PDF教程 macOS软件 IOS软件 Android AI 专题
全部分类

当前位置:

首页 > 软件教程 > 第4章:类与面向对象编程详解

第4章:类与面向对象编程详解

第4章类与面向对象编程在前面的章节中,我们学习了Kotlin的语言基础知识、类型系统等相关的知识。在本章节以及下一章中,我们将一起来学习Kotlin对面向对象编程以及函数式编程的支持。本章我们介绍Kotlin的面向对象编程。4.1面向对象编程简史50年代后期,在用FORTRAN语言编写大型程序时,由于没有封装机制,那个时候的变量都是“全局变量”,那么就会不可避免的经常出现变量名冲突问题。在ALGOL60中采用了以Begin-End为标识的程序块,使块内变量名是局部的,以避免它们与程序中块外的

本文目录
    第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运行复制
    class AnEmptyClassfun main(args: Array) {    val anEmptyClass = AnEmptyClass() // Kotlin中不需要使用new    println(anEmptyClass)    println(anEmptyClass is AnEmptyClass) // 对象实例是AnEmptyClass类型    println(anEmptyClass::class)}

    输出

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

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

    代码语言:javascript代码运行次数:0运行复制
    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')"    }}

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

    在代码中这样使用Person类

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

    输出

    代码语言:javascript代码运行次数:0运行复制
    person = Person(name='Jack', age=29, sex='M')

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

    代码语言:javascript代码运行次数:0运行复制
    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')"    }}

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

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

    输出

    代码语言:javascript代码运行次数:0运行复制
    person1 = Person1(name='Jack', age=29, sex='M')

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

    代码语言:javascript代码运行次数:0运行复制
    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')"    }}

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

    代码语言:javascript代码运行次数:0运行复制
    class Person2 {    lateinit var name: String    var age: Int = 0    lateinit var sex: String}

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

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

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

    右击鼠标操作

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

    选择Generate

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

    生成次级构造函数

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

    一个属性都不选,生成

    代码语言:javascript代码运行次数:0运行复制
    constructor()

    选择一个 name 属性,生成

    代码语言:javascript代码运行次数:0运行复制
        constructor(name: String) {        this.name = name    }

    选择name,age属性生成

    代码语言:javascript代码运行次数:0运行复制
        constructor(name: String, age: Int) : this(name) {        this.name = name        this.age = age    }

    3个属性都选择,生成

    代码语言:javascript代码运行次数:0运行复制
        constructor(name: String, age: Int, sex: String) : this(name, age) {        this.name = name        this.age = age        this.sex = sex    }

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

    代码语言:javascript代码运行次数:0运行复制
        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}")

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

    代码语言:javascript代码运行次数:0运行复制
    class Person(var name: String, var age: Int, var sex: String)

    而当确实需要通过比较复杂的逻辑来构建一个对象的时候,可采用构建者(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运行复制
    package com.easy.kotlinabstract class Shapeclass Rectangle : Shape() // 继承类的语法是使用冒号 : , 父类需要在这里使用构造函数初始化class Circle : Shape()class Triangle : Shape()

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

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

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

    代码语言:javascript代码运行次数:0运行复制
    val r = Rectangle()println(r is Shape) // true

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

    代码语言:javascript代码运行次数:0运行复制
    abstract class Shape {    abstract var width: Double    abstract var heigth: Double    abstract var radius: Double    abstract fun area(): Double}

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

    代码语言:javascript代码运行次数:0运行复制
    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    }}

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

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

    代码语言:javascript代码运行次数:0运行复制
    fun main(args: Array) {    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}

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

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

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

    代码语言:javascript代码运行次数:0运行复制
        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!

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

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

    在子类中这样覆盖重写

    代码语言:javascript代码运行次数:0运行复制
    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) {    val r = Rectangle(3.0, 4.0, 0.0)    println(r.area())    r.onClick()}

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

    上面的代码运行输出

    代码语言:javascript代码运行次数:0运行复制
    12.0Rectangle is Clicked!

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

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

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

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

    4.3.2 接口

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

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

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

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

    代码语言:javascript代码运行次数:0运行复制
    interface ProjectService

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

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

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

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

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

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

    覆盖冲突

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

    代码语言:javascript代码运行次数:0运行复制
    super.print()super.print()
    4.4 object对象

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

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

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

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

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

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

    4.5 数据类

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

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

    代码语言:javascript代码运行次数:0运行复制
    data class LoginUser(val username: String, val password: String)

    在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运行复制
    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;      }   }}

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

    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运行复制
    package com.easy.kotlindata class LoginUser(val username: String, val password: String)fun main(args: Array) {    val loginUser = LoginUser("admin", "admin")    val (username, password) = loginUser    println("username = ${username}, password = ${password}") // username = admin, password = admin}

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

    4.6 注解

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

    代码语言:javascript代码运行次数:0运行复制
    @Retention(AnnotationRetention.RUNTIME)@Target(AnnotationTarget.CLASS)internal annotation class Metadata

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

    代码语言:javascript代码运行次数:0运行复制
    annotation class Suspendable // Java中使用的是@interface Suspendable

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

    代码语言:javascript代码运行次数:0运行复制
    @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 {}

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

    代码语言:javascript代码运行次数:0运行复制
    interface ImageRepository : PagingAndSortingRepository {       @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    @Throws(Exception::class)    @Modifying    @Transactional    @Query("update #{#entityName} a set a.isFavorite=1,a.gmtModified=now() where a.id=?1")    fun addFavorite(id: Long)}

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

    代码语言:javascript代码运行次数:0运行复制
    @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")    }}

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

    4.7 枚举

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

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

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

    代码语言:javascript代码运行次数:0运行复制
        public final val name: String    public final val ordinal: Int

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

    代码语言:javascript代码运行次数:0运行复制
    >>> val north = Direction.NORTH>>> north.nameNORTH>>> north.ordinal0>>> north is Directiontrue

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

    代码语言:javascript代码运行次数:0运行复制
    enum class Color(val rgb: Int) {    RED(0xFF0000),    GREEN(0x00FF00),    BLUE(0x0000FF)}

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

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

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

    代码语言:javascript代码运行次数:0运行复制
    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            }        }    }}

    测试代码:

    代码语言:javascript代码运行次数:0运行复制
        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()

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

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

    代码语言:javascript代码运行次数:0运行复制
    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            }        }}}
    4.8.2 嵌套内部类

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

    代码语言:javascript代码运行次数:0运行复制
    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) {    val innerClass = NestedClassesDemo.Outer().Inner().accessOuter()}

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

    4.8.3 匿名内部类

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

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

    代码语言:javascript代码运行次数:0运行复制
    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()        }    }}

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

    代码语言:javascript代码运行次数:0运行复制
    @FunctionalInterfacepublic interface Runnable {    public abstract void run();}

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

    代码语言:javascript代码运行次数:0运行复制
                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()            }

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

    本章小结

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

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

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

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    软件教程
    相关文章 更多
    精品专题 更多
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    macOS软件

    正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

    IOS软件

    正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

    AI

    正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

    Mac软件 更多
    Adobe Acrobat Pro macOS版
    Adobe Acrobat Pro macOS版

    Adobe Acrobat Pro 是面向办公、法务、财务、教育及出版用户的专业PDF处理软件,可在Mac上创建、编辑、转换、合并和保护文档,并提供OCR文字识别、表单、批注、内容比较、敏感信息遮盖及电子签名功能。

    Blender
    Blender

    Blender 是一款免费开源、跨平台的专业 3D 创作软件,集建模、动画、渲染、视频编辑与视觉合成等功能于一体,广泛应用于影视动画、游戏设计和建筑可视化等领域。软件支持 Cycles 物理渲染器与 Eevee 实时渲染引擎,并提供多边形建模、骨骼绑定、物理模拟等专业工具。Blender 兼容 Windows、macOS 和 Linux 系统,安装包轻巧、运行流畅,依托活跃的全球开发者社区持续更新,是从初学者到专业创作者都值得选择的正版 3D 创作工具。

    Shapr3D macOS版
    Shapr3D macOS版

    Shapr3D是一款面向工业设计、机械工程、建筑概念和三维打印工作流的CAD软件。Mac版采用Parasolid建模内核,支持草图约束、实体建模、工程图、可视化渲染及常见CAD格式交换,并可通过账户在多台设备之间同步项目。

    Adobe Illustrator macOS版
    Adobe Illustrator macOS版

    Adobe Illustrator 是 Adobe 推出的专业矢量图形设计软件,适合在 Mac 上制作标志、图标、插画、包装、信息图及印刷版面。它以路径和锚点构建可无损缩放的图形,并提供钢笔、形状、文字、图像描摹、渐变、画板及生成式功能。

    Adobe InDesign macOS版
    Adobe InDesign macOS版

    Adobe InDesign 是面向印刷品与数字出版物的专业版面设计软件,可在 Mac 上制作书籍、杂志、宣传册、海报、报告及交互式文档。它提供精细的文字样式、网格、母版、长文档管理和印前输出能力。

    Affinity Designer macOS版
    Affinity Designer macOS版

    Affinity Designer 的矢量设计能力现已整合进 Affinity by Canva,可在 Mac 上进行标志、图标、插画、界面原型及印刷品设计。个人用户使用免费 Canva 账户即可使用完整的矢量、像素和版面工具。

    Archicad macOS版
    Archicad macOS版

    Archicad是Graphisoft推出的建筑信息模型设计软件,可在Mac上完成概念设计、参数化建模、图纸编制、工程量统计、渲染展示及团队协同。模型与平立剖面、明细表和布局保持关联,适合建筑师、室内设计师、BIM团队及相关专业学生使用。

    AutoCAD LT macOS版
    AutoCAD LT macOS版

    AutoCAD LT是Autodesk推出的专业二维CAD设计与文档编制软件,Mac版本提供原生界面、多点触控及分屏查看体验。用户可精确创建和编辑DWG图形,完成图层管理、尺寸标注、表格、PDF参考、外部参照及打印输出。

    AutoCAD macOS版
    AutoCAD macOS版

    AutoCAD for Mac 是 Autodesk 面向 macOS 提供的专业计算机辅助设计软件,支持精确的二维绘图、标注、布局输出以及实体、曲面和网格建模。软件采用适配 Mac 操作习惯的界面,可处理 DWG 图纸。

    Autodesk Fusion macOS版
    Autodesk Fusion macOS版

    Autodesk Fusion 是面向产品设计与制造的一体化云连接平台,在 Mac 上提供参数化建模、自由曲面、装配、工程图、仿真、PCB 设计和 CAM 加工等工具。它适合机械工程师、工业设计师、创客及制造团队使用。

    Autodesk Maya macOS版
    Autodesk Maya macOS版

    Autodesk Maya是一套面向影视动画、游戏开发和视觉特效制作的专业三维创作软件,集多边形与曲面建模、角色绑定、关键帧动画、动力学模拟、程序化特效及Arnold渲染于一体,并提供脚本、插件和开放式制作管线支持。

    Blender macOS版
    Blender macOS版

    Blender是一款免费开源的综合三维创作软件,可在Mac上完成建模、数字雕刻、材质制作、角色绑定、动画、物理模拟、渲染、合成及视频剪辑。软件提供Cycles与Eevee渲染工具,并支持几何节点、蜡笔绘制和Python扩展。

    WINDOWS 更多
    AutoCAD Windows版
    AutoCAD Windows版

    AutoCAD 是 Autodesk 推出的专业计算机辅助设计软件,Windows 版支持精确二维绘图、三维实体与曲面建模、图纸标注、布局发布和任务自动化,并包含多套行业专业化工具组合,适用于建筑、机械、电气、工程建设及制造设计人员。

    Adobe Acrobat Pro Windows版
    Adobe Acrobat Pro Windows版

    Adobe Acrobat Pro 是面向专业 PDF 工作的综合解决方案,可在 Windows 中创建、编辑、转换、比较、整理和保护文档,并支持表单、电子签名、扫描件文字识别及敏感信息永久遮盖。订阅还包含网页、移动服务和云端协作能力,

    Blender
    Blender

    Blender 是一款免费开源、跨平台的专业 3D 创作软件,集建模、动画、渲染、视频编辑与视觉合成等功能于一体,广泛应用于影视动画、游戏设计和建筑可视化等领域。软件支持 Cycles 物理渲染器与 Eevee 实时渲染引擎,并提供多边形建模、骨骼绑定、物理模拟等专业工具。Blender 兼容 Windows、macOS 和 Linux 系统,安装包轻巧、运行流畅,依托活跃的全球开发者社区持续更新,是从初学者到专业创作者都值得选择的正版 3D 创作工具。

    3dmax(3ds max)
    3dmax(3ds max)

    Autodesk 3ds Max 是一款专业的三维建模、动画与渲染软件,广泛应用于建筑可视化、游戏开发、影视动画、广告设计和产品展示等领域。

    Adobe InDesign Windows版
    Adobe InDesign Windows版

    Adobe InDesign 是面向印刷与数字出版的专业页面排版工具,可制作书籍、杂志、宣传册、海报、交互式 PDF 和电子书。它提供精细的文字样式、多页面管理、印前检查及多格式输出能力,并能与 Photoshop、Illustrator

    Adobe Illustrator Windows版
    Adobe Illustrator Windows版

    Adobe Illustrator 是用于绘制矢量图形、插画、图标、徽标和版式的专业设计工具。它以路径和锚点为核心,作品放大后仍能保持清晰,并提供画板、文字、渐变、图像描摹及精确变换等功能,适合品牌视觉、印刷物料、数字界面和商业插画创作。

    Figma Windows版
    Figma Windows版

    Figma 是面向产品设计与开发团队的云端协作平台,可在 Windows 桌面客户端中完成界面绘制、组件管理、交互原型、评论评审和开发交付。文件保存在云端,多名成员能够同步编辑并共享设计系统;官方同时提供免费 Starter 方案及按席位计

    Affinity Designer Windows版
    Affinity Designer Windows版

    Affinity Designer 是面向矢量图形、数字插画和界面设计的专业创作工具,可在矢量、像素与导出工作区之间快速切换。它提供曲线编辑、形状构建、画板、排版、图层和专业色彩管理,并支持常见设计文件的导入及多种印刷、网页格式输出。

    Cinema 4D Windows版
    Cinema 4D Windows版

    Cinema 4D是Maxon推出的专业三维创作软件,将建模、动画、MoGraph动态图形、粒子与动力学模拟、材质灯光及Redshift渲染整合在统一工作环境中,适用于广告包装、影视特效、产品和建筑可视化、游戏素材等制作场景。

    Blender Windows版
    Blender Windows版

    Blender 是免费开源的三维创作套件,在 Windows 上提供建模、雕刻、材质、动画、物理模拟、合成、跟踪、视频剪辑及 Cycles、EEVEE 渲染工具。它适合个人创作者、学生和小型工作室,

    Autodesk Maya Windows版
    Autodesk Maya Windows版

    Autodesk Maya 是面向影视、电视、动画和游戏行业的专业三维制作软件,集多边形与 NURBS 建模、角色绑定、关键帧和动作捕捉动画、Bifrost 特效模拟、材质开发及 Arnold 渲染于一体,

    SketchUp Pro Windows版
    SketchUp Pro Windows版

    SketchUp Pro 是面向建筑、室内、景观、施工和产品设计等专业场景的三维建模工具,提供桌面建模、LayOut 二维文档、模型资源库、扩展程序及云端协作能力。其操作逻辑直观,适合从概念推敲、空间规划到图纸表达和方案汇报的完整工作流程。