Kotlin学习教程(五)

简介: 《Kotlin学习教程(五)》介绍了Kotlin中的泛型、嵌套类、内部类、匿名内部类、枚举、密封类、异常处理、对象、单例、对象表达式、伴生对象、委托等高级特性。具体内容包括泛型的定义和类型擦除、嵌套类和内部类的区别、匿名内部类的创建、枚举类的使用、密封类的声明和用途、异常处理机制、对象和单例的实现、对象表达式的应用、伴生对象的作用以及类委托和属性委托的使用方法。通过这些内容,读者可以深入理解Kotlin的高级特性和设计模式。

Kotlin学习教程(五)

参考往期教程:
Kotlin学习教程(一)
Kotlin学习教程(二)
Kotlin学习教程(三)
Kotlin学习教程(四)
Kotlin学习教程(五)
Kotlin学习教程(六)
Kotlin学习教程(七)
Kotlin学习教程(八)
Kotlin学习教程(九)
Kotlin学习教程(十)

===

泛型
class Data(var t : T)
interface Data
fun logic(t : T){}
定义:

class TypedClass(parameter: T) {
val value: T = parameter
}
这个类现在可以使用任何的类型初始化,并且参数也会使用定义的类型,我们可以这么做:

val t1 = TypedClass("Hello World!")
val t2 = TypedClass(25)
但是Kotlin很简单并且缩减了模版代码,所以如果编译器能够推断参数的类型,我们甚至也就不需要去指定它:

val t1 = TypedClass("Hello World!")
val t2 = TypedClass(25)
val t3 = TypedClass(null)
类型擦除
class Data{}

Log.d("test", Data().javaClass.name)
Log.d("test", Data().javaClass.name)

// 输出
com.study.jcking.weatherkotlin.exec.Data
com.study.jcking.weatherkotlin.exec.Data
声明了一个泛型类Data,并实现了两种不同类型的实例。但是在获取类名是,却发现得到了同样的结果
com.study.jcking.weatherkotlin.exec.Data,这其实是在编译期擦除了泛型类型声明。

嵌套类
嵌套类顾名思义,就是嵌套在其他类中的类。而嵌套类外部的类一般被称为包装类或者外部类。

class Outter{
class Nested{
fun execute(){
Log.d("test", "Nested -> execute")
}
}
}

// 调用
Outter.Nested().execute()

//输出
Nested -> execute
嵌套类可以直接创建实例,方式是包装类.嵌套类
val nested : Outter.Nested()

内部类
内部类和嵌套类有些类似,不同点是内部类用关键字inner修饰。

class Outter{
val testVal = "test"
inner class Inner{
fun execute(){
Log.d("test", "Inner -> execute : can read testVal=$testVal")
}
}
}

// 调用
val outter = Outter()
outter.Inner().execute()

// 输出
Inner -> execute : can read testVal=test
内部类不能直接创建实例,需要通过外部类调用

val outter = Outter()
outter.Inner().execute()
匿名内部类

// 通过对象表达式来 创建匿名内部类的对象,可以避免重写抽象类的子类和接口的实现类,这和Java中匿名内部类的是接口和抽象类的延伸一致。
text.setOnClickListener(object : View.OnClickListener{
override fun onClick(p0: View?) {
Log.d("test", p0.string())
}
})


mViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}

override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
    TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}

override fun onPageSelected(position: Int) {
    TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}

})
枚举
enum class Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY
}
枚举可以带参数:

enum class Icon(val res: Int) {
UP(R.drawable.ic_up),
SEARCH(R.drawable.ic_search),
CAST(R.drawable.ic_cast)
}

val searchIconRes = Icon.SEARCH.res
枚举可以通过String匹配名字来获取,我们也可以获取包含所有枚举的Array,所以我们可以遍历它。

val search: Icon = Icon.valueOf("SEARCH")
val iconList: Array = Icon.values()
而且每一个枚举都有一些函数来获取它的名字、声明的位置:

val searchName: String = Icon.SEARCH.name()
val searchPosition: Int = Icon.SEARCH.ordinal()
密封类
密封类用来表示受限的类继承结构:当一个值为有限集中的
类型、而不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合
也是受限的,但每个枚举常量只存在一个实例,而密封类
的一个子类可以有可包含状态的多个实例。

要声明一个密封类,需要在类名前面添加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()

