Kotlin分享(一)

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

Kotlin分享(一)

Kotlin分享(二)

Kotlin分享(三)

Kotlin分享(四)

Kotlin分享(五)

Kotlin 协程 coroutines

 

前言

    和java无缝衔接。

    各类工具配置https://kotlinlang.org/docs/tutorials/

基础语法

 

包定义

package my.demo

import java.util.*

和java 相同的package 和 import 关键字。但是其中有一点区别:

                                            Kotlin分享(一)_第1张图片

                                        Kotlin分享(一)_第2张图片

    可以看到包名 != 文件夹路径,文件名!=类名(当你你也可以相同。)对于某些时候的文件组织可能有些作用,但是大多数情况下我们还是默认使用java的路径规则。

    import 可以使用一个as关键字对类进行重命名

                    import bar.Bar as bBar // bBar stands for 'bar.Bar'    

变量定义

    变量定义关键字:

                            val   &   var

    区别在于 val相当于final 类型变量,var表示可变的变量。

    最简单的定义    val a: Int = 1    val(r) 变量名 : 变量类型 = 初始值

    在变量类型可推测的情况下甚至可以省略 比如: val b = 2  //Int 被省略

    变量分为三种

class ClassNew{
    var a:String = "你好"   //成员变量
    fun print(){
        var b : String = "kotlin"  //本地变量
        Log.d("sss",a + b +c)
        c = "我是程序猿2"
    }
}
var c : String = "我是程序猿"  //全局变量

    成员变量和全局变量需要在定义的时候进行初始化(成员变量可以使用lateinit 延迟初始化,后面会介绍),  而本地变量只需要保证在使用前初始化即可。

方法定义

    方法定义的一般形式如下

                fun 方法名(参数名:参数类型 ,参数名:参数类型 ... ) : 返回类型{

                 }

    使用关键字fun来定义一个方法类似如下:

fun test(num:Int):Int{
    return num+1;
}

    对于这种方法体只是一个表达式的情况,我们还可以如下简写:

fun test(num:Int)= num+1

    对于没有返回值的方法来说可以使用类型Unit或者直接省略返回类型

fun print(){
    
}

默认值

    方法可以有默认值,比如这样

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

    我们可以直接 reformat(str) 这样调用来省略其他默认值。当然不使用默认值也可以reformat(str,normalizeCase) 至于你想要几个参数 那是你的事情了。

    那么假设我们只有divideByCameHumps不想要默认值呢?在java中这是无法实现的,那么kotlin中是否能实现呢?可以,kotlin中引入了一个新东西,这样

    reformat(str,divideByCameHumps = true)

    这样带上参数名 = ,就能指定不适用默认值的参数了。不过我们发现,第一个参数还是使用了位置来匹配。我们当然可以使用参数名来指定。

    如果所有参数都指定了名字,我们甚至可以不用按顺序来调用方法,像这样

    reformat(divideByCameHumps = true,str = str)

    对于即使用位置,又使用参数名指定的方法调用来说,有一个基本规则,使用位置的参数一定要在指定参数名的参数前面。比如 test(1, y =2 )是可以的 但是 test(x = 1,2) 这样就是错误的。

    在java中有默认值的参数一定要放在最后,但是在kotlin中这就不是必须的,比如

fun momo(str:String ="haha",age:Int){....}

    完全可以,但是在调用的时候,不能再使用位置匹配了,而是必须制定参数名,像这样

momo(age = 12)

    但是这条规则对于最后一个参数是lambda表达式的方法有些不太适用。

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

    我们可以这样调用

                foo(1) { println("hello") } // 使用位置参数,bar =1 baz默认

                 foo { println("hello") } // bar和baz都默认

    主要原因在于,我们看到lambda表达式并没有写进括号内,而是直接写在方法后面。我们后面会介绍。

vararg 不定长参数

    在java中使用 ... 来定义不定长参数,但是必须要放在参数列表最后,不过在kotlin中没有这个限制。

fun foo2(vararg num:Int,str:String){}

    比如我们可以将它定义在开头,或者中间。使用时

base.foo2(1,2,3,4,str = "sss")

    注意一旦遇到vararg参数,它会吃掉随后的所有参数传入,如果这个时候想要传入str,那么就需要指定参数名了。

    另外在vararg参数中,实际上可以传入已经存在的数组,比如上面的定义,可以这样

