kotlin基础

kotlin基础,第1张

后端应用,跨平台移动应用,前端应用,安卓应用

一、定义常量与变量

声明变量的关键字有两个:

  • val(value / varible+final)——不可变引用。使用 val 声明的变量不能在初始化之后再次赋值,对应的是 Java 中的 final 变量
  • var(variable)——可变引用。var 变量的值可以被改变,对应的是 Java 中的非 final 变量
val a: Int = 1
val b = 1       // 系统自动推断变量类型为Int
val c: Int      // 如果不在声明时初始化则必须提供变量类型
c = 1           // 明确赋值

var x = 5        // 系统自动推断变量类型为Int
x += 1           // 变量可修改
1、基本数据类型

kotlin 并不区分基本数据类型和它的包装类,在 kotlin 中一切都是对象,可以在任何变量上调用其成员函数和属性。kotlin 没有像 Java 中那样的原始基本类型

Kotlin的基本数值类型包括:Byte、Short、Int、Long、Float、Doubel  数据类型包含数值类型

所有未超出int最大值的整型值初始化的变量都默认为int类型,如果初始值超过了其最大值,那么推断为Long类型。在数字后面显式添加一个L表示这是一个Long类型

    //编辑器会根据复制100,推断number的数据类型为int
    val number=100
    //虽然没有明确赋值,但是编译器发现8000000000超过int最大值,所以升级为Long
    val bigNumber=8000000000
    //在赋值后加L,会自动推断为Long类型
    val longNumber=20L
    //浮点类型
    //默认是double类型
    val doubleNumber=3.14159268888
    //尾部加f或者F显式表示这是一个Float类型浮点数
    val floatNumber=3.14159268888f
    //赋值的时候用单引号引起来
     val char:Char='1'
    //布尔类型
    val isVisible:Boolean=false
    //自动推断为布尔类型
    val isVisible2=true
    //字符串类型
    //字符串取值
    val str:String="123456" 
 2、Any 和 Any?

Any 类型是 kotlin 所有非空类型的超类型,包括像 Int 这样的基本数据类型,如果把基本数据类型的值赋给 Any 类型的变量,则会自动装箱

val any: Any = 100
println(any.javaClass) //class java.lang.Integer
//如果想要使变量可以存储包括 null 在内的所有可能的值,则需要使用 Any?
val any: Any? = null
二、表达式和条件循环

If 表达式

与 Java 不同,kotlin 中的 if 是作为表达式存在的,其可以拥有返回值。 if 的返回值,完全可以用来替代 Java 中的三元运算符,因此 kotlin 并没有三元运算符

val maxValue = if (20 > 10) {
        println("maxValue is 20")
        20
    } else {
        println("maxValue is 10")
        10
    }


val list = listOf(1, 4, 10, 4, 10, 30)
val value = if (list.size > 0) list.size else null

when 表达式

与 Java 中的 switch/case 类似,但是要强大得多 ,when 既可以被当做表达式使用也可以被当做语句使用,when 将参数和所有的分支条件顺序比较直到某个分支满足条件,然后它会运行右边的表达式,与 Java 的 switch/case 不同之处是 when 表达式的参数可以是任何类型,并且分支也可以是一个条件,和 if 一样,when 表达式每一个分支可以是一个代码块,它的值是代码块中最后的表达式的值,如果其它分支都不满足条件将会求值于 else 分支

    val value = 2
    when (value) {
        in 4..9 -> println("in 4..9") //区间判断
        3 -> println("value is 3")    //相等性判断
        2, 6 -> println("value is 2 or 6")    //多值相等性判断
        is Int -> println("is Int")   //类型判断
        else -> println("else")       //如果以上条件都不满足,则执行 else
    }

 for 循环

    val list = listOf(1, 4, 10, 34, 10)
    for (value in list) {
        println(value)
    }
    //通过索引来遍历List
    for (index in items.indices) {
        println("${index}对应的值是:${items[index]}")
    }
    //在每次循环中获取当前索引和相应的值
    for ((index, value) in list.withIndex()) {
        println("index : $index , value :$value")
    }
    //自定义循环区间
    for (index in 2..10) {
        println(index)
    }
三、修饰符

kotlin 中的类和方法默认都是 final 的,即不可继承的,如果想允许创建一个类的子类,需要使用 open 修饰符来标识这个类,也需要为每一个希望被重写的属性和方法添加 open 修饰符

