一、Kotlin基础
1、在线kotlin沙箱: https://play.kotlinlang.org/
2、在Android中使用Kotlin(Groovy在进行插值时会使用双引号,不需要插值时也可以使用单引号)
顶层build.gradle
buildscript{
ext.kotlin_version = '1.3.50'
repositories{
google()
jcenter()
}
dependencies{
classpath 'com.amdroid.tools.build:gradle:3.5.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
应用程序目录中build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android{
//android information
}
dependencies{
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version
// 其他无关依赖
}
3、可空类型
class Person(
val first:String,
val middle: String?,
val last : String)
var p = Person(first = "North",middle = null,last = "West")
val middleNameLength = p.middle?.length
//返回的结果类型是Int?
val middleNameLength = p.middle?.length ?: 0
4、安全转换操作符 as?
避免在强制转换时抛出ClassCastException
val p1 = p as? Persion
//p1的类型为Persion?
转换成功结果是一个Person,失败值降为null
5、显式类型转换
kotlin 不会自动将原生类型转换到一个位数更大的原生类型,例如Int转型到Long。
使用toInt、toLong等显示转换函数来显示转换位数较小的类型。
val intVar : Int =3
//val longVar: Long = intVar 不编译该行
val longVar: Long = intVar.toLong()
kotlin利用重载操作符来透明执行了类型转换
val longSum = 3L+ intVar
加号操作符自动将intVar的值转换成long并相加
6、使用to 函数创建Pair实例
mapOf函数的签名如下:
fun <k,v> mapOf(vararg pairs: Pair<K,V>): Map<K,V>
Pair签名如下
data class Pair<out A,out B> : Serializable
to函数定义如下
public infix fun <A,B> A.to(that:B):Pair<A,B> = Pair(this,that)
实际应用
val map = mapOf("a" to 1,"b" to 2,"c" to 3)
//创建Pair两种方式
val p1 = Pair("a",1)
val p2 = "a" to 1
二、Kotlin中的面向对象编程
初始化对象,自定义getter和setter,延迟初始化,惰性初始化,创建单例,理解Nothing类
1、const与val的不同
const修饰的是编译时常量;val 是运行时常量
在Java程序里,常量用关键字static final修饰,常量又分为:
编译期常量
运行时常量
static final int A = 1024;//编译期常量,类型必须是基本类型或String!
static final int len = "Rhine".length();//运行时常量!运行时才能确定它的值。
编译期常量不依赖类,不会引起类的初始化;而运行时常量依赖类,会引起类的初始化。
class Task (val name:String,_priority:Int = DEFAULT_PRIORITY){
companion object{
const val MIN_PRIORITY =1 //编译时常量
const val MAX_PRIORITY = 5 //编译时常量
const val DEFAULT_PRIORITY = 3 //编译时常量
}
var priority = validPriority(_priority) //自定义set属性
set(value){
field = validPriority(value)
}
private fun validPriority(p:Int) = //私有的验证函数
p.coerceIn(MIN_PRIORITY,MAX_PRIORITY)
}
2、自定义getter和setter
自定义set属性
var priority = 3
set(value){
field = value.coerceIn(value)
}
自定义get属性
var isLowPriority
get() = priority < 3//isLowPriority是布尔类型的
3、定义数据类
使用data关键字,指明特定类的目的是保存数据。与java中实体类相似。
将data添加到类的定义中会使编译器生成一系类函数,包括equals、hashCode、toString、copy、component函数
data class Product(
val name : String,
val price :Double,
val onSale:Boolean = false
)
fun main() {
println("Hello, world!!!")
val p1 = Product("ball",10.0)
val p2 = Product(price = 10.0,onSale = false,name = "ball")
val pros = setOf(p1,p2)
println("Hello, world!!!"+p1.hashCode()+"|||"+p2.hashCode()) //Hello, world!!!1897955903|||1897955903
println("Hello, world!!!"+pros.size)//1
}
copy方法
fun Pcopy(){
val p1 = Product("ball",10.0)
val p2 = p1.copy(price = 12.0) //仅仅更改price的值
}
注意:copy函数仅仅执行浅拷贝,而不执行深拷贝
4、幕后属性技术
class Customer(val name :String){
private var _messages:List<String>? = null
val messages:List<String>
get(){
if(_messages == null){
_messages = loadMessages()
}
return _messages!!
}
private fun loadMessages():MutableList<String> =
mutableListOf(
"Initial contact",
"Convinced hem to use Kotlin",
"Sold training class. Sweet."
).also{println("Loaded messages")}
}
避免了messages属性初始化,添加了_messages,类型相同但可空
使用Lazy委托函数 实现懒加载(lazy 委托在后面小结中讨论)
class Customer(val name :String){
val messages:List<String> by lazy {loadMessages()}
private fun loadMessages():MutableList<String> =
mutableListOf(
"Initial contact",
"Convinced hem to use Kotlin",
"Sold training class. Sweet."
).also{println("Loaded messages")}
}
5、使用lateinit进行延迟初始化
在依赖注入的情况下很有用
通常在可能情况下考虑其他替代方法,如lazy
lateinit修饰符智能修饰类中声明的var属性,并且该属性不能拥有自定义的getter和setter。从1.2开始,可以修饰顶层属性甚至局部变量,被修饰的类型必须是非空的且不能是原始类型。
class LateInitDemo{
lateinit var name : String
}
在name属性还没有初始化的时候访问他会抛出uninitializedPropertyAccessExcrption.
class LateInitDemo{
lateinit var name : String
fun initName(){
println("before init: ${::name.isInitialized}")//before init: false
name = "world"
println("after init: ${::name.isInitialized}")//after init: true
}
}
isInitialized检查一个属性是否初始化了
lateinit 与lazy:
lateinit 修饰符用于修饰var属性,但是它具有上面的使用限制,而lazy委托则使用lambda表达式,该lambda表达式会在首次访问这个属性时赋值
如果初始化非常昂贵或者属性也许永远不会被使用时,请使用lazy。同样,lazy只能用于val属性,而lateinit只能用于var属性。最后,lateinit属性可以在任何该属性可见的地方初始化
6、使用安全转换函数、恒等操作符、以及Elvis操作符覆盖equals函数
===、as?、?:
java中==操作符用于检查是否指向同一个对象。
kotlin中==操作符会自动调用equals函数
7、创建单例
kotlin中使用object关键字替代class来实现单例模式,被称为object声明
object MySingleton{
val myProperty = 3
fun myFunction() = "Hello"
}
如果反编译,会得到下面代码:
public final class MySingleton{
private static final int myProperty = 3;
public static funal MySingleton INSTANCE;
private MySingleton(){}
public final int getMyProperty(){
return myProperty ;
}
public final void myFunction(){
return "Hello";
}
static {
MySingleton var0 = new MySingleton ();
INSTANCE = var0;
myProperty = 3;
}
}
引用:
https://oreil.ly/P8QCv 的“Kotlin Singletons with Argument”讨论了基于双重校验锁以及@volatile 来实现单例实例化线程安全的复杂性
https://blog.csdn.net/mq2553299/article/details/87128674(国内)
简单地声明object以创建一个单例:
object SomeSingleton
与类不同,object 不允许有任何构造函数,如果有需要,可以通过使用 init 代码块进行初始化的行为:
object SomeSingleton{
init{
println("init complete")
}
}
这样object将被实例化,并且在初次执行时,其init代码块将以线程安全的方式懒惰地执行。 为了这样的效果,Kotlin对象实际上依赖于Java的 静态代码块 。上述Kotlin的 object 将被编译为以下等效的Java代码:
public final class SomeSingleton{
public static final SomeSingleton INSTANCE;
private SomeSingleton(){
INSTANCE = (SomeSingleton)this;
System.out.println("init complete");
}
static {
new SomeSingleton();
}
}
这是在JVM上实现单例的首选方式,因为它可以在线程安全的情况下懒惰地进行初始化,同时不必依赖复杂的双重检查加锁(double-checked locking)等加锁算法。 通过在Kotlin中简单地使用object进行声明,您可以获得安全有效的单例实现。
但是,如果初始化的代码需要一些额外的参数呢?你不能将任何参数传递给它,因为Kotlin的object关键字不允许存在任何构造函数。
在Kotlin中,您必须通过不同的方式去管理单例的另一种情况是,单例的具体实现是由外部工具或库(比如Retrofit,Room等等)生成的,它们的实例是通过使用Builder模式或Factory模式来获取的——在这种情况下,您通常将单例通过interface或abstract class进行声明,而不是object。
我们可以通过封装逻辑来懒惰地在SingletonHolder类中创建和初始化带有参数的单例。
为了使该逻辑的线程安全,我们需要实现一个同步算法,它是最有效的算法,同时也是最难做到的——它就是 双重检查锁定算法(double-checked locking algorithm)。
请注意,为了使算法正常工作,这里需要将@Volatile
注解对instance
成员进行标记。
这可能不是最紧凑或优雅的Kotlin
代码,但它是为双重检查锁定算法生成最行之有效的代码。请信任Kotlin
的作者:实际上,这些代码正是从Kotlin
标准库中的 lazy() 函数的实现中直接借用的,默认情况下它是同步的。它已被修改为允许将参数传递给creator
函数。
有鉴于其相对的复杂性,它不是您想要多次编写(或者阅读)的那种代码,实际上其目标是,让您每次必须使用参数实现单例时,都能够重用该SingletonHolder
类进行实现。
声明getInstance()
函数的逻辑位置在singleton类的伴随对象内部,这允许通过简单地使用单例类名作为限定符来调用它,就好像Java
中的静态方法一样。Kotlin
的伴随对象提供的一个强大功能是它也能够像任何其他对象一样从基类继承,从而实现与仅静态继承相当的功能。
在这种情况下,我们希望使用SingletonHolder
作为单例类的伴随对象的基类,以便在单例类上重用并自动公开其getInstance()
函数。
对于SingletonHolder
类构造方法中的creator
参数,它是一个函数类型,您可以声明为一个内联(inline)
的lambda,但更常用的情况是 作为一个函数引用的依赖交给构造器,最终其代码如下所示:
class Manager private constructor(context:Context){
init{
//init using context argument
}
companion object SingletonHolder<Manager,Context>(::Manager)
}
现在可以使用以下语法调用单例,并且它的初始化将是lazy并且线程安全的:
Manager.getInstance(context).doStuff()
8、Nothing类
在永不返回的函数中使用Nothing类
下面两种情况下Nothing类会自然的出现:
1、函数主体完全由抛出异常组成;
fun doNothing():Nothing = throw Exception("Nothing at all")
当三方库生成单例实现并且Builder需要参数时,您也可以使用这种方式,以下是使用Room 数据库的示例:
@Database(entitier = arrayOf(User::class),version = 1)
abstract class UsersDatabase :RoomDatabase(){
absract fun userDao():UserDao
companion object :SingletonHolder<UsersDatabase,Context>({
Room.databaseBuilder(it.applicationContext,
UsersDatabase::class.java,"Sample.db")
.build()
})
}
2、将变量赋值为null而不显示声明类型时。
val x = null
kotlin中Nothing类是所有其他类型的子类
TODO函数返回Nothing,其实现就是抛出NotImplementedError