Kotlin基础3——定义类、初始化、继承、对象、接口、抽象类、泛型

一.定义类

1.field

针对定义的每一个属性,Kotlin都会生成一个field、一个getter、以及一个setter,field用来存储属性数据,你不能直接定义field,Kotlin会封装field,保护它里面的数据,只暴露给getter和setter使用。属性的getter方法决定你如何读取属性值,每个属性都有getter方法,setter方法决定你如何给属性赋值,所以只有可变属性才会有setter方法,尽管Kotlin会自动提供默认的getter和setter方法,但在需要控制如何读写属性时,也可以自定义它们。

定义一个Player类,自定义(重写)属性的setter和getter方法

image.png

2.计算属性

计算属性是通过一个覆盖的get或set运算符来定义,这时field就不需要了。

image.png

3.防范竞态条件

如果一个类属性既可空又可变,那么引用它之前你必须保证它非空,一个办法是用also标准函数。

image.png

二.初始化

1.主构造函数

我们在Player类的定义头中定义一个主构造函数,使用临时变量为Player的各个属性提供初始值,在Kotlin中,为便于识别,临时变量(包括仅引用一次的参数),通常都会以下划线开头的名字命名。

image.png

2.在主构造函数里定义属性

Kotlin允许你不使用临时变量赋值,而是直接用一个变量同时指定参数和类属性,通常,我们更喜欢用这种方式定义类属性,因为他会减少重复代码。

image.png

3.次构造函数(关键字:constructor)

有主就有次,对应主构造函数的就是次构造函数,我们可以定义多个次构造函数来配置不同的参数组合。

image.png

4.使用次构造函数,定义初始化代码逻辑。

image.png

5.默认参数

定义构造函数时,可以给构造函数参数指定默认值,如果用户调用时不提供值参,就使用这个默认值。

image.png

6.初始化块(关键字:init)

初始化块可以设置变量或值,以及执行有效性检查,如检查传给某构造函数的值是否有效,初始化块代码会在构造类实例时执行。

image.png

7.初始化顺序

1.主构造函数里声明的属性;
2.类级别的属性赋值;
3.init初始化块里的属性赋值和函数调用;
4.次构造函数里的属性赋值和函数调用;

image.png

image.png

8.延迟初始化

使用lateinit关键字相当于做了一个约定:在用它之前由开发人员负责初始化
如果无法确认lateinit变量是否完成初始化,可以执行isInitialized进行检查

image.png

9.惰性初始化

延迟初始化并不是推后初始化的唯一方式,你也可以暂时不初始化某个变量,直到首次使用它,这个叫作惰性初始化。

image.png

10.初始化陷阱一

在使用初始化块时,顺序非常重要,你必须保证块中的所有属性已经完成初始化。

错误代码:

image.png

正确代码:

image.png

11.初始化陷阱二

这段代码编译没有问题,因为编译器看到name属性已经在init块里初始化了,但代码一运行就会抛出空指针异常,因为name属性还没有赋值,firstLetter函数就应用了它了。

image.png

12.初始化陷阱三

因为编译器看到所有属性都初始化了,所以代码编译没问题,但运行结果却是null,问题出在哪了?在用initPlayerName函数初始化playerName时,name属性还未完成初始化,只需要将playName和name位置互换即可运行正常。

错误代码:

image.png

正确代码:

image.png

三.继承

1.继承

类默认都是封闭的,要让某个类开放继承,必须使用open关键字修饰它。

image.png

2.函数重载

父类的函数也要以open关键字修饰,子类才能覆盖它。

image.png

3.类型检测

Kotlin的is运算符是个不错的工具,可以用来检查某个对象的类型。

image.png

运行结果:

image.png

4.Kotlin层次

无需再代码里显示指定,每一个类都会共同继承一个叫Any的超类

image.png

5.类型转换

as操作符声明,这是一个类型转换

image.png

6.智能类型转换

Kotlin编译器很聪明只要能确定any is 父类条件检查属实,它就会将any当作子类类型对待,因此,编译器允许你不经类型转换直接使用。

image.png

四.对象

1.object关键字

使用object关键字,你可以定义一个只能产生一个实例的类——单例。