fun eval(expr: Expr): Double = when (expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
}
使用密封类的关键好处在于使用when表达式 的时候,如果能够
验证语句覆盖了所有情况,就不需要为该语句再添加一个else子句了。

fun eval(expr: Expr): Double = when(expr) {
is Expr.Const -> expr.number
is Expr.Sum -> eval(expr.e1) + eval(expr.e2)
Expr.NotANumber -> Double.NaN
// 不再需要 else 子句,因为我们已经覆盖了所有的情况
}
异常
在Kotlin中,所有的Exception都是实现了Throwable,含有一个message且未经检查。这表示我们不会强迫我们在任何地方使用try/catch。
这与Java中不太一样,比如在抛出IOException的方法,我们需要使用try-catch包围代码块。但是通过检查exception来处理显示并不是一个
好的方法。

抛出异常的方式与Java很类似:

throw MyException("Exception message")
try表达式也是相同的:

try{
// 一些代码
}
catch (e: SomeException) {
// 处理
}
finally {
// 可选的finally块
}
在Kotlin中,throw和try都是表达式,这意味着它们可以被赋值给一个变量。这个在处理一些边界问题的时候确实非常有用:

val s = when(x){
is Int -> "Int instance"
is String -> "String instance"
else -> throw UnsupportedOperationException("Not valid type")
}
或者

val s = try { x as String } catch(e: ClassCastException) { null }
对象(Object)
声明对象就如同声明一个类,你只需要用保留字object替代class,其他都相同。只需要考虑到对象不能有构造函数,因为我们不调用任何构造函数来访问
它们。事实上,对象就是具有单一实现的数据类型。

object Resource {
val name = "Name"
}
单例
object Resource {
val name = "Name"
}
因为对象就是具有单一实现的数据类型,所以在kotlin中对象就是单例。
对象的实例在我们第一次使用时,被创建。所以这里有一个懒惰实例化:如果一个对象永远不会被使用,这个实例永远不会被创建。

对象表达式
对象也能用于创建匿名类实现。

recycler.adapter = object : RecyclerView.Adapter() {

override fun onBindViewHolder(holder: RecyclerView.ViewHolder?, position: Int) {

}

override fun getItemCount(): Int {

}

override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {

}

}
例如,每次想要创建一个接口的内联实现,或者扩展另一个类时,你将使用上面的符号。

伴生对象(Companion Object)
每个类都可以实现一个伴生对象,它是该类的所有实例共有的对象。它将类似于Java中的静态字段。

class App : Application() {

companion object {

     lateinit var instance: App
         private set
 }

 override fun onCreate() {

     super.onCreate()
     instance = this
}

}
在这例子中,创建一个由Application扩展的(派送)的类,并且在companion object中存储它的唯一实例。
lateinit表示这个属性开始是没有值得,但是,在使用前将被赋值(否则,就会抛出异常)。
private set用于说明外部类不能对其进行赋值。

委托(代理)
类委托
委托模式是最常用的设计模式的一种,在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。
kotlin中的委托可以算是对委托模式的官方支持。
Kotlin直接支持委托模式,更加优雅,简洁。Kotlin通过关键字by实现委托。

interface Base{
fun print()
}

class BaseImpl(val x : Int) : Base{
override fun print() {
Log.d(JTAG, "BaseImpl -> ${x.string()}")
}
}

class Printer(b : Base) : Base by b

fun test(){
val b = BaseImpl(5)
Printer(b).print()
}

// 输出
BaseImpl -> 5
可以看到Printer类没有实现接口Base的方法print(),而是通过关键字by将实现委托给了b,而输出也和预想的一样。

属性委托
语法是val/var <属性名>: <类型> by <表达式>。在by后面的表达式是该委托,因为属性对应的get()和set()会被委托给它的getValue()
和setValue()方法。 属性的委托不必实现任何的接口,但是需要提供一个getValue()函数(和setValue()——对于var属性)。

class Example {
var property : String by DelegateProperty()
}

class DelegateProperty {
var temp = "old"

operator fun getValue(ref: Any?, p: KProperty<*>): String {
    return "DelegateProperty --> ${p.name} --> $temp"
}

operator fun setValue(ref: Any?, p: KProperty<*>, value: String) {
    temp = value
}

}

fun test(){
val e = Example()
Log.d(JTAG, e.property)
e.property = "new"
Log.d(JTAG, e.property)
}

