阅读 385

一、kotlin基础语法学习

kotlin基础语法学习

简单的入个门, 复杂的操作放在下一章节

kotlin 包

  • kotlin包和java的包不同, java类必须放在对应包名的目录下, 而kotlin则没有强制要求
  • kotlin的类名也可以和文件名不同
  • kotlin甚至可以不用写类

image.png

package base

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

fun main(args: Array<String>) {
   var a = 10
   var b = 20
   println(funcMax(a, b))
}
复制代码

学过java的配合源码看

public final class FuncDemo01Kt {
   public static final int funcMax(int a, int b) {
      return a > b ? a : b;
   }

   public static final void main(@NotNull String[] args) {
      int a = 10;
      int b = 20;
      int var3 = funcMax(a, b);
      System.out.println(var3);
   }
}
复制代码

看到这里就一目了然了

kotlin编译器把文件FuncDemo01 + Kt 做成了 java 的类名, 而max 是静态方法, 在 kotlin 中被称之为 顶层函数

所以如果在java的类中调用的没有类名的kotlin方法, 可以考虑直接使用 类名.静态函数(FuncDemo01Kt.funcMax)的方式使用它

下面是java调用kotlin方法的方式

package base.java;

import base.FuncDemo01Kt;

public class JavaTestDemo01 {
    public static void main(String[] args) throws Exception {
        int a = 10;
        int b = 30;
        System.out.println(FuncDemo01Kt.funcMax(a, b));
    }
}
复制代码

函数声明和定义

kotlin函数定义使用 fun 关键字完成:

  • 函数体形式
fun sum(a: Int, b: Int):Int {
    return a + b
}
复制代码

fun关键字 + 空格 + 函数名字(参数名: 参数类型, ...): 函数返回值 {函数体}

  • 表达式函数体形式
fun sum(a: Int, b: Int) = a + b
复制代码

fun关键字 + 空格 + 函数名字(参数名: 参数类型, ...) 表达式函数体( = a + b)

// kotlin函数无返回值时使用 `Unit` 关键字, 注意这里和`Uint`做区分, 表示`unsigned int`无符号
fun sum(a: Int, b: Int):Unit {
    // 字符串模板操作关键字 `$`
    println("max = ${if(a > b) a else b}")
}
复制代码

语句和表达式的区别
表达式有值, 可以嵌套使用, 而语句没有, 语句通常都是包围着代码块
在kotlin中 if 语句就是表达式, 它存在返回值, 例如: val max = if(a > b) a else b
在java中 = 赋值操作是表达式, 可以 int a = c = 1; 但在kotlin中赋值操作是语句, 所以不能够

什么是表达式函数体

如果一个函数的整个函数体都是一个表达式时, 我们就可以称之为表达式函数体

fun max(a: Int, b: Int) = if (a > b) a else b
复制代码

其中 = if(a > b) a else b 便是表达式函数体

在表达式函数体中, 我们可以省略 return 关键字, 如上面的 if表达式

可变函数参数

fun printArray(vararg arr: Int) {
    for(v in arr) {
        println(v)
    }
}
复制代码

lambda和匿名函数

匿名函数

非匿名函数是这样的:

fun max(x: Int, y: Int): Int {
   return x + y
}
复制代码

推导匿名函数:
把有名函数的名字去掉, 他就是匿名函数了, 但是, 如果去掉名字, 这个匿名函数就无法被程序员拿来使用, 所以一般都需要定义一个变量或者函数类型参数来接收

// 下面这段代码是错误的, 没有办法使用的匿名函数
fun (x: Int, y: Int): Int {
   return x + y
}
复制代码
// 下面的 f 就是函数类型变量
val f = fun (x: Int, y: Int): Int {
   return x + y
}

// 现在我们就可以用 f 变量使用该函数
val sum = f(190, 29)
复制代码

我们还可以这样用:

fun running(x: Int, y: Int, funType: (Int, Int) -> Int): Int {
   return funcType(x, y)
}