val a = arrayOf(1,2,3).toIntArray()
base.foo2(1,2,*a,3,4,str = "sss")

    我已经存在了a的数组,使用*a可以把数组中的元素一道传入到varary参数中。

infix 中缀符

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

                ...

} // call extension function using infix notation

1 shl 2 // 等同于 1.shl(2)

比如上面,我们给Int定义了一个额外方法。然后可以通过   1 shl 2 这种类似二元操作符的方式调用。

这样使用有几个规则

    1. 方法一定要是成员函数或者额外函数(extension function)

    2.只有一个参数

    3.使用infix修饰。

PS:这是一个纯种的语法糖,而且使用效果其实也不太明显,所以算不上关键用法。

 

本地方法

    我们知道在方法中定义的变量叫做本地变量,实际上在kotlin中 我们可以在方法中定义方法,这种方法就称为本地方法。

    本地方法能够调用本地变量

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

    dfs(graph.vertices[0])
}

 

数据类型

    数字类型

    kotlin提供一下数字类型,注意

                                                Kotlin分享(一)_第3张图片

    使用常量初始化时类型规则其实和java是差不多的

var a = 123 //Int
var b = 123f //Float
var c = 123.0 //Double
var d = 123.0f //Float

    对象比较

    kotlin中没有基本类型,连Int Float都是对象,和java的对象比较使用equal和 == 不太相同,kotlin中 == 就是equal,使用===来表示值得比较(是否是同一个变量)

        val a: Int = 10000
        Log.d("sss",(a === a).toString()) // Prints 'true'
        val boxedA: Int? = a
        val anotherBoxedA: Int? = a
        Log.d("sss",(boxedA === anotherBoxedA).toString())// !!!Prints 'false'!!!
        //PS:当 a的值在 128以内的时候,返回true!!


        val a: Int = 10000
        Log.d("sss",(a == a).toString()) // Prints 'true'
        val boxedA: Int? = a
        val anotherBoxedA: Int? = a
        Log.d("sss",(boxedA == anotherBoxedA).toString())// !!!Prints 'true'!!!

    另外需要注意的是,不同类型的变量是不能相互赋值和比较的比如

        var a: Int = 10000
        //var b: Long = a  错误,提示int 不能给long 赋值
        var b: Long = a.toLong()
        //Log.d("sss",(b == a).toString()) 错误,long 和 int 不能比较

字符类型

    Char 和 java 类似,但是Char 不能当做int来使用,比如

              var ch:Char = '1'            if(ch == 49)   这样是错误的!

布尔类型

    Boolean 只有 true 或者false两个值

var a: Int = 10000
if(a) //错误,不是Boolean类型,没有这种操作

 

数组

    kotlin中使用类型 Array 来表示数组

var array:Array = Array(5,{i -> i.toString()})

    或许你会对后面初始化的内容不解,后面会有介绍,现在你只需要知道 {i -> i.toString()} 整体是一个参数,它代表了一个方法。类似c++的函数指针。

    虽然Array中提供了 get 和set方法,但是更多的,我们会使用 [] 下标来访问 : array[i]

    我们知道,在java中 表示模板,kotlin中也是如此,Array是一个类型不确定的模板类。但是,kotlin还提供了一些类型固定的数组给我们使用,比如IntArray,ShortArray,另外还提供了方便的初始化方法。

    val x: IntArray = intArrayOf(1, 2, 3) x[0] = x[1] + x[2]

String

    kotlin的String 很多规则都和java类似,当然也有独特的地方。

    首先String 可以使用 [] 下标来访问其中任意一个Char。

    另外就是两种非常有用的用法,java君表示梦寐以求

val i = 10
val s = "i = $i" // evaluates to "i = 10"

val s = "abc"
val str = "$s.length is ${s.length}"  // evaluates to "abc.length is 3"

    另外就是关于转义,kotlin并不适用 \ 反斜杠来进行转义,而是使用如下方法

var price = "${'$'}9.99"

    讲道理,相比java经常转义不清,虽然写起来复杂,奇怪,但是懂了以后看起来挺方便的。

流程控制

if

    几乎有所语言的if控制没有太大区别。

    相比于java,kotlin中不再有  boolean ? a:b  这种用法,而是使用if来替代

    if(boolean) a else b 或者

val max = if (a > b) {
    print("Choose a")
    a
} else {
    print("Choose b")
    b
}

    这里,if语句就代表了一个表达式,有返回值!    

 

when

    kotlin移除了关键字kotlin,而是使用when来代替:

