目录
companion object 修饰个别方法类似 “静态”
as - 类型转化
例如 List 从 可变到不可变.
val a = mutableListOf(1, 2, 3)
val b = a as List<Int>
静态方法的声明
object 修饰类中所有方法类似 “静态”
和 Java 一样通过类名就可进行对方法的调用,但其并非是真正的静态方法.
object Test {
fun test() {
println("我并非是真的静态方法~")
}
}
fun main() {
Test.test()
}
对应的 Java 文件如下:
实际上,这只是 Kotlin 提供的语法糖,通过静态代码块初始化内部持有的静态 Test 对象.
companion object 修饰个别方法类似 “静态”
如果我们只想让某些方法变成类似静态方法,就可以通过 companion object 实现
class Test {
companion object {
fun test1() {
println("我是静态的")
}
}
fun test2() {
println("我是非静态的")
}
}
fun main() {
Test.test1()
}
Java 如下:
public final class Test {
@NotNull
public static final Companion Companion = new Companion((DefaultConstructorMarker)null);
public final void test2() {
String var1 = "我是非静态的";
System.out.println(var1);
}
public static final class Companion {
public final void test1() {
String var1 = "我是静态的";
System.out.println(var1);
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
public final class TestKt {
public static final void main() {
Test.Companion.test1();
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
实际上是 Test 类内部存在一个 Companion 的静态内部类,而在 Test 中又 new 了一个 Companion 对象,并修饰为 static.
因此调用的时候是通过 Test 类名调用其中的静态对象 Companion,在该对象调用内部的非静态方法 test1().
@JvmStatic 修饰一个方法为静态方法
要想使用真正的静态方法,必须要通过 @JvmStatic,并且其注解只能在 companion object 内部的方法上.
public final class Test {
@NotNull
public static final Companion Companion = new Companion((DefaultConstructorMarker)null);
public final void test2() {
String var1 = "我是非静态的";
System.out.println(var1);
}
@JvmStatic
public static final void test1() {
Companion.test1();
}
public static final class Companion {
@JvmStatic
public final void test1() {
String var1 = "我是静态的";
System.out.println(var1);
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
可以发现有两个 test1() 方法,一个是 Test 类的 test1 静态方法,一个是静态内部类中的非静态方法,调用时是通过调用 Test 中的静态方法,进而在静态方法内部继续调用静态内部类中的非静态方法.
这样设计主要是为了兼容 Java 的调用. 如果 Java 和 Kotlin 混写,需要在 Java 代码中调用到真正的静态方法,才需要使用该注解,否则没必要.
顶层方法为静态
在 Kotlin 文件中的类外直接写变量会变成静态的,方法也是如此.
创建 kt 文件,命名为 Solution,然后直接写一个 test 方法,如下:
fun test() {
}
对应的 Java 代码如下:
public final class SolutionKt {
public static final void test() {
}
}
静态变量的声明
object 类中定义
Kotlin 如下
object Solution {
val aaa = 1
}
对应 Java 如下:
public final class Solution {
private static final int aaa;
@NotNull
public static final Solution INSTANCE;
public final int getAaa() {
return aaa;
}
private Solution() {
}
static {
Solution var0 = new Solution();
INSTANCE = var0;
aaa = 1;
}
}
可以看到,这里的 aaa 也是通过静态代码块初始化的.
如果 Java 想要调用该类中的静态变量 aaa,需要通过 public 的 instance 实例才能调用到,如果在 Java 中想直接调用,可以通过 const 来解决.
object Solution {
const val aaa = 1
}
对应的 Java 如下:
public final class Solution {
public static final int aaa = 1;
@NotNull
public static final Solution INSTANCE;
private Solution() {
}
static {
Solution var0 = new Solution();
INSTANCE = var0;
}
}
可以看出,通过 const 直接将 aaa 修饰成 public.
companion object 定义静态变量
和上面的 object 定义几乎没区别.
class Solution {
companion object {
val aaa = 1
}
}
对应的 Java 代码:
public final class Solution {
private static final int aaa = 1;
@NotNull
public static final Companion Companion = new Companion((DefaultConstructorMarker)null);
public static final class Companion {
public final int getAaa() {
return Solution.aaa;
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
另外 const 修饰后可在 Java 中直接调用,没有 const 在 Java 中需要借助静态对象获取.
顶层定义静态变量
在 Solution.kt 中直接定义全局变量
val aaa = "hello"
对应 Java 如下:
public final class SolutionKt {
@NotNull
private static final String aaa = "hello";
@NotNull
public static final String getAaa() {
return aaa;
}
}
标准函数
let
let 是一个函数,提供了函数式 API 接口,会将调用者作为参数传递到 Lambda 表达式,调用之后会立马执行 Lambda 表达式的逻辑.
aaa.let { it -> // it 就是 aaa(调用者)
//执行业务逻辑
}
作用:主要用于对象辅助判空操作
fun test(name: String?) {
//不为空才执行内部逻辑
name?.let {
println("name: $it") //it 就是 name
}
}
fun main() {
test(null)
}
with
with 用来接收两个参数,一个是任意类型,另一个是 Lambda 表达式,第一个参数会传给 Lambda 使用.
Lambda 内部执行的方法都是 with 的第一个参数所提供的.
Lambda 的最后一行代码会当成返回值返回.
class Test {
fun test1() {
println("test1")
}
fun test2() {
println("test2")
}
}
fun main() {
val result = with(Test()) {
//this 就表示 obj,一般省略 this
test1()
test2()
"ok"
}
println(result)
}
作用:主要用于简化对一个对象中多个方法的调用,避免在每次调用方法时都重复写对象名. 使用场景通常是需要对一个对象进行多个操作,
例如如下场景可读性提高了不少
data class Student(
val name: String,
val age: Int,
val gender: Int
)
fun main() {
val student = Student("cyk", 18, 1)
val result = with(student) {
println("name: $name, age: $age, gender: $gender")
"ok"
}
}
run
run 与 with 的使用场景类似,run 可以直接在任意对象上调用,而 with 需要显式的传递参数才能进行调用.
data class Student(
val name: String,
val age: Int,
val gender: Int
)
fun main() {
val student = Student("cyk", 18, 1)
val result = student.run {
println("name: $name, age: $age, gender: $gender")
"ok"
}
}
如果说 with 和 run 到底有什么使用场景上的区别,那么就是:
- with 强调对某个对象进行多次操作,它更像是为某个对象创建一个临时的作用域,用于减少重复引用对象名
- run 适用于在对象上执行某些操作,并返回操作的结果。因为是扩展函数,所以它常用于链式调用,或在表达式中进行某种计算。
run 实际上也经常这样玩(和 with 就区别开了):
val result = run {
val a = 1
val b = 2
a + b
}
apply
apply 和 run 相似. 不同的是必须要要在对象上调用,并且返回值是调用对象本身.
val list = listOf("beef", "fish", "egg")
//吃东西
val result = StringBuilder().apply {
append("开始吃")
list.forEach { str ->
append(str)
}
append("吃完了")
}
also
返回值是对象本身,作为 it 被调用.
适合在操作对象时执行一些副作用(例如,日志记录、调试).
fun main() {
val cmd = ResendAccountOcCmd().also {
println("Create person: $it")
}
}
上述提高了代码的可读性,表示这个日志信息和 cmd 是绑定关系
use(扩展)
use 是一个扩展函数,它是用于自动关闭实现了 Closeable 接口的资源的。
use 函数确保在读取完输入流和写入完输出流后,它们都会被自动关闭,而无需手动调用close() 方法。
private fun clientHandler(client: Socket) {
println("客户端上线: address: ${client.inetAddress}, port: ${client.port}")
client.getInputStream().use { inputStream ->
client.getOutputStream().use { outputStream ->
Scanner(inputStream).use {
//读取客户端请求...
}
PrintWriter(outputStream).use {
//返回服务器响应...
}
}
}
}
Ps:InputStream、OutputStream、Scanner、PrintWriter 都是 Closeable 的子类,因此它们都有 user 函数.
require(扩展)
require 的作用类似于断言,用于确保给定的条件为真,如果条件不满足,则 require 会抛出 IllegalArgumentException 异常.
a)require 函数定义如下:
fun require(condition: Boolean, lazyMessage: () -> Any): Unit
- condition:要检查的条件,如果条件为 false,则抛出异常。
- lazyMessage:一个 lambda 表达式,用于生成异常消息。当 condition 为 false 时,会调用该 lambda 表达式生成异常消息。
b)使用如下:
fun divide(a: Int, b: Int): Int {
require(b != 0) { "除数不能为零!" }
return a / b
}
上述代码中,如果 b 为 0,则会抛出带有指定信息的异常.
inner - 非静态内部类声明
inner 用于修饰内部类为非静态的
Kotlin 中内部类默认是静态的,这意味在着他们没有外部类实例的隐式调用,减少了内存泄露的可能性. 然而,如果一定要在内部类中访问外部类的成员,就可以使用 inner 关键字来声明内部类,使其变为非静态的.
Ps:为什么静态内部类可以减少内存泄露的可能?因为静态内部类无法持有外部类的隐式调用. 换言之,如果一个内部类不是静态的,那么他会持有外部类的隐式调用,也就意味着,只要内部类实例存在,外部类的实例就不会被垃圾回收器回收.
例如如下示例,Student 中调用就可以拿到 name,而 Teacher 中则不可以.
class Human {
private val name = 1
inner class Student {
fun test () {
println("name: $name") //正确
}
}
class Teacher {
fun test() {
println("name: $name") //报错
}
}
}
Kotlin 示例如下:
class Human {
inner class Student {
}
class Teacher {
}
}
对应的 Java 如下:
public final class Human {
public final class Student {
}
public static final class Teacher {
}
}
lateinit 懒加载
a)写 Kotlin 的时候可能会常常遇到一个问题:“ 就是有时候我们并不想给某个变量在声明的时候就进行初始化,此时只能给这个对象赋值为 null,又由于 Kotlin 的空检查,类型必须加上 ? 才能编译通过,很不方便.
如下代码:
fun test(): String {
var result: String? = null
if(true) {
result = "yes"
} else {
result = "no"
}
return result
}
想要解决上述问题,就需要使用 lateinit 关键字:
fun test(): String {
lateinit var result: String
if(true) {
result = "yes"
} else {
result = "no"
}
return result
}
Ps:上述代码中,若函数结束还未初始化 result,就会抛出 UninitializedPropertyAccessException 异常
b)另外,Kotlin 提供了语法检查此对象是否有初始化
lateinit var result: String
fun test(): String {
if(!::result.isInitialized) { //未初始化
result = "yes"
} else { //已初始化
result = "no"
}
return result
}
此处的 :: 为固定写法,记住就行.
sealed 密封类
a)先来看一个栗子:
定义 result 接口
interface Result
class Success(val code: Int): Result
class Failure(val code: Int): Result
方法如下:
fun getResultCode(result: Result) = when(result) {
is Success -> {
println("ok")
result.code
}
is Failure -> {
println("非法 code: ${result.code}")
result.code
}
else -> throw IllegalArgumentException()
}
由于 Kotlin 的 when 特性,else 必须写,但是上述有些情况 else 纯属多余.
想要解决上述问题,需要借助 sealed,密封类以及子类只能定义在同一个文件的顶层位置,不可嵌套在其他类中.
b)使用密封类修改上述代码:
sealed class Result
class Success(val code: Int): Result()
class Failure(val code: Int): Result()
fun getResultCode(result: Result) = when(result) {
is Success -> {
println("ok")
result.code
}
is Failure -> {
println("非法 code: ${result.code}")
result.code
}
}
此时就可以去掉 else,当 when 传入参数为密封类时,则分支必须包含其全部子类,否则编译不通过.