Kotlin 学习笔记(五)类型、密封类、数据协变、类型投射和泛型

PipingPlover_ZH-CN0992806167_1920x1080.jpg

前言
本文章只是用于记录学习,所以部分地方如果有错误或者理解不对的地方,麻烦请指正。本篇为 csdn 原文章 转移修改版 原文章

Kotlin 学习笔记(四)

简述:

  1. kotlin 中数据类的声明及条件
  2. kotlin 中 密封类/ 枚举
  3. kotlin 中类型判断
  4. kotlin 中泛型 和 数据协变
  5. kotlin 中的类型投射

1. 数据类

  在java 中我们通常会创建很多 Bean 类来存储 数据,在kotlin 中有专门的数据类,“data”

data class User(val name: String, val age: Int)

数据类必须满足几个条件

  1. 主构造函数需要至少有一个参数;
  2. 主构造函数的所有参数需要标记为 val 或 var;
  3. 数据类不能是抽象、开放、密封或者内部的;

数据类 也为我们自动生成了部分代码:

  1. equals()/hashCode() 对;
  2. toString() 格式是 "User(name=John, age=42)";
  3. componentN() 函数 按声明顺序对应于所有属性;
  4. copy() 函数

  在 JVM 中,如果生成的类需要含有一个无参的构造函数,则所有的属性必须指定默认值。

data class User(val name: String = "", val age: Int = 0)

  下面我们使用代码简单联系一下

data class User(val name: String, val age: Int)


fun main(args: Array<String>) {
    var json = User(name = "ymc",age = 1)
    val json1 = json.copy(age = 2)
    println(json1)  // 默认调用 User的 tostring()
}

   在java 中我们通常想要赋值一个值,但是只需要改变某一项的值的数据信息,kotlin 中的 copy函数,只需要传入不一样的数据,就会自动化改变,并返回给你修改后的所有数据信息。

2.密封类

  密封类,可以理解为枚举,规定了有限个类型,不可以存在其他类型,但枚举每个枚举常量只存在一个示例,但是密封类的子类可以有多个示例,所以可以将密封类看做是枚举的拓展,基于枚举,高于枚举,青出于蓝而胜于蓝。

  声明一个密封类,需要在类名前面添加 sealed 修饰符。虽然密封类也可以有子类,但是所有子类都必须在与密封类自身相同的文件中声明。eg:

// 密封类
sealed class Expr

data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

  相对于密封类的子类 必须要在一个文件中,扩展密封类子类的类(间接继承者)可以放在任何位置,而无需在同一个文件中。

密封类注意点:

1.一个密封类是自身抽象的,它不能直接实例化 ,但是可以有抽象(abstract)成员。
2.密封类不允许有非-private 构造函数(其构造函数默认为 private)。

  使用密封类的关键好处在于使用 when 表达式 的时候,如果能够验证语句覆盖了所有情况,就不需要为该语句再添加一个 else 子句了。

fun eval(expr: Expr): Double{
    return when(expr) {
        is Const -> expr.number
        is Sum -> eval(expr.e1) + eval(expr.e2)
        NotANumber -> Double.NaN
    }
}

2. 类型检测以及自动类型转换

我们可以使用 is 运算符检测一个表达式是否某类型的一个实例(类似于Java中的instanceof关键字)。

fun getStringLength(obj: Any): Int? {
    if (obj is String) {
        // 做过类型判断以后,obj会被系统自动转换为String类型
        return obj.length
    }

    //在这里还有一种方法,与Java中instanceof不同,使用!is
    // if (obj !is String){
    //   // XXX
    // }

    // 这里的obj仍然是Any类型的引用
    return null
}

或者 可以再运算式中 使用

fun getStringLength(obj: Any): Int? {
  // 在 `&&` 运算符的右侧, `obj` 的类型会被自动转换为 `String`
  if (obj is String && obj.length > 0)
    return obj.length
  return null
}

3. 泛型

  kotlin中的泛型和 java 中的差不多,但是很多方面更加简洁。

// kotlin中的 泛型
class Box<T>(t: T) {
    var value = t
}

如果我们要创建

