【Kotlin】类与接口

文章目录

    • 类的定义
      • 创建类的实例
      • 构造函数
        • 主构造函数
        • 次构造函数
        • init语句块
    • 数据类的定义
      • 数据类定义了componentN方法
    • 继承
      • Any
        • Any:非空类型的根类型
        • Any?:所有类型的根类型
    • 覆盖
          • 方法覆盖
          • 属性覆盖
    • 抽象类
    • 接口:使用interface关键字
    • 函数:fun
      • Unit:让函数调用皆为表达式
      • 表达式函数体
      • 类头格式化

类的定义

类可以包含:

  • 构造函数和初始化块
  • 函数
  • 属性
  • 嵌套类和内部类
  • 对象声明

你可以将类想象成一个对象的模板,因为它告诉编译器如何创建该特定类的对象。它还将告诉编译器每个对象应该具有哪些属性,并且从该类生成的每个对象都可以
拥有自己独有的属性值。例如,每个Dog对象都有自己的名称、重量和品种属性,每个Dog的属性值都可以是不同的。

class Dog(val name: String, var weight: Int, val breed: String){fun woo() {}
}

如果有参数的话你只需要在类名后面写上它的参数,如果这个类没有任何内容可以省略大括号:

class Dog(val name: String, var weight: Int, val breed: String)

创建类的实例

val myDog = Dog("Fido", 70, "Mixed" )

上面的类有一个默认的构造函数。

注意:创建类的实例不用new

kotlin_class_dog_sample

构造函数

Kotlin中的一个类可以有一个主构造函数和一个或多个次构造函数。

主构造函数

主构造函数是类头的一部分:它跟在类名(和可选的类型参数)后:

class Person constructor(name: String, surname: String) {
}

如果主构造函数没有任何注解或者可见性修饰符,可以省略constructor关键字:

class Person(name: String, surname: String) {
}

主构造函数不能包含任何的代码。初始化的代码可以放到以init关键字作为前缀的初始化块中:

class Person constructor(name: String, surname: String) {init {print("init")}
}

如果构造函数有注解或可见性修饰符,那么constructor关键字是必需的,并且这些修饰符在它前面

次构造函数

类也可以声明前缀有constructor的次构造函数:

class Person{constructor(name: String) {print("name is $name")}
}

如果类有一个主构造函数,每个次构造函数都需要委托给主构造函数(不然会报错), 可以直接委托或者通过别的次构造函数间接委托。
委托到同一个类的另一个构造函数用this关键字即可:

class Person constructor(name: String) {constructor(name: String, surName: String) : this(name) {print( "name is : $name surName is : $surName")}
}
init语句块

Kotlin引入了一种叫作init语句块的语法,它属于上述构造方法的一部分,两者在表现形式上却是分离的。构造方法在类的外部,它只能对参数进行赋值。
如果我们需要在初始化时进行其他的额外操作,那么我们就可以使用init语句块来执行。比如:

class Bird(weight: Double, age: Int, color: String) {init {println("the weight is ${weight}")}
}

当没有val或者var的时候,构造函数的参数可以在init语句块被直接调用。除此之外,不能在其他地方使用。以下是一个错误的用法:

class Bird(weight: Double, age: Int, color: String) {fun printWeight() {print(weight) // Unresolved reference: weight}
}

事实上,我们的构造方法还可以拥有多个init,他们会在对象被创建时按照类中从上到下的顺序先后执行。例如:

class Bird(weight: Double, aget: Int, color: String) {val weight: Doubleval age: Intval color: Stringinit {this.weight = weightthis.age = age}init {this.color = color}
}

可以发现,多个init语句块有利于进一步对初始化的操作进行职能分离,这在复杂的业务开发中显得特别有用。

数据类的定义

数据类通常需要重写equals()hashCode()toString()这几个方法.
但是在Kotlin中你只需要一行代码。

数据类是一种非常强大的类:

使用Kotlin:

data class Artist(var id: Long,var name: String,var url: String,var mbid: String)

数据类自动覆盖它们的equals方法以改变操作符的行为,由此通过检查对象的每个属性值来判断是否相等。
例如,假设你创建了两个属性值完全相同的Artist对象,使用操作符对它们进行比较将返回true,因为它们存放了相同的数据:除了提供从Any父类继承的equals方法的新实现,数据类还覆盖了hashCode和toString方法。

通过数据类,会自动提供以下函数:

  • 所有属性的get() set()方法
  • equals()
  • hashCode()
  • copy()
  • toString()
  • componentN()

如果我们使用不可修改的对象,就像我们之前讲过的,假如我们需要修改这个对象状态,必须要创建一个新的或者多个属性被修改的实例。
这个任务是非常重复且不简洁的。

举个例子,如果要修改Person类中xoliuage:

data class Person(val name: String,val age: Int)
val p1 = Person("xoliu", 19)
val p2 = p1.copy(age = 22)

如上,我们拷贝了对象然后只修改了age的属性而没有修改这个对象的其它状态。

如果你要在Kotlin声明一个数据类,必须满足以下几点条件:

  • 数据类必须拥有一个构造方法,该方法至少包含一个参数,一个没有数据的数据类是没有任何用处的。
  • 与普通的类不同,数据类构造方法的参数强制使用var或者val进行声明
  • data class之前不能用abstract、open、sealed或者inner进行修饰

与任何其他类一样,你可以向数据类添加属性和方法,只需要将它们包含在类主体中。但是有一个大问题,就是在编译器生成数据类的方法实现时,
比如覆盖equals方法和创建copy方法,它仅包含在主构造函数中定义的属性。因此如果你在数据类主体中定义添加的属性,则它们不会被包含到任何编译器生成的方法中。

数据类定义了componentN方法

定义数据类时,编译器会自动向该类添加一组方法,你可以将其作为访问对象属性值的替代方法。它们被称为componentN方法,其中N表示被访问属性的编号(按声明排序)。多声明,也可以理解为变量映射

继承

Kotlin中所有类都有一个共同的超类Any(java是Object),这对于没有超类型声明的类是默认超类:

class Person // 从 Any 隐式继承

Any不是java.lang.Object。它除了equals()hashCode()toString()外没有任何成员。
在Java中,类默认是可以被继承的,除非你主动加final修饰符。而在Kotlin中恰好相反,默认是不可被继承的,除非你主动加可以继承的修饰符open,如果不加open,那它在转化为Java代码时就是final的:

所以Kotlin中所有的类默认都是不可继承的(final)

所以我们只能继承那些明确声明open或者abstract的类:要声明一个显式的超类型,我们把类型放到类头的冒号之后:

open class Person(num: Int)
// 继承
class SuperPerson(num: Int) : Person(num)

冒号后面的Person(num)会调用Person类的构造函数,以确保所有的初始化代码(例如给属性赋值)能够被执行。
调用父类构造函数是强制性的:如果父类有主构造函数,你必须在子类头中调用它,否则代码将无法通过编译。
请记住,即使你没有在父类中显式地添加构造函数,编译器也会在编译代码的时候自动创建一个空构造函数。
假如我们不想为Person类添加构造函数,因此编译器在编译代码的时候创建了一个空构造函数。该构造函数通过使用Person()被调用。

注意: 上面在说到继承的时候class SuperPerson(num: Int) : Person(num)在父类后面必须加上括号,这是为了能够调用到父类的主构造函数。
Kotlin中规定,当一个类既有主构造函数又有次构造函数时,所有的次构造函数都必须调用主构造函数(包括间接调用)
但是如果类没有主构造函数,那么每个次构造函数必须使用super关键字初始化其基类型,或委托给另一个构造函数做到这一点。 这里很特殊,在Kotlin
中是允许类中只有次构造函数,没有主构造函数的。当一个类没有显式的定义主构造函数且定义了次构造函数时,它就是没有主构造函数的。

如果该类有一个主构造函数,其基类必须用基类型的主构造函数参数就地初始化。

如果类没有主构造函数,那么每个次构造函数必须使用super关键字初始化其基类型,或委托给另一个构造函数做到这一点。
注意,在这种情况下,不同的次构造函数可以调用基类型的不同的构造函数:

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

也就是MyView类的后面没有显式的定义主构造函数,同时又定义了次构造函数。所以现在MyView类是没有主构造函数的。那么既然没有主构造函数,继承View类
的时候也就不需要再在View类后加上括号了。

另外,由于没有主构造函数,次构造函数只能直接调用父类的构造函数,上述代码也是将this关键字换成了super关键字,这部分就很好理解了。

Any

我们都知道,Java并不能在真正意义上被称为一门"纯面向对象"语言,因为它的原始类型(如int)的值与函数等并不能被视作对象。

但是Kotlin不同,在Kotlin的类型系统中,并不区分原始类型(基本数据类型)和包装类型,我们使用的始终是同一个类型。

Any:非空类型的根类型

与Object作为Java类层级结构的顶层类似,Any类型是Kotlin中所有非空类型(如String、Int)的超类,如:

与Java不同的是,Kotlin不区分"原始类型"(primitive type)和其他的类型,他们都是同一类型层级结构的一部分。 如果定义了一个没有指定父类型的类型,
则该类型将是Any的直接子类型。如:

class Animal(val weight: Double)
Any?:所有类型的根类型

如果说Any是所有非空类型的根类型,那么Any?才是所有类型(可空和非空类型)的根类型。这也就是说?Any?是?Any的父类型。

覆盖

方法覆盖

只能重写显示标注可覆盖的方法:

open class Person(num: Int) {open fun changeName(name: String) {}fun changeAge(age: Int) {}
}class SuperPerson(num: Int) : Person(num) {override fun changeName(name: String) {// 通过super关键字调用超类实现super.changeName(name)}
}

SuperPerson.changeName()方法前面必须加上override标注,不然编译器将会报错。如果像上面Person.changeAge()方法没有标注open,则子类中不能定义相同的方法: (不能重写,但能重载)

class SuperPerson(num: Int) : Person(num) {override fun changeName(name: String) {super.changeName(name)}// 编译器报错fun changeAge(age: Int) {}// 重载是可以的fun changeAge(name: String) {}// 重载是可以的fun changeAge(age: Int, name: String) {}
}

标记为override的成员本身是开放的,也就是说,它可以在子类中覆盖。如果你想禁止再次覆盖,可以使用final关键字:

open class SuperPerson(num: Int) : Person(num) {final override fun changeName(name: String) {super.changeName(name)}
}
属性覆盖

属性覆盖与方法覆盖类似,只能覆盖显式标明open的属性,并且要用override开头:

open class Person(num: Int) {open val name: String = ""open fun changeName(name: String) {}fun changeAge(age: Int) {}
}open class SuperPerson(num: Int) : Person(num) {override val name: Stringget() = super.namefinal override fun changeName(name: String) {super.changeName(name)}}

每个声明的属性可以由具有初始化器的属性或者具有get方法的属性覆盖,如果某个属性在父类中被定义为val,你可以在子类中使用var属性覆盖它。
只需要覆盖该属性并将其声明为var即可。请注意,这只适用于这一种方式。如果尝试使用val覆盖var属性,编译器将会报错

抽象类

类和其中的某些成员可以声明为abstract。抽象成员在本类中可以不用实现。需要注意的是,我们并不需要用open标注一个抽象类或者函数——因为这不言而喻,这些属性一定需要去实现的

我们可以用一个抽象成员覆盖一个非抽象的开放成员:

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

接口:使用interface关键字

接口可以让你在父类层次结构之外定义共同的行为,接口用于为共同行为定义协议,使你可以不依赖严格的继承结构却又可以利用多态。与抽象类类似,接口不能被实例化且可以定义抽象或具体的方法和属性,但两者有一个关键的不同点:类可以实现多个接口,但是只能继承于一个直接父类。所以接口不仅拥有抽象类的优点,而且使用起来更加灵活。

interface FlyingAnimal {fun fly()
}

虽然Kotlin接口支持属性声明,然而它在Java源码中是通过一个get方法来实现的。在接口的属性并不能像Java接口那样,被直接赋值一个常量。如以下这样是错误的:

interface Flyer {val height = 1000 // error Property initializers are not allowed in interfacesval speed: Int// 可以支持默认实现方法,反编译可以看到是通过静态内部类来提供fly方法的默认实现的,Java8也开始支持了接口方法的默认实现fun fly() {println("I can fly")}
}

Kotlin提供了另外一种方式来实现这种效果:

interface Flyer {val height get() = 1000
}

一个类实现接口时:

class Bird() : Flyer {//  ...
}

接口的后面不用加上括号,因为它没有构造函数可以去调用。

函数:fun

fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)
}

