Android数据库高手秘籍(十),如何在Kotlin中更好地使用LitePal

2024-01-02 07:08

本文主要是介绍Android数据库高手秘籍(十),如何在Kotlin中更好地使用LitePal,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转载请注明出处:https://blog.csdn.net/guolin_blog/article/details/82714414

本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭霖 即可关注,每个工作日都有文章更新。

自从LitePal在2.0.0版本中全面支持了Kotlin之后,我也一直在思考如何让LitePal更好地融入和适配Kotlin语言,而不仅仅停留在简单的支持层面。

Kotlin确实是一门非常出色的语言,里面有许多优秀的特性是在Java中无法实现的。因此,在LitePal全面支持了Kotlin之后,我觉得如果我还视这些优秀特性而不见的话,就有些太暴殄天物了。所以在最新的LitePal 3.0.0版本里面,我准备让LitePal更加充分地利用Kotlin的一些语言特性,从而让我们的开发更加轻松。

本篇文章除了介绍LitePal 3.0.0版本的升级内容之外,还会讲解一些Kotlin方面的高级知识。

升级到3.0.0

首先还是来看如何升级。

为什么这次的版本号跨度如此之大,直接从2.0升到了3.0呢?因为这次LitePal在结构上面有了一个质的变化。

为了更好地兼容Kotlin语言,LitePal现在不再只是一个库了,而是变成了两个库,根据你使用的语言不同,需要引入的库也不同。如果你使用的是Java,那么就在build.gradle中引入如下配置:

dependencies {implementation 'org.litepal.android:java:3.0.0'
}

而如果你使用的是Kotlin,那么就在build.gradle中引入如下配置:

dependencies {implementation 'org.litepal.android:kotlin:3.0.0'
}

好了,接下来我们就一起看一看LitePal 3.0.0版本到底变更了哪些东西。

泛型的优化

不得不说,其实LitePal的泛型设计一直都不是很友好,尤其在异步查询的时候格外难受,比如我们看下如下代码:

在异步查询的onFinish()回调中,我们直接得到的并不是查询的对象,而是一个泛型T对象,还需要再经过一次强制转型才能得到真正想要查询的对象。

如果你觉得这还不算难受的话,那么再来看看下面这个例子:

可以看到,这次查询返回的是一个List<T>,我们必须要对整个List进行强制转型。不仅要多写一行代码,关键是开发工具还会给出一个很丑的警告。

这样的设计无论如何都算不上友好。

