Kotlin,简直是 Java 的 Pro Max!(笔记2)

本文详细解释了Kotlin中的静态方法、companionobject、类型转换(如List的不可变化)、@JvmStatic注解、顶层方法、静态变量声明、标准函数(let、with、run、apply、use、require)以及lateinit和sealed类的用法和区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

as - 类型转化

静态方法的声明

object 修饰类中所有方法类似 “静态”

companion object 修饰个别方法类似 “静态”

@JvmStatic 修饰一个方法为静态方法

顶层方法为静态

静态变量的声明

object 类中定义

companion object 定义静态变量

顶层定义静态变量

标准函数

let

with

run

apply

also

use(扩展)

require(扩展)

inner - 非静态内部类声明

lateinit 懒加载

sealed 密封类


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 传入参数为密封类时,则分支必须包含其全部子类,否则编译不通过.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

陈亦康

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

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

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

打赏作者

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

抵扣说明:

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

余额充值