Kotiln基础语法总结(三)

一、扩展属性


除了给类添加扩展函数外,你还可以给类定义扩展属性,给String类添加一个扩展,这个扩展属性可以统计字符串里有多少个元音字母。

val String.numVowels
    get() = count { "aeiou".contains(it) }

fun <T> T.easyPrint(): T{
    println(this)
    return this
}

fun main() {
    
    "if this char sequence contains the specified"
            .numVowels.easyPrint()
}

1.1、可空类扩展

你也可以定义扩展函数用于可空类型,在可空类型上定义扩展函数,你就可以直接在扩展函数体内解决可能出现的空值问题。

infix fun String?.printWithDefault(default: String)
    = println(this ?: default)

fun main() {

    val nullableString: String? = null
    nullableString.printWithDefault("abc")
}

1.2、infix关键字

infix关键字适用于有单个参数的扩展和类函数,可以让你以更简洁的语法调用函数,如果一个函数定义使用了infix关键字,那么调用它时,接收者和函数之间的点操作以及参数的一对括号都可以不要

infix fun String?.printWithDefault(default: String)
    = println(this ?: default)

fun main() {

    val nullableString: String? = null
//    nullableString.printWithDefault("abc")
    //加infix可以这么写
    nullableString printWithDefault "abc"

}

二、DSL


使用这样的编程规范,就可以写出业界知名的“领域特定语言”(DSL),一种API编程规范,暴露接收者的函数和特性,以便于使用你定义的lambda表达式来读取和配置它们。

2.1、带接收者的函数字面量

apply函数是如何做到支持接收者对象的隐私调用的。


File("").apply{
	//这种就是隐式调用
    setReadable(true)
}

public inline fun <T> T.apply(block: T.() -> Unit): T {
    block()
    //返回对象是自己
    return this
}

//block: T.() -> Unit
//T.() -> Unit   泛型的扩展函数,好处就是扩展函数自带隐式调用

等价写法

//1
val file: File = File("").apply {
    setReadable(true)
}

//和上面代码等价的代码
//1.定义扩展函数
fun File.ext():Unit{
    setReadable(true)
}

//2.给block变量赋值
val block = File::ext

//3.传入apply函数
File("xx").apply(block)

三、函数式编程


3.1、函数类别

一个函数式应用通常由三大类函数构成:变换transform、过滤filter、合并combine。每类函数都针对集合数据类型设计,目标是产生一个最终结果。函数式编程用到的函数生来都是可组合的,也就是说,你可以组合多个简单函数来构建复杂的计算行为。

3.2、变换map

变换是函数式编程的第一大类函数,变换函数会遍历集合内容,用一个以值参形式传入的变换器函数,变换每一个元素,然后返回包含已修改元素的集合给链上的其他函数。

最常见的两个变换函数式map和flapMap。

val animals = listOf("zebra", "giraffe", "elephant", "rat")
val babies = animals
        .map { animal -> "A bay $animal" }
println(animals)
println(babies)

val animalsLength = animals.map { it.length }
println(animalsLength)

可以看到,原始集合没有被修改,map变换函数和你定义的变换函数做完事情后,返回的是一个新集合,这样,变量就不用变来变去了。

事实上,函数式编程范式支持的设计理念就是不可变数据的副本在链上的函数间传递。

3.3、flapMap

flapMap函数操作一个集合的集合,将其中多个集合中的元素合并后返回一个包含所有元素的单一集合。

val result = listOf(listOf(1, 2, 3), listOf(4, 5, 6))
        .flatMap { it }
println(result)

3.4、过滤

过滤是函数式编程的第二类函数,过滤函数接受一个predicate函数,用它按给定条件检查接收者集合里的元素并给出true或false的判定。如果predicate函数返回true,受检元素就会添加到过滤函数返回的新集合里。如果predicate函数返回false,那么受检元素就被移出新集合。

3.4.1、filter

过滤集合中元素含有“J”字母的元素

val animals = listOf("Jack", "zebra", "giraffe", "elephant", "rat")
        .filter { it.contains("J") }
println(animals)

filter过滤函数接受一个predicate函数,在flatMap遍历它的输入集合中的所有元素时,filter函数会让predicate函数按过滤条件,将符合条件的元素都放入它返回的新集合里。最后,flatMap会把变换器函数返回的子集合并在一个新集合里面。

val listOf = listOf(
         listOf("aa", "bb", "cc"),
         listOf("dd", "ee", "ff"),
         listOf("gg", "hh", "ii")
 )

 val result = listOf.flatMap { it.filter { it.contains("a") } }
 println(result)

找素数,除了1和它本身,不能被任何数整除的数。仅使用了几个简单函数,我们就解决了找素数这个比较复杂的问题,这就是函数式编程的独特魅力:每个函数做一点,组合起来就能干大事。

//除了1和它本身,不能被任何整除的数
//取模等于0,说明能够整除,如果没有一个是等于0,说明是素数
val numbers = listOf(7, 4, 8, 4, 3, 22, 18, 17)
val primes = numbers.filter { number ->
    (2 until number).map { number % it }.none { it == 0 }
}
println(primes)

3.5、合并

3.5.1、zip

zip合并函数来合并两个集合,返回一个包含键值对的新集合。

val a = listOf("aa", "bb", "cc")
val b = listOf("dd", "ee", "ff")

val zip = a.zip(b).toMap()
println(zip) //{aa=dd, bb=ee, cc=ff}
3.5.2、fold

另一个可以用来合并值的合并函数是fold,这个合并函数接受一个初始累加器值,随后会根据匿名函数的结果更新。

