数据类(关键字:data)
介绍:在Kotlin之中可以创建一个只包含数据的类,关键字为:data
实例1:
data class User(val name:String ,val age:Int){
}
数据类会自动生成以下几个通用函数。 当然是在编译器编译时自动生成的。
- equals()
- hashCode()
- toString() : 按照上面的例子来说,格式会自动生成“User(name=xxx,age=xxx)”
- componentN() 这个N会替代数字,按下面例子可以参考
- copy()
实例2:
data class User(var name: String, var age: Int) {
}
fun main(array: Array<String>) {
var user =User("Zinyan",18)
//根据构造器之中的顺序和数量自动生成对应的componentN
user.component1() //返回 Zinyan
user.component2() //返回 18
println(user.toString())
}
输出:
User(name=Zinyan, age=18)
注意:为了确保数据类生成代码的一致性。数据类需要满足以下条件
- 主构造器至少包含一个参数
- 所有主构造函数的参数必须标识
val
或者var
代表该参数属于类变量 - 数据类不可以声明为
abstract
,open
,sealed
,inner
。 - 数据类不能继承其他类,但是可以继承接口对象。
实例3:
interface fs{
}
data class User(var name: String, var age: Int):fs {
}
复制
介绍:复制一个数据类的数据时,我们应该使用copy()
函数。所有数据类对象都有一个自动生成的copy()
函数
我们可以在复制的过程之中,修改指定参数的值。
实例1:
data class User(var name: String, var age: Int) {
}
fun main(array: Array<String>) {
val user1 = User(name = "zinyan", age = 12)
val user2 = user1.copy(age = 18)
println(user1.toString())
println(user2.toString())
}
输出:
User(name=zinyan, age=12)
User(name=zinyan, age=18)
那么我们在复制之后,修改了user1会影响user2的结果么?
实例2:
data class User(var name: String, var age: Int) {
}
fun main(array: Array<String>) {
val user1 = User(name = "zinyan", age = 12)
val user2 = user1.copy(age = 18)
println(user1.toString())
user1.name="Z同学"
println(user2.toString())
}
输出:
User(name=zinyan, age=12)
User(name=zinyan, age=18)
结果没有变化,说明我们的copy()
是数据复制。而不是引用复制。
解构声明
介绍:数据类对象的属性,允许在解构声明之中使用。
实例:
data class User(var name: String, var age: Int) {
}
fun main(array: Array<String>) {
val user1 = User(name = "zinyan", age = 12)
val (name, age) = user1
println("打印这两个参数值:$name,$age ")
}
输出:
打印这两个参数值:zinyan,12
标准数据类
介绍:Kotlin给我们提供了两个标准的数据类。我们也可以直接使用这两个类。
它们分别是: Pair
和Triple
。 Pair是一个二元数据类,Triple 是一个三元数据类。
实例1:
var pair1 = Pair(1, "2")
pair1.first// 得到第一项参数
pair1.second //得到第二项的参数
var pai2 = Triple(1, "2", User(name = "zinyan", age = 12))
pai2.first //得到第一项的参数
pai2.second //得到第二项的参数
pai2.third //得到第三项的参数
注意:Pair 和Triple 的属性是泛型,也就是说可以存储任何对象进去。
实例2:
fun main(array: Array<String>) {
val user1 = User(name = "zinyan", age = 12)
println(user1)
var s = mapOf("name" to "Zinyan","age" to 12)
println(s)
}
输出:
User(name=zinyan, age=12)
{name=Zinyan, age=12}
我们可以通过mapOf()
创建 一元,二元,三元。最多能创建三元对象。因为Triple
实例3:
var s = mapOf("name" to "Zinyan","age" to 12,"six" to "男")
println(s)
密封类(关键字:sealed)
介绍:密封类用来表示受限的类继承结构。关键字 sealed
修饰。当一个值为有限几种的类型而不能有任何其他类型时表示密封。
枚举对象也是值的集合,但是每一个值只有一个实例。而密封类的值可以有多个实例。
密封类可以有子类,被继承。但是所有的子类都必须是密封类的嵌套类。
注意:密封类不能使用interface,abstract进行修饰。
实例1:
//密封类
sealed class Expr{
}
其实,我们大部分密封类都是配合When语句一起使用。
实例1:
//密封类
sealed class Expr {
object Show : Expr()
object Hide : Expr()
}
fun execute(op: Expr) = when (op) {
Expr.Show -> println("Show 显示")
Expr.Hide -> println("Hide 隐藏")
}
fun main(array: Array<String>) {
var expr:Expr = Expr.Show
execute(expr)
}
输出:
Show 显示
密封类,实现子类扩展:
实例2:
//密封类
sealed class Expr {
object Show : Expr()
object Hide : Expr()
//普通嵌套类
class Exxx1(val name: String) : Expr()
//数据类嵌套类
data class Exxx2(val age: Int) : Expr()
}
fun execute(op: Expr) = when (op) {
Expr.Show -> println("Show 显示")
Expr.Hide -> println("Hide 隐藏")
is Expr.Exxx1 -> println(op.name)
is Expr.Exxx2 -> println(op.age)
}
fun main(array: Array<String>) {
var expr: Expr = Expr.Show
execute(expr)
var e1 = Expr.Exxx1("Z同学")
var e2 = Expr.Exxx2(100)
execute(e1)
execute(e2)
}
输出:
Show 显示
Z同学
100
密封类里面 封装的类,这种实现思路是枚举等不容易实现的。
泛型
介绍:Kotlin泛型的概念和java之中的概念是一样的。都是将数据类型参数化。可以不同固定参数类。用在类,接口,函数等传参上。
其实泛型主要就是为了消除类型的强制转换的烦恼。 和java之中一样也是用<T>
指带
类传参泛型
实例:
class A<T>(t: T) {
var value = t
}
fun main(array: Array<String>) {
var ss1 = A("Zinyan")
println(ss1.value)
var ss2 = A(21)
println(ss2.value)
}
输出:
Zinyan
21
函数传参泛型
介绍:Kotlin之中泛型函数的声明和java相同。类型参数要放在函数名的前面
实例1:
class A<T>(t: T) {
var value = t
}
/**
* 定义一个泛型传参,返回一个泛型参数
*/
fun <T> fanxin(value: T): A<T> {
return A(value)
}
//上一个方法的简写
fun <T> fanxin1(value: T) = A(value)
fun main(array: Array<String>) {
var ff1 = fanxin1("Z同学")
println(ff1.value)
var ff2 = fanxin("Zinyan")
println(ff2.value)
}
输出:
Z同学
Zinyan
加深印象,在When之中使用泛型
实例2:
fun <T> doPrintln(value: T) {
when (value) {
is Int -> println("这是一个整数$value")
is String -> println("这是一个字符串$value")
is Long -> println("这是一个Long 长整数$value")
else -> println("这个数据格式我没判断 $value")
}
}
fun main(array: Array<String>) {
doPrintln("Zinyan")
doPrintln(10)
doPrintln(10L)
doPrintln(A("Zinyan"))
}
输出:
这是一个字符串Zinyan
这是一个整数10
这是一个Long 长整数10
这个数据格式我没判断 cn.zinyan.demo.kotlin.A@6e0be858
泛型约束
介绍:可以使用泛型约束来设定一个给定参数的允许使用的类型。
Kotlin之中使用 :
符号对泛型类型上限进行约束。
实例1:
fun <T : Comparable<T>> sort(list: List<T>) {
}
针对泛型T 的范围进行了约束
型变
介绍:Kotlin之中没有通配符类型。而是其他两个变型。
分别为:声明处型变(declaration-site variance)
类型投射(type projections)
声明处型变
介绍:声明处的类型变异,使用协变注解修饰符:in
,out
其中:int 指消费者。 out 指生产者。
in 关键字
介绍:in会使得一个类型参数逆变,逆变类型参数只能用作输入,可以作为入参的类型,但是不能作为返回值的类型。
实例:
class Zinyan<in A>(a: A) {
fun test(a: A) {
}
}
fun main(array: Array<String>) {
var strD = Zinyan("a")
var strE = Zinyan<Any>("b")
strD = strE
}
out 关键字
介绍:out 会使得一个类型参数协变。协变类型参数只能作为输出。可以作为返回值类型但是无法作为入参类型。
和in是相反的。
实例:
class Zinyan1<out A>(val a: A) {
//返回a的参数
fun test(): A {
return a
}
}
fun main(array: Array<String>) {
var s= Zinyan1("A")
var any = Zinyan1<Any>("B")
any = s
println(any.test())
}
输出:
A
总结: 主要就是申明泛型的时候,如果入参和出参想规范。定义入参不能作为出参。那就在入参添加 in
出参添加 out
数据类Pair
就是将两个入参作为out
声明。我们可以随意定义两个入参类型。
了解一下就可以。
类型投射
介绍:如果你想表示你并不知道类型参数的任何信息,但是任然希望能够安全的使用它。这个将会对泛型类型定义一个类型投射。要求这个泛型类型的所有实体实例,都是这个投射的子类型。
Kotlin提供了一种语法。该语法称为 星号投射(start-projection):
概念:
- 假如类型定义为
Foo<out T>
: T是一个协变的类型参数,范围约束为TUpper,Foo<>
等价于Foo<out TUpper>
. 表示:当前T未知时,你可以安全的从Foo<>
获取TUpper类型的值。 - 假如类型定义为
Foo<int T>
: T是一个逆变的类型参数,Foo<>
等价于Foo<in Nothing>
.表示:当前T未知时,你不能安全的向Foo<>
写入任何东西。 - 假如类型定义为
Foo<T>
,T是一个协变的类型参数,范围约束为TUpper。对于读取值的场合,Foo<*>
等价于Foo<out Tupper>
。对于写入值的场合,等价于Foo<in Nothing>
如果定义泛型为: interface Funcation<in T,out U>
。那么可以出现以下几种星号投射:
1. Funcation<*,String>,代表Function<in Nothing,String>;
2. Funcation<Int,*>,代表Funcation<Int,out Any?>;
3. Funcation<*,*>,代表Funcation<in Nothing,out Any?>;
注意:星号投射与java的原生类型非常相似,但是在Kotlin中可以安全使用。
实例:
//定义了一个XX 类,传参为泛型,三个常量
class XX<T>(val t1: T, val t2: T, val t3: T)
//定义了一个Apple类,传参为String类型。一个变量
class Apple(var name: String)
fun main(array: Array<String>) {
val a1: XX<*> = XX(12, "String", Apple("苹果"))
val a2: XX<Any?> = XX(12, "String", Apple("苹果"))
//a1 和a2 的实现是一样的。
val apple = a1.t3 //参数类型为Any
println(apple.toString()) //因为是Any 类型所以没有name 参数
val apple2 = apple as Apple //类型转换,强转成Apple类
println(apple2.name)
//数组
val temp: ArrayList<*> = arrayListOf("String", 1, 1.2f, Apple("苹果"))
for (item in temp) {
println(item)
}
}
输出:
cn.zinyan.demo.kotlin.Apple@6e0be858
苹果
String
1
1.2
cn.zinyan.demo.kotlin.Apple@2626b418
所以,如果对这个投射还是不太能理解的话,可以将*
理解成指代了所有的类型,相当于Any?
。 可以是Null的Any类。
枚举(关键字:menu)
介绍:枚举最基本的使用方法就是实现一个类型安全的枚举。
枚举常量用逗号,
分割,每个枚举常量都是一个对象。
前面有简单提及到枚举,这篇将详细介绍枚举。
创建
实例1:
//定义了一个枚举类,Color
enum class Color {
RED, BLACK, BLUE, GREEN, WHITE
}
我们还可以针对常量进行初始化赋值。
实例2:
enum class Color1(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
两个枚举对象输出以下
实例3:
fun main(array: Array<String>) {
//默认值
println(Color.RED)
println(Color.BLACK)
println(Color.BLUE)
//赋值后
println(Color1.RED.rgb)
println(Color1.GREEN.rgb)
println(Color1.BLUE.rgb)
}
输出:
RED
BLACK
BLUE
16711680
65280
255
枚举类还支持以声明自己的匿名类及相关方法,以及覆盖基类的方法。
实例4:
enum class State {
WAITING {
override fun signal() = TALKING
},
TALKING {
override fun signal() = WAITING
};
abstract fun signal(): State
}
fun main(array: Array<String>) {
println(State.WAITING.signal())
}
输出:
TALKING
如果枚举类定义任何成员,要使用分号将成员定义中的枚举常亮定义分隔。
也即是说,我们在枚举类中写其他函数的时候,要在最后一个枚举对象的后面加上;
。 上面的例子也体现了这点
使用
介绍:Kotlin之中,枚举类具有合成方法,允许遍历定义的枚举常亮,并通过其名称获取枚举常数。
有两个基本常量:
- val name:String //获取枚举名称
- val ordial:Int //获取枚举值在所有枚举数组中定义的顺序
有两个常用函数:
- values() //获取枚举对象
- valueOf() //通过枚举名称得到枚举对象。
实例1:
//定义了一个枚举类,Color
enum class Color {
RED, BLACK, BLUE, GREEN, WHITE
}
fun main(array: Array<String>) {
var temp: Color = Color.RED
println(Color.values())
println(Color.valueOf("RED"))
println(temp.name)
println(temp.ordinal)
}
输出:
[Lcn.zinyan.demo.kotlin.Color;@6e0be858
RED
RED
0
还可以使用 enumValues
实例2:
//定义了一个枚举类,Color
enum class Color {
RED, BLACK, BLUE, GREEN, WHITE
}
inline fun <reified T : Enum<T>> printAllValues() {
println(enumValues<T>().joinToString { it.name })
}
inline fun <reified T : Enum<T>> printAllValues(s: String) {
println(enumValueOf<T>(s).name)
}
fun main(array: Array<String>) {
printAllValues<Color>()
printAllValues<Color>("RED")
}
输出:
RED, BLACK, BLUE, GREEN, WHITE
RED
附录
上面例子的Demo代码。
https://zinyan.com/upload/2021/07/User-3ffd0ddc5269483aaf27a945a6b6865b.kt
评论区