自定义一个打印集合的方法
fun <T> joinToString(
collection: Collection<T>,
separator: String,
prefix: String,
postfix: String
): String {
val stringBuffer = StringBuilder(prefix)
for ((index, value) in collection.withIndex()) {
if (index > 0) {
stringBuffer.append(separator)
}
stringBuffer.append(value)
}
stringBuffer.append(postfix)
return stringBuffer.toString()
}
println(joinToString(list, separator = ",", prefix = "[ ", postfix = " ]"))
println(joinToString(list, separator = ",", "[ ", postfix = " ]"))
list, separator = ",", prefix = "[ ", postfix = " ]"
给参数配上了名字, 然后根据名字传递给相同名字的参数位置上
反着传递参数
配合默认参数值, 跳过默认参数只传递下一个参数
给函数的参数添加默认值
fun <T> joinToString(
collection: Collection<T>,
separator: String = ", ",
prefix: String = "[ ",
postfix: String = " ]"
): String {
val stringBuffer = StringBuilder(prefix)
for ((index, value) in collection.withIndex()) {
if (index > 0) {
stringBuffer.append(separator)
}
stringBuffer.append(value)
}
stringBuffer.append(postfix)
return stringBuffer.toString()
}
separator: String = ", ",
prefix: String = "[ ",
postfix: String = " ]"
使用默认参数值之后, 可以这样调用这种函数
println(joinToString(list, separator = ",", postfix = " ]"))
println(joinToString01(list, ", ", postfix = " ]"))
我们可以选择省略掉默认参数值
函数默认参数就是生成一个叫running$default
的函数, 在函数体内添加 一堆 if 语句赋值默认值
在 java 中没有默认参数值, 不过我们可以用
@JvmOverloads
, 让一个函数生成重载函数
@JvmOverloads
fun <T> joinToString03(
collection: Collection<T>,
separator: String = ", ",
prefix: String = "[ ",
postfix: String = " ]"
): String {
val stringBuffer = StringBuilder(prefix)
for ((index, value) in collection.withIndex()) {
if (index > 0) {
stringBuffer.append(separator)
}
stringBuffer.append(value)
}
stringBuffer.append(postfix)
return stringBuffer.toString()
}
他会生成这些函数
而java中每个函数的默认参数值都被省略
如果函数名存在关键字或者别的不能使用的特殊字符, 可以使用 反引号
fun `fun`() {
println("ffffff")
}
反引号函数还可以使用在, 只有 kotlin 才能拥有 java 不能调用的情况下
kotlin没有静态
static
关键字, 也就意味着, 没有显示的static
函数或者属性, 很多人都说有, 那是伴生对象(companion object
), 对, 那个让人看起来好像是代替java的static
对象的, 不过本质上, 他仅仅定义了个静态类和一个静态对象罢了, 其中伴生对象的函数还是非静态的, 不过借助静态的伴生对象(静态单例对象)让其看起来像是静态函数
private class User01 private constructor(val nickName: String) {
companion object {
fun newSubscribingUser(email: String): User01 {
return User01(email.substringBefore('@'))
}
}
}
反编译后
final class User01 {
public static final Companion Companion = new Companion();
private final String nickName;
private User01(String nickName) {
this.nickName = nickName;
}
public final String getNickName() {
return this.nickName;
}
public static final class Companion {
private Companion() {
}
public final User01 newSubscribingUser(String email) {
return new User01(StringsKt.substringBefore$default((String)email, (char)'@', null, (int)2, null), null);
}
}
}
而真正的静态属性和静态函数在kotlin中被叫做顶层属性/函数, 只不过如果没有
@file:JvmName("PrintCollection")
特意的注释文件名, 则默认顶层属性/函数属于文件名+Kt
顶层函数和顶层属性把定义的位置放在文件中, 而不是定义在类中, 该函数或者属性属于包
翻译成java代码, 该属性通常存在于 Kt 结尾的文件中
在java源码中看出顶层函数都是 static 函数
顶层函数反编译成 java 后
public static final String joinToString(Collection collection, String separator, String prefix, String postfix)
如果要在 java 中调用这种顶层函数
import java.util.List;
// 导入了 kotlin 的文件名(文件名 + Kt)
import function.PrintCollectionDemoKt;
public class Test01 {
public static void main(String[] args) throws Exception {
List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
String string = PrintCollectionDemoKt.joinToString(list, ", ", "[ ", " ]");
System.out.println(string);
}
}
如果不要以 文件名 + Kt 的形式在java中调用, 也可以在 kotlin 的文件顶上, 加上
@file:JvmName("PrintCollection")
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VTwd9FaK-1655378724821)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7fd476519e574ee4a09513721d47f1fa~tplv-k3u1fbpfcp-watermark.image)]
这里只针对顶层函数, 而不是 类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V4gwpLYB-1655378724827)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e57831ba0c034ba3854eb95a19bf5564~tplv-k3u1fbpfcp-watermark.image?)]
顶层属性就是在文件顶层的属性, 不属于kotlin能看到的任何类中, 它属于包
kotlin顶层函数/顶层属性都属于全局作用域
var count = 0
fun main() {
for (i in 1..100) {
count++
}
println(count)
}
反编译成java代码看看:
public final class TopPropertiesDemoKt {
// 看这里, static
private static int count;
public static final int getCount() {
return count;
}
public static final void setCount(int var0) {
count = var0;
}
public static final void main(String[] args) {
int var1 = 1;
for(byte var2 = 100; var1 <= var2; ++var1) {
int var10000 = count++;
}
var1 = count;
System.out.println(var1);
}
}
如果顶层属性是 val
定义的, 那么只会生成 get
方法, 我们就不能进行任何的写操作了, java源码是public static final int count
, 他是一个常量
Ps: 在kotlin中
static
不再是关键字
var 定义的顶层属性, 如果setter 需要自定义, 可以这样:
var count = 0
private set
fun setCount(c: Int) {
count = c
}
不仅仅是顶层属性可以这样, 普通类中的属于也可以, 但是主构造函数中的属性不行, 需要放在类体内定义
const
+ val
修饰 顶层属性, 将会被解析成常量
var count = 0 // 顶层属性
const val INT_MAX = Int.MAX_VALUE // 常量
val INT_MIN = Int.MIN_VALUE
var INT_MID = Int.MIN_VALUE + Int.MAX_VALUE
fun main(args: Array<String>) { }
反编译后
private static int count;
public static final int INT_MAX = Integer.MAX_VALUE;
private static final int INT_MIN = Integer.MIN_VALUE;
private static int INT_MID = -1;
// var count = 0 // 顶层属性
public static final int getCount() { return count; }
// var count = 0 // 顶层属性
public static final void setCount(int var0) { count = var0; }
// val INT_MIN = Int.MIN_VALUE
public static final int getINT_MIN() { return INT_MIN; }
// var INT_MID = Int.MIN_VALUE + Int.MAX_VALUE
public static final int getINT_MID() { return INT_MID; }
// var INT_MID = Int.MIN_VALUE + Int.MAX_VALUE
public static final void setINT_MID(int var0) { INT_MID = var0; }
(1) 常量不能放在类的内部, 他只能属于顶层
或者伴生对象
(有点像静态代码块
)
const val INT_MIN = Int.MIN_VALUE
class Test {
companion object {
const val INT_MAX = Int.MAX_VALUE
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-azG0tFU7-1655378724829)(https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/df60ddfda9d44f59982bc6828d7eb831~tplv-k3u1fbpfcp-watermark.image)]
扩展包含 扩展函数
和 扩展属性
, 第三方库中的类通常不让修改, 但可以使用 扩展函数(扩展属性)为第三方库添加新的函数和属性, 不需要修改库源码
扩展函数对类进行扩展, 不需要修改类中任何源码, 仅需要给函数添加 receiver 便可
库中代码无法修改, 此时可以通过扩展函数扩展该库特定类的功能
非常简单, 扩展函数说到底还是函数, 可以按照普通函数写
fun last01(): Char { return xxx }
receiver
)fun String.last01(): Char { return this[this.length - 1] }
至此扩展函数写完了
该扩展函数给 String
类添加了 last01
函数
fun main() {
println("${"zhazha".last01()}")
}
扩展函数的本质: fun String.print(): Char
==> fun print(str: String): Char
对扩展函数的本质是:
fun print(str: String): Char
, 记住这点, 后续遇到的任何问题都可以解释
反编译代码:
private final char last01(String $this$last01) {
return $this$last01.charAt($this$last01.length() - 1);
}
从 String.last01(): Char
变成了 char last01(String this)
, 这便是 kotlin 编译器为扩展函数做的处理
因此可如下使用:
@Test
public final void test01() {
println("${last01("zhazha")}")
}
此后遇见扩展函数便可看成: Int.xxx()
==> xxx(this: Int)
由此推导: Int() -> Char <==> (Int) -> Char
可相互转换使用
fun String.last01(): Char {
return this[this.length - 1]
}
fun main() {
val vLast: (String) -> Char = String::last01
vLast("zhazha")
val extLast: String.() -> Char = vLast
"zhazha".extLast()
}
String.() -> Char
和 (String) -> Char
相互转换
注意:扩展函数可以以
String::last01
如此形式获取扩展函数引用
如若出现扩展函数与扩展属性同名时,
String::last01
默认引用成员函数
效仿
with
标准函数复写一个属于自己的with
扩展函数
参考标准库代码如下:
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
可以效仿模拟出两种写法:
private fun <A, B> A.with01(block: A.() -> B) = this.block()
private fun <A, B> A.with02(block: (A) -> B) = block(this)
@Test
fun test02() {
println("zhazha".with01 { // this ->
this.length
})
println("zhazha".with02 { // it ->
it.length
})
}
依据前面的总结, 在此处还可认为: A.() -B
与 (A) -> B
有着相同的效果
只不过使用的方式不同, 前者可以使用 this
后者 使用it
扩展函数的调用实际上不走底层java多态那一套, 是编译器在操作
open class Shape
class Rectangle: Shape() {}
class Triangle: Shape() {}
private fun Shape.getName() = "Shape = ${s.javaClass}"
private fun Rectangle.getName() = "Rectangle"
private fun Triangle.getName() = "Triangle"
private fun printName(s: Shape) = println(s.getName())
@Test
fun test01() {
val s = Shape()
printName(s)
val r = Rectangle()
printName(r)
val t = Triangle()
printName(t)
}
Shape
Shape
Shape
要记住, 扩展函数的原型是这样:
private fun getName(s: Shape) = "Shape class = ${s.javaClass}"
private fun getName(r: Rectangle) = "Rectangle"
private fun getName(t: Triangle) = "Triangle"
private fun printName(s: Shape) = println(getName(s))
在执行这段代码fun printName(s: Shape) = println(getName(s))
时, getName(s)
调用的是 fun getName(s: Shape)
这个函数
这是简单的函数调用不是对象的方法调用
对象的方法调用才有多态, 函数调用不存在 虚函数指针
和虚函数表
, 对象才有
除非代码改成这样:
open class Shape {
open fun getName(): String = "Shape"
}
class Rectangle: Shape() {
override fun getName(): String = "Rectangle"
}
class Triangle: Shape() {
override fun getName(): String = "Triangle"
}
private fun printName(s: Shape) = println(s.getName())
所以不要被扩展函数的 s.getName()
误导了, 其本质上是getName(s)
是函数调用
类对象的 s.getName()
才会有多态
当类中存在的一个函数和扩展函数有这相同的函数签名, 那么kotlin编译器会优先选择成员函数, 如果扩展函数和成员函数的函数签名不同时, 则不会收到任何影响
class Example {
fun printFunctionType() = println("class Method")
}
fun Example.printFunctionType() = println("extension method")
fun main(args: Array<String>) {
val example = Example()
example.printFunctionType() // class Method
}
Any?
给所有对象添加扩展函数)fun Any?.toString(): String = if (this == null) "null" else toString()
// 上面这段代码可以简化, 看来我kotlin能力还是不行, 想到了可以用 ?: 表达式, 可是脑抽了, 用了 if 表达式, 上了 idea 才知道还能这样写, 失策失策~~~
fun Any?.toString(): String = this?.toString() ?: "null"
Any?
是 kotlin 中所有类的父类, 给 Any?
添加扩展函数则是给所有类添加了一个函数
匿名扩展函数如何定义? |
---|
函数? |
fun print(): Char |
匿名? |
fun (): Char |
扩展? |
fun receiver.(): Char |
val c: String.() -> Char = fun String.(): Char = this[this.length - 1]
"zhazha".c()
可以变成:
val c: String.() -> Char = { this[this.length - 1] }
再加强一点点, 把他变成参数呢? 匿名扩展函数类型参数
fun last03(str: String, last: String.() -> Char) {
last(str)
str.last()
}
fun main () {
val toC: String.() -> Char = fun String.(): Char = this[this.length - 1]
"zhazha".toC()
last03("zhazha", fun String.(): Char = this[this.length - 1])
}
java源码:
public static final void last03(String str, Function1<? super String, Character> last) {
// ...
}
Function1 toC2 = main.toC.1.INSTANCE;
toC2.invoke((Object)"zhazha");
ExtensionFuncPropertyDemoKt.last03("zhazha", (Function1<? super String, Character>)((Function1)main.1.INSTANCE));
匿名扩展函数对象不论作为变量还是参数都被kotlin定义成了 Function1
记住仅仅是对扩展函数的 变量 和 参数, 和定义的扩展函数无关
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SugbiLkt-1655378724832)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cfd1c1148b62401092e3ecf507264708~tplv-k3u1fbpfcp-watermark.image)]
Function1
接口, 看到 in
和 out
了么? 有进, 有出. p1 进
, R 出
如果有学过 java 的话 , 你就把
last
当作接口Function1
的匿名对象(Function1
的子类), 而匿名对象里面有个invoke
函数
上面那段代码还可以这样写
fun last03(str: String, last: String.() -> Char) {
str.last()
}
它既是的匿名对象 也是 扩展函数, 这点需要注意
回到 last03
函数的调用
fun last03(str: String, last: String.() -> Char) {
str.last()
}
它的调用:
last03("zhazha", fun String.(): Char = this[this.length - 1])
第二个参数是 匿名扩展函数, 那要怎么把第二个参数变成 传递 lambda 呢?
在 kotlin 中, lambda 表达式是以 {}
标识作用域, 使用 ->
区分 参数 和 函数体 的表达式, 所以先写上 {}
发现idea报错提示
你听了它的话 last03
函数声明就变成:
fun last(str: String, param: String.() -> Unit) = str.param()
从 Char
变成 Unit
然后函数调用就变成这样:
last("xixi") {}
懂了吧? 改回原先的代码, 试试返回个 Char 看看
last("xixi") { 'a' }
没报错
fun last(str: String, param: String.() -> Char) = str.param()
fun main(args: Array<String>) {
last("xixi") { // this: String // 这里隐藏着一个 this
this.last()
}
}
★ 总结: 这说明了
String.() -> Char
转成 lambda 表达式的话, 可以 不用管 接收者 是怎样, 相当于写一个() -> Char
要怎么转成lambda就行, 只不过 lambda 作用域内多了个this
, 该对象由 我们主动以str.param()
形式调用,被 kotlin 编译器识别注入了receiver
罢了
如果上面的解释看不懂, 理解不了
我们可以将其还原成: fun last(str: String, param: String.() -> Char) = str.param()
这样我们可以这么使用:
last("xixi") { it -> // 这是自带的
it.last()
}
匿名扩展函数
String.(): Char
也就是(String): Char
它是一个属性, 有 get/set 方法, 没有字段(field)
既然是扩展的属性, 核心是属性
(1) 先写个字段
val count: Int = 0
(2) 加上扩展
这里的扩展我选择 List
val List<Int>.count: Int
(3) 最后加上自定义访问器(get/set)
由于这里是 val 修饰, 所以只有 get 没有 set 访问器
val List<Int>.count: Int
get() = this.size
完成
val list = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
println(list.count)
翻译成java源码
public final class ExtensionPropertiesDemoKt {
public static final int getCount(List<Integer> count) {
return count.size();
}
public static final void main(String[] args) {
Object[] arrobject = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
List list = CollectionsKt.listOf((Object[])arrobject);
int n = ExtensionPropertiesDemoKt.getCount(list);
System.out.println(n);
}
}
学完扩展函数, 再学扩展属性就简单过下完事
你可以认为扩展函数/属性就是往函数或者属性的作用域中添加一个receiver
对象
比如:
fun String.first(): Char = this[0]
就是往一个 first
的函数作用域内添加一个 String
类型的对象
接着你就可以在该函数的作用域中使用 String
对象
上面的 in、step、downTo和until
这些可以不通过点号调用的函数, 就是传说中的中缀表达式
infix fun <A, B> A.to(that: B): Pair<A, B>
A to B
==> A + 中缀表达式名字 + B
中缀表达式必须满足:
可以自己实现一个,比如 函数名叫 vs
class Score(val name: String, var score: Double) { }
private infix fun Score.vs(other: Score): String = when {
this.score > other.score -> "congratulations!!! ${this.name}: ${this.score} win!!!"
this.score == other.score -> "${this.name} and ${other.score} have the same score!!!"
else -> "congratulations!!!${other.name}: ${other.score} win!!!"
}
@Test
fun test() {
val xiaoMing = Score("xiangming", 98.5)
val xiaoHong = Score("xiaoHong", 99.0)
println(xiaoMing vs xiaoHong)
}
还可以这么写
class Score(val name: String, var score: Double) {
infix fun vs(other: Score): String = when {
this.score > other.score -> "congratulations!!! ${this.name}: ${this.score} win!!!"
this.score == other.score -> "${this.name} and ${other.score} have the same score!!!"
else -> "congratulations!!!${other.name}: ${other.score} win!!!"
}
}
fun main() {
val xiaoMing = Score("xiangming", 98.5)
val xiaoHong = Score("xiaoHong", 99.0)
println(xiaoMing vs xiaoHong)
}
中缀表达式还支持函数调用方式调用, 只需要:
xiaoMing.vs(xiaoHong)
经常会看到 我们在遍历 map 的时候会这样:
for((index, value) in map) {
println("index = ${index}, value = ${value}")
}
不对, 这是解构,不是中缀, 虽然他有
toPair
函数
vararg
fun arrOf(vararg arr: Int): Array<Int> {
return arr.toTypedArray()
}
val arr = arrOf(1, 2, 3)
arr.forEach { println(it) }
我们可以使用 * 星号
来传入外部变量作为可变参数的变量
val arr = arrOf(1, 2, 3)
arrOf(*arr.toIntArray())
trimMargin
带边界的"""zhazha"""
这就是三重引用字符串
fun parsePath(path: String) {
// 文件目录
val directory = path.substringBeforeLast("""""")
// 文件名+扩展名
val fullName = path.substringAfterLast("""""")
// 获取文件名
val fileName = fullName.substringBefore(""".""")
// 获取扩展名
val extension = fullName.substringAfter(""".""")
println("文件目录: $directory \nfullName: $fullName \n文件名: $fileName \n扩展名: $extension")
}
fun parsePathRegex(path: String) {
val regex = """(.+)\(.+).(.+)""".toRegex()
val matchResult = regex.matchEntire(path)
if (null != matchResult) {
val (directory, fileName, extension) = matchResult.destructured
println(matchResult.destructured.toList().toString())
println("文件目录: $directory \nfileName: $fileName\n扩展名: $extension")
}
}
fun main(args: Array<String>) {
val path = """D:\programs\codes\java\autoShutdown\src\test\java\com\zhazha\test\FooBar.java"""
parsePathRegex(path)
}
被包围的字符串, 保留换行符, 各种奇奇怪怪的符号
val str = """
ddddddddd
ggg
hhhhhhhhh
wwwwwwwww
"""
println(str)
ddddddddd
ggg
hhhhhhhhh
wwwwwwwww
去除每一行前面共同的缩进
str = """ ddddddddd
ggg\n
hhhhhhhhh
wwwwwwwww""".trimIndent()
println(str)
ddddddddd
ggg\n
hhhhhhhhh
wwwwwwwww
去除边界前缀之前的所有字符
str = """ .ddddddddd
.ggg\n
.
.hhhhhhhhh
.wwwwwwwww""".trimMargin(".")
println(str)
ddddddddd
ggg\n
hhhhhhhhh
wwwwwwwww
trimMargin
函数默认使用|
做边界前缀, 上面代码使用的.
做边界前缀
在函数中写上函数
fun saveUser(user: User) {
fun validate(user: User, value: String, fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException("can't save user ${user.id}: empty $fieldName")
}
}
validate(user, user.name, "name")
validate(user, user.address, "address")
println(user)
}
反编译成 java
public static final void saveUser(User user) {
Intrinsics.checkNotNullParameter((Object)user, (String)"user");
LocalFuncDemo01Kt.saveUser$validate(user, user.getName(), "name");
LocalFuncDemo01Kt.saveUser$validate(user, user.getAddress(), "address");
boolean bl = false;
System.out.println(user);
}
private static final void saveUser$validate(User user, String value, String fieldName) {
CharSequence charSequence = value;
boolean bl = false;
if (charSequence.length() == 0) {
throw new IllegalArgumentException("can't save user " + user.getId() + ": empty " + fieldName);
}
}
hhh
wwwwwwwww
`trimMargin`函数默认使用`|`做边界前缀, 上面代码使用的`.`做边界前缀
## 局部函数和扩展
### 什么是局部函数?
在函数中写上函数
```kotlin
fun saveUser(user: User) {
fun validate(user: User, value: String, fieldName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException("can't save user ${user.id}: empty $fieldName")
}
}
validate(user, user.name, "name")
validate(user, user.address, "address")
println(user)
}
反编译成 java
public static final void saveUser(User user) {
Intrinsics.checkNotNullParameter((Object)user, (String)"user");
LocalFuncDemo01Kt.saveUser$validate(user, user.getName(), "name");
LocalFuncDemo01Kt.saveUser$validate(user, user.getAddress(), "address");
boolean bl = false;
System.out.println(user);
}
private static final void saveUser$validate(User user, String value, String fieldName) {
CharSequence charSequence = value;
boolean bl = false;
if (charSequence.length() == 0) {
throw new IllegalArgumentException("can't save user " + user.getId() + ": empty " + fieldName);
}
}
没找到应用场景, 看起来也挺麻烦的, 目前对我来说没啥用