Kotlin分享(二)

Kotlin分享(一)

Kotlin分享(二)

Kotlin分享(三)

Kotlin分享(四)

Kotlin分享(五)

Kotlin 协程 coroutines

扩展 Extensions

    kotlin中有一种特殊的用法,叫做Extensions,作用是在不修改和继承类的情况下,给类添加一个额外的方法,这是kotlin种很神奇也很有用的一个语法糖。

扩展方法

    fun MutableList<Int>.swap(index1: Int, index2: Int) {
        val tmp = this[index1] // 'this' corresponds to the list
        this[index1] = this[index2]
        this[index2] = tmp
    }

    val l = mutableListOf(1, 2, 3)
    l.swap(0, 2)
   

    比如这里,我给MutableList<Int> 这个类,添加了swap方法。然后我们就可以像使用类内方法一样使用swap。其中this表示调用该方法的对象。

    当然我们也可以使用泛型来实现

fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
        val tmp = this[index1] // 'this' corresponds to the list
        this[index1] = this[index2]
        this[index2] = tmp
    }

扩展是静态的

    当我们使用扩展方法的时候,我们需要知道,我们实际上并没有向类的内部注入了方法,实际上的处理方式是在编译的时候生成了一个隐藏的新类叫做xxxkt(无法被显示调用的)。

    看下面代码

open class C

class D: C()

fun C.foo() = "c"

fun D.foo() = "d"

fun printFoo(c: C) {
    println(c.foo())
}

printFoo(D())

    printFoo中传入了D的对象,但是由于printFoo方法中使用的类型是C,所以调用的就是C的方法,最终输出了"c"

class C {
    fun foo() { println("member") }
}

fun C.foo() { println("extension") }

    对于这种情况 member优先级永远大于extension。

可空类型扩展

fun Any?.toString(): String {
    if (this == null) return "null"
    // after the null check, 'this' is autocast to a non-null type, so the toString() below
    // resolves to the member function of the Any class
    return toString()
}

    X?的类型也能够扩展,但是一般需要使用 this == null的判断。

属性扩展

    属性的扩展相对来说多了更多限制

val <T> List<T>.lastIndex: Int
    get() = size - 1

// val <T> List<T>.lastIndex: Int //错误,需要初始化

// val <T> List<T>.lastIndex: Int = 1 //错误,扩展属性不能初始化

//var <T> List<T>.lastIndex: Int  //错误需要初始化
//    get() = size - 1

//var <T> List<T>.lastIndex: Int
//        get() = size - 1
//        set(value){field = value}  //错误 没有 field 字段

//var <T> List<T>.lastIndex: Int
//      get() = lastIndex
//      set(value){lastIndex = value}  编译通过,set 和 get的时候循环调用报错

      所以我们看到属性扩展的功能存在局限性,由于不能调用field,所以实际上限制了很多set的get的使用。

Companion Object 扩展

    关于companion后面我们会讲,现在你只需要知道,这个东西差不多类似于static

class MyClass {
    companion object { }  // will be called "Companion"
}

fun MyClass.Companion.foo() {
    // ...
}

    比如这样,compaion object也可以扩展。

扩展的作用域

    如果我们将扩展定义在文件的最外层,那么这个扩展在全局都是可用的,只要import这个扩展方法的名字即可。

//file 1
package foo.bar
 
fun Baz.goo() { ... } 

//file2
package com.example.usage

import foo.bar.goo  //然后就能调用goo方法了

    如果我们将一个扩展定义在类内部,那么这个扩展就相当于这个类的成员函数,拥有和成员函数一样的性质。

    还有一种冲突

class C {
    fun D.foo() {
        toString()         // calls D.toString()
        this@C.toString()  // calls C.toString()
    }

 

Data Class

    如果一个类的作用仅仅是用来保存数据,就像c语言中的结构体Struct,那么kotlin中可以使用data类

 data class User(val name: String,val age: Int = 10){
        var isWoman: Boolean = false
    }

    data类必须提供一个主构造函数,并且需要至少一个参数,而且必须使用val或者var。可以包含变量,方法,这个和普通类一样的。

    不同点在于,Data Class自动重写了部分函数:

    equal 和 hashCode方法,只要主构造函数中变量的值相同,就会有相同的hashCode 并且equal返回true(其他变量不管)。

    重写了toString方法,会以"User(name=John, age=42)"格式输出

    再次重写这些方法会覆盖默认实现的!!

