【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.mzph.cn/news/170974.shtml

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

相关文章

RocketMq 队列(MessageQueue)

RocketMq是阿里出品(基于MetaQ)的开源中间件,已捐赠给Apache基金会并成为Apache的顶级项目。基于java语言实现,十万级数据吞吐量,ms级处理速度,分布式架构,功能强大,扩展性强。 官方…

Kerberos 高可用配置和验证

参考 https://cloud.tencent.com/developer/article/1078314 https://mp.weixin.qq.com/s?__bizMzI4OTY3MTUyNg&mid2247485861&idx1&snbb930a497f63ac5e63ed20c64643eec5 机器准备 Kerberos主 ip-172-31-22-86.ap-southeast-1.compute.internal 7.common2.hado…

【华为数通HCIP | 网络工程师】821-IGP高频题、易错题之OSPF(7)

个人名片: 🐼作者简介:一名大三在校生,喜欢AI编程🎋 🐻‍❄️个人主页🥇:落798. 🐼个人WeChat:hmmwx53 🕊️系列专栏:🖼️…

C语言盐水的故事(ZZULIOJ1214:盐水的故事)

题目描述 挂盐水的时候,如果滴起来有规律,先是滴一滴,停一下;然后滴二滴,停一 下;再滴三滴,停一下...,现在有一个问题:这瓶盐水一共有VUL毫升,每一滴是D毫升&…

【brpc学习实践八】bvar及其应用

什么是bvar bvar是多线程环境下的计数器类库,支持单维度bvar和多维度mbvar,方便记录和查看用户程序中的各类数值,它利用了thread local存储减少了cache bouncing,相比UbMonitor(百度内的老计数器库)几乎不会给程序增加性能开销&a…

STM32 SCF文件

文章目录 1 SCF文件2 SCT分散加载文件3 SCF文件编写 1 SCF文件 keil编译器在链接的时候,是根据分散加载(.scf后缀的文件)来确定程序的加载域和运行域的。 加载域就是程序运行前在flash中具体分区情况执行域就是程序运行后,程序在flash和ram中的分区情况…

在Windows系统上安装git-Git的过程记录

01-上git的官网下载git的windows安装版本 下载页面链接: https://git-scm.com/downloads 选择Standalone Installer的版本进行下载: 这里给大家一全git-2.43.0的百度网盘下载链接: https://pan.baidu.com/s/11HwNTCZmtSWj0VG2x60HIA?pwdut…

Linux 基础-常用的命令和搭建 Java 部署环境

文章目录 目录相关查看目录中的内容查看目录当前的完整路径切换目录 文件相关创建文件查看文件内容写文件vim 基础 创建删除创建目录 移动和复制移动(剪切粘贴)复制(复制粘贴) 搭建 Java 部署环境1. 安装 jdk2. 安装 tomcat1). 我们在自己电脑上下好 tomcat2). 从官网下载的 .z…

MySQL的基础知识

目录 关系型数据库 SQL通用语法 数据类型 数值类型 字符串类型 日期类型 SQL分类 DDL 数据库操作 表操作 DML 添加数据 修改数据 删除数据 DQL 基本查询 条件查询 聚合函数 分组查询 排序查询 分页查询 执行顺序 DCL 管理用户 权限控制 函数 字符串…

iar如何全擦芯片内存

Project ->Download -> Erase memory

js逆向-某敏感网站登录参数分析

声明 本文仅供学习参考,如有侵权可私信本人删除,请勿用于其他途径,违者后果自负! 如果觉得文章对你有所帮助,可以给博主点击关注和收藏哦! 前言 目标网站:aHR0cHM6Ly9tZGZnaGcuNXhwb2lqaHRm…

五种多目标优化算法(MSSA、MOAHA、MOPSO、NSGA3、NSGA2)求解微电网多目标优化调度(MATLAB)

一、多目标优化算法简介 (1)多目标鳟海鞘算法MSSA 多目标优化算法:多目标鳟海鞘算法(Multi-objective Salp Swarm Algorithm ,MSSA)-CSDN博客 参考文献: S. Mirjalili, A.H. Gandomi, S.Z. M…

flex布局实战之自动填充剩余

案例目标 文字部分自适应并且居中 图中是一个弹窗&#xff0c;我现在使用flex的布局来实现&#xff0c;标题和关闭按钮。因为是uni-app,所以标签是view 。你可以自行替换为 代码 <view class"popup-box"><view class"title"><view class&…

线程的状态以及状态转移

一. 线程的状态 NEW: 线程刚被创建, 但是并未启动. 还没调用start方法.RUNNABLE: 这里没有区分就绪和运行状态. 因为对于Java对象来说, 只能标记为可运行, 至于什么时候运行, 不是JVM来控制的了, 是OS来进行调度的, 而且时间非常短暂, 因此对于Java对象的状态来说, 无法区分.T…

97、Text2NeRF: Text-Driven 3D Scene Generation with Neural Radiance Fields

简介 论文地址 使用扩散模型来推断文本相关图像作为内容先验&#xff0c;并使用单目深度估计方法来提供几何先验&#xff0c;并引入了一种渐进的场景绘制和更新策略&#xff0c;保证不同视图之间纹理和几何的一致性 实现流程 简单而言&#xff1a; 文本-图片扩散模型生成一…

STM32入门学习(一):STM32 简介与软件安装

参考引用 STM32 入门教程-江科协 1. STM32 简介 1.1 STM32 套件介绍 1.2 STM32 简介 STM32 是 ST 公司基于 ARM Cortex-M 内核开发的 32 位微控制器 应用&#xff1a;嵌入式领域&#xff0c;如智能车、无人机、机器人、无线通信、物联网、工业控制、娱乐电子产品等 1.3 ARM …

go对rabbitmq基本操作

一、安装rabbitmq 1、直接使用docker拉取镜像 docker pull rabbitmq:3.82、启动容器 docker run \-e RABBITMQ_DEFAULT_USERadmin \-e RABBITMQ_DEFAULT_PASS123456 \-v mq-plugins:/plugins \--name rabbit01 \--hostname rabbit01 --restartalways \-p 15672:15672 \-p 5672:…

《C++PrimePlus》第9章 内存模型和名称空间

9.1 单独编译 Visual Studio中新建头文件和源代码 通过解决方案资源管理器&#xff0c;如图所示&#xff1a; 分成三部分的程序&#xff08;直角坐标转换为极坐标&#xff09; 头文件coordin.h #ifndef __COORDIN_H__ // 如果没有被定义过 #define __COORDIN_H__struct pola…

【开源】基于Vue.js的城市桥梁道路管理系统的设计和实现

项目编号&#xff1a; S 025 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S025&#xff0c;文末获取源码。} 项目编号&#xff1a;S025&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 查询城市桥…

学生信息管理系统程序Python

系统主界面 在该界面中可以选择要使用功能对应的菜单进行不同的操作。在选择功能菜单时&#xff0c;有两种方法&#xff0c; 一种是输入1&#xff0c;另一种是按下键盘上的↑或↓方向键进行选择。这两种方法的结果是一样的&#xff0c;所以使用哪种方法都可以。 &#xff08;…