when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> { // Note the block
        print("x is neither 1 nor 2")
    }
}

    when 使用关键字else来捕获其他情况(相当于final)。

    多分支合并使用逗号

when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}

    另外,分支判断除了使用常数,还可以使用表达式,比如

when (x) {
    parseInt(s) -> print("s encodes x")
    else -> print("s does not encode x")
}

    另外,可以使用 is 关键字来判断是否是某个指定的类 

when(x) {
    is String -> xxxx
    else -> xxxx
}

    还可以使用in  关键字来判断是否在某个范围以内

when (x) {
    in 1..10 -> print("x is in the range")
    !in 10..20 -> print("x is outside the range")
    else -> print("none of the above")
}

    PS: 注意 1..10 是左右闭合区间!    

when 也可以用来替代 if-else if -else这样的策略,这种情况下输入值为空,可以这样用

when {
    x.isOdd() -> print("x is odd")
    x.isEven() -> print("x is even")
    else -> print("x is funny")
}

    最后,when和if一样,同样可以作为表达式,如果是表达式,需要每一个分支都有返回值,另外,必须yon拥有else分支!

 

for

    kotlin中的 for语句和java中有了一定的变化,没有了传统的for应用

    所有使用拥有迭代器的对象都能够遍历,相当于foreach

val items = lostOf("apple","banana","kiwi")
for (item in items) {
  println(item)
}

    可以使用index来做

val items = lostOf("apple","banana","kiwi")
for (index in items.indices) {
  println(${items[index]})
}

    虽然不具备类似于 C 中那样的 for 循环,但是并不意味着 Kotlin 不可以实现那种需求:

for (i in 1..5) {   // for (int i = 1;i <= 5;i++) {print(i)}
  print(i)  // 12345
}

    但是1..5这种写法用于步进,想要步退需要使用downTo关键字

for (i in 5 downTo 1) {
  print(i) // 54321
}

    如果实际的步长不是1,那么需要使用 step 函数来指定步长:

for (i in 1..5 step 2) {
  print(i) //1 3 5
}

    另外,又有一些时候(大部分的时候)可能并不需要包括结束区间。那么,这时候需要使用到 until 函数来替代 ..

for (i in 1 until 5) {
  print(i) // 1234
}

While

    和java一样了

 

Return 和 jump

    kotlin和java 类似,关于结构跳转的关键字有三个 return break 和continue

    和 java 区别在于kotlin的这些关键字带label功能,什么是label功能呢?大概就是这样

loop@ for (i in 1..100) {
    for (j in 1..100) {
        if (...) break@loop
    }
}

    原本break是跳出内层循环,但是我们可以使用  xxx@给循环添加label,然后在break的时候指定label来直接跳出多层。

    看下面foo函数,其中使用forEach进行便利,传入的参数是一个lambda表达式,这时候,return 直接从foo函数返回。

fun foo() {
    ints.forEach {
        if (it == 0) return  // nonlocal return from inside lambda directly to the caller of foo()
        print(it)
    }
}

    如果我们只想退出forEach怎么办呢?

fun foo() {
    ints.forEach lit@ {
        if (it == 0) return@lit
        print(it)
    }
}

    我们可以给forEach添加label。甚至forEach其实提供了一个隐藏的label,像这样

fun foo() {
    ints.forEach {
        if (it == 0) return@forEach
        print(it)
    }
}

    需要注意一点,只有在使用lambda的时候才能够直接返回,如果是一个函数,那么return只会回到上一层调用,比如

fun foo() {
    ints.forEach(fun(value: Int) {
        if (value == 0) return  // local return to the caller of the anonymous fun, i.e. the forEach loop
        print(value)
    })
}

    下面这种情况,虽然实现的功能一样,但是return 会返回到forEach,因为forEach的参数并不是一个lambda表达式,而是一个匿名函数!

类 Class

    kotlin同样是面向对象的语言所以势必还是会有类的概念,和java一样使用class关键字来定义一个类

          class   A { }

    另外,如果这个class是一个空的类,我们甚至能省略类体,像这样

          class A

构造函数

    一个类可以有一个主构造方法,多个辅构造方法,我们先来看主构造方法,定义的方式如下

class NewClass2 constructor(name:String){
}

    就是在类名后面加上一个 constructor关键字,然后输入构造方法需要的参数。

    如果,这个构造方法没有注解,同样不需要可见性修饰符(public private等),也没有注解(比如@Inject),那么可以省略constructor关键字

