kotlin类与对象——>数据类、密封类、泛型
数据类,用来保存数据的类,使用data对class进行标记
data class User(val name: String, val age: Int) //编译器自动从主构造函数中声明的所有属性导出以下成员:
//— equals() / hashCode() 对;
//— toString() 格式是 "User(name=John, age=42)" ;
//— componentN() 函数 按声明顺序对应于所有属性;
//— copy() 函数(⻅下文)。 //为了确保生成的代码的一致性以及有意义的行为,数据类必须满足以下要求:
//— 主构造函数需要至少有一个参数;
//— 主构造函数的所有参数需要标记为 val 或 var ;
//— 数据类不能是抽象、开放、密封或者内部的; — (在1.1之前)数据类只能实现接口。 //此外,成员生成遵循关于成员继承的这些规则:
//— 如果在数据类体中有显式实现 equals()、hashCode() 或者 toString(),或者这些函数在
//父类中有 final 实现,那么不会生成这些函数,而会使用现有函数;
//— 如果超类型具有 open 的 componentN() 函数并且返回兼容的类型,那么会为数据类生成相应的
//函数,并覆盖超类的实现。如果超类型的这些函数由于签名不兼容或者是 final 而导致无法覆盖,那 么会报错;
//— 从一个已具 copy(......) 函数且签名匹配的类型派生一个数据类在 Kotlin 1.2 中已弃用,并且在 Kotlin 1.3 中已禁用。
//— 不允许为 componentN() 以及 copy() 函数提供显式实现。 自 1.1 起,数据类可以扩展其他类(示例请参⻅密封类)。
//在 JVM 中,如果生成的类需要含有一个无参的构造函数,则所有的属性必须指定默认值。(参⻅构造函 数)。 data class User(val name: String = "", val age: Int = 0)
2.在类体中声明的属性,对于自动生成的函数,编译器只使用在主构造函数内部定义的属性,如需在生成的实现中排除一个属性,请将其声明在类体中:
data class Person(val name: String) {
var age: Int = 0
} //在 toString() 、equals() 、hashCode() 以及 copy() 的实现中只会用到 name 属性
//并且 只有一个 component 函数 component1() 。虽然两个 Person 对象可以有不同的年龄,但它们会视 为相等 val person1 = Person("John") val person2 = Person("John") person1.age = 10
person2.age = 20
3.复制,在很多情况下,需要复制一个对象改变它的一些属性,其他部分保持不变,copy() 函数就是为 此而生成。对于上文的 User 类,其实现会类似下面这样:
fun copy(name: String = this.name, age: Int = this.age) = User(name, age) //这让我们可以写:
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
4.数据类和解构声明,为数据类生成的 Component 函数 使它们可在解构声明中使用:
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // 输出 "Jane, 35 years of age"
5.标准数据类,标准库提供了 Pair 与 Triple 。尽管在很多情况下具名数据类是更好的设计选择,因为它们通过为 属性提供有意义的名称使代码更具可读性
6.密封类,密封类用来表示受限的类继承结构:当一个值为有限几种的类型、而不能有任何其他类型时。在某种意 义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在一个实例,而密封 类的一个子类可以有可包含状态的多个实例
6.1 要声明一个密封类,需要在类名前面添加 sealed 修饰符。虽然密封类也可以有子类,但是所有子类都 必须在与密封类自身相同的文件中声明。(在 Kotlin 1.1 之前,该规则更加严格:子类必须嵌套在密封类声明的内部)。
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
//上文示例使用了Kotlin1.1的一个额外的新功能:数据类扩展包括密封类在内的其他类的可能性。)
6.2 一个密封类是自身抽象的,它不能直接实例化并可以有抽象(abstract)成员。密封类不允许有非-private 构造函数(其构造函数默认为 private)。 请注意,扩展密封类子类的类(间接继承者)可以放在任何位置,而无需在同一个文件中。使用密封类的关键好处在于使用 when 表达式 的时候,如果能够验证语句覆盖了所有情况,就不需要为 该语句再添加一个 else 子句了。当然,这只有当你用 when 作为表达式(使用结果)而不是作为语句 时才有用。
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// 不再需要 `else` 子句,因为我们已经覆盖了所有的情况
}
7.泛型,与 Java 类似,Kotlin 中的类也可以有类型参数
class Box<T>(t: T) {
var value = t
} //一般来说,要创建这样类的实例,我们需要提供类型参数:
val box: Box<Int> = Box<Int>(1) //但是如果类型参数可以推断出来,例如从构造函数的参数或者从其他途径,允许省略类型参数:
val box = Box(1) // 1 具有类型 Int,所以编译器知道我们说的是 Box<Int>。
8.型变,在java系统中有通配符类型,而kotlin中没有,而换成两个其他的东西:声明处型变(declaration-site variance)与类型投影(type projections)
8.1 声明处型变
//假设有一个泛型接口 Source<T>,该接口中不存在任何以 T 作为参数的方法,只是方法返回 T 类型值
// Java
interface Source<T> {
T nextT();
} //那么,在 Source <Object> 类型的变量中存储 Source <String> 实例的引用是极为安全的⸺ 没有消费者-方法可以调用。
//但是 Java 并不知道这一点,并且仍然禁止这样操作
// Java
void demo(Source<String> strs) {
Source<Object> objects = strs; // !!!在 Java 中不允许
// ......
}
为了修正这一点,我们必须声明对象的类型为 Source<? extends Object>,这是毫无意义的,因 为我们可以像以前一样在该对象上调用所有相同的方法,所以更复杂的类型并没有带来价值。但编译器并不知道
在 Kotlin 中,有一种方法向编译器解释这种情况。这称为声明处型变:我们可以标注 Source 的类型参数 T 来确保它仅从 Source<T> 成员中返回(生产),并从不被消费。为此,我们提供 out 修饰符
interface Source<out T> {
fun nextT(): T
}
fun demo(strs: Source<String>) {
val objects: Source<Any> = strs
// 这个没问题,因为 T 是一个 out-参数 // ......
}
一般原则是:当一个类 C 的类型参数 T 被声明为 out 时,它就只能出现在 C 的成员的输出-位置,但 回报是 C<Base> 可以安全地作为 C<Derived> 的超类。
简而言之,他们说类 C 是在参数 T 上是协变的,或者说 T 是一个协变的类型参数。你可以认为 C 是 T 的生产者,而不是 T 的消费者。
out修饰符称为型变注解,并且由于它在类型参数声明处提供,所以我们称之为声明处型变。这与 Java 的使用处型变相反,其类型用途通配符使得类型协变。
另外除了 out,Kotlin 又补充了一个型变注释:in。它使得一个类型参数逆变:只可以被消费而不可以被 生产。逆变类型的一个很好的例子是 Comparable :
interface Comparable<in T> {
operator fun compareTo(other: T): Int
}
fun demo(x: Comparable<Number>) {
x.compareTo(1.0)
// 1.0 拥有类型 Double,它是 Number 的子类型
// 因此,我们可以将 x 赋给类型为 Comparable <Double> 的变量
val y: Comparable<Double> = x // OK!
}
我们相信 in 和 out 两词是自解释的(因为它们已经在 C# 中成功使用很⻓时间了),因此上面提到的助 记符不是真正需要的,并且可以将其改写为更高的目标
8.2 类型投影,使用处型变:类型投影
将类型参数 T 声明为 out 非常方便,并且能避免使用处子类型化的麻烦,但是有些类实际上不能限制为只返回 T !一个很好的例子是 Array:
class Array<T>(val size: Int) {
fun get(index: Int): T { ...... }
fun set(index: Int, value: T) { ...... }
} //该类在 T 上既不能是协变的也不能是逆变的。这造成了一些不灵活性。考虑下述函数 fun copy(from: Array<Any>, to: Array<Any>) {
assert(from.size == to.size)
for (i in from.indices)
to[i] = from[i]
} //这个函数应该将项目从一个数组复制到另一个数组。让我们尝试在实践中应用它
val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" }
copy(ints, any)
// ^ 其类型为 Array<Int> 但此处期望 Array<Any>
这里我们遇到同样熟悉的问题:Array <T> 在 T 上是不型变的,因此 Array <Int> 和 Array <Any> 都不是另一个的子类型。为什么?再次重复,因为 copy 可能做坏事,也就是说,例如它可能尝 试写一个String到 from,并且如果我们实际上传递一个 Int 的数组,一段时间后将会抛出一个ClassCastException 异常。
//那么,我们唯一要确保的是 copy() 不会做任何坏事。我们想阻止它写到 from,我们可以
fun copy(from: Array<out Any>, to: Array<Any>) { ...... }
这里发生的事情称为类型投影:我们说 from 不仅仅是一个数组,而是一个受限制的(投影的)数组:我们 只可以调用返回类型为类型参数 T 的方法,如上,这意味着我们只能调用 get() 。这就是我们的使用 处型变的用法,并且是对应于 Java 的 Array<? extends Object> 、但使用更简单些的方式。
//你也可以使用 in 投影一个类型:
fun fill(dest: Array<in String>, value: String) { ...... }
Array<in String> 对应于Java的 Array<? super String>,也就是说,你可以传递一个 CharSequence 数组或一个 Object 数组给 fill() 函数
8.3 星投影,有时你想说,你对类型参数一无所知,但仍然希望以安全的方式使用它。这里的安全方式是定义泛型类 型的这种投影,该泛型类型的每个具体实例化将是该投影的子类型,Kotlin 为此提供了所谓的星投影语法
— 对于 Foo <out T : TUpper> ,其中 T 是一个具有上界 TUpper 的协变类型参数,Foo <*> 等价于 Foo <out TUpper> 。这意味着当 T 未知时,你可以安全地从 Foo <*> 读取 TUpper 的值。
— 对于 Foo <in T> ,其中 T 是一个逆变类型参数,Foo <*> 等价于 Foo <in Nothing> 。这 意味着当 T 未知时,没有什么可以以安全的方式写入 Foo <*> 。
— 对于 Foo <T : TUpper> ,其中 T 是一个具有上界 TUpper 的不型变类型参数,Foo<*> 对于 读取值时等价于 Foo<out TUpper> 而对于写值时等价于 Foo<in Nothing> 。
如果泛型类型具有多个类型参数,则每个类型参数都可以单独投影。例如,如果类型被声明为 interface Function <in T, out U>,我们可以想象以下星投影:
— Function<*, String> 表示 Function<in Nothing, String> ; — Function<Int, *> 表示 Function<Int, out Any?> ;
— Function<*, *> 表示 Function<in Nothing, out Any?> 。
注意:星投影非常像 Java 的原始类型,但是安全。
9.泛型函数
//不仅类可以有类型参数。函数也可以有。类型参数要放在函数名称之前:
fun <T> singletonList(item: T): List<T> {
// ......
}
fun <T> T.basicToString(): String { // 扩展函数
// ......
}
//要调用泛型函数,在调用处函数名之后指定类型参数即可:
val l = singletonList<Int>(1) //可以省略能够从上下文中推断出来的类型参数,所以以下示例同样适用
val l = singletonList(1)
10. 泛型约束,能够替换给定类型参数的所有可能类型的集合可以由泛型约束限制。
11. 上界
//最常⻅的约束类型是与 Java 的 extends 关键字对应的 上界
fun <T : Comparable<T>> sort(list: List<T>) { ...... } //冒号之后指定的类型是上界:只有 Comparable<T> 的子类型可以替代 T 。例如:
sort(listOf(1, 2, 3)) // OK。Int 是 Comparable<Int> 的子类型 sort(listOf(HashMap<Int, String>())) // 错误:HashMap<Int, String>不是 Comparable<HashMap<Int, String>> 的子类型 //默认的上界(如果没有声明)是 Any? 。在尖括号中只能指定一个上界。如果同一类型参数需要多个上界,我们需要一个单独的 where-子句
fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
where T : CharSequence,
T : Comparable<T> {
return list.filter { it > threshold }.map { it.toString() }
}
//所传递的类型必须同时满足 where 子句的所有条件。在上述示例中,类型 T 必须既实现了 CharSequence 也实现了 Comparable
12.类型擦除,
Kotlin 为泛型声明用法执行的类型安全检测仅在编译期进行。运行时泛型类型的实例不保留关于其类 型实参的任何信息。其类型信息称为被擦除。例如,Foo<Bar> 与 Foo<Baz?> 的实例都会被擦除为Foo<*> 。
因此,并没有通用的方法在运行时检测一个泛型类型的实例是否通过指定类型参数所创建 ,并且编译器禁止这种 is 检测。
类型转换为带有具体类型参数的泛型类型,如 foo as List<String> 无法在运行时检测。当高级程序逻辑隐含了类型转换的类型安全而无法直接通过编译器推断时,可以使用这种非受检类型转换。编 译器会对非受检类型转换发出警告,并且在运行时只对非泛型部分检测(相当于 foo as List<*> )。
泛型函数调用的类型参数也同样只在编译期检测。在函数体内部,类型参数不能用于类型检测,并且类 型转换为类型参数(foo as T)也是非受检的。然而,内联函数的具体化的类型参数会由调用处内联函数体中的类型实参所代入,因此可以用于类型检测与转换,与上述泛型类型的实例具有相同限制。
kotlin类与对象——>数据类、密封类、泛型的更多相关文章
- Kotlin——最详细的数据类、密封类详解
在前面几个章节章节中,详细的讲解了Koltin中的接口类(Interface).枚举类(Enmu),还不甚了解的可以查看我的上一篇文章Kotlin--接口类.枚举类详解.当然,在Koltin中,除了接 ...
- 【学习笔记】【oc】类和对象及类的三大基本特征
1.类和对象 类是抽象化,对象是具体化. (1)定义类: 分为两个步骤,类的声明:定义类的成员变量和方法:@interface 用于声明定义类的接口部分,@end表面定义结束:. 成员变量的定义:{} ...
- 0604-面向对象、类与对象、类、static、构造方法/析构方法
一.面向对象 1.面向过程:一个人分步骤完成某个事情 2.面向对象:某件事情拆分为多个任务,由每个对象独立完成,最后调用整合为一个完整的项目 3.三要素:继承.封装.多态. 封装:私有化属性 提供公共 ...
- C#类,对象,类成员简介
本节内容 1.类(class)是现实世界事物的模型 2.类与对象的关系,什么时候叫“对象”什么时候叫“实例” 3.引用变量与实例的关系 4.类的三大成员: ①属性(Property): ②方法(Met ...
- C++类的对象和类的指针的区别
#include <iostream> #include <string> using namespace std; class Student { public: stati ...
- c++中的类的对象与类的指针
以上内容来自:http://wenku.baidu.com/link?url=haeRBhswlEcqddk48uW8YVMsdFNWsllimn_dzUYchb6G9NdT4pqgluCpnLQId ...
- python的类和对象(类的静态字段)
转自:http://www.cnblogs.com/Eva-J/p/5044411.html 什么是静态字段 在开始之前,先上图,解释一下什么是类的静态字段(我有的时候会叫它类的静态变量,总之说的都是 ...
- python: 面向对象:类和对象调用类中的变量和方法
一. 面向对象初识 我们在生活中做事都是面向过程的,前面实现一些基本逻辑功能代码也是用面向过程的语句实现的,后来学了函数,把这些功能又装到了函数里.但用面向过程的方法去写程序,只能实现一个功能,我们要 ...
- php函数、类和对象以及类的封装、继承、类的静态方法、静态属性
1.函数 php内置函数可以直接使用,如果没有安装php扩展即可 自定义函数 //函数function 函数名 function dump($var = null){ //支出默认参数 ...
- python__高级 : 动态添加 对象属性, 类属性, 对象实例方法, 类静态方法, 类方法
给对象添加实例属性,可以直接这样 t.age = 18 ( 假设 t = Test() ) 给类添加类属性 , 也可以直接这样 Test.age = 18 那给对象添加实例方法,可以在类外面 ...
随机推荐
- 【MongoDB】Re04 副本集 ReplicationSet
MongoDB中的副本集(Replica Set)是一组维护相同数据集的mongod服务. 副本集可提供冗余和高 可用性,是所有生产部署的基础. 也可以说,副本集类似于有自动故障恢复功能的主从集群.通 ...
- 【SpringBoot】07 探索配置方式 Part3 多环境配置
1.按多个Profile文件来配置 SpringBoot默认会使用第一个 我们可以在默认的application.properties中设置激活哪种环境配置 profile的命名规则 2.按Yml可以 ...
- 自动驾驶开源数据库 —— nuscenes
地址: https://www.nuscenes.org/
- 《Python数据可视化之matplotlib实践》 源码 第三篇 演练 第九章
图 9.1 import matplotlib.pyplot as plt import numpy as np fig=plt.figure() ax=fig.add_subplot(111) f ...
- multi-GPU环境下的batch normalization需要特殊实现吗?
3年前曾经写过关于分布式环境下batch normalization是否需要特殊实现的讨论:batch normalization的multi-GPU版本该怎么实现? [Tensorflow 分布式P ...
- 如何对MIL-STD-1553B进行选型
MIL-STD-1553B产品选型是一个复杂而细致的过程,需要综合考虑多个因素以确保所选产品能够满足特定应用场景的需求. 一.引言 MIL-STD-1553B作为一种广泛应用于航空航天领域的数据总 ...
- 深度解析HarmonyOS SDK实况窗服务源码,Get不同场景下的多种模板
HarmonyOS SDK实况窗服务(Live View Kit)作为一个实时呈现应用服务信息变化的小窗口,遍布于设备的各个使用界面,它的魅力在于将复杂的应用场景信息简洁提炼并实时刷新,在不影响当前其 ...
- LaTeX 编译 acmart 文档报错:hyperref must be loaded before hyperxmp.
在编译一篇从 arXiv 下载的文档时遇到如下错误: Package hyperxmp Error: hyperref must be loaded before hyperxmp. 根据 GitHu ...
- python pyqt6 设定窗口置顶
self.setWindowFlag(Qt.WindowType.WindowStaysOnTopHint)即可效果一般,页面会出现闪烁一次, # 置顶按钮 self.top_button = QPu ...
- 【YashanDB知识库】收集分区表统计信息采样率小于1导致SQL执行计划走偏
[问题分类]性能优化,BUG [关键字]分区表,统计信息,采样率 [问题描述]收集表(分区表)级别的统计信息时,如果采样率小于1,dba_ind_statistics中partition_name i ...