// 调用方法
running(10, 299, fun (x, y): Int {
   return x + y
})

// lambda表达式
running(10, 299, { x, y -> x + y })
// lambda表达式
running(10, 299) { x, y ->
   // 在lambda 表达式中写上多行代码
   val ret = x + y
   println("ret = $ret")
   ret
}
复制代码

要记住: 匿名函数是对象, 通过函数类型 new 出来的对象, 而函数类型仅仅是个类型, 类似于 Int Double Float String Byte Long等等, 他不是对象, 他不是对象, 他不是对象, 这个后面真的很有用

lambda表达式

在kotlin中凡是同时被 {} 包裹的使用 -> 分割参数和函数体的表达式都可以称之为 lambda 表达式, 例如: { x, y -> if (x > y) x else y }

// lambda 表达式
val x: (Int, Int) -> Int = { x, y -> if (x > y) x else y }
// 匿名函数方式
val y: (Int, Int) -> Int = fun(x: Int, y: Int): Int {
   return if (x > y) x else y
}
复制代码

(Int, Int) -> Int 是函数类型, 但一定记住 他不是对象 他不是对象 他不是对象 他不是对象, 他是一个类型

image.png

var f = (Int, Int) -> Int 所以这样是错误的, 类型怎么可以赋值???

var f: (Int, Int) -> Intvar f: Int 一个东西, 都是定义了一个叫 f的变量, 类型在: 之后一个是 (Int, Int) -> Int 另一个是 Int, 但是未初始化, 需要初始化使用

// kotlin 的初始化, 使用 lambda表达式初始化 f 类型
fun f: (Int, Int) -> Int = { x, y -> x + y }
复制代码
fun max(x: Int, y: Int): Int {
    return if(x > y) x else y
}
复制代码
fun sum2(x: Int, y: Int) = x + y
复制代码

上面这两函数除了函数名和函数体不同外, 其返回值和参数类型都是相同的, 都可以直接赋值给对应的参数

下面这个函数就不行了

fun printXY(x: Int, y: Int): Unit = println("x = $x, y = $y")
复制代码

他的返回值为Unit, 也就是没有返回值, 和 (Int, Int) -> Int类型不匹配

// 这样子写也可以, 但返回值将不再是 Int 类型
fun sum1(x: Int, y: Int): (Int, Int) -> Int = {x: Int, y: Int -> x + y}
复制代码

我在学习的过程中, 发现 fun sum1(x: Int, y: Int) = {x, y -> x + y}会报错,

image.png

翻译: 无法推断此参数的类型。 请明确指定。

然后我就手动为x, y加上了 Int 类型, 但我好奇为什么会这样? 不就是把 lambda表达式对象 = 给 函数么? 这里其实我还不太了解在函数上的 = 编译器对其到底做了什么?

fun sum1(x: Int, y: Int) = {x: Int, y: Int -> x + y}这样子写其实也可以, 但返回值将不再是 x + y 的结果 Int 类型, 是(Int, Int) -> Int , 这样子用, 只能说明, 我们忘记了, lambda表达式也是一个对象, 一个匿名函数对象., 并且不清楚函数上的=到底做了什么?

话说把 fun sum1(x: Int, y: Int) = {x: Int, y: Int -> x + y}中把 = 去掉, 不就是函数了么? 为什么要多那么一步? 导致, 返回值类型变了, 当然你可以这么用, 这里只是探讨 函数上的 = 到底做了什么?

// 强行用上lambda, 知道错后又死不悔改的解决方式. emmm
fun sum1(x: Int, y: Int) = {x: Int, y: Int -> x + y}.invoke(x, y)
复制代码

但还不如: fun sum(x: Int, y: Int) = x + y 来的直接

不过需要注意的是上面这种 x+y 的形式已经不是lambda表达式了, 还记得开头我说的么?? { 参数 -> 函数体 } 这才是lambda表达式

对了 invoke 函数?

