Kotlin知识总结:变量和函数

前言

这几天在学习kotlin,因为是新语言,看的时候能看懂,看完容易忘记,所以想着写一个博客,将一些知识点记录下来,可以随时查看

1.变量和函数

声明变量关键字:
•val (来自 value ) 一一不可变引用。使用 val 声明的变量不能在初始化之后再次赋值。它对应的是 Java 的 final 变量。
• var (来自 variable ) 一一可变引用。这种变量的值可以被改变。这种声明对应的是普通(非 final )的 Java 变量。
应该尽可能地使用 val 关键字来声明所有的 Kotlin 变量,仅在必要的时候换成 var。尽管 val 引用自身是不可变的,但是它指向的对象可能是可变的。下边代码是允许的

val languages = arrayListOf ("java")
languages.add ("kotlin")

Kotlin 的变量是没有默认值的,所以必须初始化。

val answer: Int = 42

可空性

在java中经常因为null产生NullPointerException异常, Kotiin 解决这类问题的方法是把运行时的错误转变成编译期的错误。对可空类型是显示支持的,默认情况下如果传入null,编译器就会报错。
如果需要传入null,可以在变量后边加上"?",所有常见类型默认都是非空的,除非显式地把它标记为可空。

class User {
    var name: String? = null
}
fun setLen(s: String?) = s?.length

非空断言 “!!”,说明变量一定是非空的,编译器不会再检查

view!!.setBackgroundColor(Color.RED)

安全调用运算符:“?.” 它允许你把一次null检查和一次方法调用合并成一个操作。例如,表达式 s?.toUpperCase()等同于下面这种烦琐的写法: if (s!=null) s.toUpperCase() else null 。

Elvis运算符:“ ?: " ,接收两个运算数,如果第一个运算数不为 null ,运算结果就是第一个运算数;如果第一个运算数为 null ,运算结果就是第二个运算数

val t: String= s ?: ""

安全转换:“as?” 运算符尝试把值转换成指定的类型, 如果值不是合适的类型就返回 null

“ let”函数 做的所有事情就是把一个调用它的对象变成 lambda 表达式的参数 。 如果结合安
全调用语法,它能有效地把调用 let 函数的可空对象,转变成非空类型。
比如:foo?.let{ …it…} 在foo!=null的时候,在lambda方法里,it就是非空的,如果foo==null则什么事情都不会发生

email?.let { email -> sendEmailTo(email ) }

简单点的写法:

email?.let{ sendEmailTo(it)

方法里有个it,如果当前上下文期望的是只有一个参数的 lambda 且这个参数的类型可以推断出来,就会生成这个名称。

延迟初始化 lateinit告诉编译器我没法第一时间就初始化,但我肯定会在使用它之前完成初始化的。属性必须是var

lateinit var view: View
override fun onCreate(...) {
    ...
    view = findViewById(R.id.tvContent)
}

String类中的isEmpty和isBlank方法,可以允许接收者是null,在函数中对null进行处理,不用确保变量不
为 null 之后再调用它的方法。这个只有扩展函数才能做到,因为成员方法需要实例调用,实例为null,永远不会调用成员方法

fun verifyUserInput(input:String?){
    if(input.isNullOrBlank()){
        println("输入为空")
    }
}

verifyUserInput(null)

Kotlin 中所有泛型类和泛型函数的类型参数默认都是可空的

类型推断

如果在声明变量时赋值,就可以不用写类型,Kotlin会自动推断,声明以后,不可以再赋值另一类型

var name = "Mike"
name = 1
// 会报错,The integer literal does not conform to the expected type String

数据类型

Kotlin 并不区分基本数据类型和它们的包装类。在运行时,大多数情况下,对于变量、属性、返回值和参数,Int等类型,会编译成java的基本数据类型int,在泛型类,如集合中,会编译成对应的java包装类型,Integer。如果使用了基本类型的可空版本,就会被编译成对应的包装类型。
Kotlin 不会自动地把数字从一种类型转换成另外一种,必须显示转换,每 一 种基本数据类型( Boolean 除外)都定义有转换函数: toByte()、toShort() 、 toChar()等

val x = l
val list = listOf (lL, 2L, 3L)
x in list

这个是错误的,不会隐式转换

val x = 1
println(x.toLong() in listOf(lL , 2L, 3L))

Kotlin Any和java的Object的区别是,Object 只是所有引用类型的超类型(引用类型的根),而基本数据类型并不是类层级结构的一部分。Any 是所有类型的超类型(所有类型的根),包括像 Int 这样的基本数据类型 。 把基本数据类型的值赋给 Any 类型的变量时会自动装箱
Kotlin Unit相当于Java的Void,也有区别,Unit 是一个完备的类型,可以作为类型参数,而 void 却不行
Noting类型,说明这个函数永远不会返回,表示从来不会成功结束

fun fail(message: String): Nothing {
throw IllegalStateException (message)
}
>> fail (”Error occurred”)
java . lang.IllegalStateException: Error occurred

字符串模板:在字符串字面值中引用局部变量,只需要在变量名称前面加上字符$,

 val result = operation(2, 3)
    println("result is $result")

原生字符串:有时候不想要转移,可以用一对 “”" 将字符串括起来

val text = """
      Hi $name!
    My name is $myName.\n
"""

可以通过 trimMargin() 函数去除每行前面的空格

集合和数组

数组

val strs: Array<String> = arrayOf("a", "b", "c")

取值和修改:

println(strs[0])
strs[1] = "B"

Kotlin数组不支持协变,就是子类数组对象不能赋值给父类的数组变量

val strs: Array<String> = arrayOf("a", "b", "c")
val anys: Array<Any> = strs // compile-error: Type mismatch

在一些性能需求比较苛刻的场景,并且元素类型是基本类型时,用数组好一点。Kotlin 中要用专门的基本类型数组类 (IntArray FloatArray LongArray) 才可以免于装箱,会被编译成int []、 float[], long[].
Array< Int >,它将会是一个包含装箱整型的数组

var arrays = intArrayOf(1,2,3)

集合
List 以固定顺序存储一组元素,元素可以重复。
Set 存储一组互不相等的元素,通常没有固定顺序。
Map 存储 键-值 对的数据集合,键互不相等,但不同的键可以对应相同的值。

val strList = listOf("a", "b", "c")

val strSet = setOf("a", "b", "c")

val map = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 3)

to 表示将「键」和「值」关联,这个叫做「中缀表达式」

List支持 covariant(协变),可以把子类的 List 赋值给父类的 List 变量

val strs: List<String> = listOf("a", "b", "c")
val anys: List<Any> = strs // success

取值

val value1 = map.get("key1")
val value2 = map["key2"]

赋值

val map = mutableMapOf("key1" to 1, "key2" to 2)
map.put("key1", 2)
map["key1"] = 2 

继承自Collection的集合,可以遍历集合中的元素、获取集合大小、判断集合中是否包含某个元素,以及执行其他从该集合中读取数据的操作。但这个接口没有任何添加或移除元素的方法 。如果需要修改,需要继承MutableCollection接口。有 mutable 前缀的函数创建的是可变的集合,没有 mutbale 前缀的创建的是不可变的集合,不过不可变的可以通过 toMutable*() 系函数转换成可变的集合

val strList = listOf("a", "b", "c")
strList.toMutableList()
val strSet = setOf("a", "b", "c")
strSet.toMutableSet()
val map = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 3)
map.toMutableMap()

只读集合不一定是不可变的,两个不同的引用,一个只读,另一个可变 , 指向同一个集合对象。使用集合的时候它被其他代码修改了,这会导致 concurrentModificationException 错误和其他一些 问题。因此,必须了解只读集合并不总是线程安全的

lambda的一些说明
处理集合类,经常使用lambda,这里有个约定

val persons = listOf<Person>(Person("张三"), Person("李四", 20))

    val oldest = persons.maxBy { it.age ?: 0 }

persons.maxBy完整写法:

persons. maxBy ({ p: Person-> p.age })

Kotlin 有这样一种语法约定,如果lambda 表达式是函数调用的最后一个实参,它可以放到括号的外边。

persons. maxBy () { p: Person-> p.age }

当 lambda 是函数唯一的实参时,还可以去掉调用代码中的空括号对

persons.maxBy { p: Person -> p.age }

