Kotlin 与 Java 语法差异
目标:用可运行/可理解的代码对比展示 Kotlin 与 Java 在现代 Android 开发中的语法差异。
说明:Java 示例以 JDK 17+ 常见写法为主;涉及 Compose 的部分会给出 Java 侧"等效思路"(通常是传统 View 或接口回调),因为 Compose 本身是 Kotlin-first。
文档目录(按模块)
- A. 基础语法与类型系统(1~8)
- B. 并发与状态建模(9~11)
- C. Compose 基础范式(12~14)
- D. Kotlin 表达式与异常风格(15~19)
- E. 面向对象与语言组织方式(20~30)
- F. Kotlin 进阶特性(31~36)
- G. 协程与 Compose 进阶(37~45)
A. 基础语法与类型系统
1. 变量声明:val/var vs final/普通变量
Kotlin
kotlin
val name = "Tom" // 只读引用(不可二次赋值)
var age = 18 // 可变
// name = "Jerry" // 编译错误
age = 19
Java
java
final String name = "Tom"; // 只读引用
int age = 18; // 可变
// name = "Jerry"; // 编译错误
age = 19;
2. 类型推断:Kotlin 更彻底,Java var 仅限局部变量
Kotlin
kotlin
val title = "Park" // String
val score = 99 // Int
val ratio = 0.8f // Float
Java (JDK 10+)
java
var title = "Park"; // String
var score = 99; // int
var ratio = 0.8f; // float
class User {
// var field = "x"; // 编译错误:字段不能用 var
String field = "x";
}
3. 空安全:String? + ?. + ?:
Kotlin
kotlin
fun lenOrZero(name: String?): Int {
return name?.length ?: 0
}
Java
java
int lenOrZero(String name) {
return name == null ? 0 : name.length();
}
4. 数据类:data class vs 手写/record
Kotlin
kotlin
data class User(val id: Long, val name: String)
val u1 = User(1, "Tom")
val u2 = u1.copy(name = "Jerry")
Java (record)
java
public record User(long id, String name) {}
User u1 = new User(1, "Tom");
User u2 = new User(u1.id(), "Jerry");
5. 默认参数 + 命名参数 vs 方法重载
Kotlin
kotlin
fun showToast(msg: String, duration: Int = 0, withIcon: Boolean = false) {
// ...
}
showToast(msg = "保存成功")
showToast("失败", duration = 1)
Java
java
void showToast(String msg) {
showToast(msg, 0, false);
}
void showToast(String msg, int duration) {
showToast(msg, duration, false);
}
void showToast(String msg, int duration, boolean withIcon) {
// ...
}
6. when vs switch
Kotlin
kotlin
fun level(score: Int): String = when (score) {
in 90..100 -> "A"
in 80..89 -> "B"
in 60..79 -> "C"
else -> "D"
}
Java
java
String level(int score) {
if (score >= 90 && score <= 100) return "A";
if (score >= 80) return "B";
if (score >= 60) return "C";
return "D";
}
7. 扩展函数 vs 工具类静态方法
Kotlin
kotlin
fun String.maskPhone(): String {
if (length < 7) return this
return replaceRange(3, 7, "****")
}
val s = "13812345678".maskPhone()
Java
java
public final class StringExt {
public static String maskPhone(String s) {
if (s == null || s.length() < 7) return s;
return s.substring(0, 3) + "****" + s.substring(7);
}
}
String result = StringExt.maskPhone("13812345678");
8. 高阶函数与 Lambda(函数类型)
Kotlin
kotlin
fun request(onSuccess: (String) -> Unit, onError: (Throwable) -> Unit) {
try {
onSuccess("OK")
} catch (e: Throwable) {
onError(e)
}
}
request(
onSuccess = { println(it) },
onError = { println(it.message) }
)
Java
java
void request(java.util.function.Consumer<String> onSuccess,
java.util.function.Consumer<Throwable> onError) {
try {
onSuccess.accept("OK");
} catch (Throwable e) {
onError.accept(e);
}
}
request(
s -> System.out.println(s),
e -> System.out.println(e.getMessage())
);
B. 并发与状态建模
9. 协程 suspend vs CompletableFuture
Kotlin
kotlin
suspend fun loadUser(): User = api.getUser()
suspend fun loadPosts(): List<Post> = api.getPosts()
suspend fun loadPage(): Pair<User, List<Post>> = kotlinx.coroutines.coroutineScope {
val u = async { loadUser() }
val p = async { loadPosts() }
u.await() to p.await()
}
Java
java
CompletableFuture<User> loadUser() {
return CompletableFuture.supplyAsync(() -> api.getUser());
}
CompletableFuture<List<Post>> loadPosts() {
return CompletableFuture.supplyAsync(() -> api.getPosts());
}
CompletableFuture<PageData> loadPage() {
return loadUser().thenCombine(loadPosts(), PageData::new);
}
10. Kotlin 密封类(状态建模)vs Java sealed
Kotlin
kotlin
sealed interface UiState {
data object Loading : UiState
data class Success(val data: List<String>) : UiState
data class Error(val msg: String) : UiState
}
fun render(state: UiState) = when (state) {
UiState.Loading -> println("loading")
is UiState.Success -> println(state.data)
is UiState.Error -> println(state.msg)
}
Java
java
sealed interface UiState permits Loading, Success, Error {}
record Loading() implements UiState {}
record Success(java.util.List<String> data) implements UiState {}
record Error(String msg) implements UiState {}
void render(UiState state) {
if (state instanceof Loading) {
System.out.println("loading");
} else if (state instanceof Success s) {
System.out.println(s.data());
} else if (state instanceof Error e) {
System.out.println(e.msg());
}
}
11. 委托属性:by lazy / by viewModels()
Kotlin
kotlin
class HomeActivity : AppCompatActivity() {
private val vm: HomeViewModel by viewModels()
private val token: String by lazy { loadTokenFromDisk() }
}
Java(等效思路)
java
public class HomeActivity extends AppCompatActivity {
private HomeViewModel vm;
private String token;
HomeViewModel getVm() {
if (vm == null) {
vm = new ViewModelProvider(this).get(HomeViewModel.class);
}
return vm;
}
String getToken() {
if (token == null) {
token = loadTokenFromDisk();
}
return token;
}
}
C. Compose 基础范式
12. Compose DSL 与函数插槽(slot API)
这是 Kotlin 与 Java 在 UI 表达方式上的关键差异 之一。Compose 通过函数参数传递 UI 片段(
@Composable () -> Unit),Java 传统 View 一般通过接口回调、布局 ID、Adapter 等实现"可插拔区域"。
Kotlin Compose:函数插槽
kotlin
@Composable
fun AppScaffold(
title: String,
topBar: @Composable () -> Unit = { Text(title) },
content: @Composable () -> Unit,
bottomBar: @Composable (() -> Unit)? = null,
) {
Column {
topBar()
Box(modifier = Modifier.weight(1f)) {
content()
}
bottomBar?.invoke()
}
}
@Composable
fun DemoScreen() {
AppScaffold(
title = "首页",
topBar = { Text("自定义头部") },
content = { Text("页面内容") },
bottomBar = { Button(onClick = {}) { Text("提交") } }
)
}
Java 传统 View:接口/容器注入的等效思路
java
public class LegacyScaffold extends LinearLayout {
public interface Slot {
void render(ViewGroup container);
}
public void setup(String title, Slot topBar, Slot content, @Nullable Slot bottomBar) {
ViewGroup top = findViewById(R.id.top_container);
ViewGroup body = findViewById(R.id.content_container);
ViewGroup bottom = findViewById(R.id.bottom_container);
top.removeAllViews();
body.removeAllViews();
bottom.removeAllViews();
if (topBar != null) topBar.render(top);
content.render(body);
if (bottomBar != null) bottomBar.render(bottom);
}
}
13. Compose 状态提升(State Hoisting)vs Java 回调更新
Kotlin Compose
kotlin
@Composable
fun CounterScreen() {
var count by remember { mutableStateOf(0) }
Counter(count = count, onInc = { count++ })
}
@Composable
fun Counter(count: Int, onInc: () -> Unit) {
Row {
Text("count = $count")
Button(onClick = onInc) { Text("+") }
}
}
Java(View + Listener)
java
public class CounterView extends LinearLayout {
private int count = 0;
private TextView tv;
public CounterView(Context c) {
super(c);
// inflate...
tv = findViewById(R.id.tv);
Button btn = findViewById(R.id.btn);
btn.setOnClickListener(v -> {
count++;
tv.setText("count = " + count);
});
}
}
14. Compose 列表声明式渲染 vs RecyclerView Adapter
Kotlin Compose
kotlin
@Composable
fun MessageList(items: List<String>) {
LazyColumn {
items(items) { msg ->
Text(text = msg)
}
}
}
Java RecyclerView
java
public class MessageAdapter extends RecyclerView.Adapter<MessageVH> {
private final List<String> items;
public MessageAdapter(List<String> items) {
this.items = items;
}
@Override
public void onBindViewHolder(@NonNull MessageVH holder, int position) {
holder.textView.setText(items.get(position));
}
@Override
public int getItemCount() {
return items.size();
}
}
D. Kotlin 表达式与异常风格
15. 表达式函数:最后一行即返回值(可省略 return)
Kotlin
kotlin
fun max(a: Int, b: Int): Int = if (a > b) a else b
fun levelText(score: Int): String {
if (score >= 60) {
return "及格"
}
return "不及格"
}
// 也可写成表达式体
fun levelText2(score: Int): String = if (score >= 60) "及格" else "不及格"
Java
java
int max(int a, int b) {
return a > b ? a : b;
}
String levelText(int score) {
if (score >= 60) {
return "及格";
}
return "不及格";
}
16. 作用域函数:let / also / apply / run / with
Kotlin 通过作用域函数减少样板代码。Java 通常通过临时变量、链式 setter、工具方法实现。
Kotlin
kotlin
data class User(var name: String = "", var age: Int = 0)
val u1 = User().apply {
name = "Tom"
age = 18
} // 返回对象本身(this)
val len = " park ".let {
it.trim().length
} // 返回 lambda 最后一行
val u2 = User("Jerry", 20).also {
println("before save: $it")
} // 返回对象本身(it)
val text = User("Alice", 22).run {
"$name-$age"
} // 返回 lambda 结果(this)
val text2 = with(User("Bob", 30)) {
"$name-$age"
} // 返回 lambda 结果(this)
Java(等效思路)
java
User u1 = new User();
u1.setName("Tom");
u1.setAge(18);
int len = " park ".trim().length();
User u2 = new User("Jerry", 20);
System.out.println("before save: " + u2);
User tmp = new User("Alice", 22);
String text = tmp.getName() + "-" + tmp.getAge();
User tmp2 = new User("Bob", 30);
String text2 = tmp2.getName() + "-" + tmp2.getAge();
17. 异常处理:try 作为表达式 + runCatching
Kotlin
kotlin
fun parseOrZero(s: String): Int {
val result = try {
s.toInt() // try 可直接返回值
} catch (e: NumberFormatException) {
0
}
return result
}
fun parseOrNull(s: String): Int? {
return runCatching { s.toInt() }
.onFailure { println("parse error: ${it.message}") }
.getOrNull()
}
Java
java
int parseOrZero(String s) {
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
return 0;
}
}
Integer parseOrNull(String s) {
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
System.out.println("parse error: " + e.getMessage());
return null;
}
}
18. 受检异常(Checked Exception)差异
Kotlin 没有 Java 风格的受检异常语法约束;Java 需要
throws或try/catch。
Kotlin
kotlin
fun readText(path: java.nio.file.Path): String {
// Kotlin 调 Java API 时,不强制你写 throws/try-catch
return java.nio.file.Files.readString(path)
}
Java
java
String readText(java.nio.file.Path path) throws java.io.IOException {
return java.nio.file.Files.readString(path);
}
19. 空安全 + 异常风格结合:?.let {}
Kotlin
kotlin
fun printUserName(user: User?) {
user?.let {
println(it.name)
} ?: println("user is null")
}
Java
java
void printUserName(User user) {
if (user != null) {
System.out.println(user.getName());
} else {
System.out.println("user is null");
}
}
E. 面向对象与语言组织方式
20. 类型声明位置差异(你提到的 Int 左右问题)
Kotlin(类型在变量/参数名后)
kotlin
val age: Int = 18
fun sum(a: Int, b: Int): Int = a + b
val list: List<String> = listOf("A", "B")
Java(类型在变量/参数名前)
java
int age = 18;
int sum(int a, int b) { return a + b; }
List<String> list = List.of("A", "B");
21. 类构造器写法:主构造器/次构造器 vs 多构造器
Kotlin
kotlin
class User(val id: Long, var name: String) {
constructor(id: Long) : this(id, "")
}
Java
java
class User {
private final long id;
private String name;
User(long id, String name) {
this.id = id;
this.name = name;
}
User(long id) {
this(id, "");
}
}
22. 属性(property)vs 字段 + getter/setter
Kotlin
kotlin
class Account {
var balance: Int = 0
set(value) {
field = value.coerceAtLeast(0)
}
}
Java
java
class Account {
private int balance = 0;
public int getBalance() {
return balance;
}
public void setBalance(int value) {
this.balance = Math.max(0, value);
}
}
23. 字符串模板 vs 字符串拼接
Kotlin
kotlin
val name = "Tom"
val age = 18
val s = "name=$name, next=${age + 1}"
Java
java
String name = "Tom";
int age = 18;
String s = "name=" + name + ", next=" + (age + 1);
24. 静态成员:companion object vs static
Kotlin
kotlin
class IdGenerator {
companion object {
private var seed = 0
fun nextId(): Int = ++seed
}
}
val id = IdGenerator.nextId()
Java
java
class IdGenerator {
private static int seed = 0;
static int nextId() {
return ++seed;
}
}
int id = IdGenerator.nextId();
25. 顶层函数/常量 vs 工具类
Kotlin
kotlin
const val API_HOST = "https://api.demo.com"
fun joinPath(a: String, b: String): String = "$a/$b"
Java
java
public final class ApiUtils {
public static final String API_HOST = "https://api.demo.com";
public static String joinPath(String a, String b) {
return a + "/" + b;
}
}
26. 集合操作:链式函数式 API 差异
Kotlin
kotlin
val names = listOf("tom", "jerry", "alice")
val result = names
.filter { it.length > 3 }
.map { it.replaceFirstChar { c -> c.uppercase() } }
Java
java
List<String> names = List.of("tom", "jerry", "alice");
List<String> result = names.stream()
.filter(s -> s.length() > 3)
.map(s -> s.substring(0, 1).toUpperCase() + s.substring(1))
.toList();
27. 区间与遍历:.. / until / downTo / step
Kotlin
kotlin
for (i in 0..3) println(i) // 0,1,2,3
for (i in 0 until 3) println(i) // 0,1,2
for (i in 10 downTo 0 step 2) println(i)
Java
java
for (int i = 0; i <= 3; i++) System.out.println(i);
for (int i = 0; i < 3; i++) System.out.println(i);
for (int i = 10; i >= 0; i -= 2) System.out.println(i);
28. 智能类型转换(smart cast)vs 显式强转
Kotlin
kotlin
fun printLen(x: Any) {
if (x is String) {
println(x.length) // 自动当作 String
}
}
Java
java
void printLen(Object x) {
if (x instanceof String) {
String s = (String) x;
System.out.println(s.length());
}
}
29. 运算符重载(operator)vs 显式方法调用
Kotlin
kotlin
data class Vec(val x: Int, val y: Int) {
operator fun plus(other: Vec): Vec = Vec(x + other.x, y + other.y)
}
val v = Vec(1, 2) + Vec(3, 4)
Java
java
class Vec {
final int x;
final int y;
Vec(int x, int y) {
this.x = x;
this.y = y;
}
Vec add(Vec other) {
return new Vec(this.x + other.x, this.y + other.y);
}
}
Vec v = new Vec(1, 2).add(new Vec(3, 4));
30. 资源释放:use {} vs try-with-resources
Kotlin
kotlin
val text = java.io.BufferedReader(java.io.StringReader("hello")).use { br ->
br.readLine()
}
Java
java
String text;
try (BufferedReader br = new BufferedReader(new StringReader("hello"))) {
text = br.readLine();
}
F. Kotlin 进阶特性
31. 泛型变型:out/in vs ? extends / ? super
Kotlin
kotlin
open class Animal
class Dog : Animal()
fun readAnimals(list: List<out Animal>) {
// 只读场景
println(list.size)
}
fun addDogs(list: MutableList<in Dog>) {
list.add(Dog())
}
Java
java
class Animal {}
class Dog extends Animal {}
void readAnimals(List<? extends Animal> list) {
System.out.println(list.size());
}
void addDogs(List<? super Dog> list) {
list.add(new Dog());
}
32. 内联 + 具体化泛型:inline reified vs Class<T> 传参
Kotlin
kotlin
inline fun <reified T> Gson.fromJsonTyped(json: String): T {
return fromJson(json, T::class.java)
}
val user: User = gson.fromJsonTyped("{\"id\":1,\"name\":\"Tom\"}")
Java
java
<T> T fromJsonTyped(Gson gson, String json, Class<T> clazz) {
return gson.fromJson(json, clazz);
}
User user = fromJsonTyped(gson, "{\"id\":1,\"name\":\"Tom\"}", User.class);
33. 委托接口实现:by vs 手写包装类
Kotlin
kotlin
interface Logger {
fun log(msg: String)
}
class ConsoleLogger : Logger {
override fun log(msg: String) = println(msg)
}
class PrefixLogger(private val prefix: String, private val delegate: Logger) : Logger by delegate {
override fun log(msg: String) {
delegate.log("[$prefix] $msg")
}
}
Java
java
interface Logger {
void log(String msg);
}
class ConsoleLogger implements Logger {
@Override
public void log(String msg) {
System.out.println(msg);
}
}
class PrefixLogger implements Logger {
private final String prefix;
private final Logger delegate;
PrefixLogger(String prefix, Logger delegate) {
this.prefix = prefix;
this.delegate = delegate;
}
@Override
public void log(String msg) {
delegate.log("[" + prefix + "] " + msg);
}
}
34. 解构声明(destructuring)vs 手动取值
Kotlin
kotlin
data class Point(val x: Int, val y: Int)
val (x, y) = Point(10, 20)
println("x=$x, y=$y")
val pair = "Tom" to 18
val (name, age) = pair
Java
java
record Point(int x, int y) {}
Point p = new Point(10, 20);
int x = p.x();
int y = p.y();
System.out.println("x=" + x + ", y=" + y);
Map.Entry<String, Integer> entry = Map.entry("Tom", 18);
String name = entry.getKey();
int age = entry.getValue();
35. 注解使用位点:@field: / @get:(Kotlin 特有)
Kotlin(Android 常见:校验/序列化注解)
kotlin
data class Form(
@field:NotBlank
val name: String,
@get:JsonProperty("user_id")
val userId: String
)
Java
java
class Form {
@NotBlank
private final String name;
@JsonProperty("user_id")
public String getUserId() {
return userId;
}
private final String userId;
Form(String name, String userId) {
this.name = name;
this.userId = userId;
}
}
36. 可见性差异:internal vs Java 包可见
Kotlin
kotlin
internal class InternalRepo
class UserService {
internal fun load() {}
private fun onlyMe() {}
}
Java
java
class PackageRepo {} // 包可见(无修饰符)
public class UserService {
void load() {} // 包可见
private void onlyMe() {}
}
G. 协程与 Compose 进阶
37. 协程异常传播:launch vs async
Kotlin
kotlin
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
fun demo() {
scope.launch {
throw IllegalStateException("launch error")
}
scope.launch {
val deferred = async {
throw IllegalArgumentException("async error")
}
try {
deferred.await() // async 的异常通常在 await 时抛出
} catch (e: Exception) {
println("catch async: ${e.message}")
}
}
}
Java(CompletableFuture 等效思路)
java
ExecutorService pool = Executors.newCachedThreadPool();
void demo() {
CompletableFuture.runAsync(() -> {
throw new IllegalStateException("runAsync error");
}, pool).exceptionally(e -> {
System.out.println("catch runAsync: " + e.getMessage());
return null;
});
CompletableFuture<String> f = CompletableFuture.supplyAsync(() -> {
throw new IllegalArgumentException("supplyAsync error");
}, pool);
f.handle((v, e) -> {
if (e != null) {
System.out.println("catch supplyAsync: " + e.getMessage());
}
return null;
});
}
38. 协程监督:coroutineScope vs supervisorScope
Kotlin
kotlin
suspend fun normalScope() = coroutineScope {
launch {
delay(50)
println("child-1 done")
}
launch {
throw RuntimeException("child-2 fail")
}
// child-2 失败会取消同级 child-1
}
suspend fun supervisor() = supervisorScope {
launch {
delay(50)
println("child-1 still run")
}
launch {
throw RuntimeException("child-2 fail")
}
// child-2 失败不影响 child-1
}
Java(等效思路)
java
ExecutorService pool = Executors.newFixedThreadPool(2);
void supervisorLike() {
Future<?> f1 = pool.submit(() -> {
Thread.sleep(50);
System.out.println("child-1 still run");
return null;
});
Future<?> f2 = pool.submit(() -> {
throw new RuntimeException("child-2 fail");
});
// Java 里需要你手动决定是否 cancel 其它任务,默认不是结构化并发
}
39. Flow vs Java Stream(异步流 vs 同步流)
Kotlin Flow
kotlin
fun numberFlow(): kotlinx.coroutines.flow.Flow<Int> = flow {
emit(1)
delay(100)
emit(2)
emit(3)
}
suspend fun collectDemo() {
numberFlow()
.map { it * 10 }
.filter { it >= 20 }
.collect { println(it) }
}
Java Stream
java
Stream<Integer> stream = Stream.of(1, 2, 3)
.map(i -> i * 10)
.filter(i -> i >= 20);
stream.forEach(System.out::println);
40. StateFlow 驱动 UI 状态 vs Java Observable/Listener
Kotlin(ViewModel)
kotlin
class MessageVm : ViewModel() {
private val _uiState = MutableStateFlow("Idle")
val uiState: StateFlow<String> = _uiState
fun load() = viewModelScope.launch {
_uiState.value = "Loading"
delay(200)
_uiState.value = "Success"
}
}
Java(等效思路:LiveData/Listener)
java
public class MessageVm extends ViewModel {
private final MutableLiveData<String> uiState = new MutableLiveData<>("Idle");
LiveData<String> getUiState() {
return uiState;
}
void load() {
uiState.setValue("Loading");
// 异步后
uiState.postValue("Success");
}
}
41. Compose 副作用:LaunchedEffect
Kotlin Compose
kotlin
@Composable
fun UserScreen(userId: String, vm: UserVm = viewModel()) {
LaunchedEffect(userId) {
vm.load(userId) // userId 变化时重新执行
}
val ui by vm.uiState.collectAsState()
Text("state=$ui")
}
Java View(等效思路)
java
public class UserActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String userId = getIntent().getStringExtra("userId");
vm.load(userId); // 手动在生命周期节点触发
}
}
42. Compose 生命周期清理:DisposableEffect
Kotlin Compose
kotlin
@Composable
fun SensorObserver(sensorManager: SensorManager) {
DisposableEffect(Unit) {
val listener = SensorEventListener { /*...*/ }
sensorManager.registerListener(listener, /*sensor*/ null, SensorManager.SENSOR_DELAY_NORMAL)
onDispose {
sensorManager.unregisterListener(listener)
}
}
}
Java View(等效思路)
java
public class SensorActivity extends AppCompatActivity {
private SensorEventListener listener;
@Override
protected void onStart() {
super.onStart();
listener = new MySensorListener();
sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
protected void onStop() {
super.onStop();
sensorManager.unregisterListener(listener);
}
}
43. Compose 状态保存:rememberSaveable vs onSaveInstanceState
Kotlin Compose
kotlin
@Composable
fun FormScreen() {
var text by rememberSaveable { mutableStateOf("") }
TextField(value = text, onValueChange = { text = it })
}
Java View
java
public class FormActivity extends AppCompatActivity {
private EditText et;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
String text = savedInstanceState.getString("text", "");
et.setText(text);
}
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("text", et.getText().toString());
}
}
44. Compose 性能状态:derivedStateOf
Kotlin Compose
kotlin
@Composable
fun ScrollToTopButton(listState: LazyListState) {
val showButton by remember {
derivedStateOf { listState.firstVisibleItemIndex > 0 }
}
if (showButton) {
FloatingActionButton(onClick = { /*scrollToTop*/ }) {
Text("Top")
}
}
}
Java(等效思路)
java
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView rv, int dx, int dy) {
boolean show = layoutManager.findFirstVisibleItemPosition() > 0;
fab.setVisibility(show ? View.VISIBLE : View.GONE);
}
});
45. 总结与学习路径
模块回顾
- A. 基础语法与类型系统(1~8)
- B. 并发与状态建模(9~11)
- C. Compose 基础范式(12~14)
- D. Kotlin 表达式与异常风格(15~19)
- E. 面向对象与语言组织方式(20~30)
- F. Kotlin 进阶特性(31~36)
- G. 协程与 Compose 进阶(37~44)
推荐学习顺序
- 先掌握 A + D(写 Kotlin 代码的"手感"差异最大)
- 再学习 E + F(理解 Kotlin 的抽象能力和工程写法)
- Android 方向重点学 B + C + G(ViewModel、Flow、Compose)
迁移建议(Java/View -> Kotlin/Compose)
- 数据层:先改为 Kotlin + 空安全 + data class
- 状态层:统一到
ViewModel + StateFlow(并处理协程异常边界) - UI 层:从新页面开始用 Compose,逐步替换旧 View 页面
建议:你项目里如果已经大量使用 ViewModel,可先把状态层统一到
StateFlow,再逐步把页面迁移到 Compose,这样风险最低。