open class View {
    open fun click() {

    }
	//不能在子类中被重写
    fun longClick() {

    }
}

class Button : View() {
    override fun click() {
        super.click()
    }
}


四、空安全

一个定义为 internal 的包成员,对其所在的整个 module 可见,想要发布一个开源库,库中包含某个类,我们希望这个类对于库本身是全局可见的,但对于外部使用者来说它不能被引用到,此时就可以选择将其声明为 internal 。一个 module 应该是一个单独的功能性的单位,可以看做是一起编译的 kotlin 文件的集合,它应该是可以被单独编译、运行、测试、debug 的。相当于在 Android Studio 中主工程引用的 module 

 在 Kotlin 中,类型系统区分可以保存的引用(可为 null 的引用)和不能保存的引用(非 null 引用)。

var a: String = "abc" // Regular initialization means non-null by default
a = null // compilation error
val l = a.length

var b: String? = "abc" // can be set to null
b = null // ok
val l = b.length // error: variable 'b' can be null

访问可为 null 变量的属性是使用安全调用运算符:?.允许把一次 null 检查和一次方法调用合并为一个 *** 作,如果变量值非空,则方法或属性会被调用,否则直接返回 null

val a = "Kotlin"
val b: String? = null
println(b?.length)
println(a?.length) // Unnecessary safe call

非空断言:!! 把任何值转换为非空类型

 五、函数

Kotlin 应用程序的入口点是函数

fun main() {
    println("Hello world!")
}

fun main(args: Array) {
    println(args.contentToString())
}

具有两个参数和返回类型的函数

fun sum(a: Int, b: Int): Int {
    return a + b
}

函数体可以是表达式。其返回类型是推断出来的。 

fun sum(a: Int, b: Int) = a + b

没有返回值的函数。

 fun printSum(a: Int, b: Int): Unit {
    println("sum of $a and $b is ${a + b}")
}
//Unit可以省略 
fun printSum(a: Int, b: Int) {
    println("sum of $a and $b is ${a + b}")
}
  • 函数可以定义在文件的最外层,不需要把它放在类中
  • 用关键字 fun 来声明一个函数
  • 参数类型写在变量名之后,这有助于在类型自动推导时省略类型声明
  • 使用 println 代替了 System.out.println ,这是 kotlin 标准库提供的对 Java 标准库函数的简易封装
  • 可以省略代码结尾的分号

默认参数 

如果默认参数位于没有默认值的参数之前,则只能通过调用具有命名参数的函数来使用默认值 

fun foo(
    bar: Int = 0,
    baz: Int,
) { /*...*/ }

foo(baz = 1) // The default value bar = 0 is used

 如果默认参数后的最后一个参数是 lambda,则可以将其作为命名参数传递,也可以在括号外传递

fun foo(
    bar: Int = 0,
    baz: Int = 1,
    qux: () -> Unit,
) { /*...*/ }

foo(1) { println("hello") }     // Uses the default value baz = 1
foo(qux = { println("hello") }) // Uses both default values bar = 0 and baz = 1
foo { println("hello") }        // Uses both default values bar = 0 and baz = 1
命名参数

调用函数时,可以命名其一个或多个参数。当函数具有许多参数并且很难将值与参数相关联时,这可能很有用

fun reformat(
    str: String,
    normalizeCase: Boolean = true,
    upperCaseFirstLetter: Boolean = true,
    divideByCamelHumps: Boolean = false,
    wordSeparator: Char = ' ',
) { /*...*/ }
//调用此函数时,不必为其所有参数命名:

reformat(
    "String!",
    false,
    upperCaseFirstLetter = false,
    divideByCamelHumps = true,
    '_'
)

可变数量的参数(varargs) 

vararg修饰符标记函数的参数(通常是最后一个参数),只能将一个参数标记为vararg

fun  asList(vararg ts: T): List {
    val result = ArrayList()
    for (t in ts) // ts is an Array
        result.add(t)
    return result
}
//在这种情况下,可以向函数传递可变数量的参数:

val list = asList(1, 2, 3)

中缀表示法

必须是成员函数或扩展函数。必须具有单个参数。该参数不能接受可变数量的参数,并且不能具有默认值。

infix fun Int.shl(x: Int): Int { ... }