val fold = listOf(1, 2, 3, 4).fold(0) { accmulator, number ->
    println("Accmulator value: $accmulator")
    accmulator + (number * 3)
}
println(fold) //30

四、序列


List、Set、Map集合类型,这几个集合类型统称为及早集合(eager collection)这些集合的任何一个实例在创建后,它要包含的元素都会被加入并允许你访问。对应及早集合,Kotlin还有另外一类集合:惰性集合(lazy collection)类似于类的惰性初始化,惰性集合类型的性能表现优异,尤其用于包含大量的集合时,因为集合元素是按需产生的

Kotlin有个内置惰性集合类型叫序列(Sequence),序列不会索引排序它的内容,也不记录元素数目,事实上,在使用一个序列时,序列里的值可能有无限多,因为某个数据源能产生无限多个元素。

4.1、generateSequence

针对某个序列,你可能会定义一个只要序列有数值产生就被调用一下的函数,这样的函数叫迭代器函数,要定义一个序列和它的迭代器,你可以使用Koltin的序列构造函数generateSequence,generateSequence函数接受一个初始化种子作为序列的起步值,在用generateSequence定义的序列上调用一个函数时,generateSequence函数会调用你指定的迭代器函数,决定下一个要产生的值。

惰性集合有什么用了?为什么要用它而不是List集合了?假设你想产生头1000个素数。

fun Int.isPrime(): Boolean{
    (2 until this).map{
        if(this % it == 0)
            return false
    }
    return true
}

fun main() {

    val toList = (1..5000).toList().filter { it.isPrime() }.take(1000)
    println(toList.size) //670

    //用generateSequence
    val oneTousandPrimes = generateSequence(2) { value ->
        value + 1
    }.filter { it.isPrime() }.take(1000)
    println(oneTousandPrimes.toList().size)
}

五、互操作性和可空性


Java世界里所有对象都可能是null,当一个Kotlin函数返回String类型值,你不能想当然地认为它的返回值就能符合Kotlin关于空值的规定。

//java
public class Jhava {

    public String utterGreeting(){
        return "HELLO";
    }

    public String determineFriendshipLevel(){
         return null;
    }

}

//kotlin
val adversary = Jhava()
println(adversary.utterGreeting())

val level = adversary.determineFriendshipLevel()
level?.toLowerCase()

代码运行时,所有的映射类型都会重新映射回对应的Java类型。

//获取映射成java的类型
adversary.hitPoints.javaClass

5.1、属性访问

不需要调用相关setter方法,你可以使用赋值语法来设置一个Java字段值了。

//java
public class Jhava {

    public int hitPoints = 10;

    public int getHitPoints() {
        return hitPoints;
    }
    public void setHitPoints(int hitPoints) {
        this.hitPoints = hitPoints;
    }
}

//kotlin
val jhava = Jhava()
jhava.hitPoints = 3
println(jhava.hitPoints)

5.2、JvmName

可以使用@JvmName注解指定编译类的名字

//kotlin
@file:JvmName("Demo")

fun makeProclamation() = "Greetings, beast!"

//java
Demo.makeProclamation();

5.3、JvmField

在Java里,不能直接访问spells,所以必须调用getSpeels,然而,你可以给Kotlin属性添加@JvmField注解,暴露它的支持字段给Java调用者,从而避免用getter方法

//kotlin
class Speelbook{
	
	//加了这个注解,Java代码中可以直接调用这个字段
    @JvmField
    val speel = listOf("")

}

//java
Speelbook speelbook = new Speelbook();
//直接访问speel字段
speelbook.speel.add("");

5.4、JvmOverloads

@JvmOverloads注解协助产生Kotlin函数的重载版本。设计一个可能会暴露给Java用户使用的API时,记得使用@JvmOverloads注解,这样,无论是Kotlin开发者还是Java开发者,都会对这个API的可靠性感到满意。

@JvmOverloads
fun handOverFood(leftHand: String = "berries", rightHand: String = "beef"){
    println("...")
}

在这里插入图片描述

5.5、JvmStatic

@JvmField注解还能用来以静态方式提供伴生对象里定义的值
@JvmStatic注解的作用类似于@JvmField,允许你直接调用伴生对象里的函数

class Speelbook{

    //加了这个注解,Java代码中可以直接调用这个字段
    @JvmField
    val speel = listOf("")

    companion object{
        @JvmField
        val MAX_SPELL_COUNT = 10
        @JvmStatic
        fun getSpellbookGreeting() = println("")
    }
}

5.6、Throw

抛出一个需要检查的指定异常,Java和Kotlin有关异常检查的差异让@Throw注解给解决掉了,在编写供Java开发者调用的Kotlin Api时,要考虑使用@Throws注解,这样,用户就知道怎么处理任何异常了。

//kotlin
@Throws(IOException::class)
fun acceptApology(){
    throw IOException()
}

//java
try {
    DemoKt.acceptApology();
} catch (IOException e) {
    e.printStackTrace();
}

六、函数类型操作


函数类型和匿名函数能提供高效的语法用于组件间的交互,是Kotlin编程语言里比较新颖的特性。他们简洁的语法因->操作符而实现,但Java8之前的JDK版本并不支持lambda表达式。在Java里Koltin函数类型使用FunctionN这样的名字的接口来表示的,FunctionN中的N代表值参数目。这样的Function接口有23个,从Function0到Function22,每一个FunctionN都包含一个invoke函数,专用于调用函数类型函数,所以,任何时候需要调一个函数类型,都用它调用invoke。

//kotlin
val translator = {utterance: String ->
    println(utterance.toLowerCase().capitalize())
}
//java
Function1<String, Unit> translator = DemoKt.getTranslator();
translator.invoke("TRUCE");
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值