// 输出
DelegateProperty --> property --> old
DelegateProperty --> property --> new
像上面的DelegateProperty这样,被一个属性委托的类,我叫它被委托类,委托它的属性叫委托属性。其中:

如果委托属性是只读属性即val,则被委托类需要实现getValue方法
如果委托属性是可变属性即var,则被委托类需要实现getValue方法和setValue方法
getValue方法的返回类型必须是与委托属性相同或是其子类
getValue方法和setValue方法必须要用关键字operator标记
Kotlin通过属性委托的方式,为我们实现了一些常用的功能,包括:

延迟属性lazy properties
可观察属性observable properties
map映射
延迟属性
延迟属性我们应该不陌生,也就是通常说的懒汉,在定义的时候不进行初始化,把初始化的工作延迟到第一次调用的时候。kotlin中实现延迟属性很简单,
来看一下。

val lazyValue: String by lazy {
Log.d(JTAG, "Just run when first being used")
"value"
}

fun test(){
Log.d(JTAG, lazyValue)
Log.d(JTAG, lazyValue)
}

// 输出
Just run when first being used
value
value
可以看到,只有第一次调用了lazy里的日志输出,说明lazy方法块只有第一次执行了。按照个人理解,上面的lazy模块可以这么翻译

String lazyValue;
String getLazyValue(){
if(lazyValue == null){
Log.d(JTAG, "Just run when first being used");
lazyValue = "value";
}
return lazyValue;
}

void test(){
Log.d(JTAG, getLazyValue());
Log.d(JTAG, getLazyValue());
}
可观察属性
可观察属性对应的是我们常用的观察者模式,机制类似于我们给View增加Listener。同样的kotlin给了我们很方便的实现:

class User {
var name: Int by Delegates.observable(0) {
prop, old, new -> Log.d(JTAG, "$old -> $new")
}

var gender: Int by Delegates.vetoable(0) {
    prop, old, new -> (old < new)
}

}

fun test(){
val user = User()
user.name = 2 // 输出 0 -> 2
user.name = 1 // 输出 2 -> 1

user.gender = 2
Log.d(JTAG, user.gender.string())   // 输出 2
user.gender = 1
Log.d(JTAG, user.gender.string())   // 输出 2

}
Delegates.observable()接受两个参数:初始值和修改时处理程序handler。 每当我们给属性赋值时会调用该处理程序(在赋值后执行)。
它有三个参数:被赋值的属性、旧值和新值。在上面的例子中,我们对user.name赋值,set变化触发了观察者,执行了Log.d代码段。

除了Delegates.observable()之外,我们还把gender委托给了Delegates.vetoable(),和observable不同的是,observable是执行了
set变化之后,才触发observable,而vetoable则是在set执行之前被触发,它返回一个Boolean,如果为true才会继续执行set。
在上面的例子中,我们看到在第一次赋值user.gender = 2时,由于2>0,所以old<new判断成立,所以执行了set方法,gender为2,
第二次赋值user.gender = 1则没有通过判断,所以gender依然为2。

map映射
一个常见的用例是在一个映射map里存储属性的值。这经常出现在像解析JSON或者做其他“动态”事情的应用中。在这种情况下,你可以使用映射实例自身作
为委托来实现委托属性。

class User(val map: Map) {
val name: String by map
val age: Int by map
}
// 在这个例子中,构造函数接受一个映射参数
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
委托属性会从这个映射中取值(通过字符串键——属性的名称)
println(user.name) // Prints "John Doe"
println(user.age) // Prints 25
这也适用于var属性,如果把只读的Map换成MutableMap的话
class MutableUser(val map: MutableMap) {
var name: String by map
var age: Int by map
}

