当前位置: 首页 > news >正文

请简述一下什么是 Kotlin?它有哪些特性?

1 JVM 语言的共性:编译成字节码文件

Kotlin 和 Java 同属于 JVM(Java Virtual Machine)语言,它们的代码最终都会被编译成 JVM 字节码(.class)文件。

编译流程:

  • Kotlin 编译:Kotlin 代码(.kt 文件)—> Kotlin 编译器(kotlinc)—> JVM 字节码(.class 文件)—> JVM 执行;
  • Java 编译:Java 代码(.java)—> Java 编译器(javac) —> JVM 字节码(.class 文件)—> JVM 执行;

虽然 Kotlin 的字节码(.class)文件可以反编译成 Java 代码,但结果可能不够直观。以下是用 Kotlin 实现的单例模式:

object Singleton {
}

反编译成 Java 代码:

public final class Singleton {@NotNullpublic static final Singleton INSTANCE;private Singleton() {}static {Singleton var0 = new Singleton();INSTANCE = var0;}
}

2 语法糖

2.1 扩展
2.1.1 扩展属性(Extension Properties)

扩展属性允许开发者为现有的类添加新的属性,不过这种做法并没有真正的给类添加成员变量,而是提供了自定义的 getter(val) 和 setter(var) 方法。

语法形式为: val ClassName.属性名: 类型

val MutableList<Int>.sum: Intget() = this.sum()fun main() {val numbers = mutableListOf(1, 2, 3, 4, 5)println(numbers.sum)
}

在上面的例子中,MutableList<Int> 是接收者类型,sum 是扩展属性名,get() 是属性的 getter 方法。

2.1.2 扩展函数(Extension Functions)

扩展函数允许开发者为现有的类添加新的函数,而无需对该类进行继承、修改或者使用装饰器模式(一种结构型设计模式,它允许向一个现有的对象中添加新的功能,同时又不改变其结构)。

语法形式为:fun ClassName.函数名(): 类型

fun String.reverseString(): String {return this.reversed()
}fun main() {val str = "Hello"val reversedStr = str.reverseString()println(reversedStr)
}

在上面的例子中,String 是接收者类型,reverseString 是扩展函数名。this 关键字在扩展函数中代指接收者对象。

需要注意的是:扩展函数是静态解析的,这意味着调用扩展函数是由调用函数的表达式类型决定的,而非运行时对象的类型。

场景设定 —— 扩展函数:

open class Animal
class Dog : Animal()// 扩展函数
fun Animal.speak() = println("Animal Speaks")
fun Dog.speak() = println("Dog barks")fun main() {val animal: Animal = Dog()animal.speak() // Animal Speaks
}

场景设定 —— 成员函数:

// 成员函数
open class Animal {open fun speak() = println("Animal Speaks (member)")
}class Dog : Animal() {override fun speak() = println("Dog barks (member)")
}fun main() {val animal: Animal = Dog()animal.speak() // Dog barks (member)
}

结果分析:

  • 扩展函数:根据变量声明类型 Animal 调用 Animal.speak(),忽略实际类型 Dog;
  • 成员函数:根据实际运行时类型 Dog 调用 Dog.speak(),体现多态性;
2.2 Lambda 表达式,函数式编程
2.2.1 Lambda 表达式

Lambda 表达式并不是 Kotlin 的专利,Java 中也可以使用,但是有限制。

在 Java 中,Lambda 表达式主要用于简化实现单一抽象方法的接口(即函数式接口)。这是因为 Java 的系统类型需要明确的类型信息,Lambda 表达式要和特定的函数式接口类型匹配。

例如,在 Android 开发中常见的 setOnClickListener

// Java 示例:匿名内部类实现 OnClickListener
button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 处理点击事件}
});// 使用 Lambda 表达式简化
button.setOnClickListener(v -> {// 处理点击事件
});

只有像 OnClickListener 这种单抽象方法的接口才能使用 Lambda 表达式简化,对于多抽象方法的接口无法实现用。

同样依赖单一抽象方法接口的转换,但语法更加简洁,且对 Java 的兼容性更好:

// Kotlin 的转换 (OnClickListener 是 Java 定义的接口)
button.setOnClickListener {// 处理点击事件
}

Kotlin 同样支持对单抽象方法的接口使用 Lambda 表达式,但更推荐用闭包来实现功能。闭包是一个可以捕获其所在上下文变量的代码块,在 Kotlin 中,闭包可以独立存在,不依赖于特定接口。 例如:

fun doSomething(action: () -> Unit) {action()
}// 调用高阶函数,传入闭包
doSomething {println("执行操作")
}

这里的闭包 { printlin("执行操作") } 不依赖于任何接口,具有更高的灵活性。

Kotlin 闭包的优势:

  1. 更灵活的 Lambda:Kotlin 的 Lambda 可以直接捕获并修改外部变量(非 final 变量),而 Java 要求捕获的变量必须是 final:

    var counter = 0
    button.setOnClickListener {counter++ // 直接修改外部变量(Java 需要通过数组或对象包装)
    }
    
  2. 高阶函数支持:Kotlin 允许函数直接接收 Lambda,无需定义接口:

    // 自定义高阶函数
    fun executeAfterDelay(delay: Long, block: () -> Unit) {Handler(Looper.getMainLooper().postDelayed(block, delay))
    }// 调用
    executeAfterDelay(1000) { println("Hello after 1s")}
    
2.2.2 函数式编程

Java 8 引入的 Stream API(流式编程)为集合操作提供了强大的函数式编程能力,如对集合进行 mapfilterreduce(累积操作)等操作。但在 Android 开发中,由于兼容低版本 Android 系统(Java 8 之前的版本),使用 Stream API 会受到很大的限制:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squareNumbers = numbers.stream().map(n -> n * n).collect(Collectors.toList());
System.out.println(squareNumbers); // [1, 4, 9, 16, 25]

需要注意的是:要在 Android 低版本上运行这段代码,需要额外的配置或使用第三方库。

Kotlin 为集合提供了丰富的扩展函数,功能与 Java Stream API 类似,但无需担心版本兼容性问题。

Kotlin 的集合操作可以直接在 Android 低版本上使用:

val numbers = listOf(1, 2, 3, 4, 5)
val squaredNumbers = numbers.map { it * it }
println(squaredNumbers) // [1, 4, 9, 16, 25]

Kotlin 的集合操作函数可以在任何支持 Kotlin 的 Android 版本上运行,这使得开发者可以更方便地使用函数式编程风格进行开发。

综上所述,Kotlin 在 Lambda 表达式和函数式编程方面提供了更灵活、更易用的特性,并且在 Android 开发中避免了 Java 一些特性的兼容性问题。

2.3 判空语法

Kotlin 提供了多种强大且简洁的判空语法,以增强代码的安全性,避免出现空指针异常(NullPointerException)。

2.3.1 可空类型和非空类型

在 Kotlin 中,类型默认是非空的,这意味着变量不能被赋值为 null。若要允许变量为 null,需要在类型后面加上 ? 来声明为可空类型:

var nonNullableStr: String = "Hello" // 非空,不能赋值为 null
var nullableStr: String? = null // 可空
2.3.2 安全调用操作符(?.

安全调用操作符 ?. 能在对象不为 null 时调用其方法或访问属性,若对象为 null 则直接返回 null,不会抛出空指针异常:

val length: Int? = nullableStr?.length // 若 nullableStr 为 null,length 也为 null
2.3.3 Elvis 操作符(?:

Elvis 操作符 ?. 可在可空表达式为 null 时提供一个默认值:

val lengthOrDefault: Int = nullableStr?.length ?:0 // 若为 null,返回 0
2.3.4 非空断言操作符(!!

非空断言操作符 !! 会将可空类型转换为非空类型,若对象为 null 时则会抛出空指针异常:

val forcedLength: Int = nullableStr!!.length // 慎用!可能引发崩溃
2.3.5 let 函数结合安全调用操作符

可以使用 let 函数结合安全调用操作符,在对象不为 null 时执行一系列操作:

var nullableString: String? = "Hello"
nullalbeString?.let {// 只有当 nullableString 不为 null 时,才会执行这里的代码块println(it.length)
}nullableString = null
nullableString?.let {println(it.length)
}
2.4 默认参数,减少方法重载

在 Kotlin 中,默认参数是一种强大的特性,可以显著的减少方法重载的需求。通过对函数参数设定默认值,可以用单个函数定义覆盖多种调用场景。

当调用该函数时,如果没有为这些参数提供具体的值,就会使用默认值。这样做可以显著减少方法重载的需求,这与 Java 中为了实现不同参数组合而进行方法重载的做法形成了鲜明的对比。

Java 中需要多个重载方法:

public void showToast(String message) {showToast(message, Toast.LENGTH_SHORT);
}public void showToast(String message, int duration) {// 实际逻辑
}

Kotlin 只需要一个函数:

// Kotlin 默认参数
fun showToast(message: String,duration: Int = Toast.LENGTH_SHORT // 默认值
) {}

调用时可以选择性提供参数:

showToast("Hello") // 使用默认 duration
showToast("Hi", Toast.LENGTH_LONG) // 显式指定 duration
2.5 省略了 findViewById

Kotlin 中可以通过 Kotlin Android Extensions 或 View Binding 来省略 findViewById 的手动调用,从而简化视图绑定的代码。

2.5.1 Kotlin Android Extensions(已弃用)

Kotlin Android Extension 是早期 Kotlin 官方提供的插件,它自动为布局文件中的视图生成对应的属性,我们可以直接使用这些属性来访问视图,无需在再调用findViewById

第一步:添加插件,在项目的 build.gradle 文件中应用 kotlin-android-extensions 插件
plugins {id 'com.android.application'id 'kotlin-android'id 'kotlin-android-extensions'
}
第二步:布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewandroid:id="@+id/text_view"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello World!" /><Buttonandroid:id="@+id/button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Click Me" />
</LinearLayout>
第三步:在代码中使用,直接使用视图的 ID 来访问视图
class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 直接使用视图 ID 访问视图text_view.text = "New Text"button.setOnClickListener {// 处理按钮点击事件}}
}

需要注意的是,Kotlin-androi-Extension 已经被弃用,不再推荐使用。官方建议使用 View Binding 来替代它。

2.5.2 View Binding

View Binding 是 Android 官方推出的功能,它为每个布局文件生成一个绑定类,通过这个绑定类可以直接访问布局文件中的视图,避免了 findViewById 的使用。

第一步,开启 View Binding,在 build.gradle 文件中开启 View Binding
android {...viewBinding {enabled = true}
}
第二步,布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewandroid:id="@+id/text_view"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello World!" /><Buttonandroid:id="@+id/button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Click Me" />
</LinearLayout>
第三步,在代码中使用:
class MainActivity : AppCompatActivity() {private lateinit var binding: ActivityMainBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 初始化绑定类binding = ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root) // 设置根视图// 使用绑定类访问视图binding.text_view.text = "New Text"binding.button.setOnClickListener {// 处理按钮点击事件}}
}
http://www.xdnf.cn/news/214237.html

相关文章:

  • C++ 红黑树
  • 第14讲:科研图表的导出与排版艺术——高质量 PDF、TIFF 输出与投稿规范全攻略!
  • Java 基础--运算符全解析
  • Ubuntu搭建 Nginx以及Keepalived 实现 主备
  • ‘WebDriver‘ object has no attribute ‘find_element_by_class‘
  • 咖啡的功效与作用及副作用,咖啡对身体有哪些好处和坏处
  • 什么是缓冲区溢出?NGINX是如何防止缓冲区溢出攻击的?
  • [逆向工程]什么是CPU寄存器(三)
  • Qt开发之C++泛型编程进阶
  • C语言教程(二十五):C 语言函数可变参数详解
  • 机器学习-入门-决策树(1)
  • 大模型微调之LLaMA-Factory 系列教程大纲
  • 面试篇 - LoRA(Low-Rank Adaptation) 原理
  • java每日精进 4.29【框架之自动记录日志并插入如数据库流程分析】
  • C++ 单例对象自动释放(保姆级讲解)
  • 马井堂-区块链技术:架构创新、产业变革与治理挑战(马井堂)
  • python用切片的方式取元素
  • 基于GPT 模板开发智能写作辅助应用
  • 1.PowerBi保姆级安装教程
  • HarmonyOS运动开发:如何监听用户运动步数数据
  • 怎么查自己手机连接的ip归属地:完整指南
  • E2E 测试
  • 在 JMeter 中使用 BeanShell 获取 HTTP 请求体中的 JSON 数据
  • 某建筑石料用灰岩矿自动化监测
  • dify升级最新版本(保留已创建内容)
  • React 第三十五节 Router 中useNavigate 的作用及用途详解
  • 【Java学习】动态代理有哪些形式?
  • Windows服务管理
  • Electron-vite中ELECTRON_RENDERER_URL环境变量如何被设置的
  • 偶然发现Git文件夹非常大,使用BGF来处理Git历史Blob文件