class NewClass2 (name:String){
}

    和上面一样,表示有一个String参数的构造函数。

    PS:和JAVA一样,一旦定义了构造函数(无论是主还是辅),就会隐藏默认构造函数。

    但是我们发现,上面这种写法存在一个问题,在哪里初始化?所以class还提供了一个关键的块来进行初始化,就像这样

class NewClass2 (name:String){
    init {
        //TODO
    }
}

    通过一个init的块来进行初始化操作。

    实际上,无论我们是否手动定义构造函数(无论是主还是辅),init块都会在对象被创建的时候第一时间运行(比辅构造函数的函数体还要早)

    另外,我们可以在init块中调用主构造函数的参数,比如上面,我们可以在init块中使用name这个变量。

    再来说一下这个主构造函数的参数,它可以在init块中使用,也可以用来初始化类内的字段。但是无法被类内方法访问。

class NewClass2 constructor(name:String){
    
    var mName = name //类内字段初始化使用

    init {
        //TODO
        Log.d("sss","${name}") //init中使用
    }

    fun print(){
        Log.d("sss",name)  //错误
    }
}

    但是,kotlin很厉害,我们可以在name 前加上变量定义的符号,var 或者val,这之后就能够像使用成员变量一样使用它。

class NewClass2 constructor(var name:String){
    fun print(){
        Log.d("sss",name)  //正确
    }
}

    除了主构造函数,kotlin还可以使用辅构造函数,ps,虽然是辅构造函数,但是实际上单独使用也可以的。并且可以有多个辅构造函数。

class NewClass2{

    constructor(name:String,age:Int){
        Log.d("sss","new NewClass2!!  constructor ")
    }

    constructor(name:String){
        Log.d("sss","new NewClass2!!  constructor ")
    }

}

    辅助构造函数同样使用关键字 constructor,并且放在类的内部,没有函数名。

    上面展示了,没有主构造函数,也可以定义多个辅助构造函数。

    当存在主构造函数的时候

class NewClass2 constructor(var name:String){

    var mName = name //类内字段初始化使用
    init {
        //TODO
        Log.d("sss","${name}") //init中使用
    }

    constructor(name:String,age:Int):this(name){
        Log.d("sss","new NewClass2!!  constructor ")
    }

    fun print(){
        Log.d("sss",name)  //正确
    }
}

    注意,辅助构造函数一定要使用this来调用主构造函数!!

    构造函数可以有默认值

                    class NewClass2 constructor(var name:String="")

   最后,关于对象的创建,java需要使用关键字new,但是kotlin中没有new关键字,而是直接调用构造方法

    val newClass2 = NewClass2("test")

继承

    kotlin中所有的类都继承与Any,any中只有equal hashCode 和toString三个方法。

    kotlin中使用如下方法来书写继承关系

open class Base(p:Int){

}

class ChildClass(p:Int):Base(p){

}

   在类后面使用 : 来表示继承与某个类。

    需要注意,kotlin中所有类默认都是final的,不允许继承的,所以如果Base想要被继承,那么类就需要定义成open。

    如果基类有主构造函数,那么在继承的时候要如上一样提供构造参数。

    使用辅构造函数和super当然也是可以的,如下

open class Base(p:Int){
    init {
        Log.d("sss","init base ${p}")
    }
}

class ChildClass:Base {
    init {
        Log.d("sss","init ChildClass")
    }

    constructor(p:Int):super(p){

    }
}

override    

    重写方法虽然和java类似,但是多了更多的限制

open class Base{
    open fun print(){
        
    }
}

class ChildClass:Base {
    override fun print(){
        
    }
}

    比如上面的代码,我们被重写的方法必须是open的,并且重写方法必须要添加override关键字。然后如果一个类中有open的方法,那么该类应该是open的,否则将会没有意义(无法被继承的类不需要open方法)。

    PS:这些限制在封装性上面确实有一定帮助,但是对于库文件而言就多了很多自由使用的限制。

    使用了override定义的方法默认是open的,如果想要这个方法不能再被重写,我们可以添加关键字final

final override fun print()

    重写变量和重写方法基本相同,区别在于一点,我们能够使用var类型的变量来重写val类型的,但是不能使用val类型来重写var类型。    

    重写方法后想要在类内调用父类方法,这个和java是一样的,都是使用super关键字。但是有一点可以一提的,对于inner class(就是能够使用外层class的变量和函数的类)

