Kotlin学习笔记——归纳整理

Kotlin简介来自于著名的IDE IntelliJ IDEA(Android Studio基于此开发) 软件开发公司 JetBrains(位于东欧捷克)
是一种基于JVM的静态类型编程语言,2017年谷歌I/O大会确定Kotlin为Android的官方语言

一:基础语法

1.1 特性

在Kotlin中定义包与Java有点不同,在Kotlin中目录与包结构无需匹配,
Kotlin的源码可以在磁盘上的任意位置。

// 包格式 和 java 一致
package com.ymc.hellokotlin

fun main(args: Array<String>) {
    println("Hello world")
    println(max(2,3))
}

fun max(a:Int ,b:Int):Int{
    return if(a>b) a else b;
}
  1. main 函数不需要在class 中就可以运行
  2. fun 代表一个函数,后边紧跟函数名称,参数列表和返回值类型
  3. 参数实现写 参数名称 然后冒号隔开,再写参数类型(和java 相反)
  4. 函数的返回值是在后边的(和java刚好相反的)当然 有返回值 也可以不写返回类型(前提是:只有表达式体 函数返回类型可以省略,如果是代码块体函数就必须要 写明函数返回类型),因为kotlin 通过 类型推导 也是可以知道返回值类型的
  5. system.out.println 被包装为 println
  6. 在行末可以省略 分号 (类似 js)
  7. 看到max函数中 if类似于三元表达式 kotlin中,if 是有结果值的表达式
  8. 如果返回值 类似于 java 中的 void 则可以写成 :Unit ,当然也可以省略不写

在kotlin 中,除了部分循环(for do 和 do/while)大多控制结构都是表达式,是有返回值的。另一方面 java 中的赋值语句为表达式,而kotlin 中则为语句。

与Java定义包名一样,在源文件的开头定义包名:但是不同的是,包名和文件夹路径可以不一致。

1.2 变量

kotlin 的变量声明是可以省略类型的,所以kotlin 变量声明有别于java ,kotlin变量声明顺序为关键字变量名称类型(可不加),如果变量没有初始化则需要明确表明变量类型。

常量与变量都可以没有初始化值,但是在引用前必须初始化编译器支持自动类型判断,即声明时可以不指定类型,由编译器判断。如果不在声明的时候初始化则必须提供变量的类型

主要分为两种定义:
(官方推荐 尽量使用val 声明变量,使程序更接近函数式编程风格)

  • val :不可变引用 ,在val声明变量后不能再初始化之外再次赋值与java final一致
  • var :可变引用 , 该类型变量可以随便赋值

1.2.1 可变变量的定义: var 关键字

var <变量名> : <变量类型> = <初始值>

var sum: Int = 3
sum = 8

//由于编译支持类型推导可以省略Int类型
var sum = 3
sum = 8

1.2.2不可变变量的定义: val 关键字

( 不能进行二次赋值,类似Java中的final类型)

val <常量名> : <常量类型> = <初始值>

val sum: Int //没有赋值初始化之前必须指定类型
sum = 5

1.2.3 常量

已知值的属性可以使用 const 修饰符标记为 编译期常量。需要满足如下几种条件 (类似 java 中的 constanUtil 中的 常量值)

  1. 位于顶层或者是 object 的一个成员
  2. 用 String 或原生类型 值初始化
  3. 没有自定义 getter
const val SUBSYSTEM_KEY: String = "key"

@Deprecated(SUBSYSTEM_KEY) fun foo() { …… }

1.2.4 字符串模板

字符串可以包含模板表达式,以$开始

val book = Book("Thinking In Java", 59.0f, "Unknown")
val extraValue = "extra"
Log.d("MainActivity", "book.name = ${book.name}; book.price=${book.price};extraValue=$extraValue")

1.3 类与属性

1.3.1 基础

在Kotlin中所有类都有一个共同的超类Any

//类定义,继承类和实现接口
class FeedBackActivity : NativeBaseActivity(), View.OnLongClickListener, BitmapUtil.SaveImageCall {
    
}

