Kotlin -特殊用法详解

Posted wzgiceman

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin -特殊用法详解相关的知识,希望对你有一定的参考价值。

本文适合已经了解Kotlin基本用法,想更加深入的使用Kotlin方法和性能的同学们
如果你是Kotlin小白,可以先了解一下Kotlin的基础用法

Kotlin-中文官网基础用法

解构声明

我们在一个文件中定义一个Person类

data class Person(val name: String, val age: Int)

我们可以通过简单的语法获得这个类的name和age属性

val (name, age) = Person("tom", 11)
println(name)
println(age)

上面的Person是数据类(data class)当我们定义一个普通的类,不能简单的通过下面的方法获取name和age的值

class Dog(val name: String, val age: Int)
val (nameDog, ageDog) = Dog("James", 5)

为什么数据类可以,普通类就不可以呢? 因为数据类帮我们实现了解构声明需要的componentN方法这个N可以是1或者2等.

标准库中的let, takeIf, takeUnless,apply, also,with,run方法

let

let方法接收一个参数,参数是个方法。这个方法的名字叫block。let接收的方法是一个穿入值是T类型返回值是R类型的方法。并且我们知道这个方法的反回值就是整个let方法的返回值。绕口,见谅

// Java
if (message != null) 
    System.out.println(message)

// Kotlin 如果message是null下面这行忽略
message?.let  println(it) 

takeIf

定义

