Skip to content

第 2 章 环境搭建 & 第一行 Kotlin

学习目标:能在自己的电脑上跑通 Kotlin 代码;理解 Kotlin 编译流程;会用 IDEA / 命令行 / Playground 三种方式写 Kotlin;理解一个 Kotlin 项目长啥样、为啥那样组织。


2.1 三种"试水"方式:先跑起来再说

不要一上来就装 IDEA + 配 Gradle,那是给做工程的人准备的。先用最快的方式让代码跑起来,建立信心。

2.1.1 方式 A:浏览器 Playground(5 秒入门,零安装)

打开 https://play.kotlinlang.org/ ,直接写代码点 ▶ 运行。

kotlin
fun main() {
    println("Hello, Kotlin!")
}

适合场景

  • 临时验证一个语法
  • 看博客里的 demo 想试一下
  • 完全没装环境时的应急方案

不适合:写超过 50 行的代码(没有自动补全、断点调试)。

2.1.2 方式 B:命令行 kotlinc(学习首选)

适合"想搞清楚编译产物长啥样"的人。

安装步骤(macOS / Linux)

bash
# 1. 必须先有 JDK 17+(kotlinc 依赖 JVM)
brew install openjdk@17    # macOS
# 或
apt install openjdk-17-jdk # Ubuntu

# 2. 装 Kotlin 命令行工具
brew install kotlin        # macOS
# 或用 SDKMAN(推荐,跨平台)
curl -s "https://get.sdkman.io" | bash
sdk install kotlin 2.0.21

Windows 用户

powershell
# 用 Scoop 或 Chocolatey
scoop install kotlin
# 或
choco install kotlinc

验证安装

bash
kotlinc -version
# 输出:info: kotlinc-jvm 2.0.21 (JRE 17.0.10+7)

第一段代码

bash
# 1. 写一个 Hello.kt
echo 'fun main() { println("Hello, Kotlin!") }' > Hello.kt

# 2. 方式 1:编译成 jar 然后用 java 运行
kotlinc Hello.kt -include-runtime -d hello.jar
java -jar hello.jar
# → Hello, Kotlin!

# 3. 方式 2:直接运行 .kts 脚本(无需编译)
cat > hello.kts <<EOF
println("Hello from script!")
EOF
kotlinc -script hello.kts
# → Hello from script!

# 4. 方式 3:进入 REPL 交互式环境
kotlinc
# >>> println("hi")
# hi
# >>> :quit

📌 理解一下-include-runtime 把 Kotlin 标准库(约 5MB)也打进 jar,这样脱离 Kotlin 环境也能跑。生产项目一般用 Maven/Gradle 的依赖管理,不会用这个 flag。

2.1.3 方式 C:IntelliJ IDEA(生产首选)

JetBrains 自家 IDE,对 Kotlin 支持是最强的(毕竟同一家公司)。

下载

创建第一个项目

File → New → Project
├─ Name:        my-kotlin-demo
├─ Location:    ~/projects/my-kotlin-demo
├─ Language:    Kotlin              ← 选这个
├─ Build sys:   Gradle              ← 推荐 Gradle,新手也行(IDEA 帮你配)
├─ JDK:         17 (Temurin)        ← 选 JDK 17 或更高
├─ Gradle DSL:  Kotlin              ← build.gradle.kts,写起来更顺
└─ 点击 Create

第一次建项目时 IDEA 会自动下载 Gradle、Kotlin 编译器、依赖,网速慢的话可能要 5-10 分钟,请耐心等待右下角进度条。

💡 国内加速:在 ~/.gradle/init.gradle.kts 加阿里云镜像,能快 10 倍。第 2.4 节会专门讲。


2.2 一个最简单的 Kotlin 项目长啥样

新建好的项目目录结构(删掉无关的):

my-kotlin-demo/
├── build.gradle.kts          ← Gradle 构建脚本(Kotlin DSL)
├── settings.gradle.kts       ← Gradle 项目设置
├── gradle.properties         ← Gradle 全局参数
├── gradlew  / gradlew.bat    ← Gradle Wrapper 启动器
├── gradle/
│   └── wrapper/              ← Gradle 版本锁定
└── src/
    ├── main/
    │   └── kotlin/           ← 主代码
    │       └── Main.kt       ← 入口
    └── test/
        └── kotlin/           ← 测试代码