// calling the function using the infix notation
1 shl 2

// is the same as
1.shl(2)
局部函数

kotlin 支持在函数中嵌套函数,这些函数是其他函数中的函数: 

fun dfs(graph: Graph) {
    fun dfs(current: Vertex, visited: MutableSet) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v, visited)
    }

    dfs(graph.vertices[0], HashSet())
}
成员函数

成员函数是在类或对象中定义的函数: 

class Sample {
    fun foo() { print("Foo") }
}

Sample().foo() // creates instance of class Sample and calls foo

六、类

Kotlin 中的类是使用class关键字声明的 

class Person { /*...*/ }
//如果类没有正文,则可以省略大括号。
class Empty

构造函数 

Kotlin 中的类可以有一个主要构造函数和一个或多个辅助构造函数。主构造函数是类头的一部分,它以类名和可选类型参数为后。

class Point constructor(val x: Int, val y: Int) {

}
//如果主构造函数没有任何注解或者可见性修饰符,可以省略 constructor 关键字
class Point(val x: Int, val y: Int) {

}
//如果构造函数有注解或可见性修饰符,则 constructor 关键字是必需的,并且这些修饰符在它前面
class Point public @Inject constructor(val x: Int, val y: Int) {

}

主构造函数不能包含任何代码。初始化代码可以放在以关键字init 为前缀的初始化中。在实例初始化期间,初始值设定项块的执行顺序与它们在类体中出现的顺序相同 

class InitOrderDemo(name: String) {
    val firstProperty = "First property: $name".also(::println)
    
    init {
        println("First initializer block that prints $name")
    }

    val secondProperty = "Second property: ${name.length}".also(::println)
    
    init {
        println("Second initializer block that prints ${name.length}")
    }
}

类声明还可以包含类属性的默认值: 

class Person(val firstName: String, val lastName: String, var isEmployed: Boolean = true)
 辅助构造函数

构造函数的前缀为 :constructor

class Person(val pets: MutableList = mutableListOf())

class Pet {
    constructor(owner: Person) {
        owner.pets.add(this) // adds this pet to the list of its owner's pets
    }
}

如果类具有主构造函数,则每个辅助构造函数都需要直接或间接地通过另一个辅助构造函数委托给主构造函数。使用关键字this对同一类的另一个构造函数进行委派: 

class Person(val name: String) {
    val children: MutableList = mutableListOf()
    constructor(name: String, parent: Person) : this(name) {
        parent.children.add(this)
    }
}

//即使类没有主构造函数,委派仍然隐式发生,并且仍然执行初始值设定项块:
class Constructors {
    init {
        println("Init block")
    }

    constructor(i: Int) {
        println("Constructor $i")
    }
}

初始化块中的代码实际上会成为主构造函数的一部分,委托给主构造函数会作为次构造函数的第一条语句,因此所有初始化块中的代码都会在次构造函数体之前执行。即使该类没有主构造函数,这种委托仍会隐式发生,并且仍会执行初始化块。如果一个非抽象类没有声明任何(主或次)构造函数,会默认生成一个不带参数的公有主构造函数

创建类的实例

调用构造函数,就好像它是常规函数一样:

val invoice = Invoice()

val customer = Customer("Joe Smith")

Kotlin 没有new关键字。

覆写方法 

open class Shape {
    open fun draw() { /*...*/ }
    fun fill() { /*...*/ }
}

class Circle() : Shape() {
    override fun draw() { /*...*/ }
}
//如果要禁止重新覆写
open class Rectangle() : Shape() {
    final override fun draw() { /*...*/ }
}

 覆写属性

open class Shape {
    open val vertexCount: Int = 0
}

class Rectangle : Shape() {
    override val vertexCount = 4
}

 Getters 和 setters

可以为属性定义自定义访问器。如果定义了自定义 getter,则每次访问该属性时都会调用它 

class Rectangle(val width: Int, val height: Int) {
    val area: Int // property type is optional since it can be inferred from the getter's return type
        get() = this.width * this.height
}

如果定义了自定义setter,则每次为属性赋值时都会调用它,但初始化除外。

var stringRepresentation: String
    get() = this.toString()
    set(value) {
        setDataFromString(value) // parses the string and assigns values to other properties
    }

 延迟初始化