使用obect关键字有三种方式:
-对象声明;
-对象表达式;
-伴生对象;

2.对象声明

对象声明有利于组织代码和管理状态,尤其是管理整个应用运行生命周期内的某些一致性状态。

image.png

运行结果:

image.png

init初始化块只执行了一次,说明对象只创建了一次。

3.对象表达式

有时候你不一定非要定义一个新的命名类不可,也许你需要某个现有类的一种变体实例,但只需用一次就行了,事实上,对于这种用完就丢的类的实例,连命名都可以省了。这个对象表达式是某个类的子类,这个匿名类依然遵循object关键字的一个规则,即一旦实例化,该匿名类只能由唯一一个实例存在。

image.png

运行结果:

image.png

4.伴生对象

如果你想将某个对象的初始化和一个类实例捆绑在一起,可以考虑使用伴生对象,使用companion修饰符,你可以在一个类定义里声明一个伴生对象,一个类里只能有一个伴生对象。

image.png

5.嵌套类

如果一个类只对另一个类有用,那么将其嵌入到该类中并使这两个类保持在一起是合乎逻辑的,可以使用嵌套类。

image.png

运行结果:

image.png

6.数据类(关键字:data)

数据类,是专门设计用来存储数据的类。
数据类提供了toString的个性化实现。
==符号默认情况下,比较对象就是比较它们的引用值,数据类提供了equals和hashCode的个性化实现。

image.png

运行结果:

image.png

7.copy

除了重写Any类的部分函数,提供更好用的默认实现外,数据类还提供了一个函数,它可以用来方便的复制一个对象。假设你想创建一个Student实例,除了name属性,它拥有和另一个现有Student实例完全一样的属性值,如果Student是个数据类,那么复制现有Student实例就很简单了,只要调用copy函数,给想修改的属性传入值参就可以了。

image.png

运行结果:

image.png

8.结构声明

结构声明的后台实现就是声明component1、component2等若干个组件函数,让每个函数负责管理你想返回的一个属性数据,如果你定义一个数据类,它会自动为所有定义在主构造函数的属性添加对应的组件函数。

image.png
image.png

9.使用数据类的条件

正是因为上述这些特征,你才会倾向于用数据类来表示存储数据的简单对象,对于那些经常需要比较、复制或打印自身内容的类,数据类尤其适合它们。然而,一个类要成为数据类,也要符合一定条件。总结下来,主要有三个方面:
-数据类必须有主构造函数,该主构造函数至少带一个参数。
-数据类主构造函数的参数必须是val或var。
-数据类不能使用abstract、open、sealed和inner修饰符。

10.枚举类

枚举类,用来定义常量集合的一种特殊类。

image.png

运行结果:EAST

枚举类也可以定义函数


image.png

运行结果:Coordinate(x=15, y=19)

11.运算符重载(关键字:operator)

如果要将内置运算符应用在自定义类身上,你必须重写运算符函数,告诉编译器该如何操作自定义类。

image.png

运行结果:Coordinate(x=15, y=26)

常见操作符

image.png

12.代数数据类型

可以用来表示一组子类型的闭集,枚举类就是一种简单的ADT(代数数据类型)。

image.png

13.密封类(关键字:sealed)

对于更复杂的ADT,你可以使用Kotlin的密封类(sealed class)来实现更复杂的定义,密封类可以用来定义一个类似于枚举类的ADT,但你可以更灵活地控制某个子类型。
密封类可以有若干个子类,要继承密封类,这些子类必须和它定义在同一个文件里。

image.png

五.接口

1.接口定义

Kotlin规定所有的接口属性和函数实现都要使用override关键字,接口中定义的函数并不需要open关键字修饰,它们默认就是open的。

image.png

2.默认实现

只要你愿意,你可以在接口里提供默认属性的getter方法和函数实现。

image.png

六.抽象类

要定义一个抽象类,你需要在定义之前加上abstract关键字,除了具体的函数实现,抽象类也可以包含抽象函数——只有定义,没有函数实现。

image.png

七.定义泛型类

泛型类的构造函数可以接受任何类型。
MagicBox类指定的泛型参数由放在一对<>里的字母T表示,T是个代表item类型的占位符。MagicBox类接受任何类型的item作为主构造函数值(item:T),并将item值赋给同样是T类型的subject私有属性。