创建类的实例

val invoice = Invoice()
val customer = Customer("Joe Smith")
//注意 Kotlin 并没有 new 关键字。

我们比较一下 java 和 kotlin 中类的写法的不同
java:

public class DemoBean {

   private final String name;

   public DemoBean(String name) {
       this.name = name;
   }

   public String getName() {
       return name;
   }
}

Kotlin:

class DemoBean(val name: String)

类是将数据和处理数据的代码封装成一个单一的实体。

 class Person{
    var name :String  = "ymc"    // 可读可写
    val isMarried : Boolean  = false    // 只读
}

上段代码中 isMarried 会生成一个字段,一个getter ,而 name 则会生成 一个 getter和一个 setter 。

kotlin 在你声明属性的时候,你就声明了对应的访问器,默认的访问器 就是返回值的 getter 和 更新数值的 setter ,kotlin 会暴漏一个 getName 方法,当然我们也可以自定义访问器。

fun main(args: Array<String>) {
    var person = Person()
    println(person.name +";"+ person.isMarried )
}

可以看到我们直接调用而不需要get,感觉很像js…

1.3.2 自定义访问器

class Person{
     var name :String  = "ymc"
     var sex : Int = 0;  // 0 为女性 1为男性
     val isMarried : Boolean  = false
     val isBoy :Boolean
            get() {
                return if(sex==1) true else false
            }
}

将Person 添加 sex 和 isboy 属性,重写getter ,这样就可以做到自定义访问器。

如果 我们设置属性为val ,但是通过自定义 getter修改属性?
fun main(args: Array<String>) {
    val name = "Hello Kotlin"
    name = "Hello Java"
}

Error:(8, 5) Kotlin: Val cannot be reassigned

如果单纯的修改,则会报错

接下来我们通过 自定义访问器 看看

class RandomNum {
    val num: Int
        get() = Random().nextInt()
}

fun main(args: Array<String>) {
    println("the num is ${RandomNum().num}")
}

the num is -1251923160
the num is -1527833641

总结: 由以上的例子可以说明假设一是成立的,在Kotlin中的val修饰的变量不能说是不可变的,而只能说仅仅具有可读权限。

1.3.3 备用字段

kotlin 中并不允许使用字段,这个时候我们就可以使用备用字段,比如下段代码,起到局部变量的作用。

//初始化值会直接写入备用字段
var counter = 0 
    get() = field   // field可以理解为自己本身
    set(value) {
        if (value >= 0)
            field  = value
    }
// 这种情况并不需要备用字段,所有不会生成备用字段
val isEmpty: Boolean
    get() = this.size == 0

注意:field标识符只允许在属性的访问器函数内使用.

1.3.4 延迟初始化属性和变量

属性声明为非空类型必须在构造函数中初始化,为处理这种情况,你可以用 lateinit 修饰符标记该属性:

public class MyTest {
    lateinit var subject: TestSubject

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

    @Test fun test() {
        subject.method()  // 直接解引用
    }
}

这样的话,我们就可以不用再构造函数的时候对其进行初始化,后续在哪里需要调用 setup就可以。

1.4 函数

1.4.1 函数基本方法

Kotlin 中的函数使用 fun 关键字声明,参数即 name: type。参数用逗号隔开

fun double(x: Int): Int {
    return 2 * x
}

//Int 返回类型可以省略
fun sum(a: Int, b: Int): Int {
    return a + b
}

//也可以
fun sum(a: Int, b: Int) = a + b

减少方法重载

//支持默认参数值,减少方法重载
fun showToast(message: String, duration:Int = Toast.LENGTH_LONG) {
    Toast.makeText(this, message, duration).show()
}

//调用方式:没有默认值的参数,调用时,必须填写
showToast("toast");
showToast("toast", Toast.LENGTH_LONG);

Main方法比较

//Java
public static void main(String[] args){

}
//Kotlin
fun main(args:Array<String>){
   
}

1.4.2 主次构造函数