2.2.1 Main.kt

kotlin
fun main() {
    println("Hello World!")

    val name = "Kotlin"
    println("Hello, $name!")
}

📌 跟 Java 不同:Java 必须有一个 public class Main 包住,类名必须跟文件名同名;Kotlin 顶层就能写函数,文件名也不强制跟函数名同名。

2.2.2 build.gradle.kts 解读

kotlin
plugins {
    kotlin("jvm") version "2.0.21"     // Kotlin JVM 编译插件
    application                         // 让我们能用 ./gradlew run 启动 main
}

group = "com.example"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()                      // 从 Maven 中央仓库下载依赖
}

dependencies {
    testImplementation(kotlin("test"))  // 测试用 kotlin-test 库
}

application {
    mainClass.set("MainKt")             // 入口(注意 Kt 后缀,下面解释)
}

tasks.test {
    useJUnitPlatform()
}

kotlin {
    jvmToolchain(17)                    // 用 JDK 17 编译
}

为啥 mainClass 是 "MainKt"?

Kotlin 把 Main.kt 文件里的所有顶层函数 / 属性,都编译进一个MainKt 的类(自动加 Kt 后缀)。所以 JVM 找入口时找的是 MainKt.main(),不是 Main.main()

💡 你可以用 @file:JvmName("MyApp") 改这个名字,比如把 MainKt 改成 MyApp

2.2.3 运行项目

bash
# IDE 方式:点 main 函数旁边的 ▶ 绿色三角

# 命令行方式
./gradlew run
# → > Task :run
# → Hello World!
# → Hello, Kotlin!

# 打包成可分发 zip / tar
./gradlew distZip

# 跑测试
./gradlew test

2.3 编译流程:从 .kt 到字节码到运行

这是面试官常问的"Kotlin 是怎么跑起来的"。

                    源代码                  编译                运行
  ┌────────────┐           ┌──────────────┐         ┌──────────┐
  │  Hello.kt  │ ────────► │ kotlinc 编译  │ ──────► │  JVM 执行 │
  │            │           │  (含 K2)      │         │          │
  └────────────┘           └──────┬───────┘         └────▲─────┘
                                  │                       │
                          ┌───────▼────────┐              │
                          │ HelloKt.class  │ 进入 JAR 后  │
                          │ (字节码 + 元数据)│ ─────────────┘
                          └────────────────┘

2.3.1 看看实际编译产物

bash
# 写一个超简单的 Hello.kt
cat > Hello.kt <<'EOF'
fun main() {
    println("Hi")
}
EOF

# 编译
kotlinc Hello.kt -d out

# 看产物(不要被吓到)
ls out/
# → HelloKt.class                          (主类,包顶层 main)
# → META-INF/Hello.kotlin_module           (Kotlin 元信息)

2.3.2 反编译看真相

如果你装了 javap(JDK 自带):

bash
javap -c -p out/HelloKt.class

会看到 Kotlin 代码被编译成了等价的 Java 字节码:

public final class HelloKt {
  public static final void main();
    Code:
       0: ldc           #8    // String "Hi"
       2: astore_0
       3: getstatic     #14   // System.out
       6: aload_0
       7: invokevirtual #20   // PrintStream.println
      10: return

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #25   // Method main:()V
       3: return
}

📌 重点观察println 最终被编译成了 System.out.println —— Kotlin 跟 Java 在 JVM 层面就是同一种东西。

2.3.3 K1 vs K2 编译器

Kotlin 2.0(2024 年)引入了重写的 K2 编译器:

维度K1(旧)K2(新)
历史2010 年开始用2024 年正式版
编译速度基线提升 ~2 倍
错误消息一般更友好
多平台支持各端独立分析统一前端
IDE 性能显著改善

💡 建议:新项目直接用 Kotlin 2.0+,享受 K2 红利。


2.4 国内开发者的"加速三件套"

国内访问 Maven Central / Gradle 官方源经常很慢。下面是 3 个救命配置。

2.4.1 Gradle 国内镜像

新建 ~/.gradle/init.gradle.kts

