maven建立项目
- 建立项目指令
shell
mvn archetype:generate "-DarchetypeArtifactId=maven-archetype-quickstart" "-DinteractiveMode=true"
- 填写项目信息
shell
Define value for property 'groupId': com.wnan [包名]
Define value for property 'artifactId': Demo [项目名]
Define value for property 'version' 1.0-SNAPSHOT: [版本号]
Define value for property 'package' com.wnan: crykt [二级包名]
Demo
│ pom.xml
└─src
├─main
│ └─java
│ └─crykt
│ App.java
└─test
└─java
└─crykt
AppTest.java
- 配置pom文件
xml
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>21</maven.compiler.release>
<!-- Kotlin 稳定版 2.2.21(Maven仓库可正常下载) -->
<kotlin.version>2.2.21</kotlin.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
依赖:
xml
<dependencies>
<!-- Kotlin 标准库(必须) -->
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
<!-- Kotlin 反射(可选,你之前加了就保留) -->
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
插件:
xml
<!-- 🔴 核心:Kotlin Maven 编译插件(你之前完全没加!) -->
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/main/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
<execution>
<id>test-compile</id>
<goals>
<goal>test-compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/test/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
</executions>
</plugin>
<!-- 🔴 必须:让 maven-compiler-plugin 配合 Kotlin 插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<executions>
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<execution>
<id>default-testCompile</id>
<phase>none</phase>
</execution>
<execution>
<id>java-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>java-test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
对于vscode需要重建索引: 在 VS Code 按:Ctrl + Shift + P 输入 → Reload Window
创建和运行第一个程序
创建一个名为 hello.kt 文件,代码如下:
kotlin
fun main(args: Array<String>) {
println("Hello, World!")
}
使用 Kotlin 编译器编译应用:
shell
kotlinc hello.kt -include-runtime -d hello.jar
- -d: 用来设置编译输出的名称,可以是 class 或 .jar 文件,也可以是目录。
- -include-runtime : 让 .jar 文件包含 Kotlin 运行库,从而可以直接运行。
包声明
代码文件的开头一般为包的声明:
kotlin
package com.wnan.main
import java.util.*
fun test() {}
class Runoob {}
kotlin源文件不需要相匹配的目录和包,源文件可以放在任何文件目录。就相当于一个命名空间
常量与变量
可变变量定义:var 关键字,
kotlin
var <标识符> : <类型> = <初始化值>
不可变变量定义:val 关键字,不可以修改指向的引用,引用或值只能被赋值一次,且必须在初始化阶段完成。
kotlin
val <标识符> : <类型> = <初始化值>
// 这种情况也不会被允许,会报错
val weight: Double? = null
weight = weight?.toDouble() ?: 0.0
常量与变量都可以没有初始化值,但是在引用前必须初始化 编译器支持自动类型判断,即声明时可以不指定类型,由编译器判断。
函数定义
函数定义使用关键字 fun,参数格式为:参数 : 类型;不用var关键字声明
kotlin
fun sum(a: Int, b: Int): Int {
// Int 参数,返回值 Int
return a + b
}
表达式作为函数体,返回类型自动推断:
kotlin
fun sum(a: Int, b: Int) = a + b
public fun sum(a: Int, b: Int): Int = a + b
// public 方法则必须明确写出返回类型
无返回值的函数
kotlin
fun printSum(a: Int, b: Int): Unit {
print(a + b)
}
// 如果是返回 Unit类型,则可以省略(对于public方法也是这样):
public fun printSum(a: Int, b: Int) {
print(a + b)
}
可变长参数函数 函数的变长参数可以用 vararg关键字进行标识:
kotlin
fun vars(vararg v:Int){
for(vt in v){
print(vt)
}
}
// 测试
fun main(args: Array<String>) {
vars(1,2,3,4,5) // 输出12345
}
lambda(匿名函数)表达式使用实例:
kotlin
// 测试
fun main(args: Array<String>) {
val sumLambda: (Int, Int) -> Int = {x,y -> x+y}
println(sumLambda(1,2)) // 输出 3
}
kotlin
// 简化写法(更常见)
val sumLambda = { x: Int, y: Int -> x + y }
kotlin
// 普通函数写法
fun sum(x: Int, y: Int): Int = x + y
下面的代码时错误的
kotlin
fun feature3(x: Int){
x=5
println(x)
}
// Val cannot be reassigned,编译都过不了
基本数据类型
整数类型 Byte,Short,Int,Long 浮点数类型 Float, Double 字符类型 Char 布尔类型 Boolean 数组类型 Kotlin 中数组是不协变的(invariant)。 IntArray: 存储 Int 类型的数组。 DoubleArray: 存储 Double 类型的数组。 Array<T>: 泛型数组,可以存储任意类型。
- 类型转换 Kotlin 在比较不同数据类型时会自动进行类型转换。由于不同的表示方式,较小类型并不是较大类型的子类型,较小的类型不能隐式转换为较大的类型。 这意味着在不进行显式转换的情况下我们不能把 Byte 型值赋给一个 Int 变量。
kotlin
val b: Byte = 1 // OK, 字面值是静态检测的
val i: Int = b // 错误
val b: Byte = 1 // OK, 字面值是静态检测的
val i: Int = b.toInt() // OK
When 表达式
when 将它的参数和所有的分支条件顺序比较,直到某个分支满足条件。 when 既可以被当做表达式使用也可以被当做语句使用。如果它被当做表达式,符合条件的分支的值就是整个表达式的值,如果当做语句使用, 则忽略个别分支的值。 when 类似其他语言的 switch 操作符。其最简单的形式如下
kotlin
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // 注意这个块
print("x 不是 1 ,也不是 2")
}
}
在 when 中,else 同 switch 的 default。如果其他分支都不满足条件将会求值 else 分支。 如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔。 when 也可以用来取代 if-else if链。 如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支:
kotlin
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}
标签
Kotlin 的标签(Label)语法是一种用于控制流跳转 的特殊机制,主要用于解决多层嵌套循环或复杂逻辑中的精准跳转问题。它本质上是给代码块(如循环、lambda表达式)起一个名字,然后通过 break、continue 或 return 精准指定跳转目标。在 Kotlin 中任何表达式都可以用标签(label)来标记。 标签的格式为标识符后跟 @ 符号,例如:abc@、fooBar@都是有效的标签。 要为一个表达式加标签,我们只要在其前加标签即可。
kotlin
package org.wnan
fun main() {
println("=== 场景1:使用标签跳出多层循环 ===")
outerLoop@ for (i in 1..3) {
innerLoop@ for (j in 1..3) {
println("i=$i, j=$j")
if (i == 2 && j == 2) {
println("触发 break@outerLoop - 跳出外层循环")
break@outerLoop // 直接跳出标签为 outerLoop 的循环
// break
}
}
}
println("外层循环已终止\n")
println("=== 场景2:使用标签继续外层循环 ===")
outer@ for (i in 1..3) {
inner@ for (j in 1..3) {
if (j == 2) {
println("i=$i, j=$j → 触发 continue@outer")
continue@outer // 跳过外层循环的当前迭代
}
println("i=$i, j=$j")
}
}
println("外层循环结束\n")
println("=== 场景3:在lambda中使用标签返回 ===")
val numbers = listOf(1, 2, 3, 4, 5)
// 使用标签从lambda返回
numbers.forEach lit@ { num ->
if (num == 3) {
println("遇到3,从lambda返回")
return@lit // 仅从当前lambda返回,继续执行forEach后的代码
}
println("处理数字: $num")
}
println("lambda处理完成\n")
println("=== 场景4:嵌套lambda中的标签返回 ===")
run outer@ {
println("进入外层run块")
listOf("A", "B", "C").forEach inner@ { item ->
if (item == "B") {
println("遇到B,从外层run块返回")
return@outer // 直接从标签为outer的run块返回
}
println("处理项目: $item")
}
println("这行不会执行") // 因为遇到B时已经从outer返回
}
println("外层run块已终止")
}
plain
=== 场景1:使用标签跳出多层循环 ===
i=1, j=1
i=1, j=2
i=1, j=3
i=2, j=1
i=2, j=2
触发 break@outerLoop - 跳出外层循环
外层循环已终止
=== 场景2:使用标签继续外层循环 ===
i=1, j=1
i=1, j=2 → 触发 continue@outer
i=2, j=1
i=2, j=2 → 触发 continue@outer
i=3, j=1
i=3, j=2 → 触发 continue@outer
外层循环结束
=== 场景3:在lambda中使用标签返回 ===
处理数字: 1
处理数字: 2
遇到3,从lambda返回
处理数字: 4
处理数字: 5
lambda处理完成
=== 场景4:嵌套lambda中的标签返回 ===
进入外层run块
处理项目: A
遇到B,从外层run块返回
外层run块已终止
空类型
Kotlin 中,类型系统默认所有类型都是非空 的。这意味着一个普通的 String 类型的变量,你无法给它赋值为 null
kotlin
fun mian(){
var name: String = "Kotlin"
// name = null // 编译错误!Null 不能赋值给非空类型
}
如果你需要一个变量能够存储 null,必须显式地在类型后面加上 ? 来声明为可空类型
kotlin
fun main() {
var name: String? = "kotlin"
name = null
}
Elvis 操作符 ?:通常与安全调用操作符配合使用,用于在表达式结果为 null 时提供一个默认值。
kotlin
fun main() {
var name: String? = "kotlin"
name = null
var length:Int = name?.length ?: 0
println(length)
}
智能转换
当你使用 if 语句对一个可空变量进行了非空检查后,在该 if 代码块内部,编译器会自动将该变量视为非空类型,你可以直接使用它,无需任何额外操作。
kotlin
fun printName(name: String?) {
if (name != null) {
// 在这里,name 已经被智能转换为非空的 String 类型
println("Name length: ${name.length}")
} else {
println("Name is null")
}
}
let函数
let 是一个作用域函数,与 ?. 结合使用时,可以在变量不为空时执行一段代码块。
kotlin
fun main() {
val v1: Int = 1
val v2: Int? = null
val v3: Int = 3
var ls: List<Int?> = listOf(v1, v2, v3)
ls.forEach { p -> p?.let { println(p) } }
}
//输出
// 1
// 3
非空断言操作符 !!
这是一个"危险"的操作符。它的作用是告诉编译器:"我保证这个值不为空,如果为空,就抛出异常!"。不建议使用
kotlin
val name: String? = null
// 下面这行代码会抛出 KotlinNullPointerException
val length = name!!.length
延迟初始化
第一种:lateinit 关键字
- 用在 类的成员变量 、顶层属性 、局部变量
- 只能是 引用类型,不能是基本类型(Int/Long/Boolean 不行)
- 只能用 var,不能用 val
- 后续手动提前赋值,用之前必须赋值,否则崩溃
- 不是懒加载,只是允许先不初始化,后面再赋值
kotlin
class Person {
// 延迟初始化,先不赋值
lateinit var name: String
fun initName() {
// 后面再赋值
name = "张三"
}
fun show() {
// 赋值后才能使用
println(name)
}
fun check() {
if (::name.isInitialized) {
println("已初始化:$name")
} else {
println("还没初始化")
}
}
}
fun main() {
val p = Person()
p.initName()
p.show()
}
第二种:lazy 懒加载(最常用)
- 只能用 val 只读变量
- 第一次使用时才初始化,之后永远用这个值
- 线程安全、自带缓存
- 不需要手动管理赋值,不用怕空指针
- 纯 Kotlin 开发、后端、普通类都常用
kotlin
fun main() {
val info: String by lazy {
println("执行初始化逻辑")
"我是懒加载字符串"
}
println("开始执行 main")
// 第一次访问,才会执行 lambda 初始化
println(info)
// 第二次访问,直接拿缓存,不再执行 lambda
println(info)
}
/*
输出:
开始执行 main
执行初始化逻辑
我是懒加载字符串
我是懒加载字符串
*/
协程
#协程 导入依赖
kotlin
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")