   能够使用componentN来访问变量。比如上面 user.component1 实际上就代表了name变量,user.component2代表了age变量。

    最后它默认实现了一个很厉害的copy方法

val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)

    我们可以在copy的时候修改部分属性,其余属性原样保持。

枚举 Enum

    kotlin不出意外,也提供了枚举这种东西。

enum class Direction {
    NORTH, SOUTH, WEST, EAST
}

    关于枚举,不论是java还是kotlin,枚举的本质还是一个类,而其中每一个枚举元素就是这个类的一个实例(使用枚举实现单例模式还是很舒服的。) 

enum class Color(val rgb: Int) {
        RED(0xFF0000),
        GREEN(0x00FF00),
        BLUE(0x0000FF)
}

    可以看到,我们为类提供了一个主构造函数,然后当我们产生实例的时候就需要提供参数。很类有没有!

enum class ProtocolState{
        WAITING{
            override fun signal() = TALKING
        },
        TALKING {
            override fun signal() = WAITING
        };
        
        abstract fun signal(): ProtocolState
        fun print(){
            Log.d("sss","ProtocolState.print")
        }
    }

    enum 有print方法,所以实例都能够调用,也有一个abstract方法,所有实例都需要重写它。其实这个操作很有用的(java中也一样,一种优雅的分支。)

    每一个enum隐藏了两个变量

                val name: String   //实例名字

                val ordinal: Int      //实例序号

  还是和java一样,我们已通过valueOf方法获取指定名字的实例(如果不存在会报错IllegalArgumentException)

    EnumClass.valueOf(value: String): EnumClass

    或者获取所有定义的实例

    EnumClass.values(): Array<EnumClass>

    

Sealed Class 密封类

    考虑枚举,枚举中的每一个元素的类型都是这个枚举类型的,我们无法让每个元素拥有不同的操作和方法。

    所以kotlin提供了一种叫做Sealed Class的类,用来实现上述功能

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr() //object我们在后面介绍

    我定义了一个sealed的类,然后定义了几个其他的类,继承与这个类。

fun eval(expr: Expr): Double = when(expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
    // the `else` clause is not required because we've covered all the cases
}

    与普通class的继承相比,我们省略了else分支,因为系统知道我们已经罗列出了所有状况。

    什么就这点作用吗?枚举的意义在哪里,它的意义就在哪里……

    PS 关于sealed的定义,如果是在包下面的,那么可以像上面这样定义,但是所有直接继承与sealed的类都必须定义在同一个文件中。

    如果sealed是定义在某个类内部的,那么就需要这样定义

sealed class Expr{
   data class Const(val number: Double) : Expr()
   data class Sum(val e1: Expr, val e2: Expr) : Expr()
   object NotANumber : Expr() //object我们在后面介绍
}

        

泛型

class Box<T>(t: T) {
    var value = t
}

val box: Box<Int> = Box<Int>(1)

    和java类似这是泛型的最简单使用,如果T的类型可推测,那么我们甚至可以胜率T,像这样

                                            val box = Box(1)

    我们知道1 是int类型的,所以T的类型应该是int。

Java的通配符类型

    这里要插播一些java的知识,在java中,泛型的类型是固定的,比如List<String> 整个是一个类型,另外,他并不是List<Object>的子类。

    所以,如果你打算这样做

                List<String> strs = new ArrayList<String>();

                List<Object> objs = strs;        

    那么编译器会报错。

    为了实现将strs中的数据搬运到objs中,假设我们自己来实现addAll,理所当然的相反是这样的

interface Collection<E> ... {
  void addAll(Collection<E> items);
}

    然后我们调用addAll

    objes.addAll(strs)。抱歉,报错了,因为List<String>的类型并不是List<Object>的子类,所以参数类型错误!这个时候java就推出了通配符类型  

                                ? extends E

interface Collection<E> ... {
  void addAll(Collection<? extends E> items);
}

    List<String> 是List<? extends Object>的子类!!

    还有一种类型是 ? super E ,相对来说用得就比较少了,List<Object> 是 List<? super String>的子类。一般会用在泛型返回值类型的时候。

    在kotlin中也有这样的问题

abstract class Source<T> {
    abstract fun nextT(): T
}

fun doSomething(){
    var strs: Source<String> = object : Source<String>() {
        override fun nextT(): String {
            Log.d("sss","haha")
            return "haha"
        }
    }

    var objects : Source<Any> = strs //报错!!,类型不符合
}

    比如上面代码,我们尝试吧Source<String> 赋值给Source<Any>。

    怎么办,进行小修改