val f = {x, y -> x + y}
// 这也需要调用 invoke ???
val ret = f.invoke(x, y)
println(ret)
复制代码

上面的 f 也是 (Int, Int) -> Int 类型

要记住: lambda是对象, 对象的类型是(Int, Int) -> Int, lambda只有在执行的时候才会有 x + y 的返回值 Int 类型
还有看下下面这段源码, 你可能会更加理解上面的fun sum(x: Int, y: Int): (Int, Int) -> Int = { x: Int, y: Int -> x + y }为什么他的返回类型是 (Int, Int) -> Int

val f: (Int, Int) -> Int = {x, y -> x + y}
fun sun3(x: Int, y: Int): (Int, Int) -> Int = f
// 上下这两有区别么???
fun sum4(x: Int, y: Int): Double = 3.14159265
复制代码

是不是就类似于:

val f: (Int, Int) -> Int = {x, y -> x + y}

fun sum3(x: Int, y: Int): (Int, Int) -> Int {
    return f
}

val d: Double = 3.141592653

fun sum4(x: Int, y: Int): Double {
    return d
}
复制代码

现在懂了么?

还不懂的话, 直接点明了吧, fun sum4(x: Int, y: Int): Double = 3.14159265中的 = 被编译器识别后, 它就把 3.14159265对象 放到一个 {} 里面(函数体中) 并在对象前面加上 return, 完事

比如:

fun sum5(x: Int, y: Int): Unit = println("x + y = ${x + y}")
复制代码

如果我按照上面的方式变化的话

fun sum5(x: Int, y: Int): Unit {
    return println("x + y = ${x + y}")
}
复制代码

image.png

没毛病吧??? 同理

fun sum1(x: Int, y: Int): (Int, Int) -> Int {
    return {x: Int, y: Int -> x + y}
}
复制代码

不禁让我想起以前有位老师告诉我学习编程语言要站在编译器的角度学习...

函数内联

  • 什么是函数内联?

我们学习过C语言的人应该都知道 宏定义 吧?

#define MAX xxx
复制代码

他的本质功能是代码替换, 而 kotlin 函数内联就类似于这样

private inline fun running(x: Any, funType: (Any) -> String) = funType(x)

fun main(args: Array<String>) {

    val number: Double = 1.13121356456
    running(number) {
        when (it) {
            is Byte -> Byte::class.java.name
            is Char -> Char::class.java.name
            is Boolean -> Boolean::class.java.name
            is Int -> Int::class.java.name
            is Long -> Long::class.java.name
            is Float -> Float::class.java.name
            is Double -> Double::class.java.name
            is String -> String::class.java.name
            else -> throw Exception("Unknown Type")
        }
    }
}
复制代码

如果不使用内联的话, 代码是这样:

private static final String running(Object x, Function1 funType) {
   return (String)funType.invoke(x);
}

public static final void main(@NotNull String[] args) {
   double number = 1.13121356456D;
   running(number, (Function1)null.INSTANCE);
}
复制代码

如果使用内联的话, 代码是这样:

private static final String running(Object x, Function1 funType) {
   return (String)funType.invoke(x);
}

public static final void main(@NotNull String[] args) {
   double number = 1.13121356456D;
   Object x$iv = number;
   int $i$f$running = false;
   int var6 = false;
   if (x$iv instanceof Integer) {
      Intrinsics.checkNotNullExpressionValue(Integer.TYPE.getName(), "Int::class.java.name");
   } else if (x$iv instanceof String) {
      Intrinsics.checkNotNullExpressionValue(String.class.getName(), "String::class.java.name");
   } else if (x$iv instanceof Long) {
      Intrinsics.checkNotNullExpressionValue(Long.TYPE.getName(), "Long::class.java.name");
   } else if (x$iv instanceof Float) {
      Intrinsics.checkNotNullExpressionValue(Float.TYPE.getName(), "Float::class.java.name");
   } else {
      Intrinsics.checkNotNullExpressionValue(Double.TYPE.getName(), "Double::class.java.name");
   }
}
复制代码