val box: Box<Int> = Box<Int>(1)
// 1 具有类型 Int,所以编译器知道我们说的是 Box<Int>。
val box = Box(1) 

在学习下边Kotlin 的 泛型特性的时候,我们先回顾一下 java 中的 泛型 使用

  1. 通配符上界,只能从中读取元素,不能添加元素,称为生产者(Producers),用< ? extends T>表示。
  2. 通配符下界,只能添加元素,不能直接读取下界类型的元素,称为消费者(Consumers),用< ? super T>表示。

3.1 通配符上界

  < ? extends T>(T表示通配符的上界),表示可以接收T以及T的子类参数,也就是说可以安全的读取到T的实例,事实上所有的集合元素都是T的子类的实例,但不能向其添加元素,因为没法确定添加的实例类型跟定义的类型是否匹配

List<String> strs = new ArrayList<String>();
strs.add("0");
strs.add("1");
List<? extends Object> objs = strs;

objs.get(0); // 可以获取

objs.add(1); // 但是添加的时候报错

  经过本人测试,不管添加 Int ,String 类型都提示无法添加,上面的例子说明了objs可以读取值,但是再往objs里面添加值的时候,就会出错,没法确定添加的实例类型跟定义的类型是否匹配。

3.2 通配符下界

< ? super T>,其中T就表示通配符的下界。
举个栗子:Collection< ? super String>是Collection< String>的父类型,所以可以直接add和set,但是get的时候获取到的类型是Object而不是String类型。

List<String> strs = new ArrayList<String>();
strs.add("0");
List<? super String> objs = strs;
objs.add("1");
objs.set(0, "2");
// 得到Object类型,如果想要String 还需要强转
Object s = objs.get(0);

Kotlin 中的泛型

  不管是Java还是Kotlin,泛型都是使用擦除来实现的,这意味着当你在使用泛型时,任务具体的类型信息都被擦除的,你唯一知道的就是你再使用一个对象。比如,Box<String>和Box<Int>在运行时是想的类型,都是Box的实例。在使用泛型时,具体类型信息的擦除是我们不不懂得不面对的,在Kotlin中也为我们提供了一些可供参考的解决方案:、

  1. 类型协变
  2. 类型投射
  3. 泛型约束

3.3 类型协变

  假设我们有一个泛型接口Source< in T, out R >, 其中T由协变注解in修饰,R由协变注解Out修饰.

internal interface Source<in T, out R> {

    // in 函数,可以当做参数使用,消费,但是不能作为返回值
    fun mapT(t: T): Unit
    
    // out 函数,不能用来当参数,不能消费,但是可以作为返回值
    fun nextR(): R
}

in T:     来确保Source的成员函数只能消费T类型,而不能返回T类型
out R:  来确保Source的成员函数只能返回R类型,而不能消费R类型

  从上面的解释中,我们可以清楚的知道了协变注解in和out的用意,其实际上是定义了类型参数在该类或者接口的用途,是用来消费的还是用来返回的,对其做了相应的限定。

3.4 类型投射

  从上述代码中我们了解到 泛型的 in 和 out 的使用,下面我们通过一段 代码了解 到底什么是 类型投射

fun copy(from: Array<out String>, to: Array<Any>) {
    // ...
}

fun fill(dest: Array<in String>, value: String) {
    // ...
}

  from的泛型参数使用了协变注解out修饰,意味着该参数不能在该函数中消费,在该方法中 禁止 对该参数进行任何操作。

  对于fill函数中,dest的泛型参数使用了协变注解in修饰,Array<in String>与Java的 Array < ? super String> 相同, 也就是说, 你可以使用CharSequence数组,或者 Object 数组作为 fill() 函数的参数、

  这种声明在Kotlin中称为类型投射(type projection),类型投射的主要用于对参数做了相对因的限定,避免了对该参数类的不安全操作。

3.5 泛型函数

  类可以有类型参数。函数也可以有。类型参数要放在函数名称之前:

fun <T> singletonList(item: T): List<T> {
    // ……
}

fun <T> T.basicToString() : String {  // 扩展函数
    // ……
}

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