abstract class Source<out T> {
    abstract fun nextT(): T
}

    我只要在模板类T前天添加 out 关键字,这样就能够通过编译。

    out关键字的作用是提示编译器,这个模板参数只会用作输出,类型和你不搭嘎的。

abstract class Source<out T> {
    abstract fun take(t:T) //报错,因为T是out类型,只能用作输出
    abstract fun nextT(): T
}

    相应的,也提供了一个in 类型

abstract class Origin<in T> {
    abstract fun originT(other: T): Int
}

fun demo() {
    var strs :Origin<Any> = object :Origin<Any>(){
        override fun originT(other: Any): Int {
            TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
        }
    }
    
    var objs :Origin<String> = strs
}

    被out 修饰的参数在使用中可以使用父类代替(使用Any 代替了 String),被in 修饰的参数使用中可以被子类代替(使用String 代替了 Any)。

    虽然看上去很美好,但是实际上,大多数时候我们无法确定一个模板参数只被输入,或者只被返回。

class Array<T>(val size: Int) {
    fun get(index: Int): T { /* ... */ }
    fun set(index: Int, value: T) { /* ... */ }
}

    我们写一个copy函数,然后调用它

fun copy(from: Array<Any>, to: Array<Any>) {
    assert(from.size == to.size)
    for (i in from.indices)
        to[i] = from[i]
}

val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" } 
copy(ints, any) //报错类型不符合!!

    还是遇到了最开始的问题,如何解决?修改copy方法

fun copy(from: Array<out Any>, to: Array<Any>) {
 // ...
}

    在from的模板参数前加入out方法,这就说明只会调用from中返回模板的方法,不会调用输入模板的方法。然后编译器就乖乖接受了这个设定,成功运行。

    同样的in也可以这种用,只是含义和out相反而已。其实吧 kotlin弄了自己的一套东西,但是实际上和java的没有区别,也不见得更方便。

其他

    方法的泛型写法和java 几乎一模一样

fun <T> singletonList(item: T): List<T> {
    // ...
}

    需要在方法开头使用<T>标注泛型类型。

    

    另外,我们可以给泛型添加约束

fun <T : Comparable<T>> sort(list: List<T>) {
    // ...
}

    比如这里的T的类型需要是Comparable<T>的子类。

    默认的约束条件是 Any? 表示所有类型都可以。

内部类

    kotlin有两种正宗的内部类(还有一种匿名类,这里就不算内部类了,晚点介绍)

class Outer {
    private val bar: Int = 1
    class Nested {
        fun foo() = 2
    }
}

val demo = Outer.Nested().foo() // == 2



class Outer {
    private val bar: Int = 1
    inner class Inner {
        fun foo() = bar
    }
}

val demo = Outer().Inner().foo() 

    第一种就是普通的类内定义类,第二种是使用inner修饰。

    第一种类相当于java中 static 内部类,不持有外部类对象,能够单独定义。

    第二种就相当于java中的普通内部类,持有外部类对象,依附于外部类存在。

Object表达式 (匿名类)

    java中,我们经常使用匿名类来定义一个接口对象。在kotlin中不再有这样的匿名类概念,而是使用Object表达式来完成这项工作。

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        // ...
    }

    override fun mouseEntered(e: MouseEvent) {
        // ...
    }
})

    这是最简单的应用,在java中就相当于 new MouseAdapter。

    在kotlin中,object表达式有着更广泛的应用,它用来完成直接生成一个类对象(省略类定义)。

    它能实现这样的功能

open class A(x: Int) {
    public open val y: Int = x
}

interface B {...}

val ab: A = object : A(1), B {
    override val y = 15
}

    生成一个匿名类,继承与A和B,并且产生一个对象,名为ab。

    上面继承了多个类,我们甚至能够不继承任何类

fun foo() {
    val adHoc = object {
        var x: Int = 0
        var y: Int = 0
    }
    print(adHoc.x + adHoc.y)
}

    object定义了一种匿名的类型,但是这个类型有很奇特的性质,只有是private的才能在作用域内保持稳定,如果他是一个public的成员变量,或者是一个public方法的返回值,这个就会退化成object所继承的类,如果没有继承,那么久退化成Any.

class C {
    // Private function, so the return type is the anonymous object type
    private fun foo() = object {
        val x: String = "x"
    }

    // Public function, so the return type is Any
    fun publicFoo() = object {
        val x: String = "x"
    }