在Kotlin中的一个类可以有一个主构造函数和一个或多个次构造函数

  • 主构造函数:带有类名的为主构造函数(只有一个)
    1. 主构造函数不能包含任何的代码。初始化的代码可以放到以 init 关键字作为前缀的初始化块
    2. 主构造函数中声明的属性可以是可变的(var)或只读的(val)
//如果构造函数有注解或可见性修饰符,这个 constructor 关键字是必需的,并且这些修饰符在constructor前面:
class Customer public @Inject constructor(name: String) { …… }

//无修饰可不写constructor关键字
class Customer (name: String) {
    var a :Int = 1
    init{……}
}
  • 次构造函数:不带类名并且有constructor关键字修饰的函数为次构造函数(可以一个或多个),并且只能存在主构造函数代码块之内
//如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用 this 关键字即可:
class Person(val name: String) {
    constructor(name: String, parent: Person) : this(name) {
        parent.children.add(this)
    }
}
  • 无参构造函数
    1. 如果没有构造函数,将会默认一个无参数构造函数
    2. 如果主构造函数的所有的参数都有默认值,编译器会生成一个额外的无参构造函数,它将使用默认值。
//如构造函数为私有,需用private修饰
class DontCreateMe public constructor () {

}

class Customer(val customerName: String = ""){}

1.4.3 函数省略返回类型

下例是一个简单实例:

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

当函数体是由单个表达式构成时,可以用这个表达式作为完整的函数体,并且去掉花括号和return语句,上面的例子就是这种情况,因此可以改写为:

fun max(a:Int, b: Int): Int = if (a > b) a else b
}
  • 如果函数体写在花括号中,我们说这个函数有代码块体
  • 如果它直接返回了一个表达式,它就有表达式体

对于 表达式体函数,可以省略返回类型,因为编译器会分析作为函数体的表达式,并把它的类型作为函数的返回类型,这种分析称为 类型推导。
上面的例子可以简化为:

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

但是对于有返回值的代码块体函数,必须显示地写出返回类型和return语句。

如:

1.4.4 函数调用

val result = double(2)
Sample().foo() // 创建类 Sample 实例并调用 foo

1.5 控制流:if、when、for、while

1.5.1 区间

区间表达式由具有操作符形式 … 的 rangeTo 函数辅以 in 和 !in 形成。

if (i in 1..10) { // 等同于 1 <= i && i <= 10
    println(i)
}

//顺序
for (i in 1..4) print(i) // 输出“1234”
for (i in 4..1) print(i) // 什么都不输出

//倒序
for (i in 4 downTo 1) print(i) // 输出“4321”

//要创建一个不包括其结束元素的区间,可以使用 until 函数:
for (i in 1 until 10) {   // i in [1, 10) 排除了 10
     println(i)
}

//能否以不等于 1 的任意步长迭代数字? 当然没问题, step() 函数有助于此:每循环到第二个元素就剔除

for (i in 1..4 step 2) print(i) // 输出“13”

for (i in 4 downTo 1 step 2) print(i) // 输出“42”

1.5.2 if表达式

if和Java用法一致

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

1.5.3 when表达式

when它能完全取代switch/case,也可以用来取代 if-else if链,并且还有很多新的特性。

如果其他分支都不满足条件将会求值else分支。 如果 when 作为一个表达式使用,则必须有 else 分支, 除非编译器能够检测出所有的可能情况都已经覆盖了。

//分支条件可以是:Int,区间,方法,字符串,对象等
when (x) {
    0, 1 -> print("x == 0 or x == 1")
    in 2 -> print("x == 2")
    in 3..8 -> print("x == 3..8")
    parseInt(s)-> print("parseInt")
    is String -> print("x is String")
    else -> print("otherwise")
}


fun hasPrefix(x: Any) = when(x) {
    is String -> x.startsWith("prefix")//智能转换
    else -> false
}

//when 也可以用来取代 if-else if链。 如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支:
when {
    x.isOdd() -> print("x is odd")
    x.isEven() -> print("x is even")
    else -> print("x is funny")
}

1.5.4 for 循环

for (item in collection) print(item)