通常,声明为具有非 null 类型的属性必须在构造函数中初始化。但是,通常情况下,这样做并不方便。例如,可以通过依赖关系注入或在单元测试的setup方法中初始化属性。在这些情况下,不能在构造函数中提供非空初始值设定项,但在引用类主体内的属性时仍希望避免 null 检查。用 lateinit 修饰的属性或变量必须为非空类型,并且不能是原生类型

public class MyTest {
    lateinit var subject: TestSubject

    @SetUp fun setup() {
        subject = TestSubject()
    }

    @Test fun test() {
        subject.method()  // dereference directly
    }
}

数据类 

数据类是一种非常强大的类,可以避免重复创建 Java 中的用于保存状态但又 *** 作非常简单的 POJO 的模版代码,它们通常只提供了用于访问它们属性的简单的 getter 和 setter

data class Point(val x: Int, val y: Int)

数据类默认地为主构造函数中声明的所有属性生成了如下几个方法

  • getter、setter(需要是 var)
  • componentN()。按主构造函数的属性声明顺序进行对应
  • copy()
  • toString()
  • hashCode()
  • equals()

密封类

Sealed 类(密封类)用于对类可能创建的子类进行限制,用 Sealed 修饰的类的直接子类只允许被定义在 Sealed 类所在的文件中(密封类的间接继承者可以定义在其他文件中),这有助于帮助开发者掌握父类与子类之间的变动关系,避免由于代码更迭导致的潜在 bug,且密封类的构造函数只能是 private 的

例如,对于 View 类,其子类只能定义在与之同一个文件里,Sealed 修饰符修饰的类也隐含表示该类为 open 类,因此无需再显式地添加 open 修饰符

sealed class View {
    fun click() {

    }
}

class Button : View() {

}

class TextView : View() {

}

内部类 

如果需要去访问外部类的成员,需要用 inner 修饰符来标注被嵌套的类,这称为内部类。内部类会隐式持有对外部类的引用

class Outer {
    private val bar = 1
    inner class Nested {
        fun foo1() = 2
        fun foo2() = bar
    }
}

fun main() {
    val demo = Outer().Nested().foo2()
}

匿名内部类

view.setClickListener(object : OnClickListener {
        override fun onClick() {

        }
    })
七、扩展函数

 扩展函数的用途就类似于在 Java 中实现的静态工具方法。而在 kotlin 中使用扩展函数的一个优势就是我们不需要在调用方法的时候把整个对象当作参数传入,扩展函数表现得就像是属于这个类本身的一样,可以使用 this 关键字并直接调用其所有 public 方法,扩展函数并不允许你打破它的封装性,和在类内部定义的方法不同的是,扩展函数不能访问私有的或是受保护的成员

//为 String 类声明一个扩展函数 lastChar() ,用于返回字符串的最后一个字符
//get方法是 String 类的内部方法,length 是 String 类的内部成员变量,在此处可以直接调用
fun String.lastChar() = get(length - 1)

//为 Int 类声明一个扩展函数 doubleValue() ,用于返回其两倍值
//this 关键字代表了 Int 值本身
fun Int.doubleValue() = this * 2

//之后,我们就可以像调用类本身内部声明的方法一样,直接调用扩展函数
val name = "leavesC"
println("$name lastChar is: " + name.lastChar())

val age = 24
println("$age doubleValue is: " + age.doubleValue())

扩展属性

扩展函数也可以用于属性

//扩展函数也可以用于属性
//为 String 类新增一个属性值 customLen
var String.customLen: Int
    get() = length
    set(value) {
        println("set")
    }

fun main() {
    val name = "leavesC"
    println(name.customLen)
    name.customLen = 10
    println(name.customLen)
    //7
    //set
    //7
}
八、作用域函数

Kotlin 标准库包含几个函数,它们的唯一目的是在对象的上下文中执行代码块。当对一个对象调用这样的函数并提供一个 lambda 表达式时,它会形成一个临时作用域。在此作用域中,可以访问该对象而无需其名称。这些函数称为作用域函数。

作用域函数是为了方便对一个对象进行访问和 *** 作,你可以对它进行空检查或者修改它的属性或者直接返回它的值等 *** 作

let
public inline fun  T.let(block: (T) -> R): R 

let函数是参数化类型 T 的扩展函数。在let块内可以通过 it 指代该对象。返回值为let块的最后一行或指定return表达式。