相关文章
|
17天前
|
存储 弹性计算 人工智能
阿里云Alex Chen:普惠计算服务,助力企业创新
本文整理自阿里云弹性计算产品线、存储产品线产品负责人陈起鲲(Alex Chen)在2024云栖大会「弹性计算专场-普惠计算服务,助力企业创新」中的分享。在演讲中,他分享了阿里云弹性计算,如何帮助千行百业的客户在多样化的业务环境和不同的计算能力需求下,实现了成本降低和效率提升的实际案例。同时,基于全面升级的CIPU2.0技术,弹性计算全线产品的性能、稳定性等关键指标得到了全面升级。此外,他还宣布了弹性计算包括:通用计算、加速计算和容器计算的全新产品家族,旨在加速AI与云计算的融合,推动客户的业务创新。
|
7天前
|
SQL 人工智能 安全
【灵码助力安全1】——利用通义灵码辅助快速代码审计的最佳实践
本文介绍了作者在数据安全比赛中遇到的一个开源框架的代码审计过程。作者使用了多种工具,特别是“通义灵码”,帮助发现了多个高危漏洞,包括路径遍历、文件上传、目录删除、SQL注入和XSS漏洞。文章详细描述了如何利用这些工具进行漏洞定位和验证,并分享了使用“通义灵码”的心得和体验。最后,作者总结了AI在代码审计中的优势和不足,并展望了未来的发展方向。
|
3天前
|
负载均衡 算法 网络安全
阿里云WoSign SSL证书申请指南_沃通SSL技术文档
阿里云平台WoSign品牌SSL证书是由阿里云合作伙伴沃通CA提供,上线阿里云平台以来,成为阿里云平台热销的国产品牌证书产品,用户在阿里云平台https://www.aliyun.com/product/cas 可直接下单购买WoSign SSL证书,快捷部署到阿里云产品中。
1843 6
阿里云WoSign SSL证书申请指南_沃通SSL技术文档
|
1天前
|
存储 安全 Oracle
【灵码助力安全3】——利用通义灵码辅助智能合约漏洞检测的尝试
本文探讨了智能合约的安全性问题,特别是重入攻击、预言机操纵、整数溢出和时间戳依赖性等常见漏洞。文章通过实例详细分析了重入攻击的原理和防范措施,展示了如何利用通义灵码辅助检测和修复这些漏洞。此外,文章还介绍了最新的研究成果,如GPTScan工具,该工具通过结合大模型和静态分析技术,提高了智能合约漏洞检测的准确性和效率。最后,文章总结了灵码在智能合约安全领域的应用前景,指出尽管存在一些局限性,但其在检测和预防逻辑漏洞方面仍展现出巨大潜力。
|
24天前
|
存储 人工智能 弹性计算
产品技术能力飞跃,阿里云E-HPC荣获“CCF 产品创新奖”!
9月24日,在中国计算机学会举办的“2024 CCF 全国高性能计算学术年会”中,阿里云弹性高性能计算(E-HPC)荣获「 CCF HPC China 2024 产品创新奖」。这也是继 2022 年之后,阿里云E-HPC 再次荣获此奖项,代表着阿里云在云超算领域的持续创新结果,其产品能力和技术成果得到了业界的一致认可。
|
6天前
|
Web App开发 算法 安全
什么是阿里云WoSign SSL证书?_沃通SSL技术文档
WoSign品牌SSL证书由阿里云平台SSL证书合作伙伴沃通CA提供,上线阿里云平台以来,成为阿里云平台热销的国产品牌证书产品。
1777 2
|
15天前
|
编解码 Java 程序员
写代码还有专业的编程显示器?
写代码已经十个年头了, 一直都是习惯直接用一台Mac电脑写代码 偶尔接一个显示器, 但是可能因为公司配的显示器不怎么样, 还要接转接头 搞得桌面杂乱无章,分辨率也低,感觉屏幕还是Mac自带的看着舒服
|
22天前
|
存储 人工智能 缓存
AI助理直击要害,从繁复中提炼精华——使用CDN加速访问OSS存储的图片
本案例介绍如何利用AI助理快速实现OSS存储的图片接入CDN,以加速图片访问。通过AI助理提炼关键操作步骤,避免在复杂文档中寻找解决方案。主要步骤包括开通CDN、添加加速域名、配置CNAME等。实测显示,接入CDN后图片加载时间显著缩短,验证了加速效果。此方法大幅提高了操作效率,降低了学习成本。
5029 15
|
9天前
|
人工智能 关系型数据库 Serverless
1024,致开发者们——希望和你一起用技术人独有的方式,庆祝你的主场
阿里云开发者社区推出“1024·云上见”程序员节专题活动,包括云上实操、开发者测评和征文三个分会场,提供14个实操活动、3个解决方案、3 个产品方案的测评及征文比赛,旨在帮助开发者提升技能、分享经验,共筑技术梦想。
1018 147
|
17天前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1582 12