open class Base{
    open fun print(){

    }
}

open class ChildClass:Base {
 
    final override fun print(){

    }

    inner class inClass{
        fun doSomething(){
            [email protected]()
        }
    }
}

    inClass是一个inner class 想要调用base的print方法,直接super.print是不正确的,需要使用@来转移到外层类。

    另外kotlin中的interface是允许有方法实现的,所以

Abstract Classes

    kotlin中也有abstract关键字,用法和java一样

属性

    kotlin中类内的属性是有默认的getter和setter方法的,但是我们可以自定义getter和setter方法,如下这样

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

    看上去并不复杂,其实其中有很多可以探究的地方。

    比如,我们知道kotlin中变量一般都需要初始化,但是这里,我们并没有初始化这个变量(当然初始化也是可以的)!什么情况下我们能够不初始化么? 就是在get 和set中都不会用到该变量的时候。

    我们可以给set设置可见性,但是不重写set的实现

var setterVisibility: String = "abc"
    private set

    或者添加注解

var setterWithAnnotation: Any? = null
    @Inject set

    那么get方法可以吗? 可以添加注解,但是不能够修改可见性,get方法的可见性等于变量自己的可见性!!

field

    看下面代码

var counter = 0 // the initializer value is written directly to the backing field
    set(value) {
        if (value >= 0) counter = value
    }

    看上去没啥问题,但是实际上这样写是错误的,原因在于,我们使用counter这个方法的时候都是通过set和get方法访问的额,在set中我们给counter赋值,实际上这个赋值操作还是会调用set方法,这就很尴尬了,他会一直这样循环下去。那么如何解决呢 ?在get和set中,有一个特殊的关键字,field,使用它就能解决问题,像这样

var counter = 0 // the initializer value is written directly to the backing field
    set(value) {
        if (value >= 0) field = value
    }

const

    在编译时可以确定的变量可以使用const来进行修饰(这个好像和c++差不多)

lateinit

    一般来说,变量的定义的时候都需要提供初始化,但是这在类中是不方便,如果这个变量是通过依赖注入或者其他方法初始化的,这种情况下我们可以使用lateinit来进行延迟初始化

接口

    kotlin的接口和java8中的接口很像,首先,它可以有方法的默认实现,在java8中使用关键字default,而在kotlin中我们不需要使用关键字,像这样

interface InterfaceBase{
    fun foo1()
    fun foo2(){
        Log.d("sss","InterfaceBase.foo2")
    }
}

class A:InterfaceBase{

    override fun foo1() {
        foo2()
    }
}

    接口中可以包含属性

interface InterfaceBase{
    val prop: Int // abstract

    var prop2:Int

    val prop3:Int
        get() = 1
}

class A:InterfaceBase{
    override var prop2: Int =2

    override val prop: Int = 1
}

    属性默认是abstract类型的,必须被重写。另外,我们可以给属性提供默认值,通过重写get的方法,这种方法的变量一定是val类型的,并且在get中是无法使用关键字field的,这样这个属性实际上就相当于一个const类型的变量了。

    由于接口可以有方法的默认实现,所以就会出现冲突

interface InterfaceBase{
    fun foo2(){
        Log.d("sss","InterfaceBase.foo2")
    }
}

interface InterfaceBase2{
    fun foo2(){
        Log.d("sss","InterfaceBase2.foo2")
    }
}

class A:InterfaceBase,InterfaceBase2{
    override fun foo2() {
        super.foo2()
        super.foo2()
    }
}

    InterfaceBase 和 InterfaceBase2都默认实现了foo2方法,然后A同时实现这两个方法,这个时候就会出现冲突,如果调用A.foo2应该要调用哪一个?

    所以kotlin规定,如果出现这种冲突,那么A必须重写foo2这个方法,然后可以通过super.foo2()这种方法来调用相应父类的默认实现。

模块

模块式一起编译的kotlin文件的集合(a module is a set of Kotlin files compiled together)

  • an IntelliJ IDEA module;
  • a Maven project;
  • a Gradle source set;
  • a set of files compiled with one invocation of the Ant task。

    为什么要介绍模块呢?因为kotlin相比java有一个新的可见性修饰符,internal ,表示在同一个模块内可见。

    另外一点不同,kotlin种外部类无法访问内部类中的private变量。

 

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

你可能感兴趣的:(移动开发,c/c++,python)