    fun bar() {
        val x1 = foo().x        // Works
        val x2 = publicFoo().x  // ERROR: Unresolved reference 'x'
    }
}

    关于object匿名类,和inner相当,依附于类对象,所以能够访问类对象内部的变量。

 object 声明

    object还有一种非常有用的用法,实现单例模式!!

object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ...
    }

    val allDataProviders: Collection<DataProvider>
        get() = // ...
}


DataProviderManager.registerDataProvider(...)

    相比java,这倒是简单很多。这个和类的定义基本相同的,也可以继承其他类。

companion object

    kotlin有种定义在类内部的特殊对象,叫做companion object

class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}

val instance = MyClass.create()

    companion object是一个特殊的成员变量,可以通过类名直接调用内部的方法。甚至我们能够省略这个对象的成员变量名,或者继承于某个类

class MyClass {
    companion object:Adapter() {
        fun create(): MyClass = MyClass()
    }
}

val instance = MyClass.create()

    虽然这个东西使用上,甚至在理解上都和java中的static一样。但是实际上,这个东西并不是一个static类型的变量,而是一个成员变量。

 

代理(Delegation)

    在介绍代理之前,可能需要先知道一下基本设计模式中的代理模式,打个比方,用java来写

首先定一个明星的接口

public intreface IStar{
      //表演的方法
      public void show();
}

具体的明星,比如刘德华

public class LiuDeHua implements IStar{
     //具体的表演
     public void show(){
           //刘德华在表演
     }
}

好了我们应用程序要刘德华表演了

void main(){
    IStar star=new LiuDeHua();
    star.show();
}

    看上面的情况,我们需要创建一个刘德华的对象,就好比我们需要和刘德华协商出场费什么的。

    但是这个我们能和刘德华搭上话吗?显然不能,只能找他的经纪人,也就是我们的核心,代理

public class ProxyStar implements IStar{
       private IStar star;
       public ProxyStar(){
            star=new LiuDeHua();
       }
       public void show(){
           star.show();
       }
}

修改我们的程序

void main(){
    ProxyStar proxy=new ProxyStar();
    proxy.show();
}

好了现在我们直接和他的经纪人接头,并告诉经济人让他表演吧。

    这就是基本的代理模式,他的作用就是隐藏真正的实现类。

    但是大多数时候代理模式都需要这样

void main(){
    IStar star=new LiuDeHua();
    ProxyStar proxy=new ProxyStar(star);
    proxy.show();
}

    代理对象的构造函数会要求传入一个被代理的对象。

类代理

interface Base {
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}

class Derived(b: Base) : Base by b

fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).print() // prints 10
}

    kotlin中有代理模式的官方实现方式,通过by关键字,这时候Derived会自动保存b对象,并且自动实现Base的所有方法。

    如果在Dervied中重写print方法,那么这会覆盖代理方法!!

属性代理

    这是kotlin种一个比较有意思的语法。有时候我们在定义一个类的时候,其中有一些属性是能够提出来统一操作的,放到一个工具类中。比如lazy 属性(只有在第一次使用的时候才会真正去计算值),观察者属性,属性map等。

    kotlin中有一种有趣的写法

class Example {
    var p: String by Delegate()
}

class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }
 
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name} in $thisRef.'")
    }
}

val e = Example()
println(e.p)  //Example@33a17727, thank you for delegating ‘p’ to me!
e.p = "NEW"   //NEW has been assigned to ‘p’ in Example@33a17727.

    如果我定义了一个变量p,并且使用by定义了它的代理类。当我们调用这个方法的get 和set的时候,实际上会调用代理类中的getValue和setValue方法。

    其中代理的写法规则是这样  val/var <property name>: <Type> by <expression>

    其中experssion是一个表达式,不一定只能是对象的定义,也可以调用一个返回代理对象的方法。

    关于代理类,对于val变量,需要提供一个getValue方法,带有两个参数:

    thisRef 类型一定是被代理的 属性的拥有者 的类型或者其父类型。对于定义在顶层的属性或者本地变量,属性的拥有者为空,那么这个时候,他的类型就会是 Nothing?

    property 固定KProperty<*> 类型,或者他的父类型。

    返回值的类型需要和被代理属性的类型相同,或者其父类型。

    setValue也差不多,但是多了一个参数value,表示设置的新值,类型需要和被代理属性的类型相同,或者其父类型。

标准代理类

lazy

    kotlin的标准库提供了一些系统实现

val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