这里非常感谢 xiazunyang 这位朋友在GitHub上提出的这个Issue(https://github.com/LitePalFramework/LitePal/issues/396),并且给出了建议的优化方案,LitePal 3.0.0版本在泛型方面的优化很大程度上是基于他的建议。

那么我们现在来看看,到了LitePal 3.0.0版本,同样的功能可以怎么写:

LitePal.findAsync(Song.class, 1).listen(new FindCallback<Song>() {@Overridepublic void onFinish(Song song) {}
});

可以看到,这里在FindCallback接口上声明了泛型类型为Song,那么在onFinish()方法回调中的参数就可以直接指定为Song类型了,从而避免了一次强制类型转换。

那么同样地,在查询多条数据的时候就可以这样写:

LitePal.where("duration > ?", "100").findAsync(Song.class).listen(new FindMultiCallback<Song>() {@Overridepublic void onFinish(List<Song> list) {}
});

这次就清爽多了吧,在onFinish()回调方法中,我们直接拿到的就是一个List<Song>集合,而不会再出现那个丑丑的警告了。

而如果这段代码使用Kotlin来编写的话,将会更加的精简:

LitePal.where("duration > ?", "100").findAsync(Song::class.java).listen { list ->}

得益于Kotlin出色的lambda机制,我们的代码可以得到进一步精简。在上述代码中,行尾的list参数就是查询出来的List<Song>集合了。

那么关于泛型优化的讲解就到这里,下面我们来看另一个主题,监听数据库的创建和升级。

监听数据库的创建和升级

没错,LitePal 3.0.0版本新增了监听数据库的创建和升级功能。

加入这个功能是因为 JakeHao 这位朋友在GitHub上提了一个Issue(https://github.com/LitePalFramework/LitePal/issues/414),在他说明了应用场景之后,我认为监听数据库创建和升级这个功能还是非常有意义的。

)

要实现这个功能肯定要添加新的接口了,而我对于添加新接口保持着一种比较谨慎的态度,因为要考虑到接口的易用性和对整体框架的影响。

LitePal的每一个接口我都要尽量将它设计得简单好用,因此大家应该也可以猜到了,监听数据库创建和升级这个功能会非常容易,只需要简单几行代码就可以了实现了:

LitePal.registerDatabaseListener(new DatabaseListener() {@Overridepublic void onCreate() {}@Overridepublic void onUpgrade(int oldVersion, int newVersion) {}
});

需要注意的是,registerDatabaseListener()方法一定要确保在任何其他数据库操作之前调用,然后当数据库创建的时候,onCreate()方法就会得到回调,当数据库升级的时候onUpgrade()方法就会得到回调,并且告诉通过参数告诉你之前的老版本号,以及升级之后的新版本号。

Kotlin版的代码也是类似的,但是由于这个接口有两个回调方法,因此用不了Kotlin的单抽象方法(SAM)这种语法糖,只能使用实现接口的匿名对象这种写法:

LitePal.registerDatabaseListener(object : DatabaseListener {override fun onCreate() {}override fun onUpgrade(oldVersion: Int, newVersion: Int) {}
})

这样我们就将监听数据库创建和升级这部分内容也快速介绍完了,接下来即将进入到本篇文章的重头戏内容。

一次不可思议的升级

从上述文章中我们都可以看出,Kotlin版的代码普遍都是比Java代码要更简约的,Google给出的官方统计是,使用Kotlin开发可以减少大约25%以上的代码。

但是处处讲究简约的Kotlin,却在有一处用法上让我着实很难受。比如使用Java查询song表中id为1的这条记录是这样写的:

Song song = LitePal.find(Song.class, 1);

而同样的功能在Kotlin中却需要这样写:

val song = LitePal.find(Song::class.java, 1)

由于LitePal必须知道要查询哪个表当中的数据,因此一定要传递一个Class参数给LitePal才行。在Java中我们只需要传入Song.class即可,但是在Kotlin中的写法却变成了Song::class.java,反而比Java代码更长了,有没有觉得很难受?

当然,很多人写着写着也就习惯了,这并不是什么大问题。但是随着我深入学习Kotlin之后,我发现Kotlin提供了一个相当强大的机制可以优化这个问题,这个机制叫作泛型实化。接下来我会对泛型实化的概念和用法做个详细的讲解。

要理解泛型实化,首先你需要知道泛型擦除的概念。

不管是Java还是Kotlin,只要是基于JVM的语言,泛型基本都是通过类型擦除来实现的。也就是说泛型对于类型的约束只在编译时期存在,运行时期是无法直接对泛型的类型进行检查的。例如,我们创建一个List<String>集合,虽然在编译时期只能向集合中添加字符串类型的元素,但是在运行时期JVM却并不能知道它本来只打算包含哪种类型的元素,只能识别出来它是个List

Java的泛型擦除机制,使得我们不可能使用if (a instanceof T),或者是T.class这样的语法。

而Kotlin也是基于JVM的语言,因此Kotlin的泛型在运行时也是会被擦除的。但是Kotlin中提供了一个内联函数的概念,内联函数中的代码会在编译的时候自动被替换到调用它的地方,这就使得原有方法调用时的形参声明和实参传递,在编译之后直接变成了同一个方法内的变量调用。这样的话也就不存在什么泛型擦除的问题了,因为Kotlin在编译之后会直接使用实参替代内联方法中泛型部分的代码。

简单点来说,就是Kotlin是允许将内联方法中的泛型进行实化的。

那么具体该怎么写才能将泛型实化呢?首先,该方法必须是内联方法才行,也就是要用inline关键字来修饰该方法。其次,在声明泛型的地方还必须加上reified关键字来表示该泛型要进行实化。示例代码如下所示:

inline fun <reified T> instanceOf(value: Any) {}

上述方法中的泛型T就是一个被实化的泛型,因为它满足了内联函数和reified关键字这两个前提条件。那么借助泛型实化,我们到底可以实现什么样的效果呢?从方法名上就可以看出来了,这里我们借助泛型来实现一个instanceOf的效果,代码如下所示:

inline fun <reified T> instanceOf(value: Any) = value is T

虽然只有一行代码,但是这里实现了一个Java中完全不可能实现的功能 —— 判断参数的类型是不是属于泛型的类型。这就是泛型实化不可思议的地方。

那么我们如何使用这个方法呢?在Kotlin中可以这么写:

val result1 = instanceOf<String>("hello")
val result2 = instanceOf<String>(123)
// result1为true,result2为false

可以看到,第一行代码指定的泛型是String,参数是字符串"hello",因此最后的结果是true。而第二行代码指定泛型是String,参数却是数字123,因此最后的结果是false

除了可以做类型判断之外,我们还可以直接获取到泛型的Class类型。看一下下面的代码:

inline fun <reified T> genericClass() = T::class.java

这段代码就更加不可思议了,genericClass()方法直接返回了当前指定泛型的class类型。T.class这样的语法在Java中是不可能的,而在Kotlin中借助泛型实化功能就可以使用T::class.java这样的语法了。

然后我们就可以这样调用:

val result1 = genericClass<String>()
val result2 = genericClass<Int>()
// result1为java.lang.String,result2为java.lang.Integer

可以看到,我们如果指定了泛型String,那么最终就可以得到java.lang.String的Class,如果指定了泛型Int,最终就可以得到java.lang.Integer的Class。

关于Kotlin泛型实化这部分的讲解就到这里,现在我们重新回到LitePal上面。讲了这么多泛型实化方面的内容,那么LitePal到底如何才能利用这个特性进行优化呢?

回顾一下,刚才我们查询song表中id为1的这条记录是这样写的:

val song = LitePal.find(Song::class.java, 1)

这里需要传入Song::class.java是因为要告知LitePal去查询song这张表中的数据。而通过刚才泛型实化部分的讲解,我们知道Kotlin中是可以使用T::class.java这样的语法的,因此我在LitePal 3.0.0中扩展了这部分特性,允许通过指定泛型来声明查询哪张表中的内容。于是代码就可以优化成这个样子了:

val song = LitePal.find<Song>(1)

怎么样,有没有觉得代码瞬间清爽了很多?看起来比Java版的查询还要更加简约。

另外得益于Kotlin出色的类型推导机制,我们还可以将代码改为如下写法:

val song: Song? = LitePal.find(1)

这两种写法效果是一模一样的,因为如果我在song变量的后面声明了Song?类型,那么find()方法就可以自动推导出泛型类型,从而不需要再手动进行<Song>的泛型指定了。

除了find()方法之外,我还对LitePal中几乎全部的公有API都进行了优化,只要是原来需要传递Class参数的接口,我都增加了一个通过指定泛型来替代Class参数的扩展方法。注意,这里我使用的是扩展方法,而不是修改了原有方法,这样的话两种写法你都可以使用,全凭自己的喜好,如果是直接修改原有方法,那么项目升级之后就可能会造成大面积报错了,这是谁都不想看到的。

那么这里我再向大家演示另外几种CRUD操作优化之后的用法吧,比如我想使用where条件查询的时候就可以这样写:

val list = LitePal.where("duration > ?", "100").find<Song>()

这里在最后的find()方法中指定了泛型<Song>,得到的结果会是一个List<Song>集合。

想要删除song表中id为1的这条数据可以这么写:

LitePal.delete<Song>(1)

想要统计song表中的记录数量可以这么写:

val count = LitePal.count<Song>()

其他一些方法的优化也都是类似的,相信大家完全可以举一反三,就不再一一演示了。

这样我们就将LitePal新版本中的主要功能都介绍完了。当然,除了这些新功能之外,我还修复了一些已知的bug,提升了整体框架的稳定性,如果这些正是你所需要的话,那就赶快升级吧。

我没学过LitePal怎么办?

本篇文章是写给已经有LitePal基础的人看的,帮助他们快速地升级到LitePal 3.0.0。如果你之前并没有学过LitePal,可以参考《第一行代码 第2版》第6章中的内容,里面有非常详尽的LitePal使用讲解。

另外也可以阅读我写的专栏《Android数据库高手秘籍》,同样对LitePal的各种使用方法进行了详细地剖析。


关注我的技术公众号,每天都有优质技术文章推送。关注我的娱乐公众号,工作、学习累了的时候放松一下自己。

微信扫一扫下方二维码即可关注:

        

这篇关于Android数据库高手秘籍(十),如何在Kotlin中更好地使用LitePal的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/561675

相关文章

Python调用Orator ORM进行数据库操作

《Python调用OratorORM进行数据库操作》OratorORM是一个功能丰富且灵活的PythonORM库,旨在简化数据库操作,它支持多种数据库并提供了简洁且直观的API,下面我们就... 目录Orator ORM 主要特点安装使用示例总结Orator ORM 是一个功能丰富且灵活的 python O

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Python使用国内镜像加速pip安装的方法讲解

《Python使用国内镜像加速pip安装的方法讲解》在Python开发中,pip是一个非常重要的工具,用于安装和管理Python的第三方库,然而,在国内使用pip安装依赖时,往往会因为网络问题而导致速... 目录一、pip 工具简介1. 什么是 pip?2. 什么是 -i 参数?二、国内镜像源的选择三、如何

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没