Kotlin编程实践1-3章

一、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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容