class Book() {
    var name = "《数据结构》"
    var price = 60
    fun displayInfo() = print("Book name : $name and price : $price")
}
fun main(args: Array) {
    val book = Book().let {
        it.name = "《计算机网络》"
        "This book is ${it.name}"
    }
    print(book)
}

控制台输出:
This book is 《计算机网络》

如果let块中的最后一条语句是非赋值语句,则默认情况下它是返回语句。反之,则返回的是一个 Unit类型

let可用于空安全检查。

 val nameLength = name?.let {
        it.length
    }

let可对调用链的结果进行 *** 作 

fun main(args: Array) {
    val numbers = mutableListOf("One","Two","Three","Four","Five")
    numbers.map { it.length }.filter { it > 3 }.let {
        print(it)
    }
}

let可以将“It”重命名为一个可读的lambda参数。

 let是通过使用“It”关键字来引用对象的上下文,因此,这个“It”可以被重命名为一个可读的lambda参数,如下将it重命名为book:

fun main(args: Array) {
    val book = Book().let {book ->
        book.name = "《计算机网络》"
    }
    print(book)
}

 run

run函数存在两种声明方式,

1.与let一样,run是作为T的扩展函数;

inline fun  T.run(block: T.() -> R): R 

run函数以“this”作为上下文对象,且它的调用方式与let一致。 当 lambda 表达式同时包含对象初始化和返回值的计算时,run更适合。

fun main(args: Array) {

    Book().run {
        name = "《计算机网络》"
        price = 30
        displayInfo()
    }
}

控制台输出:
Book name : 《计算机网络》 and price : 30

 2、第二个run的声明方式则不同,它不是扩展函数,并且块中也没有输入值,因此,它不是用于传递对象并更改属性的类型,而是可以使你在需要表达式的地方就可以执行一个语句。

inline fun  run(block: () -> R): R

如下利用run函数块执行方法,而不是作为一个扩展函数:

run {
        val book = Book()
        book.name = "《计算机网络》"
        book.price = 30
        book.displayInfo()
    }

with 

inline fun  with(receiver: T, block: T.() -> R): R 

with属于非扩展函数,直接输入一个对象receiver,当输入receiver后,便可以更改receiver的属性,同时,它也与run做着同样的事情。with使用的是非null的对象,当函数块中不需要返回值时,可以使用with。

fun main(args: Array) {
    val book = Book()
   
    with(book) {
        name = "《计算机网络》"
        price = 40
    }
    print(book)
}

  apply

inline fun  T.apply(block: T.() -> Unit): T

apply是 T 的扩展函数,与run函数有些相似,它将对象的上下文引用为“this”而不是“it”,并且提供空安全检查,不同的是,apply不接受函数块中的返回值,返回的是自己的T类型对象。

fun main(args: Array) {
    Book().apply {
        name = "《计算机网络》"
        price = 40

    }
    print(book)
}

控制台输出:
com.fuusy.kotlintest.Book@61bbe9ba

在 let 中,没有在函数块中返回的值,最终会成为 Unit 类型,但在 apply 中,最后返回对象本身 (T) 时,它成为 Book 类型。

apply函数主要用于初始化或更改对象,因为它用于在不使用对象的函数的情况下返回自身。

also
inline fun  T.also(block: (T) -> Unit): T 

also是 T 的扩展函数,返回值与apply一致,直接返回T。also函数的用法类似于let函数,将对象的上下文引用为“it”而不是“this”以及提供空安全检查方面。

因为T作为block函数的输入,可以使用also来访问属性。所以,在不使用或不改变对象属性的情况下也使用also。

fun main(args: Array) {
    val book  = Book().also {
        it.name = "《计算机网络》"
        it.price = 40
    }
    print(book)
}

控制台输出:
com.fuusy.kotlintest.Book@61bbe9ba
  • 用于初始化对象或更改对象属性,可使用apply
  • 如果将数据指派给接收对象的属性之前验证对象,可使用also
  • 如果将对象进行空检查并访问或修改其属性,可使用let
  • 如果是非null的对象并且当函数块中不需要返回值时,可使用with
  • 如果想要计算某个值,或者限制多个本地变量的范围,则使用run

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/740429.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-28
下一篇 2022-04-28

发表评论

登录后才能评论

评论列表(0条)

保存