lambda 如果有多个语句,最后一个表达式就是lambda的结果

val sum = { x: Int, y· Int ->
println (” Computing the sum of $x and $y...”)
x + y
>> println(sum(l, 2))
Computing the sum of 1 and 2 ...
3

Kotlin 允许在 lambda 内部访问非 final 变量甚至修改它们

fun printProblems(responses:Collection<String>){
    var clientErrors = 0//
    responses.forEach{
        if(it.startsWith("4")){
            clientErrors++
        }
    }
}

如果把函数转换成一个值,你就可以传递它 。 使用::运算符来转换

data class Person(val name: String, val age: Int = 60) {
    val isOld: Boolean
        get() = age > 50

}
 println(persons.map(Person::name))

还可以引用顶层函数(不是类的成员)

fun salute() = println ("Salute!")
>> run (::salute)
Salute!

操作符:

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

forEach:遍历每一个元素

val intArray = intArrayOf(1, 2, 3)
intArray.forEach { i ->
    print(i + " ")
}

filter:对每个元素进行过滤操作,如果 lambda 表达式中的条件成立则留下该元素,否则剔除,最终生成新的集合

>> val list= listOf(l, 2, 3, 4)
>> println(list.filter { it%2==0}[2,4]
>> val people= listOf(Person(”Alice”, 29) , Person (”Bob”, 31))
>> println(people.filter { it.age > 30 })
[Person (name=Bob, age=31)]

map:对集合中的每一个元素应用给定的函数并把结果收集到一个新集合

>> val list= listOf(l, 2, 3, 4)
>> println(list.map { it * it })
[1,4,9,16]

>> val people = listOf(Person("Alice", 29), Person ("Bob", 31))
>> println(people.map { it.name })
[Alice, Bob]
>> people.filter { it .age > 30 }.map(Person::name)
[Bob]

一个案例:找出人群分组中所有年龄最大的人的名字

people. filter {it.age== people.maxBy(Person::age) .age}

这个问题就在于:每个人都会重复寻找最大年龄的过程,假设集合中有
100 个人,寻找最大年龄的过程就会执行 100 遍!
改进版:

val maxAge = people.maxBy(Person::age).age
people. filter {it .age == maxAge }

如果没有必要就不要重复计算

groupBy:把列表转换成分组的 map

>> val people= listOf(Person("Alice", 31)'
Person ("Bob", 29), Person ("Carol" , 31))
>> println(people.groupBy { it age })

>>{29=[Person(name=Bob , age=29)],
3l=[Person(name=Alice, age=31), Person(name=Carol, age=31ll}
>> val list = listOf("a","ab","b")
>> println (list .groupBy(String:: first))
{a=[a, ab], b=[bJ}

flatMap:遍历每个元素,并为每个元素创建新的集合,最后合并到一个集合中

>> val strings= listOf ("abc","def")
> > println(strings.flatMap { it.toList() } )
[a, b, c, d, e, fl

当多个元素集合的集合不得不合并成一个的时候,使用flatten

 val list2 = listOf(listOf(1, 2, 3), listOf(4, 5, 6, 1, 2, 3), listOf(7, 8, 9))
    println(list2.flatten())
[1, 2, 3, 4, 5, 6, 1, 2, 3, 7, 8, 9]

通过map和filter,每次都会创建中间集合,创建临时列表,

people.map(Person: :name) .filter { it.startsWith ("A")}

如果想避免创建中间集合,可以使用序列

people.asSequence()
. map (Person: : name)
.filter { it.startsWith ("A")}
. toList ()

没有创建任何用于存储元素的中间集合。序列操作分为两类 :中间的和末端的。 一次 中间操作返回的是另 一个序列,这个新序列知道如何变换原始序列中 的元素 。 而一次末端操作返回的是一个结果,这
个结果可能是集合、元素、数字,或者其他从初始集合的变换序列中获取的任意对象
在这里插入图片描述
中间操作始终都是惰性的,末端操作触发执行了所有的延期计算。

map和filter调用先后顺序的区别

>> val people: listOf(Person("Alice", 29), Person("Bob", 31) '
Person ("Charles", 31), Person ("Dan", 21 ))
>> println(people.asSequence() .map(Person::name).filter { it. length < 4 }. toList ())
[Bob, Dan]
>> println(people.asSequence().filter { it.name.length< 4 }
.map(Person: :name) .toList())
[Bob, Dan)

如果 map 在前,每个元素都被变换。而如果 filter 在前,不合适的元素会被尽早地过滤掉且不会发生变换.
除了在集合上调用asSequence ()创建序列,还可以通过generateSequence创建

  val naturalNumbers = generateSequence(0) { it + 1 }
    val numbersTo100 = naturalNumbers.takeWhile { it <= 100 }
    println(numbersTo100.sum())

当获取结果“sum”时,所 有被推迟的操作都被执行
java中的

public class Button {
public void setOnClickListener (OnClickListener 1 ) { ... }
}

这种只有一个抽象方法的接口,被称为函数式接 口,或者 SAM 接口, SAM 代表单抽象方法。可以用lambda表示成

button .setOnClickListener { view -> ... }

如果可以用语句对同一个对象执行多次操作,而不需要反复把对象的名称 写出来。可以使用with

fun alphabet(): String {
    var stringBuffer = StringBuffer()
    return with(stringBuffer) {
        for (letter in 'A'..'Z') {
            append(letter)
        }
        append("end")
        this.toString()
    }
}

或者

fun alphabet1()= with(StringBuilder()){
        for(letter in 'A'..'Z'){
            append(letter)
        }
        append("end")
        toString()
    }
}

with 是接收两个参数的函数:这个例子中两个参数分别是 stringBuilder 和一个 lambda 。 这里利用
了把 lambda 放在括号外的约定 ,这样整个调用看起来就像是内建的语言功能 。可以等同于with (StringBuilder , { . . . })
with 返回的值是执行 lambda 代码的结果,该结果就是 lambda 中的最后 一
个表达式(的值)。如果想返回接收者对象,而不是执行 lambda 的结果 。可以用apply。 apply 始终会返回作为实参传递给它的对象(换句话说,接收者对象)

fun alphabet () = StringBuilder () . apply {
	for (letter in  'A' ..'Z') {
append (letter)
append ("\nNow I know the alphabet !")
}. toString()

apply的接收者是StringBuilder,所以这个返回的是StringBuilder,可以调用toString()
看另一个例子

fun createCustomTextView(context:Context){
      TextView(context).apply {
          text = "Sample TextView"
          textSize= 20.0F
          setPadding(10,0,0,0)
      }
  }

在传给 apply 的 lambda 中, TextView 实例变成了( lambda的)接收者,你就可以调用它的方法并设置它的属性。
Range
Range 表示区间的意思,也就是范围

val range: IntRange = 0..1000

表示就表示从 0 到 1000 的范围,包括 1000

val range: IntRange = 0 until 1000 

表示从 0 到 1000,但不包括 1000

val range = 0..1000
//      默认步长为 1,输出:0, 1, 2, 3, 4, 5, 6, 7....1000,
for (i in range) {
    print("$i, ")
}
val range = 0..1000
//    步长为 2,输出:0, 2, 4, 6, 8, 10,....1000,
for (i in range step 2) {
    print("$i, ")
}
//     输出:4, 3, 2, 1, 
for (i in 4 downTo 1) {
    print("$i, ")
}

其中 4 downTo 1 就表示递减的闭区间 [4, 1]。这里的 downTo 以及上面的 step 都叫做「中缀表达式」

条件控制

for ,while,if等基本上和java类似
if/else

val max = if (a > b) a else b

代码块的最后一行会作为结果返回

val max = if (a > b) {
    println("max:a")
    a //  返回 a
} else {
    println("max:b")
    b //  返回 b
}

when
相当于java的switch,

when (x) {
    1 -> { println("1") }
    2 -> { println("2") }
    else -> { println("else") }

区别在于:省略了 case 和 break,Java 中的默认分支使用的是 default 关键字,Kotlin 中使用的是 else,when 允许使用任何对象
定义一个枚举

enum class Color(val r: Int, val g: Int, val b: Int) {
    BLUE(0, 0, 255),
    YELLOW(255, 255, 0);

    fun rgb() = (r * 256 + g) * 256 + b
}

fun mix(c1: Color, c2: Color) =
    when (setOf(c1, c2)) {
        setOf(Color.BLUE, Color.YELLOW) -> Color.BLUE
        else -> ""
    }

还可以不带参数

when {
    str1.contains("a") -> print("字符串 str1 包含 a")
    str2.length == 3 -> print("字符串 str2 的长度为 3")
}

for

val array = intArrayOf(1, 2, 3, 4)
for (item in array) {
    ...
}

for (i in 0..10) {
    println(i)
}

try-catch

try {
    ...
}
catch (e: Exception) {
    ...
}
finally {
    ...
}

java中处理资源文件,可以这样写

static String readFirstLineFromFile(String path) throws IOException {
    try (BufferedReader br =
            new BufferedReader(new FileleReader (path)))
        return br.readLine ()
    }
}

在kotlin中可以这样写

//资源管理
fun readFirstLineFromFile(path:String):String{
    BufferedReader(FileReader(path)).use {
         br-> return br.readLine()
    }
}

调用use 就不用手动调用close方法

“==“和”= ==”
== :可以对基本数据类型以及 String 等类型进行内容比较,相当于 Java 中的 equals
=== :对引用的内存地址进行比较,相当于 Java 中的 ==

函数

函数定义:fun开头,函数名,参数名,返回值,函数体

fun max(a: Int, b: Int) :Int{
 return if (a > b) a else b
 }

如果函数体是由单个表达式构成的,可以用这个表达式作为完整的函数体,井去掉花括号和 return 语句。直接返回了 一个表达式,它就有表达式体。

fun max(a: Int, b: Int) = if (a > b) a else b

没有返回值,返回Unit,可以省略

fun main(): Unit {}
// Unit 返回类型可以省略
fun main() {}

参数可以设置默认值,

fun joinToString(separator:String=",",
    prefix:String="",
    postfix:String="")

调用的时候,可以指定参数名来调用

joinToString(
            separator = ";",
            prefix = "(",
            postfix = ")")

使用vararg表示的是可变参数,让函数支持任意数量的参数。listOf的源码就是用的这个修饰符
在这里插入图片描述
top-level property / function
把属性和函数的声明不写在 class 里面,这些函数直接放到代码文件的顶层,不用从属于任何的类。这些放在文件顶层的函数依然是包内的成员,如果你需要从包外访问它,则需要 import, 但不再需要额外包一层。

package drag.mandala.com.kotlindemo.strings
val String.lastChar:Char
    get() = get(length-1)

调用:

import drag.mandala.com.kotlindemo.strings.lastChar
 println("hello".lastChar)

和顶层函数一样,可以定义顶层属性

var count = 1
fun main(){
 println("count : ${++count}")
 }

如果你想要把一个常量以 public static final 的属性暴露给 Java ,可以用 const 来修饰它(这个适用于所有的基本数据类型的属性,以及 String 类型) 。const 必须修饰val,const 只允许在top-level级别和object中声明
扩展函数
上个例子有String.lastChar(),这个是扩展函数,就是一个类的成员函数,不过定义在类的外面,把你要扩展的类或者接口的名称,放到即将添加的函数前面 。 这个类的名称被称为接收者类型;用来调用这个扩展函数的那个对象,叫作接收者对象。扩展函数不能重写
在这里插入图片描述

可以像调用类的普通成员函数一样去调用这个函数。可以用as修改导入的类或者函数名称:

import strings.lastChar as last
val c ="Kotlin".last()
fun <T> Collection<T>.joinToString(
    separator:String=",",
    prefix:String="",
    postfix:String="",
    transform:((T)->String)?=null
):String{
    val result = StringBuilder(prefix)
    for((index,element) in this.withIndex()){
        if(index>0) result.append(separator)
        val str = transform?.invoke(element) ?: element.toString()

        result.append(str)
    }
    result.append(postfix)
    return  result.toString()
}

 println(list.joinToString(";", "(", ")"))
 

其中,(index,element) in this.withIndex()是解构声明,一般用在数据类中

val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // 输出 "Jane, 35 years of age"

属性的 getter/setter 函数

class User {
    var name = "Mike"
  
        get() {
            return field + " nb"
        }
    
        set(value) {
            field = "Cute " + value
        }
}

field 是幕后字段

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章