fun main(args: Array<String>) {
    println(lazyValue)
    println(lazyValue)
}
//最终输出
//computed!
//Hello
//Hello

    我们看到lazy方法会返回一个lazy对象,第一次调用get的时候回调用表达式中的内容,然后记下值,下一次调用的时候直接返回上一次的值。

    默认的lazy修饰的属性会是同步的,只会在一个线程中进行计算,并且所有线程都会返回同一个值。但是,如果我们不需要同步,那么可以这样

val lazyValue: String by lazy(LazyThreadSafetyMode.PUBLICATION, {
        println("computed!")
        "Hello"
    })

    如果我们不关注是否同步,我们确定它只会在一个线程中计算值,那么可以使用

LazyThreadSafetyMode.NONE

 

被观察者(Observable)

class User {
    var name: String by Delegates.observable("<no name>") {
        prop, old, new ->
        println("$old -> $new")
    }
}

fun main(args: Array<String>) {
    val user = User()
    user.name = "first"
    user.name = "second"
}

//<no name> -> first
//first -> second

    通过by Delegates.observable,需要两个参数,一个初始值 ("<no name>"),还有一个表达式,每次给name赋值的时候都会调用表达式。表达式有三个参数,分别是被赋值的对象,原来的值,和新的值。(赋值的真实操作不是被观察者考虑的,它考虑的是被赋值之后的操作!但是通过Delegates.vetoable可以在赋值之前调用表达式,如果该表达式返回false,那么赋值会被拦截。)

 

map

    将属性存储在map中,在json操作或者一些其他场合,这样的操作还是有一定存在意义的,我们可以使用默认的map代理来实现这个功能。

class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int     by map
}

//如果想要使用var类型的变量那么map类型需要时mutableMap
//class User(val map: MutableMap<String, Any?>) {
//    var name: String by map
//    var age: Int     by map
//}

    定义特别简单,提供一个map的主构造函数(只要提供一个map变量并初始化就行),只需要by map就可以了。

    我们可以这样

val user = User(mapOf(
    "name" to "John Doe",
    "age"  to 25
))

println(user.name) // Prints "John Doe"
println(user.age)  // Prints 25

    当构造user的时候,name和age的值就已经存入到map中,当我调用user.name实际上就是从map中去获取键name的值。

    如果使用MutableMap,那么通过set去设置user.name实际上就是将name保存到map中。

PS:在早起kotlin版本中,代理属性只能用在类的成员变量上,但是现在不是了,它可以用在所有地方,就连方法的本地变量都能够使用代理!

代理原理

    其实delegate的原理挺简单的

class C {
    var prop: Type by MyDelegate()
}

// this code is generated by the compiler instead:
class C {
    private val prop$delegate = MyDelegate()
    var prop: Type
        get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

    比如哦们代理了属性prop,实际上编译器会生成一个类型为代理类型的prop$delegate的属性,然后重写prop属性的get和set方法,直接使用代理类型的 getValue 和setValue 方法。

 

转载于:https://my.oschina.net/zzxzzg/blog/1585532

最近在学习kotlin,项目中正好用到了图片浏览,就用kotlin放照微信的做了一个,效果如下:大概就是这么个效果,图片从小到大的一个效果,然后滑动切换图片,点击后会返回对应图片的位置,其实比较容易,用到的是ActivityOptions,一个activity的转场动画,下面说一下怎么实现。github地址点这里关于kotlin的配置就不过多说了,网上好多教程(最近kotlin好火)布局就是一个recyclerviewmainactivity代码:可以看到就一个初始化布局管理器和设置适配器的代码没什么了(你说没看到初始化控件?看看这篇文章)到这里之前都很好理解,关键就是adapter和点击图片跳转的代码在API 21以后,我们可以使用内置的Activity切换动画。但是这样也就意味着只能兼容5.0之后的系统,ActivityOptions是一个静态类,它提供了为数不多的几种方法,我们正是用其中的makeSceneTransitionAnimation(Activity activity,Pair… sharedElements)来完成了这个效果,有兴趣的小伙伴移步至这里,先来说下传递的两个参数,第一个activity不用说,第个pair,View是你开始动画的view对象,string是他的动画名要和结束动画的view名一致才可以,到这里应该可以理解,我们在创建适配器的时候给每个imageview起了一个transitionname当我们跳转至查看大图界面给当前的viewpage赋值相同的名称便能完成动画,然后在我们滑动时更改viewpage的transitionname为对应图片的transitionname这样在点击图片的时候就会显示对应图片的下标。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值