大家好!Android工程師們辛苦了。
從事Android開發多年,我認為Kotlin和Java的選擇確實讓人困惑。我自己從Java轉向Kotlin,自2019年Google將Kotlin定位為「推薦語言」後,現場的氛圍也大變樣了。
本文希望能夠比較2025年目前Android開發中兩種語言的實際情況,並分享一些在工作中獲得的真實體驗,讓初學者及考慮轉換的人都能有所參考!
老實說,這幾年Kotlin的勢頭可謂驚人。
不過Java也不是完全會消失。
這是我第一次對Kotlin感到震撼的地方,代碼真的變得非常簡潔。經常用Kotlin寫同樣的功能,行數僅為Java的一半以下。
Java
public class MainActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.text_view);
textView.setText("Hello, World!");
}
public void setUserData(String name, String email) {
if (name != null && email != null) {
// 設定數據處理
}
}
}
Kotlin
class MainActivity : AppCompatActivity() {
private lateinit var textView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView = findViewById(R.id.text_view)
textView.text = "Hello, World!"
}
fun setUserData(name: String?, email: String?) {
if (name != null && email != null) {
// 設定數據處理
}
}
}
結果:Kotlin實現了約40%的代碼量減少!對於評審來說更加易讀,真是太有幫助了。
這真的是革命性的變革。大家是否也有因NullPointerException而苦惱的經驗呢?
Java的NullPointerException地獄
Kotlin的神之應對
// Kotlin - Null安全
var name: String = "必填項" // 不可為Null
var nickname: String? = null // 允許為Null
// 安全調用
nickname?.let {
println("暱稱: $it")
}
我個人認為,這是Kotlin的真正魅力所在。
Java
Kotlin
// 函數式編程的例子
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }.filter { it > 4 }
這雖然看起來不起眼,但卻是相當大的不同。Java中的三元運算符,在變得複雜時會讓人難以閱讀。
在Java中,if
和switch
是語句(statement),因此無法返回值。所以經常依賴三元運算符。但在Kotlin中,if
和when
都可以當作表達式(expression)使用,寫起來好得多。
Java
int score = 85;
String grade;
if (score >= 90) {
grade = "A";
} else if (score >= 80) {
grade = "B";
} else {
grade = "C";
}
// 三元運算符
String result = (score >= 80) ? "Pass" : "Fail";
Kotlin
val score = 85
val grade = if (score >= 90) {
"A"
} else if (score >= 80) {
"B"
} else {
"C"
}
val result = if (score >= 80) "Pass" else "Fail"
==
, ===
作為Java新手,這個差異讓我犯了不少錯誤😅
Java中==
和.equals()
的區別,當初真的非常困惑。但在Kotlin中則更直觀,使用==
進行內容比較,===
用於參考比較。記住這兩點就可以了。
Java
String a = new String("text");
String b = new String("text");
System.out.println(a == b); // false (參考不同)
System.out.println(a.equals(b)); // true (內容相同)
Kotlin
val a = String("text".toCharArray())
val b = String("text".toCharArray())
println(a === b) // false (參考不同)
println(a == b) // true (內容相同)
這真的是Kotlin的精妙設計!在類型層級上對「可變與不可變」進行了明確區分,大大減少了bug的發生。
在Kotlin中,編譯時就能區分只讀集合(List
, Map
, Set
)和可變集合(MutableList
, MutableMap
, MutableSet
)。而在Java中要等到執行時才能知道,因此經常會因不小心變更而出錯。
Java
List<String> list = new ArrayList<>();
list.add("Apple"); // 可變
List<String> readOnlyList = List.of("Banana");
// readOnlyList.add("Cherry"); // UnsupportedOperationException
Kotlin
val list: List<String> = listOf("Apple")
// list.add("Banana") // 編譯錯誤
val mutableList: MutableList<String> = mutableListOf("Banana")
mutableList.add("Cherry") // OK
lateinit
和lazy
的存在,讓Android開發變得輕鬆多了...!特別是在處理Activity中的View時特別有用。
Kotlin在語言層面上提供這樣的機制,lateinit
用於var
,lazy
用於val
,各自有分工。
Java
// 需要手動實現
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
textView = findViewById(R.id.text_view); // 在這裡初始化
}
Kotlin
// lateinit: 後期一定會初始化的聲明
private lateinit var textView: TextView
// lazy: 在第一次訪問時初始化
val heavyObject: HeavyObject by lazy {
HeavyObject()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
textView = findViewById(R.id.text_view) // 初始化
}
在Java中,寫多個重載讓人疲憊的經歷,Kotlin的預設參數真的是太方便,讓代碼變得簡潔明了。
Java
void drawCircle(int x, int y, float radius) {
// ...
}
void drawCircle(int x, int y) {
drawCircle(x, y, 10.0f); // 重載支持
}
Kotlin
fun drawCircle(x: Int, y: Int, radius: Float = 10.0f) {
// ...
}
// 調用
drawCircle(10, 10) // radius為10.0f
drawCircle(10, 10, 20.0f) // radius為20.0f
copy
data class
真的是神機能!特別是copy()
方法超級方便,一旦用了就再也捨不得放棄。能輕鬆享受不變對象的好處。
Java (Record, 16+)
// 使用Java 16以後的Record來實現不變性
public record User(String name, int age) {}
User user1 = new User("Alice", 30);
// 手動創建一個更改後的副本
User user2 = new User(user1.name(), 31);
Kotlin
data class User(val name: String, val age: Int)
val user1 = User("Alice", 30)
// 使用copy方法輕鬆複製和更改
val user2 = user1.copy(age = 31)
當我第一次看到這個時會驚訝「哇,竟然可以自動轉換?」但實際使用後,發現這非常便利。因為編譯器的聰明設計,可以安心使用。
Java
void process(Object obj) {
if (obj instanceof String) {
String str = (String) obj; // 需要明確的類型轉換
System.out.println(str.length());
}
}
Kotlin
fun process(obj: Any) {
if (obj is String) {
// 自動將obj轉換為String類型
println(obj.length)
}
}
這是Kotlin中我最喜歡的功能之一!可以在現有類別中新增函數,最初讓我懷疑「這是魔法嗎?」。相比創建工具類,這樣更加自然地擴增了功能。
Java
// 工具類
public class StringUtils {
public static String shout(String s) {
return s.toUpperCase() + "!!!";
}
}
// 使用時
String excited = StringUtils.shout("hello");
Kotlin
// 擴展String類的shout()函數
fun String.shout(): String {
return this.uppercase() + "!!!"
}
// 使用時
val excited = "hello".shout()
final
和 open
這個差異起初讓我困惑。Java是「默認為open」,而Kotlin則是「默認為final」。這是出於安全性的設計,能防止意外的繼承或重寫。
Java
public class Vehicle { // 可繼承
public void drive() {} // 可重寫
}
public final class Car extends Vehicle { // 不可繼承
@Override
public final void drive() {} // 不可重寫
}
Kotlin
open class Vehicle { // open允許繼承
open fun drive() {} // open允許重寫
}
class Car : Vehicle() { // 默認為final (不可繼承)
override fun drive() {} // 默認為final (不可再次重寫)
}
那段使用+
進行字符串連接的Java時光,讓我懷念不已😅 Kotlin的字符串模板直觀,提升了可讀性並減少了bug。
Java
String name = "Alice";
int age = 30;
// 使用`+`連接或是使用String.format
String text = "Name: " + name + ", Age: " + age;
// Java 15+ 的多行字符串
String html = """
<html>
<body>
<p>Hello, World</p>
</body>
</html>
""";
Kotlin
val name = "Alice"
val age = 30
// 字符串模板
val text = "Name: $name, Age: $age"
// 多行字符串(原始字符串)
val html = '''
<html>
<body>
<p>Hello, ${name.uppercase()}</p>
</body>
</html>
'''.trimIndent()
常常有人問「性能怎麼樣?」實際情況如何,我來跟大家談談。
這個其實非常重要。Kotlin和Java之間實現了100%的互操作性。
在我們的現場,也逐漸將現有的Java專案中引入Kotlin。反過來,Kotlin專案中的Java函數也能毫無問題地使用。正因如此,轉換的難度大幅降低。
// 從Kotlin調用Java類
val javaObject = JavaClass()
javaObject.javaMethod()
// 從Java調用Kotlin類
KotlinClass kotlinObject = new KotlinClass();
kotlinObject.kotlinMethod();
※2025年追記:在過去一到兩年內,Android生態系統正朝Kotlin中心迅速變化!
最近的Android開發中,越來越多的庫是Kotlin原生的。而且,這些庫許多在Java中則難以使用(或完全無法使用)。
主要的Kotlin專用庫
特別是Jetpack Compose的問世,讓局勢發生了劇變:
// Jetpack Compose(僅以Kotlin編寫)
@Composable
fun UserCard(user: User) {
Card(
modifier = Modifier.fillMaxWidth().padding(16.dp)
) {
Column {
Text(
text = user.name,
style = MaterialTheme.typography.h6
)
Text(
text = user.email,
style = MaterialTheme.typography.body2
)
}
}
}
這真的非常革命性,現在根本提不起興致再用XML格式編寫佈局了😅
根據我的實際經驗,最近我參加的一個專案中被要求「用Java新增功能」,實話說讓我感到困惑。要應用最新的最佳實踐,現在Kotlin已經不可或缺了。
適合使用Kotlin的情況
適合使用Java的情況
在當前的Android開發中,新建專案時Kotlin已成為標準選擇。但是,對於專案的性質、團隊的技能以及與現有系統的集成需求,進行綜合評估是非常重要的。
這兩種語言都運行在Java虛擬機(JVM)上,實現相互操作,因而可以考慮逐步遷移或混合開發作為現實的選項。