/**
 * Returns `this` value if it satisfies the given [predicate] or `null`, if it doesn't.
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null
  • takeIf是个扩张方法
  • 参数是个方法,(T) -> Boolean,这个方法的参数是类型T的对象,返回值是一个布尔类型
  • if (predicate(this)) this else null,这个是方法体。通过predicate这个函数为真,返回对象T,要么返回null

举例

val result = "Hello World".takeIf 
    it.length > 1

println(result)
//打印结果System.out: Hello World

takeUnless

定义上看,takeIf和takeUnless只是在方法体中的if的判断相反,其他都一样。

/**
 * Returns `this` value if it _does not_ satisfy the given [predicate] or `null`, if it does.
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? = if (!predicate(this)) this else null

apply

apply函数是这样的,调用某对象的apply函数,在函数范围内,可以任意调用该对象的任意方法,并返回该对象

fun testApply() 
    // fun <T> T.apply(f: T.() -> Unit): T  f(); return this 
    ArrayList<String>().apply 
        add("testApply")
        add("testApply")
        add("testApply")
        println("this = " + this)
    .let  println(it) 


// 运行结果
// this = [testApply, testApply, testApply]
// [testApply, testApply, testApply]

编译过后的class文件

public static final void testApply()
  
    ArrayList localArrayList1 = new ArrayList();
    ArrayList localArrayList2 = (ArrayList)localArrayList1;
    int $i$a$1$apply;
    ArrayList $receiver;
    $receiver.add("testApply");
    $receiver.add("testApply");
    $receiver.add("testApply");
    String str = "this = " + $receiver;
    System.out.println(str);
    localArrayList1 = localArrayList1;
    ArrayList it = (ArrayList)localArrayList1;
    int $i$a$2$let;
    System.out.println(it);
  

also

/**
 * Calls the specified function [block] with `this` value as its argument and returns `this` value.
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T  block(this); return this 

also的定义跟apply很像。接收一个函数,函数的参数是类型T的对象。also扩展方法的返回值也是类型T的对象。

举例

val result = "Hello World".also 
    println(this)
    println(it)

println(result)

//结果
//System.out: com.example.kotlintest.MainActivity@ba61275
//System.out: Hello World
//System.out: Hello World

在实际开发中我们对TextView对象设置属性的时候我们可以用also扩展方法

run

run函数和apply函数很像,只不过run函数是使用最后一行的返回,apply返回当前自己的对象。

with

with函数是一个单独的函数,并不是Kotlin中的extension,所以调用方式有点不一样,返回是最后一行,然后可以直接调用对象的方法,感觉像是let和apply的结合。
代码示例:

fun testWith() 
    // fun <T, R> with(receiver: T, f: T.() -> R): R = receiver.f()
    with(ArrayList<String>()) 
        add("testWith")
        add("testWith")
        add("testWith")
        println("this = " + this)
    .let  println(it) 

// 运行结果
// this = [testWith, testWith, testWith]
// kotlin.Unit

class文件

public static final void testWith()
  
    Object localObject = new ArrayList();
    ArrayList localArrayList1 = (ArrayList)localObject;
    int $i$a$1$with;
    ArrayList $receiver;
    $receiver.add("testWith");
    $receiver.add("testWith");
    $receiver.add("testWith");
    String str = "this = " + $receiver;
    System.out.println(str);
    localObject = Unit.INSTANCE;
    Unit it = (Unit)localObject;
    int $i$a$2$let;
    System.out.println(it);
  

流程控制

if else

跟Java中的if else完全一样,就是分支选择,不同于Java,可以赋值(类似于java中等?:处理)

val age = 20
val personType = if  (age <= 12)  
  "Child"
 else if (age >= 13 && age <= 17)  
  "Teenager"
 else 
  "Adult"

println(personType)
//输出Teenager

when

when相当于Java中swich,不同于java优复制的用法

val x = "Batman"

// If "when" is used as an expression then it MUST be exhaustive.
val hasPrefix = when (x) 
  is String -> x.startsWith("Bat")
  else -> false

hasPrefix

for

循环处理,不同于java有更方便强大的处理方法

val list = ArrayList<String>()
        permissions.forEach 
            if (ContextCompat.checkSelfPermission(activity, it) != PackageManager.PERMISSION_GRANTED) 
                list.add(it)
            

        
 permissions.forEachIndexed  postion, it ->
            Log.e("postion", postion.toString())
            Log.e("item", it)

        

中缀表示法

infix

标有 infix 关键字的函数也可以使用中缀表示法(忽略该调用的点与圆括号)调用。中缀函数必须满足以下要求:

  • 它们必须是成员函数或扩展函数;
  • 它们必须只有一个参数;
  • 其参数不得接受可变数量的参数且不能有默认值。
class MyStringCollection 
    infix fun add(s: String)  …… 

    fun build() 
        this add "abc"   // 正确
        add("abc")       // 正确
        add "abc"        // 错误:必须指定接收者
    

默认/命名参数

可以在调用函数时使用命名的函数参数。当一个函数有大量的参数或默认参数时这会非常方便

fun reformat(str: String,
             normalizeCase: Boolean = true,
             upperCaseFirstLetter: Boolean = true,
             divideByCamelHumps: Boolean = false,
             wordSeparator: Char = ' ') 
……

我们可以使用默认参数来调用它:

reformat(str)
reformat(str, true, true, false, '_')
reformat(str, wordSeparator = '_')

?表达式和Elvis表达式

Kotlin特有的?表达式和Elvis表达式可以在确保安全的情况下,写出更加简洁的代码。比如我们在android页面开发中常见的删除子控件操作,用Java来写是这样的

  if(view!=null)
            if(view.getParent()!=null)
                if(view.getParent() instanceof ViewGroup)
                    ((ViewGroup)view.getParent()).removeView(view);
                
            
        

为了获得更加安全的代码,我们不得不加上很多if else 判断语句,来确保不会产生空指针异常。但Kotlin的?操作符可以非常简洁地实现上述逻辑

(view?.parent as? ViewGroup)?.removeView(view)

那么这个?表达式的内在逻辑是什么呢?以上述代码为例,若view == null,则后续调用均不会走到,整个表达式直接返回null,也不会抛出异常。也就是说,?表达式中,只要某个操作对象为null,则整个表达式直接返回null。

除了?表达式,Kotlin还有个大杀器叫Elvis表达式,即?: 表达式,这两个表达式加在一起可以以超简洁的形式表述一个复杂逻辑。

val v=A?.b ?: C

以上面表达式为例,我们以?:把它划分成两个部分。若前面部分为null,则整个表达式返回值等于c的值,否则等于前面部分的值

更简洁的字符串

同Java一样,Kotlin也可以用字面量对字符串对象进行初始化,但Kotlin有个特别的地方是使用了三引号”””来方便长篇字符串的书写。而且这种方法还不需要使用转义符。做到了字符串的所见即所得

 val name="""
           aaa
            bbb
            cccc
        """

同时,Kotlin还引入了字符串模板,可以在字符串中直接访问变量和使用表达式

  val name="""
           aaa
            bbb
            cccc
        """
        AbLogUtil.e("--->$name")

对象比较

Java的 == 操作符是比较引用值,但Kotlin 的 == 操作符是比较内容, === 才是比较引用值。基于这点,我们可以写出逻辑更简洁合理的代码

关键字object

Kotlin中一切皆为对象,object在Kotlin中是一个关键字,笼统来说是代表“对象”,在不同场景中有不同用法。

1.对象表达式,可以直接创建一个继承自某个(或某些)类型的匿名类的对象,而无须先创建这个对象的类。这一点跟Java是类似的:


    httpManager.doHttpDeal(api).observeOn(Schedulers.io()).map 
        return@map true
    .observeOn(AndroidSchedulers.mainThread()).subscribe(object : Subscriber<Boolean>() 
        override fun onNext(t: Boolean?) 
        

        override fun onCompleted() 
        

        override fun onError(e: Throwable?) 
        
    )

2.对象字面量。这个特性将数字字面量,字符串字面量扩展到一般性对象中了。对应的场景是如果我们只需要“一个对象而已”,并不需要特殊超类型。典型的场景是在某些地方,比如函数内部,我们需要零碎地使用一些一次性的对象时,非常有用。

  var user=object 
            var sex:Int=0
            var year:Int=1
        

3.对象声明。这个特性类似于Java中的单例模式,但我们不需要写单例模式的样板代码即可以实现。

object PictureCompression 

    private fun initialize(context: Context) 
        PATH.initialize(context)
    

    private val temPictureFile by lazy 
        File(PATH.imageSaveDir)
    

  

请注意上述代码是声明了一个对象,而不是类,而我们想要使用这个对象,直接引用其名称即可

有趣的冒号

从语法上来看,Kotlin大量使用了冒号(:)这一符号,我们可以总结一下,这个冒号在Kotlin中究竟代表什么。

  • 在变量定义中,代表变量的类型

  • 在类定义中,代表基类的类型

  • 在函数定义中,代表函数返回值的类型

  • 在匿名对象中,代表对象的类型

函数类型

Kotlin中一切皆是对象,函数也不例外。在Kotlin中,函数本身也是对象,可以拥有类型并实例化。Kotlin 使用类似 (Int) -> String 的一系列函数类型来处理函数的声明,比如我们常见的点击回调函数:

val onClick:(View)->Unit=

了解了Kotlin的方法,下面用java回掉来举例学习,kotlin的回调与java在写法上略有不同,直接上代码

1、生成Person类及MyInterface 接口

class Person 
    val name:String = "Person"
    lateinit var mListen: MyInterface //接口可以延时加载

    fun setListeren(listen: MyInterface)
        this.mListen = listen
        this.mListen?.poo(" poo :" +name)       
    
    
    interface MyInterface 
        fun poo(str: String)  
    


2、在Main或Activity中回调

fun main(args: Array<String>) 
    println("Hello, world!") 
    var person = Person()
    person.setListeren(object : Person.MyInterface //object的作用是调用内部匿名类
        override fun poo(str:String) 
            println(str)
        
     )   


3、打印结果

Hello, world!
poo :Person

以上是用原java写法来实现kotlin回调的,目的是从java转来的人好理解,看看kotlin是如何简化java代码的

class Person 
    val name:String = "Person"
    lateinit var mListen: (String) -> Unit // 声明mListen是一个函数(单方法接口),入参String,无返回值

    fun setListeren(listener: (String) -> Unit)
        this.mListen = listener
        this.mListen("invoke :" +name) //等于 mListen?.invoke("invoke :" +name)  X()等同于X.invoke()
           
    
   
    //不再需要声明接口类!


fun main(args: Array<String>) 
    println("Hello, world!") 
    var person = Person()
    person.setListeren println(it)   // 只有一个参数的简化结果 it代表入参 String类 


打印结果

Hello, world!
invoke :Person

以上是关于Kotlin -特殊用法详解的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin|Kotlin反射

KotlinKotlin 语言简介 ( Kotlin 语言发展 | Kotlin 语言与 Java 虚拟机 | Java 与 Kotlin 联系 | Kotlin 跨平台特性 )

Kotlin|Kotlin反射

Kotlin初识Kotlin

Kotlin|Kotlin反射

Kotlin-数组