image.png

注意:泛型参数通常用字母T(代表应为type)表示,当然,想用其他字母,甚至是英文单词都是可以的。不过,其他支持泛型的语言都在用这个约定俗成的T,所以建议继续用它,这样写出来的代码别人更容易理解。

八.泛型函数

1.泛型函数

泛型参数也可以用于函数。
定义一个函数用于获取元素,当且仅当MagicBox可用时,才能获取元素。

image.png

运行结果:you find Jack

2.多泛型参数

泛型函数或泛型类也可以有多个泛型参数。

image.png

运行结果:Man(name='Jack', age='30')

九.泛型类型约束

1.泛型类型约束

如果要确保MagicBox里面只能装指定类型的物品,如Human类型,那就需要用到泛型类型约束了。

image.png

2.vararg关键字与get函数

MagicBox能存放任何类型的Human实例,但一次只能放入一个,如果需要放入多个实例,则入参需要用vararg关键字修饰。

image.png

3.[]操作符取值

想要通过[]操作符取值,可以重载运算符函数 get函数。

image.png

4.协变,关键字:out

out(协变),如果泛型类型作为函数的返回(输出),那么使用out,可以称之为生产类/接口,因为它主要是用来生产(produce)指定的泛型对象。

image.png

5.逆变,关键字:in

in(逆变),如果泛型类只将泛型类型作为函数的入参(输入),那么使用in,可以称之为消费者类/接口,因为它主要是用来消费(consume)制定的泛型对象。

image.png

6.不变,关键字:invariant

如果泛型类既将泛型类型作为函数参数,又将泛型类型作为函数的输出,那么既不用out也不用in(其实就是什么都不用加,默认就是invariant修饰)。

image.png

7.为什么使用in&out?

举个例子,我们定义了一个汉堡类对象,它是一种快餐,也是一种食物

image.png
image.png

当我们定义一个FoodStore类实现Production接口,泛型类型为Food,再定义一个FastFoodStore类,同样实现Production接口,泛型类型为FastFood(FastFood是Food的子类)

image.png

因为两个类都实现了Production接口,所以定义一个Production类型的对象,泛型类型都是Food类型,这时如果用FoodStore来初始化没问题,因为FoodStore类的泛型就是Food类型,如果用FastFoodStore来初始化呢?Java中子类泛型对象是不可以赋值给父类泛型对象的,但是Kotlin中,泛型使用out修饰就可以实现这种初始化

image.png

同理,定义Everybody类实现Consumer接口,泛型类型为Food,定义ModernPeople类,同样实现Consumer接口,泛型类型为FastFood

image.png

如果定义一个Consumer类型的对象,泛型为Burger,使用Everybody来初始化,在Java中是不可以的,因为Java中的父类泛型对象不能给子类泛型对象赋值,在Kotlin中使用in修饰泛型就可以了

image.png

综上,总结如下:
父类泛型对象赋值给子类泛型对象时,泛型需要使用in修饰。
子类泛型对象赋值给父类泛型对象时,泛型需要使用out修饰。
类比于Java,其实Java也有相应的方案来处理协变和逆变:

协变:List <? extends Object> items,items可以用来读取其中的子元素,但是不能向items中写入数据(不能add),因为我们不确定哪个对象符合那个未知的Object
逆变:List<? super String> items,items可以用来写入数据,但是不能读取

8. reified

有时候,你可能想知道某个泛型参数具体时什么类型,reified关键字能帮你检查泛型参数类型。Kotlin不允许对泛型参数T做类型检查,因为泛型参数类型会被类型擦除,也就是说,T的类型信息在运行时是不可知的,Java也有这样的规则

image.png

如果想对泛型参数做类型检查,可以使用reified来修饰泛型

image.png

需要注意的是,使用reified来修饰泛型,方法需要内联(使用inline修饰方法)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,492评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,048评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,927评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,293评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,309评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,024评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,638评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,546评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,073评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,188评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,321评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,998评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,678评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,186评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,303评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,663评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,330评论 2 358

推荐阅读更多精彩内容