kotlin
allprojects {
    repositories {
        mavenLocal()
        maven { url = uri("https://maven.aliyun.com/repository/public") }
        maven { url = uri("https://maven.aliyun.com/repository/google") }
        maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") }
        mavenCentral()
        google()
    }
}

2.4.2 Gradle Wrapper 镜像

gradle/wrapper/gradle-wrapper.properties 里把:

properties
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-all.zip

改成:

properties
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.10-all.zip

2.4.3 Kotlin JVM 内存调大

gradle.properties

properties
org.gradle.jvmargs=-Xmx2g -XX:+UseParallelGC
org.gradle.parallel=true
org.gradle.caching=true
kotlin.incremental=true

📌 效果:以上三件套一开,新项目第一次构建从 10 分钟缩到 1-2 分钟。


2.5 写第一段"超出 Hello World"的代码

光打印 Hi 不算入门,咱来一段稍微有点意思的:

2.5.1 计算斐波那契数列

kotlin
// File: Fibonacci.kt
fun fib(n: Int): Long {
    if (n <= 1) return n.toLong()
    var a = 0L
    var b = 1L
    repeat(n - 1) {
        val tmp = a + b
        a = b
        b = tmp
    }
    return b
}

fun main() {
    println("斐波那契数列前 10 项:")
    (0..9).forEach { i ->
        println("fib($i) = ${fib(i)}")
    }
}

运行:

斐波那契数列前 10 项:
fib(0) = 0
fib(1) = 1
fib(2) = 1
fib(3) = 2
fib(4) = 3
fib(5) = 5
fib(6) = 8
fib(7) = 13
fib(8) = 21
fib(9) = 34

2.5.2 涉及到的"小知识点速览"

代码含义
fun fib(n: Int): Long函数定义,参数 n: Int,返回值 Long
n.toLong()显式类型转换(Kotlin 不像 Java 自动从 Int 升 Long)
var a = 0Lvar = 可变;0L 是 Long 字面量
repeat(n - 1) { ... }Kotlin 标准库函数,重复执行 lambda
(0..9).forEach { i -> ... }范围 + 集合操作;i -> 是 lambda 参数
"fib($i) = ${fib(i)}"字符串模板:$i 是简单插值,${...} 是表达式插值

2.6 IDEA 必备快捷键(强烈建议背下来)

这些快捷键能让你的 Kotlin 开发效率提升 3 倍:

操作macOSWindows / Linux
运行当前 mainCtrl + RShift + F10
Debug 当前 mainCtrl + DShift + F9
智能补全Ctrl + SpaceCtrl + Space
万能搜索Shift ShiftShift Shift
重构 → 重命名Shift + F6Shift + F6
跳到定义Cmd + 点击Ctrl + 点击
看反编译字节码Tools → Kotlin → Show Kotlin Bytecode
把 Kotlin 字节码反编译成 Java上一项窗口里点 "Decompile"
提取变量Cmd + Alt + VCtrl + Alt + V
自动 importAlt + EnterAlt + Enter
格式化代码Cmd + Alt + LCtrl + Alt + L

💡 新手必学的两个

  1. Alt + Enter —— Kotlin IDE 的"魔法键",光标停在任何报错或警告上按一下,IDE 会列出所有可能的修复方案
  2. Show Kotlin Bytecode —— 想搞懂"我这段 Kotlin 编译成 Java 长啥样"时神器,对学协程、data classobject 等高阶特性特别有用

2.7 常见踩坑 & 快速诊断

Q1:./gradlew 提示 Permission denied

bash
chmod +x gradlew

Q2:JDK 找不到 / "JAVA_HOME is not set"

bash
# 检查
echo $JAVA_HOME
java -version

# macOS 设置
export JAVA_HOME=$(/usr/libexec/java_home -v 17)

# Linux 设置
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64

加到 ~/.zshrc~/.bashrc 永久生效。

Q3:IDEA 报红 "Cannot resolve symbol kotlin.io.println"

通常是 Kotlin 标准库没下载。File → Invalidate Caches and Restart 一下。

Q4:编译报 "Unresolved reference: 某某类"

99% 是 import 没加。光标停在变红的类名上,按 Alt + Enter → "Import"。