看到效果了么? 看起来是不是更复杂了???

  • 为什么需要函数内联?

在未使用函数内联的情况下, kotlin的匿名函数(lmabda)通常和java一样, 需要创建一个对象, 每遇到匿名函数就需要创建一个新的对象, 导致性能损耗, 但如果使用函数内联, 编译器会在任何需要匿名函数的地方, 直接拷贝函数体的源码过去, 不需要创建对象了

lmabda的递归函数无法内联, 会导致编译器无限复制粘贴函数体

函数引用

通过函数定了出对象, 用于使用, 和定 lambda 表达式对象一个样

private inline fun running(x: Int, y: Int, myFunction: (Int, Int) -> Int): Int {
    println("x: $x, y: $y")
    return myFunction.invoke(x, y)
}

private fun max(x: Int, y: Int): Int = if (x > y) x else y

fun main(args: Array<String>) {
    val x = 10
    val y = 20
    println(running(x, y, ::max))
}
复制代码

函数类型作为返回值

private fun running(): (Int, Int) -> String {
    val prefix: String = "计算: "
    val postfix: String = "计算结果为: "
    return {x:Int, y: Int ->
        "${prefix}${x} + $y ${postfix}${x + y}"
    }
}

fun main(args: Array<String>) {
    val function = running()
    println(function(1, 6))
}
复制代码

闭包

  • 什么是闭包?

匿名函数可以引用它作用域外的变量, 匿名函数被外部的函数定义, 该匿名函数能够引用外部函数的变量, 外部函数不能访问匿名函数内部的局部变量, 这就是闭包, kotlin的lmabda就是闭包

  • 闭包的作用

闭包的好处是给予了作用域保护, 防止作用域出现相同函数名冲突. 像c++提出的namespace一样, kotlin存在顶层函数和属性, 如果不做作用域保护, 随着项目变大, 将有一堆函数重名

private fun running(): (Int, Int) -> String {
    val prefix: String = "计算: "
    val postfix: String = "计算结果为: "
    return {x:Int, y: Int ->
        "${prefix}${x} + $y ${postfix}${x + y}"
    }
}
复制代码

变量

val/var 变量名: 变量类型

val 类似于 java 中的 final 变量, 引用不可变变量
var 则是非 final 变量

val v: Int变量 v 声明时, 如果没有进行初始化, 则变量 v 需要在后续中初始化一次

// 如果是声明时, 需要确定类型
val v: Int
v = 10
复制代码

var 定义的变量, 一旦初始化了类型, 下次赋值时该变量的类型不变

var v = 10
v = "hello" // 错误, v的类型已经是 Int 了, 不可能再变成 String
复制代码

=====的区别

在java 中 == 表示引用比较, equals 比较的是对象内容

在 kotlin 中 == 比较的是对象的内容, === 比较的是引用

字符串格式化模板

fun main(args: Array<String>) {
   var a: Int = 99;
   var b = 11; // 类型推导 为 Int
   println("a = $a, b = $b, a + b = ${a + b}")
}
复制代码

上面这段代码是kotlin字符串模板的使用方式

  1. $变量 直接输出变量的值
  2. ${表达式} 在花括号内可以写上表达式

上面那段字符串操作模板被反编译成java代码时就会变成

public final class StringDemo01Kt {
   public static final void main(@NotNull String[] args) {
      Intrinsics.checkNotNullParameter(args, "args");
      int a = 99;
      int b = 11;
      String var3 = "a = " + a + ", b = " + b + ", a + b = " + (a + b);
      System.out.println(var3);
   }
}
复制代码

看亮点: String var3 = "a = " + a + ", b = " + b + ", a + b = " + (a + b);

类和属性

这个将会在后续章节中着重讲解

在java中类被写成这样的形式:

public class Person {
    private String name;
    private int age;
    
