简单的入个门, 复杂的操作放在下一章节
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
是FuncDemo01Kt
类的静态方法, 在 kotlin 中被称之为 顶层函数
java调用kotlin顶层函数
只需要如此:
FuncDemo01Kt.funcMax(1, 2)
下面是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关键字 + 空格 + 函数名字(参数名: 参数类型, ...): 函数返回值 {函数体}
kotlin中还有表达式函数体形式, 如下:
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}")
}
表达式是有返回值的语句
1
, -1
, 1 + 1
, listOf(1, 2, 3)
这些都是表达式
还有
{x:Int -> x + 1}
fun(x: Int) { println(x) }
if(x > 1) x else 1
这三个也是表达式
void isStatement(Boolean flag) {
String a = null;
if (flag) {
a = "dive into kotlin";
}
System.out.println(a.toUpperCase());
}
这里存在一个问题. 变量 a
为 null
, 如果不经过 if
语句, 则会在下面变成 null.toUpperCase()
直接报错
如果我们使用 kotlin 的方式重新实现这种方式
fun isStatement(flag: Boolean): Unit {
// 我们会主动接收 if 的返回值
val a = if (flag) "dive into kotlin" else ""
println(a.uppercase())
}
如果我们不写上 else
, idea会报错
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eAA5qm0D-1655378595637)(https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/868486cfe76641608aed938012e3201e~tplv-k3u1fbpfcp-watermark.image?)]
表达式的目的是为了创建新值
如果一个函数的整个函数体都是一个表达式
时, 我们就可以称之为表达式函数体
fun max(a: Int, b: Int) = if (a > b) a else b
表达式函数体中, 可以省略
return
关键字, 加上 等号 和表达式if (a > b) a else b
, 这意味着该函数返回了if 表达式
的 结果
fun printArray(vararg arr: Int) {
for(v in arr) {
println(v)
}
}
需要注意这种用法:
fun main() {
val list = listOf<Int>(1, 2, 3, 4, 5)
printArray(*list.toIntArray())
//
}
kotlin值对象参数默认为 val 修饰(表面上这么认为就行)
Val cannot be reassigned
如果你按住 ctrl
将鼠标放在参数 a
上面将会看到 value-parameter a: Int
字样
放在变量 b
处, 将会看到 val b: Int
这是kotlin值参数类型
kotlin所有函数的参数都是值参数
, 都是不可修改的
但其不影响值参数
当作引用地址
修改在堆中对象的情况
了解值参数这一概念没啥用, 所以当作
val 变量
就行了
如果将值再次赋值给 val 变量
:
在java中将体现为:
具名函数如下:
fun max(x: Int, y: Int): Int {
return x + y
}
匿名函数需要将函数名去除
fun (x: Int, y: Int): Int {
return x + y
}
这种形式需要变量接收, 否则将报错
所以添加一个变量来代表匿名函数:
// 下面的 f 就是函数类型变量
val f1 = fun (x: Int, y: Int): Int {
return x + y
}
// 然后使用 f 变量调用匿名函数
val sum = f1(190, 29)
前面的
fun
不能省略, 否则会出错.
可以使用 lambda 表达式代替匿名函数
val f2 = {x: Int, y: Int -> x + y}
还可以这样用:
比较超前, 不过学过 java 的应该看得懂点
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 ->
val ret = x + y
println("ret = $ret")
ret
}
匿名函数是对象, 通过函数类型 new 出来的对象, 而函数类型仅仅是个类型, 类似于
Int Double Float String Byte Long
等等
在kotlin中凡是同时被 {}
包裹的使用 ->
分割参数和函数体的表达式都可以称之为 lambda 表达式
, 例如: { x, y -> x + y }
// lambda 表达式
val x1 = { x: Int, y: Int -> if (x > y) x else y }
val x2: (Int, Int) -> Int = { x, y -> if (x > y) x else y }
// 匿名函数方式
val y1 = fun(x: Int, y: Int): Int {
return if (x > y) x else y
}
val y2: (Int, Int) -> Int = fun(x: Int, y: Int): Int {
return if (x > y) x else y
}
(Int, Int) -> Int
是函数类型, 他不是对象, 他是一个类型
var f = (Int, Int) -> Int
错误, 类型无法赋值
// kotlin 的初始化, 使用 lambda表达式初始化 f 类型
fun f: (Int, Int) -> Int = { x, y -> x + y }
在学习的过程中, 我发现
fun sum1(x: Int, y: Int) = {x, y -> x + y}
会报错
报错是 “无法推断此参数的类型。 请明确指定。”, 初学者不熟悉 kotlin 语法, 在这里会看的比较懵逼
其本质问题是:
{x, y -> x + y }
是一个对象, 而函数返回值却是Int
, 返回值类型不匹配
代码最终返回值类型是 (???, ???) -> Int
, ???
表示不清楚是什么类型? {x, y -> x + y }
根本就没给出 x 和 y 的类型是什么…
如果不理解, 可以把 =
修改成 return
// 这里报错, 返回值类型是: (???, ???) -> Int
fun sum0(x: Int, y: Int) {
return {x, y -> x + y}
}
idea智能提示应该修改成:
// 现在改成这样: (Int, Int) -> Int, 这样就不会报错了
fun sum1(x: Int, y: Int): (Int, Int) -> Int {
return {x, y -> x + y}
}
是否更加容易理解了呢?
我们还可以像下面这种方式给 lambda 对象的参数添加类型:
fun sum(x: Int, y: Int) = {a: Int, b: Int ->
println("a = $a, b = $b, x = $x, y = $y") // a = 10, b = 20, x = 1, y = 2
x + y
}
修改后, 我们可以这样调用:
sum(1, 2)(10, 20)
解析:
sum(1, 2)
返回一个 lambda 对象, 类型是(Int, Int) -> Int
, 还可以调用lambda函数 传递参数 10 和 20:lambda函数(10, 20)
我们学习过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")
}
}
}
如果不使用内联的话, 字节码翻译出的java代码是这样:
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; // 这是 running 函数的参数
if (x$iv instanceof Integer) {
return Integer.TYPE.getName();
} else if (x$iv instanceof String) {
return String.class.getName();
} else if (x$iv instanceof Long) {
return Long.TYPE.getName();
} else if (x$iv instanceof Float) {
return Float.TYPE.getName();
} else {
return Double.TYPE.getName();
}
}
① 在未使用函数内联的情况下, kotlin的匿名函数(lambda)通常和java一样, 需要创建一个对象, 每遇到匿名函数就需要创建一个新的对象, 导致性能损耗, 但如果使用函数内联, 编译器会在任何需要匿名函数的地方, 直接拷贝函数体的源码过去, 不需要创建对象了
② 泛型的实化
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))
}
MDN对闭包的说明很到位:
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).
闭包是一个函数与引用该函数周围作用域变量绑定在一起的组合
In other words, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.
换句话说, 闭包给了你从一个内部函数访问外部函数作用域的能力, 在 JavaScript 中, 每次创建一个函数都会在函数创建时创建闭包
除了 JavaScript 那段, 闭包概念应该算通用的
Closures - JavaScript | MDN (mozilla.org)
闭包给予了作用域保护, 防止作用域出现相同函数名冲突. 像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
变量这句话错了, 也没错. kotlin 的
val
强调只读性,也就是只有getter
构造器而没有setter
构造器, 不强调是不是final
因为final
是 kotlin 自带的, 任何类、变量和函数不经强调默认都是final
kotlin编译器只生成 get
函数
var
则是可读写的属性, kotlin编译器会生成 get/set
函数
在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字符串模板的使用方式
$变量
直接输出变量的值${表达式}
在花括号内可以写上表达式上面那段字符串操作模板被反编译成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/varfun sum(a: Int, b: Int)
可能是因为它不用get/set
方法. 不过可变参数需要主动声明vararg
, 例如:ListOf
(vararg arr: String)
属性和字段的区别 |
---|
在java中private String name 是成员字段需要另外加上 get/set 方法成为属性 |
在kotlin val/var 声明变量时, 如果使用 val 定义变量, 将被标记成只读属性, 仅生成 get 方法, 不生成set 方法, 如果使用的 var 定义变量, 则生成 set/get 方法 |
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
// get() {
// return height == width
// } // 普通函数体形式
// get() {height == width} // lamdba形式
get() = height == width // 表达式函数体
}
当遇到
kotlin
属性var isDelete
时,kotlin
会生成isDelete
方法和setDelete
方法
import
关键字import关键字在kotlin中可以导入类和顶层函数(在java中叫静态函数)
在 kotlin 中写一个扩展函数(就是把this当作参数传递进去的函数)
注意 this 当作参数的话, 将会被认为是 value-parameter 类型
fun String.lasts(): Char = this[this.length - 1]
而在 kotlin中使用 扩展函数 的方法, val ch = "kotlin".lasts()
在 java 中写成这样:
// 核心代码在这里, 把 调用 lasts 对象当作参数传递进去了, 这步骤由 kotlin 虚拟机完成
public static final Char lasts(String this /* 这个 this就是调用这个方法的对象, 就是前面例子的 "kotlin" */) {
// "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"
}
}
需要注意, 如果枚举类中存在额外的方法或者属性定义, 需要使用 ;
进行隔离
早期的枚举没有逗号和分号, 但定义枚举值比较麻烦, 需要这样:
RED: Color(255, 0, 0)
ORANGE: Color(255, 165, 0)
这样相当的麻烦, 还有很多多余的调用, 比较理想的方式就像我们上面那种RED(255, 0, 0)
然而带来了新的问题, 无法区分枚举值和类方法, 几经周转(加注解或者添加新关键字等等)他们没办法只能使用;
来区分
借助枚举类学习 when
表达式的使用方法
(1) 使用函数表达式体的形式
fun getWarmth() = when (this) {
RED, ORANGE, YELLOW -> "warm"
GREEN -> "neutral"
BLUE, INDIGO, VIOLET -> "cold"
else -> ""
}
(2) 函数体形式
fun getWarmth1(): String {
when (this) {
RED, ORANGE, YELLOW -> return "warm"
GREEN -> return "neutral"
BLUE, INDIGO, VIOLET -> return "cold"
else -> ""
}
}
when
不需要 break
他会默认 break
, when
的最后还有个 else
相当于 switch
的 default
when
的每个分支都有返回值, 最终 when
表达式的返回类型就是所有分支相同的返回类型, 或者公共的父类型.
when
的参数可以忽略
when {
sunny -> library()
else -> study()
}
左边的 sunny
必须是 Boolean
类型
所以只要满足左边表达式的返回值类型是 Boolean
都可以放到 when
中
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
类型
我们还可以使用
when
代替if
表达式
is
智能转换类型有前提变量必须是
val
定义的变量, 否则无法智能转换, 只能使用as表达式
显示的转换
可以使用 as
强制类型, 如果强转不成, 直接报错.
我们还可以使用 as?
, 强转不成, 直接返回 null
println((sum.left as Num).value)
val n = e as Num
在 Java 中我们经常这样:
for(int i = 0; i < 10; i++) {
System.out.println(i);
}
在 kotlin 中变成这样:
for (i in 1..10) {
println(i)
}
上面那种情况1..10
在官网上说通过 rangeTo
函数实现, 能够使用这种方式的类型还需要实现java.lang.Conparable
接口, 比如 String
"a".."z"
就会从 "a"
遍历到 "z"
for(i in 1..10) print(i)
==> 12345678910
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
kotlin
还提供了 step
函数定义迭代的步长
for (i in 1..10 step 2) println(i)
==> "1359"
如果需要使用倒序, 则可以使用 downTo
方法来实现:
for(i in 10 downTo 1 step 2) print(i)
==> 10 8 6 4 2
还可以使用 unitl
函数实现 半开区间
for (i in 1 until 10) { println(i) }
==> 123456789
"a" in listOf("a", "b", "c")
==> true
"kot" in "abc".."xyz"
==> true
上面那个等价于下面这个
"kot" >= "abc" && "abc" <= "xyz"
==> true
"a" !in listOf("a", "b", "c")
for(c in array) {
println(c)
}
for ((index, value) in array.withIndex()) {
println("the element at $index, is $value")
}
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中, 异常可以处理也可以不处理, 也不在函数声明上, 也不用写上throws Exception
, 除非需要主动抛出异常, 则需要throw new Exception()
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()
}
ap.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中, 异常可以处理也可以不处理, 也不在函数声明上, 也不用写上throws Exception
, 除非需要主动抛出异常, 则需要throw new Exception()
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()
}