Q5:用了协程报 "Suspend function 'xxx' should be called only from a coroutine or another suspend function"

suspend 函数只能在协程或另一个 suspend 函数里调,详见 第 9 章。临时验证可以包一层 runBlocking { ... }


2.8 章末小结

              ★ 第 2 章核心知识图谱 ★

        ┌─────────────┼─────────────┐
        │             │             │
     ┌──▼──┐      ┌──▼──┐      ┌──▼──┐
     │入门  │      │项目  │      │工具  │
     ├─────┤      ├─────┤      ├─────┤
     │Playgr│     │Gradle│      │IDEA  │
     │kotlinc│    │.kts  │      │快捷键│
     │REPL  │     │MainKt│      │字节码│
     └─────┘      └─────┘      └─────┘

完成本章你应该:

  • ✅ 在自己电脑装好了 JDK + Kotlin
  • ✅ 用 3 种方式都跑通了 Hello World
  • ✅ 理解了 MainKt 是怎么来的
  • ✅ 知道 Kotlin 编译成 JVM 字节码后跟 Java 等价
  • ✅ 配置了国内 Gradle 镜像(如果你在国内)

🎤 2.9 章末面试题(8 道)

Q1. Kotlin 编译需要哪些工具?产物是什么?

  • 编译工具:kotlinc(Kotlin 编译器,背后包含 K1 / K2 前端 + JVM 后端)
  • 运行依赖:JVM(JDK 8 起,推荐 17+)+ kotlin-stdlib(标准库)
  • 产物:.class 字节码文件(跟 Java 同样格式)+ Kotlin metadata(用于反射)

Q2. 为什么写一个 Main.kt 文件,编译后的类名变成了 MainKt

:JVM 强制所有方法必须属于某个类。Kotlin 把文件里的所有顶层函数和属性收集起来,编译进一个自动生成的 facade 类,类名 = <文件名>Kt。所以 Main.ktMainKt

如果你想改名,可以加注解:

kotlin
@file:JvmName("MyApp")
fun main() { ... }
// 编译后类名变成 MyApp,不再是 MainKt

Q3. kotlincjavac 的关系是什么?

:两者是平级关系,都是把源代码编译成 JVM 字节码的编译器。kotlinc 不依赖 javac,但都依赖 JDK 提供的 JVM 工具链。一个混合 Java + Kotlin 的项目里:

  • .java 文件 → javac 编译
  • .kt 文件 → kotlinc 编译
  • 两边产物在同一个 classpath 下相互调用

构建工具(Gradle/Maven)会协调它们的编译顺序。


Q4. .kt.kts 有什么区别?

  • .kt:标准 Kotlin 源文件,需要先编译再运行(即"编译后部署"模式)
  • .kts:Kotlin Script,可以直接被 kotlinc -script xxx.kts 解释执行,不需要单独编译步骤