    public final String getName() {
        return this.name;
    }
    
    public final void setName(String name) {
        this.name = name;
    }
    
    public final int getAge() {
        return this.age;
    }
    
    public final void setAge(int age) {
        this.age = age;
    }
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
复制代码

使用 idea 的java 转 kotlin 代码的转换器转换成 kotlin 源码后:

class Person(var name: String, var age: Int)
复制代码

看起来舒服多了

在java转化成kotlin后, public 被隐藏, 在kotlin中类是默认public, 而在java中默认是 default (public default protected private)

ps: 在作为类的成员字段, 也是属性所以需要显示的使用 val/var 来确定属性的 set/get 方法class Person(var name: String, var age: Int)而函数参数不需要 val/var fun sum(a: Int, b: Int)可能是因为它不用 get/set 方法. 不过可变参数需要主动声明 vararg, 例如: ListOf<String>(vararg arr: String)

属性

  1. 属性和字段的区别

①在java中: private String name;成员字段需要另外加上 get/set 方法成为属性, 而在kotlin中的成员字段var name: String是属性, 其底层是定义了成员字段就自动生成该字段的访问器set/get, 在对该字段赋值时. this.property = xxx 在java中调用的是 this.setProperty(xxx), 而读取时this.property在java中调用的是 this.getProperty()

② 在kotlin val/var 声明变量时, 如果使用 val 定义变量, 将被标记成只读属性, 仅生成 get 方法, 不生成set方法, 如果使用的 var 定义变量, 则生成 set/get 方法

当遇到 kotlin 属性 var isDelete时, kotlin会生成 isDelete 方法和 setDeelte 方法, is被替换成 set

  • 自定义属性访问器
class Rectangle(val hight: Int, val width: Int) {
   val isSquare: Boolean
//    get() {
//       return hight == width
//    } // 普通函数体形式
// get() {hight == width} // lamdba形式
   get() = hight == width // 表达式函数体
}
复制代码

kotlin中的import关键字

import关键字在kotlin中可以导入类和顶层函数(在java中叫静态函数)

在 kotlin 中写一个扩展函数(就是把this当作参数传递进去的函数)

fun String.lasts(): Char = this[this.length - 1]
复制代码

而在 kotlin中使用 扩展函数 的方法, val ch = "kotlin".lasts()

在 java 中类似写成这样:

// 核心代码在这里, 把 调用 lasts 对象当作参数传递进去了, 这步骤由 kotlin 虚拟机完成
public static final Char lasts(String /* 这个 this就是调用这个方法的对象, 就是前面例子的 "kotlin" */ this) {
    // "kotlin".charAt("kotlin".length() - 1) 类似于这样
    return this.charAt(this.length() - 1);
}
复制代码

先提出一些后面的知识点, 之后还能回头来看看

回到正题, 我们在另一个包里调用 lasts 顶层函数(静态函数), 就会在kotlin上面见到

import base.func.lastChar
复制代码

前面的 base.func 是包名, 而 lastChar 是方法

如果要在另一个包的java中调用, 就变成了

import base.func.ExtensionFuncKt;

public class ExtensionDemo01 {
    public static void main(String[] args) {
        System.out.println(ExtensionFuncKt.lastChar("zhazha"));
    }
}
复制代码

枚举类

enum class Color(val r: Int, val g: Int, val b: Int) {
   RED(255, 0, 0), ORANGE(255, 165, 0), YELLOW(255, 255, 0), GREEN(0, 255, 0), BLUE(
      0,
      0,
      255
   ),
   INDIGO(75, 0, 130), VIOLET(238, 130, 238);
   
   fun rgb() = (r * 256 + g) * 256 + b
   