如果你没有指定它的返回值,它就会返回UnitUnitJava中的void类似,但是Unit是一个类型,而void只是一个关键字。Unit可以省略。

fun maxOf(a: Int, b: Int): Int {if (a > b) {return a} else {return b}
}

Unit:让函数调用皆为表达式

如果函数返回Unit类型,该返回类型应该省略:

fun foo() { // 省略了 ": Unit"}

之所以不能说Java中的函数调用皆是表达式,是因为存在特例void。众所周知,在Java中如果声明的函数没有返回值,那么它就需要用void来修饰,如:

void foo() {System.out.println("return nothing")
}

所以foo()就不具有值和类型信息,它就不能算作一个表达式。在Kotlin中,函数在所有的情况下都具有返回类型,所以他们引入了Unit来替代Java中的void关键字。

Unit与Int一样,都是一种类型,然而它不代表任何信息,用面向对象的术语来描述就是一个单例,它的实例只有一个,可写为()。

表达式函数体

如果返回的结果可以使用一个表达式计算出来,你可以不使用括号而是使用等号:

fun add(x: Int,y: Int) : Int = x + y // 省略了{}

Kotlin支持这种单行表达式与等号的语法来定义函数,叫做表达式函数体,作为区分,普通的函数声明则可以叫做代码块函数体。如你所见,在使用表达式函数体
的情况下我们可以不声明返回值类型,这进一步简化了语法。

我们可以给参数指定一个默认值使的它们变的可选,这是非常有帮助的。这里有一个例子,在Activity中创建了一个函数用来Toast一段信息:

fun toast(message: String, length: Int = Toast.LENGTH_SHORT) {Toast.makeText(this, message, length).show()
}

上面代码中第二个参数length指定了一个默认值。这意味着你调用的时候可以传入第二个值或者不传,这样可以避免你需要的重载函数:

toast("Hello")
toast("Hello", Toast.LENGTH_LONG)

类头格式化

有少数几个参数的类可以写成一行:

class Person(id: Int, name: String)

具有较长类头的类应该格式化,以使每个主构造函数参数位于带有缩进的单独一行中。此外,右括号应该另起一行。如果我们使用继承,
那么超类构造函数调用或者实现接口列表应位于与括号相同的行上:

class Person(id: Int, name: String,surname: String
) : Human(id, name) {// ……
}

对于多个接口,应首先放置超类构造函数调用,然后每个接口应位于不同的行中:

class Person(id: Int, name: String,surname: String
) : Human(id, name),KotlinMaker {// ……
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/204771.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

听GPT 讲Rust源代码--src/tools(2)

题图来自AI生成 File: rust/src/tools/rust-analyzer/crates/hir-def/src/src.rs rust-analyzer 是一个 Rust 语言的语法分析器和语义分析器,用于提供代码补全、导航、重构等开发工具。而 rust-analyzer 的代码实现存储在 rust/src/tools/rust-analyzer 这个文件夹中…

亚马逊云科技 re:Invent 2023:引领科技前沿,探索未来云计算之窗

文章目录 一、前言二、什么是亚马逊云科技 re:Invent?三、亚马逊云科技 re:Invent 2023 将于何时何地举行四、亚马逊云科技 re:Invent 2023 有什么内容?4.1 亚马逊云科技 re:Invent 2023 主题演讲4.2 亚马逊云科技行业专家探实战 五、更多亚马逊云科技活…

Unity UGUI的自动布局-LayoutGroup(水平布局)组件

Horizontal Layout Group | Unity UI | 1.0.0 1. 什么是HorizontalLayoutGroup组件? HorizontalLayoutGroup是Unity UGUI中的一种布局组件,用于在水平方向上对子物体进行排列和布局。它可以根据一定的规则自动调整子物体的位置和大小,使它们…

万字详解,和你用RAG+LangChain实现chatpdf

像chatgpt这样的大语言模型(LLM)可以回答很多类型的问题,但是,如果只依赖LLM,它只知道训练过的内容,不知道你的私有数据:如公司内部没有联网的企业文档,或者在LLM训练完成后新产生的数据。(即使是最新的GPT-4 Turbo,训练的数据集也只更新到2023年4月)所以,如果我们…

141.【Git版本控制-本地仓库-远程仓库-IDEA开发工具全解版】

Git-深入挖掘 (一)、Git分布式版本控制工具1.目标2.概述(1).开发中的实际常见(2).版本控制器的方式(3).SVN (集中版本控制器)(4).Git (分布版本控制器)(5).Git工作流程图 (二)、Git安装与常用命令1.Git环境配置(1).安装Git的操作(2).Git的配置操作(3).为常用的指令配置别名 (可…

想成为网络安全工程师该如何学习?

一、网络安全应该怎么学? 1.计算机基础需要过关 这一步跟网安关系暂时不大,是进入it行业每个人都必须掌握的基础能力。 计算机网络计算机操作系统算法与数据架构数据库 Tips:不用非要钻研至非常精通,可以与学习其他课程同步进行。 2.渗透技…

.mat格式文件是什么?及将png,jpg,bmp,gif,tiff,psd等格式图片转为.mat格式(附代码)

很多深度学习网络的输入要求为.mat格式,当然也可以直接修改输入数据的代码,比如修改为使用OpenCV读取图片等,但有些网络修改起来比较麻烦,且.mat数据有很多优势,所以部分网络最好还是用默认的.mat格式数据 目录 一、.…

Gossip协议理解

概述 Gossip协议,又称epidemic协议,基于流行病传播方式的节点或进程之间信息交换的协议,在分布式系统中被广泛使用。 在1987年8月由施乐-帕洛阿尔托研究中心发表ACM上的论文《Epidemic Algorithms for Replicated Database Maintenance》中…

OpenStack云计算平台

目录 一、OpenStack 1、简介 2、硬件需求 3、网络 二、环境搭建 1、安全 2、主机网络 3、网络时间协议(NTP) 4、OpenStack包 5、SQL数据库 6、消息队列 7、Memcached 一、OpenStack 1、简介 官网:https://docs.openstack.org/2023.2/ OpenStack系统由…

RPA机器人如何解决非银企直联网银账户的数据自动采集?

数字时代来临,随着全球信息化水平的不断提升,企业们纷纷向自动化办公、数字化转型靠拢。财务部门作为一个企业的重要部门,承担着管理和监控公司所有项目的重要职责,因而一直被视为企业数字化转型的重要突破口。 由于企业经营理念和…

第二十章多线程

线程简介 java语言提供了并发机制,程序员可以在程序中执行多个线程,每一个线程完成一个功能,并与其他线程并发运行。 一个进程是一个包含有自身地址的程序,每个独立执行的程序都称为进程。也就是说每个正在执行的程序都是一个进程…

C语言错误处理之“非局部跳转<setjmp.h>头文件”

目录 前言 setjmp宏 longjmp函数 使用方法: 实例:测试setjmp与longjmp的使用 前言 通常情况下,函数会返回到它被调用的位置,我们无法使用goto语句改变它的返回的方向,因为goto语句只能跳转到同一函数内的某个标号…

python与机器学习1,机器学习的一些基础知识概述(完善ing)

目录 1 AI ,ML,DL,NN 等等概念分类 1.1 人工智能、机器学习、深度学习、神经网络之间的关系: 1.2 人工智能的发展 2 ML机器学习的分类:SL, USL,RL 2.1 机器学习的分类 2.2 具体的应用举例 2.3 数据分类 3 关于阈值θ和偏移量b的由来 4 不同的激…

中小型工厂如何进行数字化转型

随着科技的快速发展和市场竞争的日益激烈,中小型工厂面临着诸多挑战。为了提高生产效率、降低成本、优化资源配置,数字化转型已成为中小型工厂发展的必经之路。中小型工厂如何进行数字化转型呢? 一、明确数字化转型目标 在进行数字化转型之前…

【Linux下基本指令——(1)】

Linux下基本指令——(1) 一. ls 指令1.1.语法:1.2.功能:1.3.常用选项:1.4.举例:1.5.Xshell7展示 二. pwd 命令2.1.语法: 2.2.功能:2.3.常用选项:2.4.Xshell7展示 三. cd 指令3.1.语法…

0004Java程序设计-ssm基于微信小程序的校园第二课堂

文章目录 摘 要目录系统设计开发环境 编程技术交流、源码分享、模板分享、网课分享 企鹅🐧裙:776871563 摘 要 随着信息技术和网络技术的飞速发展,人类已进入全新信息化时代,传统管理技术已无法高效,便捷地管理信息。…

3D模型纹理集合并【Python|C#】

使用 Substance Painter 时,将模型的各个部分分成不同的纹理集非常有用。 这可以帮助遮罩,或者只是保持层栈干净。 不幸的是,Painter 无法将多个纹理集中的所有贴图导出为单个图集,即使在创建单独对象的 UV 时考虑到了这一点。 显…

SpringCloud实用-OpenFeign整合okHttp

文章目录 前言正文一、OkHttpFeignConfiguration 的启用1.1 分析配置类1.2 得出结论,需要增加配置1.3 调试 二、OkHttpFeignLoadBalancerConfiguration 的启用2.1 分析配置类2.2 得出结论2.3 测试 附录附1:本系列文章链接附2:OkHttpClient 增…

Spring Security 6.x 系列(6)—— 显式设置和修改登录态信息

一、前言 此篇是对上篇 Spring Security 6.x 系列(5)—— Servlet 认证体系结构介绍 中4.9章节显式调用SecurityContextRepository#saveContext进行详解分析。 二、设置和修改登录态 2.1 登录态存储形式 使用Spring Security框架,认证成功…

六、Lua运算符

文章目录 一、Lua 运算符(一)算术运算符(二)关系运算符(三)逻辑运算符(四)其他运算符 二、运算符优先级 一、Lua 运算符 运算符是一个特殊的符号,用于告诉解释器执行特定…