.kts 主要用于:

  • Gradle 构建脚本(build.gradle.kts
  • 一次性脚本任务
  • IDE 插件(IDEA 插件配置)

Q5. K1 编译器和 K2 编译器有什么区别?

:K2 是 Kotlin 2.0(2024)正式发布的全新编译器前端:

  • 速度:编译速度提升约 2 倍
  • 架构:统一了多平台前端,K1 时代每个平台有独立的语义分析
  • 错误信息:更准确、更友好
  • IDE 性能:K2 模式下 IDEA 索引和补全显著加速
  • 兼容性:高度向后兼容,绝大多数 K1 代码无需修改即可在 K2 编译

Q6. Gradle 项目第一次构建为什么这么慢?怎么加速?

:慢的原因:

  1. 下载 Gradle 本身(百兆级)
  2. 下载 Kotlin 编译器(百兆级)
  3. 下载所有依赖到 ~/.gradle/caches/
  4. 国内访问 Maven Central / services.gradle.org 经常被限速

加速方法:

  • 配置阿里云 / 腾讯云 Maven 镜像(~/.gradle/init.gradle.kts
  • Gradle Wrapper 用国内镜像
  • 开启并行 + 缓存:org.gradle.parallel=true org.gradle.caching=true
  • 给 Gradle 多分配内存:org.gradle.jvmargs=-Xmx2g

Q7. Kotlin 在 Android 项目里编译出来的产物是什么?怎么跑到手机上?

  1. .ktkotlinc 编译为 JVM 字节码 .class
  2. .classD8/R8(Android 工具链) 转换为 Dalvik 字节码 .dex
  3. .dex + 资源文件 → 打包成 .apk / .aab
  4. 手机的 ART 运行时(Android Runtime)执行 .dex

📌 注意:Android 上跑的不是标准 JVM,而是 ART —— 字节码格式不同,但你写代码时不感知。


Q8. 如果一个项目同时有 Java 和 Kotlin 文件,编译顺序是什么?

:Gradle 的 kotlin-jvm 插件会处理:

  1. 先用 kotlinc 编译 Kotlin 代码(同时分析 Java 源代码以解析跨语言引用,但不编译 Java)
  2. 再用 javac 编译 Java 代码(此时可以引用上一步生成的 Kotlin .class
  3. 两边产物合并打包

所以 Kotlin 调 Java 没问题(Java 已经编译好了);Java 调 Kotlin 也没问题(Kotlin 先编译完了)。这就是"100% 互操作"的工程基础。


下一章 → 第 3 章 · 基本语法(变量、类型、控制流)→

🎬 可视化演示

演示加载缓慢或样式异常?点此在新标签页打开 ↗

💻 示例代码

txt
/**
 * 第 2 章 · 环境搭建 — 标准 build.gradle.kts 模板
 *
 * 这是一个最小可用的 Kotlin JVM 项目构建脚本。
 * 适合:CLI 工具 / 服务端 / 学习用项目。
 *
 * 使用:
 *   1. 把这个文件放到项目根目录
 *   2. 在 src/main/kotlin/ 下放你的 .kt 文件
 *   3. ./gradlew run 运行
 *   4. ./gradlew build 打包
 */

plugins {
    kotlin("jvm") version "2.0.21"
    application
}

group = "com.example.kotlin.demo"
version = "1.0-SNAPSHOT"

repositories {
    // 国内开发者建议在 ~/.gradle/init.gradle.kts 配置阿里云镜像
    mavenCentral()
}

dependencies {
    // 标准库默认包含,无需声明

    // 测试
    testImplementation(kotlin("test"))

    // 协程(第 9 章会用到)
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")

    // JSON 序列化(第 10 章会用到)
    // implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")
}

application {
    // 入口类:Main.kt 的顶层 main 函数 → MainKt
    mainClass.set("MainKt")
}

tasks.test {
    useJUnitPlatform()
}

kotlin {
    // 推荐用 JDK 17+(LTS 长期支持版本)
    jvmToolchain(17)

    // 编译期可选:开启严格模式
    compilerOptions {
        // 把所有警告升级为错误(CI 推荐)
        // allWarningsAsErrors.set(true)
    }
}
kotlin
/**
 * 第 2 章 · 环境搭建 — Fibonacci 示例
 *
 * 演示了:
 *   - 顶层函数定义
 *   - val / var
 *   - 显式类型转换 (toLong)
 *   - repeat 标准库函数
 *   - Range (0..9) 与 forEach
 *   - 字符串模板 ($i / ${expression})
 *
 * 运行方式:
 *   kotlinc Fibonacci.kt -include-runtime -d fib.jar
 *   java -jar fib.jar
 */

fun fib(n: Int): Long {
    if (n <= 1) return n.toLong()
    var a = 0L
    var b = 1L
    repeat(n - 1) {
        val tmp = a + b
        a = b
        b = tmp
    }
    return b
}

fun main() {
    println("斐波那契数列前 10 项:")
    (0..9).forEach { i ->
        println("fib($i) = ${fib(i)}")
    }

    println()
    println("=== 使用尾递归优化版 ===")
    println("fib(50) = ${fibTailrec(50)}")
}

/**
 * 尾递归版 —— 用 tailrec 关键字让编译器把递归转成循环,避免栈溢出。
 * 这是 Kotlin 的"小心机"特性,Java 里写不出来。
 */
tailrec fun fibTailrec(n: Int, a: Long = 0L, b: Long = 1L): Long {
    if (n == 0) return a
    return fibTailrec(n - 1, b, a + b)
}

build.gradle.kts ↗ · Fibonacci.kt ↗