   fun getWarmth() = when (this) {
      RED, ORANGE, YELLOW -> "warm"
      GREEN -> "neutral"
      BLUE, INDIGO, VIOLET -> "cold"
   }
}
复制代码

借助枚举类学习 when 表达式的使用方法

when 表达式

(1) 使用函数表达式体的形式

fun getWarmth() = when (this) {
   RED, ORANGE, YELLOW -> "warm"
   GREEN -> "neutral"
   BLUE, INDIGO, VIOLET -> "cold"
}
复制代码

(2) 函数体形式

fun getWarmth1(): String {
   when (this) {
      RED, ORANGE, YELLOW -> return "warm"
      GREEN -> return "neutral"
      BLUE, INDIGO, VIOLET -> return "cold"
   }
}
复制代码

智能类型转换和is表达式类型转换


interface Expr {}
class Num(val value: Int): Expr
class Sum(val left: Expr, val right: Expr): Expr

fun eval(e: Expr): Int {
   if (e is Num) {
      return e.value
   }
   else if (e is Sum) {
      return eval(e.left) + eval(e.right)
   }
   throw IllegalArgumentException("Unknown expression")
}

fun main() {
   println(eval(Sum(Num(1), Sum(Num(2), Num(3)))))
}
复制代码

if (e is Num) if判断返回为 true 时, 则变量 e 被智能转化成 Num 类型

但是使用 is 表达式是有前提的: 该变量必须是 val 定义的变量, 否则无法智能转换, 只能使用 as表达式显示的转换

println((sum.left as Num).value)

val n = e as Num
复制代码

我们还可以使用 when 代替 if 表达式

区间, 数列和循环

  1. 区间

kotlin中没有常规的 for 循环 for(int i = 0; i < length; i++) 这种形式, 所以引入区间能够更好的代替这种操作

1..10 区间表达形式, 表示 [1, 10] 之间的数, 包括 1 和 10

fun main() {
   val interval: IntRange = 1..10
   for (v in interval) {
      print("$v ")
   }
   
   println()
   for (v in interval.last downTo interval.first step 1) {
      print("$v ")
   }
   println()
   
   // [1, 10)
   for (v in interval.first until interval.last) {
      print("$v ")
   }
}
复制代码
1 2 3 4 5 6 7 8 9 10 
10 9 8 7 6 5 4 3 2 1 
1 2 3 4 5 6 7 8 9 
复制代码
  1. 迭代map
fun main() {
   val map = mutableMapOf(
      1 to "zhazha",
      2 to "haha",
      3 to "haha",
      Pair(4, "xixi"),
      Pair<Int, String>(5, "heihei")
   )
   
   map.forEach(fun(m: Map.Entry<Int, String>): Unit {
      println("key = ${m.key}, value = ${m.value}")
   })
   
   for ((key, value) in map) {
      println("key = $key, value = $value")
   }
   
   // java forEach??? 调用的 java Map 里面的 BiConsumer 接口
   map.forEach { key, value ->
      println("key = $key, value = $value")
   }
   
   // Kotlin forEach??? 调用的 kotlin 的 (key, value) -> {} 遍历 Map 方法
   map.forEach { (key, value) ->
      println("key = $key, value = $value")
   }
   
}
复制代码
fun Set<String>.inSet(str: String) = str in this

val set = setOf<String>("1", "2", "3", "4", "a", "b", "c")

println(set.inSet("a"))

// (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
fun isLetter(ch: Char) = ch in 'a'..'z' || ch in 'A'..'Z'

// ch >= '0' && ch <= '9'
fun inNumber(ch: Char) = ch in '0'..'9'
复制代码

kotlin 异常

在kotlin中, 异常可以处理也可以不处理, 也不在函数声明上, 也不用写上throws Exception, 抛出异常时也不需要throw new Exception("xxxx")

fun main() {
   val bufferedReader = BufferedReader(InputStreamReader(System.`in`))
   val number = readNumber(bufferedReader)
   println(number)
}

fun readNumber(reader: BufferedReader): Int? = try {
   val line = reader.readLine()
   Integer.parseInt(line)
} catch (e: NumberFormatException) {
   throw e
} finally {
   reader.close()
}
复制代码
文章分类
后端
文章标签