//循环体可以是一个代码块。
for (item: Int in ints) {
    // ……
}


for (i in 1..3) {
    println(i)//输出“123”
}

for (i in 6 downTo 0 step 2) {
    println(i)//输出“6420”
}

//如果想索引遍历,可以通过indices函数
for (i in array.indices) {
    println(array[i])
}

//或者你可以用库函数 withIndex:
for ((index, value) in array.withIndex()) {
    println("the element at $index is $value")
}
//输出:
the element at 0 is a
the element at 1 is b
the element at 2 is c

1.5.5 While循环

while 和 do…while 照常使用

fun main(args: Array<String>) {
    println("----while 使用-----")
    var x = 5
    while (x > 0) {
        println( x--)
    }
    println("----do...while 使用-----")
    var y = 5
    do {
        println(y--)
    } while(y>0)
}

1.5.6 跳出循环

  • return,默认从最直接包围它的函数或者匿名函数返回。
  • break,终止最直接包围它的循环。
  • continue,继续下一次最直接包围它的循环。

1.6 关键字,修饰符,特殊符号

关键字:

  • Any:在Kotlin中所有类都有一个共同的超类Any,Any并不是Object
  • open:修饰类:表示能被继承;修饰方法:表示需要重写

修饰符:

final、open、abstract、override对比
在这里插入图片描述
可见性修饰符:
类、对象、接口、构造函数、方法、属性和它们的 setter 都可以有 可见性修饰符。

在 Kotlin 中有这四个可见性修饰符:private、 protected、 internal 和 public。
如果没有显式指定修饰符的话,默认可见性是 public

internal —— 能见到类声明的 本模块内 的任何客户端都可见其 internal 成员
其它和Java一样

特殊符号:

  • ?:表示当前是否对象可以为空

  • !!: 表示当前对象不为空的情况下执行

private var cloth_Rv: RecyclerView ?= null
cloth_Rv!!.setHasFixedSize(true)

1.7 object关键字

object关键字主要有三种使用场景

  • 对象声明(object declaration)
  • 伴生对象(companion object)
  • 对象表达式(object expression)

1.7.1 对象声明(object declaration)

将类的声明和定义该类的单例对象结合在一起(即通过object就实现了单例模式)
对象声明中不能包含构造器(包括主构造器和次级构造器)

对象声明实例解析以及在kotlin和java代码中的调用

object UserManager {
    fun saveUser()
}

// 反编译出的Java代码
public final class UserManager {
   public static final UserManager INSTANCE;

   public final void saveUser() {
   }

   private UserManager() {
   }

   static {
      UserManager var0 = new UserManager();
      INSTANCE = var0;
   }
}

在kotlin和java代码中,它们的调用方式有点差别:

  • kotlin代码调用:UserManager.saveUser()
  • java代码调用:UserManager.INSTANCE.saveUser();

1.7.2 伴生对象(companion object)

Kotlin给Java开发者带来最大改变之一就是废弃了static修饰符。与Java不同的是在Kotlin的类中不允许你声明静态成员或方法。相反,你必须向类中添加Companion对象来包装这些静态引用: 差异看起来似乎很小,但是它有一些明显的不同。

在kotlin中是没有static关键字的,也就意味着没有静态方法和静态成员。那么在kotlin中如果想要表达这种概念,可以使用包级别函数(package-level funcation)和伴生对象(companion object)。

伴生对象语法形式:

class ClassName {
    // 伴生对象名可以省略,默认为Companion
    companion object 伴生对象名 {
        // define field and method
    }
}

伴生对象实例解析以及在kotlin和java代码中的调用:

class App {
    companion object {
        fun getAppContext() {}
    }
}

// 反编译出的Java代码
public final class App {
   public static final App.Companion Companion = new App.Companion((DefaultConstructorMarker)null);

   public static final class Companion {
      public final void getAppContext() {
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}

与对象声明类似,它们在kotlin和java代码中的调用方式也有点差别:

  • kotlin代码调用:App.getAppContext()
  • java代码调用:App.Companion.getAppContext();

companion:这个关键字实际上只是一个快捷方式,允许你通过类名访问该对象的内容(如果伴生对象存在一个特定的类中,并且只是用到其中的方法或属性名称,那么伴生对象的类名可以省略不写)。就编译而言,下面的testCompanion()方法中的三行都是有效的语句。

class TopLevelClass {
 
    companion object {
        fun doSomeStuff() {
            ...
        }
    }
 
    object FakeCompanion {
        fun doOtherStuff() {
            ...
        }
    }
}
 
fun testCompanion() {
    TopLevelClass.doSomeStuff()
    TopLevelClass.Companion.doSomeStuff()
    TopLevelClass.FakeCompanion.doOtherStuff()
}

为了兼容的公平性,companion关键字还提供了更多选项,尤其是与Java互操作性相关选项。果您尝试在Java类中编写相同的测试代码,调用方式可能会略有不同:

public void testCompanion() {
    TopLevelClass.Companion.doSomeStuff();
    TopLevelClass.FakeCompanion.INSTANCE.doOtherStuff();
}

区别在于:
Companion作为Java代码中静态成员开放(实际上它是一个对象实例,但是由于它的名称是以大写的C开头,所以有点存在误导性)

FakeCompanion引用了我们的第二个单例对象的类名。在第二个方法调用中,我们需要使用它的INSTANCE属性来实际访问Java中的实例(你可以打开IntelliJ IDEA或AndroidStudio中的"Show Kotlin Bytecode"菜单栏,并点击里面"Decompile"按钮来查看反编译后对应的Java代码)

在这两种情况下(不管是Kotlin还是Java),使用伴生对象Companion类比FakeCompanion类那种调用语法更加简短。此外,由于Kotlin提供一些注解,可以让编译器生成一些简短的调用方式,以便于在Java代码中依然可以像在Kotlin中那样简短形式调用。

1.7.3 对象表达式(object expression)内部类/匿名内部类

  • 内部类
//如果需要调用成员变量,需要用inner修饰内部类
class Outer {
    private val bar: Int = 1
    inner class Inner {
        fun foo() = bar
    }
}

val demo = Outer().Inner().foo() // == 1
  • 匿名内部类
//匿名内部类
textView.setOnClickListener(object : View.OnClickListener {
    override fun onClick(v: View?) {
       //...
    }
})

对象表达式常用来作为匿名内部类的实现:

private val callBack = object : CallBack {

    override fun onSuccess() {}

    override fun onFailure() {}
}

// 通过对象表达式实现点击事件回调
btn.setOnClickListener(object : View.OnClickListener {
    override fun onClick(v: View) {
        TODO("Not yet implemented")
    }
})

1.8 扩展

函数扩展可以让你对任意类进行扩展,而不用继承等等复杂的操作。

对参数的解释:

  • Context:表示函数的接收者,也就是函数扩展的对象
  • . :扩展函数修饰符
  • toast:扩展函数的名称或者属性名称

1.8.1 扩展函数

fun Context.showToast(content: String, duration: Int = Toast.LENGTH_SHORT): Toast {
    val toast = Toast.makeText(MyApplication.context, content, duration)
    toast.show()
    return toast
}

这样只要是Context的子类,任何地方都可以调用Toast了

1.8.2 扩展属性

var View.padLeft: Int
    set(value) {
        setPadding(value, paddingTop, paddingRight, paddingBottom)
    }
    get() {
        return paddingLeft
    }
    //调用 textView.padLeft = 16

1.8.3 Any扩展函数

apply

  • apply 是 Any 的扩展函数,因而所有类型都能调用。
  • apply 接受一个lambda表达式作为参数,并在apply调用时立即执行,apply返回原来的对象。
  • apply 主要作用是将多个初始化代码链式操作,提高代码可读性。
val task = Runnable { println("Running") }
val thread = Thread(task)
thread.setDaemon(true)
thread.start()

以上代码可以简化为:

val task = Runnable { println("Running") }
Thread(task).apply { setDaemon(true) }.start()
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yawn__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值