背景
2024 年我开始逐渐介入客户端的研发,因此我开始学习客户端的知识。
从服务端开始,转到前端来其实完全代表着我的编码风格的转变。我喜欢 UI 编程那「所见即所得」的惊艳,也喜欢人机交互相关的内容。
做 C 端 App,前端技术实际上更多是一种强行「卷」过来的结果:首先,基础肯定是客户端技术 Android/iOS,但是前端的作用越来越清晰。当然,目前的大环境下走入前端甚至客户端开发通常被认为是一种开倒车的举动。但这种东西谁又说得好呢——难道做算法调优几个版本实验指标波动,亦或者做服务端大半夜被机器人打电话就能让人兴奋了吗?
人总有无知的时候,在已知信息差的时候我们总有一天要为认知买单。所以就让我们为梦想,做出一次不那么受到束缚的选择吧。
这是安卓系列的第一期,它包含以下内容:
安卓(1)-语法基础:Kotlin & Java & TS 对比
安卓(2)-语法基础:Kotlin 常用库(1)
安卓(3)-实用篇:UI(1)
我使用了 AI 来辅助我创作了一些重复性的工作,第一期内容会以相关知识的罗列为主,所以最好的阅读方式是阅读后进行查漏补缺。
希望大家可以喜欢这些教程!
前端研发学习客户端开发很有必要,主要体现在以下几方面:
- 拓宽技术栈,增强竞争力:当前技术就业市场青睐全栈开发能力,掌握客户端开发知识,能让前端开发者从专注浏览器端用户界面,拓展到移动、桌面等多种客户端平台的开发,满足更多类型项目的需求。在求职和职业发展中更具优势,尤其对于追求低成本、高效率的企业,一专多能的开发者更受欢迎。
- 提升用户体验理解:用户期望不同设备间体验一致,学习客户端开发能让前端开发者参与跨平台应用开发,实现网页端和客户端体验的无缝过渡。同时,客户端开发涉及更多硬件资源利用和性能优化技巧,有助于前端开发者将这些理念应用到前端开发中,提升整体用户体验。
- 更好地进行团队协作:在包含多种角色的开发团队里,了解客户端开发能使前端开发者与客户端开发人员沟通更顺畅,减少因技术认知差异产生的沟通障碍,提高协作效率。在项目规划阶段,还能结合前端和客户端的特点,提出更合理的技术方案,避免后期出现平台开发不协调的问题。
目标:
- 语言能力:for 前端同学,能看懂 Java 代码,能上手写 Kotlin 代码。
Kotlin & Java
Kotlin是一种在 Java 虚拟机上执行的静态类型编程语言,它也可以被编译成为 JavaScript 源代码。它主要是由 JetBrains 在俄罗斯圣彼得堡的开发团队所发展出来的编程语言,其名称来自于圣彼得堡附近的科特林岛。
Kotlin 的优势(对比 Java)
富有表现力且简洁 您可以使用更少的代码实现更多的功能。表达自己的想法,少编写样板代码。在使用 Kotlin 的专业开发者中,有 67% 的人反映其工作效率有所提高。
更安全的代码 Kotlin 有许多语言功能,可帮助您避免 null 指针异常等常见编程错误。包含 Kotlin 代码的 Android 应用发生崩溃的可能性降低了 20%。
可互操作 您可以在 Kotlin 代码中调用 Java 代码,或者在 Java 代码中调用 Kotlin 代码。Kotlin 可完全与 Java 编程语言互操作,因此您可以根据需要在项目中添加任意数量的 Kotlin 代码。
结构化并发 Kotlin 协程让异步代码像阻塞代码一样易于使用。协程可大幅简化后台任务管理,例如网络调用、本地数据访问等任务的管理。
基础语法
变量
1.1 变量、常量声明
在 Kotlin 中,可以使用 val 或 var 关键字声明变量。var 声明的变量可以在程序执行期间重新赋值,而 val 声明的变量则不能重新赋值,使用方式跟 TypeScript 类似。
TypeScript
常量:使用
const
声明。变量:使用
let
或var
声明。
class Example {
// 常量
static readonly CONSTANT_VALUE: number = 10;
// 变量
variableValue: number;
constructor(value: number) {
this.variableValue = value;
}
}
Java
常量:使用
final
关键字声明。变量:可使用
int
、String
等基本类型或对象类型声明。
public class Example {
// 常量
private static final int CONSTANT_VALUE = 10;
// 变量
private int variableValue;
public Example(int value) {
this.variableValue = value;
}
}
Kotlin
常量:使用
val
声明(不可变),使用const val
声明编译时常量。变量:使用
var
声明(可变)。
class Example(value: Int) {
// 常量
companion object {
const val CONSTANT_VALUE = 10
}
// 变量
var variableValue: Int = value
}
1.2 变量类型
1.2.1 基本类型
- TypeScript
- 基本类型包括
number
,boolean
,string
,bigint
,symbol
,null
,undefined
。 - 可以使用
null
和undefined
,类型可以声明为可选的。
let num: number = 42; // 基本类型
let nullableNum: number | null = null; // 可为 null
- Java
- 基本类型有
int
,boolean
,char
,byte
,short
,long
,float
,double
。 - 使用包装类来处理
null
值,例如Integer
。
int num = 42; // 基本类型
Integer nullableNum = null; // 包装类
- Kotlin
- 基本类型使用
Int
,Boolean
,Char
,Byte
,Short
,Long
,Float
,Double
。 - 默认不可为
null
,使用?
表示可为null
。
val num: Int = 42 // 基本类型
val nullableNum: Int? = null // 可为 null
1.2.2 包装类型
包装类是用于将基本数据类型(如整数、布尔值等)封装成对象类型的类。包装类的主要作用是允许基本类型与对象进行交互,提供了更多的方法和功能。
在 TypeScript 中,基本类型没有单独的包装类,但可以使用对象类型。例如,number
和 string
是基本类型,但可以与对象交互。
- 特点:
null
和undefined
可以被作为类型。- 可以使用对象类型(如
Number
和String
)来模拟包装类的功能。
let wrappedInt: Number = new Number(5); // 使用 Number 对象
let primitiveInt: number = wrappedInt.valueOf(); // 获取基本类型
let nullableInt: number | null = null; // 可为 null
console.log(wrappedInt + 10); // 输出 15
console.log(nullableInt); // 输出 null
在 Java 中,包装类是与基本类型相对应的类。每种基本类型都有一个对应的包装类:
int
->Integer
boolean
->Boolean
char
->Character
byte
->Byte
short
->Short
long
->Long
float
->Float
double
->Double
特点:
- 包装类可以为
null
,而基本类型不能。 - 提供了一些方法,比如转换、比较等。
- 包装类可以为
public class Main {
public static void main(String[] args) {
// 使用包装类
Integer wrappedInt = Integer.valueOf(5); // 将 int 转换为 Integer
int primitiveInt = wrappedInt.intValue(); // 将 Integer 转换为 int
// 可以为 null
Integer nullableInt = null;
System.out.println(wrappedInt + 10); // 输出 15
System.out.println(nullableInt); // 输出 null
}
}
在 Kotlin 中,基本类型和对象类型有更紧密的结合。Kotlin 不再使用单独的包装类,而是直接使用对象类型(如 Int
、Boolean
)。Kotlin 的基本类型会在 JVM 中优化为原始类型。
- 特点:
- 默认不可为
null
,使用?
表示可为null
。 - 不需要显式的转换方法,Kotlin 会自动进行转换。
- 默认不可为
fun main() {
// 使用基本类型
val wrappedInt: Int = 5 // 直接使用 Int 类型
val nullableInt: Int? = null // 可为 null
// 可以直接进行运算
println(wrappedInt + 10) // 输出 15
println(nullableInt) // 输出 null
}
1.2.3 模板字符串
在 TypeScript 中,使用反引号(```)来定义模板字符串,支持多行字符串和插值。可以使用 ${}
语法插入变量或表达式。
const name: string = "Alice";
const age: number = 30;
const greeting: string = `Hello, ${name}. You are ${age} years old.`;
console.log(greeting); // 输出: Hello, Alice. You are 30 years old.
// 多行字符串
const multiLine = `This is line one.
This is line two.`;
console.log(multiLine);
在 Java 中,虽然没有直接的模板字符串语法,但可以使用字符串连接运算符(+
)或 String.format()
方法来实现字符串插值。
String name = "Alice";
int age = 30;
String greeting = String.format("Hello, %s. You are %d years old.", name, age);
System.out.println(greeting); // 输出: Hello, Alice. You are 30 years old.
Kotlin 支持使用 $
符号进行字符串插值,语法更加简洁。可以直接在字符串中使用变量,也可以使用花括号 {}
来插入表达式。
val name = "Alice"
val age = 30
val greeting = "Hello, $name. You are $age years old."
println(greeting) // 输出: Hello, Alice. You are 30 years old.
// 使用表达式
val greetingWithExpression = "Hello, ${name.toUpperCase()}. You are $age years old."
println(greetingWithExpression) // 输出: Hello, ALICE. You are 30 years old.
1.2.4 枚举
Java、Kotlin: 枚举是类,可以有构造函数和方法,支持字段和行为。
TypeScript: 提供数字和字符串枚举,语法简单,主要用于定义常量集合。
在 TypeScript 中,枚举可以是数字枚举或字符串枚举。
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE",
}
// 使用枚举
let color: Color = Color.Red;
console.log(color); // 输出: "RED"
在 Java 中,枚举是一个特殊的类,可以具有字段、方法和构造函数。
public enum Color {
RED, GREEN, BLUE;
// 可以添加字段和方法
private String hex;
Color() {
this.hex = "#" + Integer.toHexString(this.ordinal());
}
public String getHex() {
return hex;
}
}
// 使用枚举
Color color = Color.RED;
System.out.println(color.getHex());
在 Kotlin 中,枚举类是一个特殊的类,支持属性和方法。
enum class Color(val hex: String) {
RED("#FF0000"),
GREEN("#00FF00"),
BLUE("#0000FF");
fun describe() = "Color: $name, Hex: $hex"
}
// 使用枚举
val color = Color.RED
println(color.describe()) // 输出: Color: RED, Hex: #FF0000
1.3 类型检查和类型转换
类型推断:
- Java 需要显式声明类型。
- Kotlin 和 TypeScript 支持隐式推断,可以省略类型声明。
类型转换:
- Java 使用强制转换语法,Kotlin 使用转换方法,TypeScript 使用
as
进行转换。
强制类型转换:
- Java 和 TypeScript 使用直接的强制转换语法,Kotlin 提供了安全转换的方式。
在 TypeScript 中,支持类型推导,可以省略类型声明,编译器会根据初始值推断类型。
- 隐式推断:TypeScript 也支持类型推断,可以省略类型声明,编译器会根据初始值推断类型。
- 类型转换:可以使用
as
进行类型转换,也可以使用类型断言。 - 强制类型转换:使用
as
进行类型断言。
// 类型推导
let num = 42; // 隐式推断为 number
let str = "Hello"; // 隐式推断为 string
// 类型转换
let d: any = 9.78;
let num: number = d as number; // 使用 as 进行类型转换
// 强制类型转换
let obj: any = "Hello";
let str: string = obj as string; // 强制转换
在 Java 中,必须显式声明变量的类型。
- 显式声明:Java 不支持类型推断,必须显式声明变量的类型。
- 类型转换:需要使用强制转换语法。
- 强制类型转换:需要使用强制转换语法。
// 类型推导
int num = 42; // 显式声明类型
// 类型转换
double d = 9.78;
int num = (int) d; // 显式强制转换,从 double 转为 int
// 强制类型转换
Object obj = "Hello";
String str = (String) obj; // 强制转换
在 Kotlin 中,支持类型推断,可以省略类型声明,编译器会根据初始值推断类型。
- 隐式推断:Kotlin 支持类型推断,可以省略类型声明,编译器会根据初始值推断类型。
- 类型转换:Kotlin 提供了转换方法,如
toInt()
。 - 强制类型转换:使用
as
关键字进行强制转换,支持安全转换(as?
)。
// 类型推导
val num = 42 // 隐式推断为 Int
val str = "Hello" // 隐式推断为 String
// 类型转换
val d = 9.78
val num = d.toInt() // 使用转换方法
// 强制类型转换
val obj: Any = "Hello"
val str: String = obj as String // 强制转换
val safeStr: String? = obj as? String // 安全转换,若失败返回 null
1.4 类型判断
- Java:使用
instanceof
进行类型判断,需手动强制转换。 - Kotlin:使用
is
进行类型判断,支持自动和安全转换,简化了语法。 - TypeScript:使用
typeof
和instanceof
,适用于基本数据类型和对象类型。
在 TypeScript 中,使用 typeof
和 instanceof
进行类型判断。
- **使用
typeof
**:主要用于基本数据类型。 - **使用
instanceof
**:用于对象和类的类型判断。
function typeCheck(value: any) {
if (typeof value === "string") {
console.log("是一个字符串: " + value);
} else if (value instanceof Array) {
console.log("是一个数组");
} else {
console.log("不是一个字符串或数组");
}
}
typeCheck("Hello, World!"); // 输出: 是一个字符串: Hello, World!
typeCheck([1, 2, 3]); // 输出: 是一个数组
typeCheck(42); // 输出: 不是一个字符串或数组
在 Java 中,使用 instanceof
关键字进行类型判断。
在 Java 中,基本数据类型没有对象的特性,因此通常通过 instanceof
来判断对象类型。如果使用包装类(如 Integer
、Double
等),可以使用 instanceof
进行判断。
public class Main {
public static void main(String[] args) {
Object obj = "Hello, World!";
if (obj instanceof String) {
String str = (String) obj; // 强制转换
System.out.println("是一个字符串: " + str);
} else {
System.out.println("不是一个字符串");
}
}
}
// 基本类型判断
public class Main {
public static void main(String[] args) {
Object obj = 42; // 基本类型的包装类
if (obj instanceof Integer) {
System.out.println("是一个 Integer 类型");
} else if (obj instanceof Double) {
System.out.println("是一个 Double 类型");
} else {
System.out.println("不是 Integer 或 Double 类型");
}
}
}
在 Kotlin 中,使用 is
关键字进行类型判断,使用 as
进行强制转换,支持安全转换。
在 Kotlin 中,基本数据类型和对象类型更紧密结合,使用 is
关键字进行判断。Kotlin 允许直接判断基本类型。
fun main() {
val obj: Any = "Hello, World!"
if (obj is String) {
// 不需要强制转换
println("是一个字符串: $obj")
} else {
println("不是一个字符串")
}
// 使用安全转换
val str: String? = obj as? String // 如果不是 String,则返回 null
println("安全转换: $str")
}
// 基本类型判断
fun main() {
val obj: Any = 42 // 基本类型
if (obj is Int) {
println("是一个 Int 类型")
} else if (obj is Double) {
println("是一个 Double 类型")
} else {
println("不是 Int 或 Double 类型")
}
}
运算符
运算符 | 异同点 |
---|---|
算术运算符(如 + , - , * , / , % ) | 完全一致 |
比较运算符(如 == , != , < , > , <= , >= ) | 除 == 外基本一致 |
逻辑运算符(如 && , ` | |
位运算符(如 & , ` | , ^, <<, >>`) |
相等运算符异同点
TypeScript 的 ==
进行类型转换,===
不进行转换。
Kotlin 的 ==
进行内容比较,===
比较引用。
Java 的 ==
在基本类型上比较值,在对象上比较引用,没有严格相等运算符。
在 TypeScript 中,==
(相等运算符)进行类型转换后比较值。
- 例如,
0 == '0'
为true
,因为字符串'0'
被转换为数字。
===
(严格相等运算符)不进行类型转换,只有在类型和值都相等时才返回 true
。
- 例如,
0 === '0'
为false
,因为类型不同。
let a: number = 0;
let b: string = '0';
console.log(a == b); // true, 因为进行类型转换
console.log(a === b); // false, 因为类型不同
let obj1 = { name: 'Alice' };
let obj2 = { name: 'Alice' };
let obj3 = obj1;
console.log(obj1 == obj2); // false, 不同的引用
console.log(obj1 === obj2); // false, 不同的引用
console.log(obj1 === obj3); // true, 同一引用
在 Java 中,==
用于基本数据类型时比较值(如 int
, char
),用于对象时比较引用。
- 例如,
int a = 5; int b = 5; a == b
为true
,但对于对象,String a = "Hello"; String b = new String("Hello"); a == b
为false
,因为它们是不同的对象。
**没有 ===
**:Java 只有一种相等运算符 ==
,因此无法直接进行严格相等检查。对象的内容比较通常使用 .equals()
方法。
public class Main {
public static void main(String[] args) {
int x = 5;
int y = 5;
String str1 = new String("Hello");
String str2 = new String("Hello");
String str3 = str1;
System.out.println(x == y); // true, 基本类型比较值
System.out.println(str1 == str2); // false, 不同的引用
System.out.println(str1.equals(str2)); // true, 内容相等
System.out.println(str1 == str3); // true, 同一引用
}
}
在 Kotlin 中,==
表示结构相等(相当于 Java 中的 equals()
方法)。
- 例如,
val a = "Hello"; val b = "Hello"; a == b
为true
,因为它们的内容相等。
===
表示引用相等,检查两个引用是否指向同一个对象。
- 例如,
val a = "Hello"; val b = a; a === b
为true
,但val c = "Hello"; a === c
可能为false
(如果c
是不同的对象)。
fun main() {
val a = "Hello"
val b = "Hello"
val c = String("Hello")
println(a == b) // true, 结构相等
println(a === b) // true, 引用相等(在字符串常量池中)
println(a == c) // true, 结构相等
println(a === c) // false, 不同的引用
}
语句
if
语句
描述: 条件判断
异同点: 完全一致
代码示例:
if (condition) {
// 执行代码
}
for
循环
描述: 循环
异同点: 基本 for
循环,除 Kotlin 外,Java 和 TypeScript 一致。区别主要在于增强型 for
循环上:
- TypeScript: 使用
for...of
遍历可迭代对象。 - Java: 使用增强型
for
循环(for (type var : collection)
)遍历数组和集合。 - Kotlin: 使用
for (item in collection)
语法遍历数组和集合。
代码示例:
- 一般语法
for (let i = 0; i < 5; i++) {
// 执行代码
}
- TypeScript
// for of 循环
const array = [1, 2, 3, 4, 5];
for (const value of array) {
console.log(value); // 输出 1, 2, 3, 4, 5
}
- Java
// for each 循环
int[] array = {1, 2, 3, 4, 5};
for (int value : array) {
System.out.println(value); // 输出 1, 2, 3, 4, 5
}
// 对于集合
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
for (int value : list) {
System.out.println(value); // 输出 1, 2, 3, 4, 5
}
- Kotlin
// 带索引遍历
for ((index, item) in arrayListOf("a", "b", "c").withIndex()) {
println("$index - $item")// 0-a 1-b 2-c
}
// 包左包右
for (i in 1..10) {
println(i) // 12345678910
}
// 包左不包右
for (i in 1 until 10) {
println(i) // 123456789
}
// 降序-包左包右
for (i in 10 downTo 1) {
println(i) // 10987654321
}
// 跳步-包左包右
for (i in 1..10 step 2) {
println(i) // 13579
}
// 包左不包右
repeat(10) {
print(it) // 0123456789
}
// for in 循环
val array = arrayOf(1, 2, 3, 4, 5)
for (value in array) {
println(value) // 输出 1, 2, 3, 4, 5
}
// 对于集合
val list = listOf(1, 2, 3, 4, 5)
for (value in list) {
println(value) // 输出 1, 2, 3, 4, 5
}
while
循环
描述: 循环
异同点: 完全一致
代码示例:
while (condition) {
// 执行代码
}
do...while
循环
描述: 循环
异同点: 完全一致
代码示例:
do {
// 执行代码
} while (condition);
switch
语句
描述: 多情况判断
异同点: 除开 Kotlin 以外,长的一样。在 Kotlin 中,这被称之为 when
语句。
代码示例:
- 一般性语法
switch (value) {
case value1:
// 执行代码
break;
case value2:
// 执行代码
break;
default:
// 执行代码
}
- Kotlin 语法
val day = 2
when (day) {
1 -> {
println("Monday")
// 其他操作
println("It's the start of the week.")
}
2 -> {
println("Tuesday")
// 其他操作
println("Keep going!")
}
3 -> {
println("Wednesday")
// 其他操作
println("Halfway through the week!")
}
else -> {
println("Other day")
}
}
try...catch
语句
描述: 异常处理
异同点: 除了 Java 在错误类型声明有写法上的差异,其余完全一致
代码示例:
try {
// 执行可能抛出异常的代码
} catch (error) {
// 处理异常
}
函数
4.1 函数声明与调用
- Java:
public int add(int a, int b) {
return a + b;
}
- TypeScript:
function add(a: number, b: number): number {
return a + b;
}
- Kotlin:
fun add(a: Int, b: Int): Int {
return a + b
}
4.2 可选参数和默认参数
- Java: Java 不支持可选参数,可以通过方法重载实现类似效果。
public int add(int a, int b) {
return a + b;
}
public int add(int a) {
return a + 10; // 默认值
}
- TypeScript: 支持可选参数和默认参数。
function add(a: number, b: number = 10): number {
return a + b;
}
- Kotlin: 也支持默认参数。
fun add(a: Int, b: Int = 10): Int {
return a + b
}
4.3 rest 参数
- Java: 使用数组来实现。
public int sum(int... numbers) {
int total = 0;
for (int number : numbers) {
total += number;
}
return total;
}
- TypeScript: 使用
...
表示 rest 参数。
function sum(...numbers: number[]): number {
return numbers.reduce((total, n) => total + n, 0);
}
- Kotlin: 使用
vararg
来实现。
fun sum(vararg numbers: Int): Int {
return numbers.sum()
}
4.4 函数类型
- Java: 使用接口或 lambda 表达式。
interface IntOperation {
int operate(int a, int b);
}
- TypeScript: 可以直接定义函数类型。
type IntOperation = (a: number, b: number) => number;
- Kotlin: 直接使用函数类型。不过它的返回值是单箭头
->
而非 ts 的等号箭头=>
fun operate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
4.5 匿名函数
- Java: 使用 lambda 表达式。
IntOperation add = (a, b) -> a + b;
- TypeScript: 使用箭头函数。
const add: IntOperation = (a, b) => a + b;
- Kotlin: 使用 lambda 表达式。
val add: (Int, Int) -> Int = { a, b -> a + b }
4.6 函数重载
三者均支持函数重载,语法上略有差别:
- Java
public int add(int a, int b) { return a + b; }
public double add(double a, double b) { return a + b; }
- TypeScript
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
return a + b;
}
- Kotlin
fun add(a: Int, b: Int): Int { return a + b }
fun add(a: Double, b: Double): Double { return a + b }
Kotlin 可以通过 关键字 <span style="color: #FBBFBC">@JvmOverloads
修饰 <span style="color: #FBBFBC">constructor
生成多个构造。
class MyFrameLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
@JvmOverloads fun f(a: String, b: Int=0, c:String="abc"){ }
// 下面3个Java方法等价上面1个kotlin方法
void f(String a)
void f(String a, int b)
void f(String a, int b, String c)
OOP 相关(施工中 🚧)
5.1 对象
字面量对象
Java: 使用匿名内部类来创建字面量对象。
TypeScript: 使用对象字面量语法,直接定义具有属性和方法的对象。
Kotlin: 使用对象表达式,定义具有属性和方法的匿名对象。
- Java
public class Example {
public static void main(String[] args) {
// 使用匿名内部类
Object obj = new Object() {
@Override
public String toString() {
return "This is a literal object";
}
};
System.out.println(obj);
}
}
- TypeScript
const obj = {
name: "Example",
value: 42,
describe() {
return `${this.name}: ${this.value}`;
}
};
console.log(obj.describe()); // 输出: Example: 42
- Kotlin
fun main() {
val obj = object {
val name = "Example"
val value = 42
fun describe() = "$name: $value"
}
println(obj.describe()) // 输出: Example: 42
}
解构赋值
Java 没有解构赋值,Kotlin 和 TypeScript 使用不同的方法实现解构赋值。
数据类:数据类自动支持解构赋值。
data class Person(val name: String, val age: Int)
fun main() {
val person = Person("Alice", 30)
val (name, age) = person
println("Name: $name, Age: $age") // 输出: Name: Alice, Age: 30
}
Pair 和 Triple:这两个类也支持解构赋值。
fun main() {
val pair = Pair("Kotlin", 2023)
val (language, year) = pair
println("Language: $language, Year: $year") // 输出: Language: Kotlin, Year: 2023
}
自定义解构声明:可以在任意类中定义 componentN()
函数,使其支持解构赋值。
class Coordinates(val x: Int, val y: Int) {
operator fun component1() = x
operator fun component2() = y
}
fun main() {
val point = Coordinates(10, 20)
val (x, y) = point
println("X: $x, Y: $y") // 输出: X: 10, Y: 20
}
5.2 类
访问控制
语言 | 说明 | 举例 |
---|---|---|
TypeScript | *public: 默认可见,所有类可见。 |
- private: 仅对同一类可见。
- protected: 对同一类和子类可见。
- readonly: 用于只读属性。
|
class Example {
public publicField: number; // 所有类可见
protected protectedField: number; // 对同一类和子类可见
private privateField: number; // 仅对本类可见
}
|
|Java |* public: 对所有类可见。
- protected: 对同一包内的类和所有子类可见。
- private: 仅对同一类可见。
- 默认(包私有,no modifier): 仅对同一包内的类可见。
|
public class Example {
public int publicField; // 对所有类可见
protected int protectedField; // 对同一包和子类可见
private int privateField; // 仅对本类可见
int packagePrivateField; // 对同一包内可见
}
|
|Kotlin |* public: 默认可见,所有类可见。
- private: 仅对同一文件或类可见。
- protected: 对同一类和子类可见。
- internal: 仅对同一模块内可见。
|
class Example {
var publicField: Int = 0 // 所有类可见
protected var protectedField: Int = 0 // 对同一类和子类可见
private var privateField: Int = 0 // 仅对本类可见
internal var internalField: Int = 0 // 对同一模块内可见
}
|
5.3 接口
语言 | 说明 | 举例 |
---|---|---|
TypeScript | TypeScript 使用 interface 关键字定义接口,支持属性和方法的定义,且可以通过 extends 进行继承。 | *定义接口 |
interface Person {
name: string;
age: number;
}
const john: Person = {
name: "John",
age: 30
};
- 接口继承:
interface Employee extends Person {
employeeId: number;
}
const jane: Employee = {
name: "Jane",
age: 28,
employeeId: 1234
};
|
|Java |Java 使用 interface
关键字定义接口,方法通过 getter 访问,且通过 implements
关键字实现接口。 |* 定义接口:
public interface Person {
String getName();
int getAge();
}
- 接口继承:
public interface Employee extends Person {
int getEmployeeId();
}
public class Employee implements Person {
private String name;
private int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
public class Manager implements Employee {
private String name;
private int age;
private int employeeId;
public Manager(String name, int age, int employeeId) {
this.name = name;
this.age = age;
this.employeeId = employeeId;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public int getEmployeeId() {
return employeeId;
}
}
|
|Kotlin |Kotlin 使用 interface
关键字定义接口,属性使用 val
或 var
声明,且通过冒号 :
来实现接口。 |* 定义接口:
interface Person {
val name: String
val age: Int
}
class Employee(override val name: String, override val age: Int) : Person
- 接口继承:
interface Employee : Person {
val employeeId: Int
}
class Manager(override val name: String, override val age: Int, override val employeeId: Int) : Employee
|
空安全
6.1 非空断言
语言 | 说明 | 举例 |
---|---|---|
TypeScript | 使用 ! 运算符,可以告诉 TypeScript 编译器某个值不会是 null 或 undefined 。 |
const value: string | null = getValue();
const nonNullValue: string = value!; // 非空断言
|
|Java |Java 没有内置的非空断言运算符,但可以使用 **Optional**
类来处理可能为 null 的值。 |
Optional<String> optionalValue = Optional.ofNullable(getValue());
String nonNullValue = optionalValue.orElse("default");
|
|Kotlin |使用 **!!**
运算符,可以将一个可空类型转换为非空类型,如果为 null 则抛出异常。 |
val value: String? = getValue()
val nonNullValue: String = value!! // 如果 value 为 null,会抛出异常
|
6.2 空值合并
语言 | 说明 | 举例 |
---|---|---|
TypeScript | 使用 ?? 运算符,当左侧值为 null 或 undefined 时,返回右侧的值。 |
const value = null;
const result = value ?? 'default'; // result 为 'default'
|
|Java |Java 没有类似于 **??**
的运算符,但可以通过 **Optional**
的 **orElse**
方法实现类似功能。 |
String value = null;
String result = Optional.ofNullable(value).orElse("default"); // result 为 'default'
|
|Kotlin |使用 **?:**
运算符,当左侧值为 null 时,返回右侧的值。 |
val value: String? = null
val result = value ?: "default" // result 为 'default'
|
6.3 可选链
可选链运算符只会在属性访问链中遇到第一个 undefined
或 null
值时停止,并返回 undefined
。如果后续的属性访问链中还有其他的 undefined
或 null
值,不会继续进行访问。
Kotlin 没有 undefined
值。
Java 可以通过通过 **Optional**
的 map 来达到类似于可选链的效果。
语言 | 说明 | 举例 |
---|---|---|
TypeScript | 使用 ?. 运算符,可以安全地访问对象的属性或调用方法,如果路径中的某个值为 null 或 undefined ,则返回 undefined |
const obj = null;
const result = obj?.property; // result 为 undefined
|
|Java |Java 没有可选链运算符,但可以通过 **Optional**
来避免 **NullPointerException**
。 |
class Address {
String street;
}
class User {
Address address;
}
User user = null;
String street = Optional.ofNullable(user) // 如果 user 为 null,链式调用会停止
.map(User::getAddress)
.map(Address::getStreet)
.orElse("default street");
|
|Kotlin |和 TypeScript 比较类似。使用 ?.
运算符,可以安全地访问对象的属性或调用方法。 |
val obj: MyClass? = null
val result = obj?.property // result 为 null
|
泛型
7.1 一般性写法
泛型写法 在三种语言中都比较相似,但语法略有不同。这里以函数举例:
- TypeScript
function identity<T>(arg: T): T {
return arg;
}
- Kotlin
fun <T> identity(arg: T): T {
return arg
}
- Java
public <T> T identity(T arg) {
return arg;
}
7.2 泛型约束
泛型上界约束 的语法在 TS 和 Java 中使用 extends
,而 Kotlin 使用冒号和 where
。
- TypeScript:使用
extends
关键字来约束泛型类型:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
- Kotlin:使用
:
、where
关键字来约束泛型类型:
// 使用 where 来约束泛型类型
fun <T> loggingIdentity(arg: T) where T : CharSequence {
println(arg.length)
}
// 使用 : 来约束泛型类型
fun <T : Number> sum(a: T, b: T): Double {
return a.toDouble() + b.toDouble()
}
// 使用 where 进行多重约束
fun <T> process(value: T) where T : Comparable<T>, T : CharSequence {
// ...
}
- Java 使用
extends
关键字来约束泛型类型:
public <T extends CharSequence> void loggingIdentity(T arg) {
System.out.println(arg.length());
}
泛型下界约束 的语法在 Java 中使用 super
,而 Kotlin 使用冒号和 in
。TypeScript 不支持下界约束。
- Kotlin 使用
in
关键字定义下界约束,限制泛型类型必须是某个类或接口的超类。
fun addNumbers(list: MutableList<in Int>) {
list.add(1) // 可以添加 Int 类型
}
fun main() {
val numbers: MutableList<Number> = mutableListOf()
addNumbers(numbers)
println(numbers)
}
- Java 使用
super
关键字来定义下界约束,限制泛型类型必须是某个类或接口的超类。
import java.util.ArrayList;
import java.util.List;
class NumberList {
public static void addNumbers(List<? super Integer> list) {
list.add(1); // 可以添加 Integer 类型
}
public static void main(String[] args) {
List<Number> numbers = new ArrayList<>();
addNumbers(numbers);
System.out.println(numbers);
}
}
7.3 泛型默认值
泛型默认值 仅在 TypeScript 中支持,Kotlin 和 Java 不支持这一特性。
- TypeScript:TypeScript 支持泛型的默认值:
function identity<T = string>(arg: T): T {
return arg;
}
const result = identity(123); // result 的类型为 number
7.4 Kotlin 泛型进阶
7.4.1 reified
在 Kotlin 中,reified
是一个关键字。使用 reified
关键字,可以在 inline 函数中保留类型信息,从而在运行时使用该类型。
通过 reified
获取参数类型:
inline fun <reified T> printType(value: T) {
println("The type of value is: ${T::class.simpleName}") // 使用 reified 获取类型信息
}
fun main() {
printType(42) // 输出: The type of value is: Int
printType("Hello") // 输出: The type of value is: String
printType(3.14) // 输出: The type of value is: Double
}
在这个示例中,printType
函数是一个 inline 函数,使用了 reified
关键字。通过 T::class.simpleName
可以获取泛型参数的类型信息。
用于类型检查/过滤:
inline fun <reified T> List<*>.filterIsInstance(): List<T> {
return this.filterIsInstance<T>() // 使用 reified 进行类型过滤
}
fun main() {
val mixedList: List<Any> = listOf(1, "two", 3.0, 4)
val intList: List<Int> = mixedList.filterIsInstance<Int>()
println(intList) // 输出: [1, 4]
}
在此示例中,filterIsInstance
函数使用 reified
关键字来过滤给定列表中的元素,返回指定类型的子集。
使用类型安全的反射:
inline fun <reified T> createInstance(): T {
return T::class.constructors.first().call() // 使用 reified 创建实例
}
data class Person(val name: String = "John Doe")
fun main() {
val person: Person = createInstance() // 创建 Person 实例
println(person) // 输出: Person(name=John Doe)
}
在这个示例中,createInstance
函数通过 reified
关键字和反射机制创建了指定类型的实例。
7.4.2 out、in、Invariant
在 Kotlin 中,out
、in
和不带任何修饰符(也称为 Invariant)是用于定义泛型类型参数的三种方式。它们分别表示不同的协变和逆变的概念,影响泛型类型的可用性和类型安全性。
- Out(协变)
使用 out
修饰符的泛型类型参数表示协变。协变允许我们可以将泛型类型作为输出类型(返回值)。这意味着可以将子类型的对象赋值给父类型的变量。
示例:
interface Producer<out T> {
fun produce(): T
}
class StringProducer : Producer<String> {
override fun produce(): String {
return "Hello, Kotlin!"
}
}
fun main() {
val producer: Producer<Any> = StringProducer() // 可以将 Producer<String> 赋值给 Producer<Any>
println(producer.produce()) // 输出: Hello, Kotlin!
}
在这个例子中,Producer
接口的类型参数 T
被标记为 out
,这允许我们将 Producer<String>
赋值给 Producer<Any>
。
- In(逆变)
使用 in
修饰符的泛型类型参数表示逆变。逆变允许我们可以将泛型类型作为输入类型(参数)。这意味着可以将父类型的对象赋值给子类型的变量。
示例:
interface Consumer<in T> {
fun consume(item: T)
}
class StringConsumer : Consumer<String> {
override fun consume(item: String) {
println("Consuming: $item")
}
}
fun main() {
val consumer: Consumer<Any> = StringConsumer() // 可以将 Consumer<String> 赋值给 Consumer<Any>
consumer.consume("Hello, Kotlin!") // 输出: Consuming: Hello, Kotlin!
}
在这个例子中,Consumer
接口的类型参数 T
被标记为 in
,这允许我们将 Consumer<String>
赋值给 Consumer<Any>
。
- Invariant(不变)
不带任何修饰符的泛型类型参数表示不变。这意味着泛型类型参数既不能被视为协变也不能被视为逆变。我们只能使用完全相同的类型。
示例:
class Box<T>(val value: T)
fun main() {
val stringBox: Box<String> = Box("Hello")
// val anyBox: Box<Any> = stringBox // 这是不允许的,会导致编译错误
}
在这个例子中,Box<T>
是不变的。不能将 Box<String>
赋值给 Box<Any>
,因为它们是不同的类型。
模块化语法
语言 | 说明 | 举例 |
---|---|---|
TypeScript | TypeScript 使用 import 和 export 关键字来显式导入和导出模块。 |
// 导入
import { ModuleName } from './module';
// 全包导入
import * as Module from './module';
// 导出
export const variableName = 'value';
export function functionName() { }
// 默认导出
export default class ClassName { }
|
|Java |Kotlin 和 Java 使用 import
语句导入,但导出则依赖于类和函数的可见性(如 public
修饰符)。Java 中的类和接口默认是包私有的。要导出它们,可以使用 public
修饰符。 |
// 导入
import com.example.ModuleName;
// 全包导入
import com.example.*;
// 导出
public class ClassName { }
public static void functionName() { }
|
|Kotlin |Kotlin 和 Java 使用 import
语句导入,但导出则依赖于类和函数的可见性(如 public
修饰符)。Kotlin 没有显式的导出语法,因为所有公共类和函数默认都是可导出的。可以使用 public
修饰符来明确表示。Kotlin 导出导入的语法和 Java 是完全一致的。导出心智也是完全一致的。 |
// 导入
import com.example.ModuleName
// 全包导入
import com.example.*
// 导出
public val variableName = "value"
public fun functionName() { }
|
Kotlin 进阶语法
异步
回调
TypeScript 和 Kotlin 都支持高阶函数,因此也都支持回调函数。但回调地狱的问题导致两者均已不常使用这种方案。
Promise
TypeScript 和 Kotlin 相同点:
- 创建 Promise:
- 两者都可以使用类似的方式来创建 Promise 对象,通常通过提供一个执行器函数(executor function),该函数接收
resolve
和reject
回调。
- 两者都可以使用类似的方式来创建 Promise 对象,通常通过提供一个执行器函数(executor function),该函数接收
- 链式调用:
- 都支持使用
.then()
和.catch()
方法进行链式调用,以处理成功和失败的回调。
- 都支持使用
TypeScript 和 Kotlin 不同点:
- Promise 的创建:
- Kotlin 使用
kotlin.js.Promise
:
val promise = Promise<String> { resolve, reject ->
// 模拟异步操作
window.setTimeout({
resolve("Hello from Kotlin!")
}, 1000)
}
- TypeScript 使用原生 JavaScript 的
Promise
:
const promise = new Promise<string>((resolve, reject) => {
// 模拟异步操作
setTimeout(() => {
resolve("Hello from TypeScript!");
}, 1000);
});
- 使用 Promise:
- Kotlin:
promise.then { result ->
console.log(result) // 输出: Hello from Kotlin!
}.catch { error ->
console.error(error)
}
- TypeScript:
promise
.then(result => {
console.log(result); // 输出: Hello from TypeScript!
})
.catch(error => {
console.error(error);
});
- 类型系统:
- Kotlin:类型是显式的,使用
Promise<Type>
的形式定义返回值类型。 - TypeScript:类型也可以显式定义,但可以利用类型推断来简化代码。
- 错误处理:
- Kotlin 的 Promise 使用
.catch()
方法与 TypeScript 类似,但在 Kotlin 中通常习惯使用try-catch
来捕获异步调用中的异常。 - TypeScript 通过
.catch()
方法处理错误,语法更为直接。
- 运行环境:
- Kotlin 的 Promise 一般在 Kotlin/JS 环境下使用,依赖于 Kotlin 的标准库。
- TypeScript 直接运行在 JavaScript 环境中,具有更广泛的兼容性。
协程
异步或非阻塞程序设计是开发领域的重要部分。 创建服务器端应用、 桌面应用或者移动端应用时,都很重要的一点是, 提供的体验不仅是从用户角度看着流畅, 而且还能在需要时伸缩(scalable,可扩充/缩减规模)。
Kotlin 以一种灵活的方式解决了这个问题,在语言层面提供了协程支持, 而将大部分功能委托给库。
我们使用安卓教程中的一个例子来认识在安卓中它是如何被使用的:
假设我们有这样的一个方法,该方法为耗时方法且为同步执行:
fun makeLoginRequest(jsonBody: String): Result<LoginResponse>
如果在主线程执行 makeLoginRequest()
,则会导致刚刚提及的阻塞状态。这对我们来说是不可能接受的。以下为这一过程的安卓原生代码:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// Create a new coroutine to move the execution off the UI thread
viewModelScope.launch(Dispatchers.IO) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
}
我们可以做以下改进:
- 在 Kotlin 项目中引入依赖:
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")
}
- 改写代码,创建一个新的协程,然后在 I/O 线程上执行网络请求:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// Create a new coroutine to move the execution off the UI thread
viewModelScope.launch(Dispatchers.IO) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
}
viewModelScope
是预定义的CoroutineScope
,包含在ViewModel
KTX 扩展中。请注意,所有协程都必须在一个作用域内运行。一个CoroutineScope
管理一个或多个相关的协程。launch
是一个函数,用于创建协程并将其函数主体的执行分派给相应的调度程序。Dispatchers.IO
指示此协程应在为 I/O 操作预留的线程上执行。
login
函数按以下方式执行:
- 应用从主线程上的
View
层调用login
函数。 launch
会创建一个新的协程,并且网络请求在为 I/O 操作预留的线程上独立发出。- 在该协程运行时,
login
函数会继续执行,并可能在网络请求完成前返回。请注意,为简单起见,我们暂时忽略掉网络响应。
class LoginRepository(...) {
...
suspend fun makeLoginRequest(
jsonBody: String
): Result<LoginResponse> {
// Move the execution of the coroutine to the I/O dispatcher
return withContext(Dispatchers.IO) {
// Blocking network request code
}
}
}
withContext(Dispatchers.IO)
将协程的执行操作移至一个 I/O 线程,这样一来,我们的调用函数便是主线程安全的,并且支持根据需要更新界面。
makeLoginRequest
还会用 suspend
关键字进行标记。Kotlin 利用此关键字强制从协程内调用函数。
所有 **suspend**
函数都必须在协程中执行,和 TypeScript 对比的时候,我认为它的意义和 await 比较类似,因为所有的 await 都必须在 async 函数下执行。suspend 方法必须在协程中执行也正是体现了这一理念。
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// Create a new coroutine on the UI thread
viewModelScope.launch {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
// Make the network call and suspend execution until it finishes
val result = loginRepository.makeLoginRequest(jsonBody)
// Display result of the network request to the user
when (result) {
is Result.Success<LoginResponse> -> // Happy path
else -> // Show error in UI
}
}
}
}
login
函数现在按以下方式执行:
- 应用从主线程上的
View
层调用login()
函数。 launch
在主线程上创建新协程,然后协程开始执行。- 在协程内,调用
loginRepository.makeLoginRequest()
现在会挂起协程的进一步执行操作,直至makeLoginRequest()
中的withContext
块结束运行。 withContext
块结束运行后,login()
中的协程在主线程上恢复执行操作,并返回网络请求的结果。
延迟初始化 lateinit, lazy
lateinit, lazy 是 Kotlin 中两种实现延迟初始化的方式.
- lateinit 只能用于 var 标示的变量,by lazy 只能用于 val 标示的变量。
- by lazy 只在第一次调用时进行初始化。
class User {
private lateinit var name: String
private val password: String by lazy {
println("lazy init")
"admin"
}
fun setName(name: String) {
this.name = name
}
fun show() {
println("name = $name")
println("--------------------")
println("第一次访问 password = $password")
println("第二次访问 password = $password")
}
}
fun main() {
val user = User()
user.setName("tomcat")
user.show()
}
// 输出结果
// name = tomcat
// --------------------
// lazy init
// 第一次访问 password = admin
// 第二次访问 password = admin
委托 by
/**
* 定义 Base 接口
*
*/
interface Base {
fun say()
}
/**
* 定义 Base 接口的实现类,并实现 say() 方法
*/
class BaseImpl : Base {
override fun say() {
println("BaseImpl say()")
}
}
/**
* 定义 BaseProxy 类,并实现了 Base 接口,
* 关键字 by 将接口 Base 中所有的方法都委托给 base 对象,这样 BaseProxy 类就不需要去实现接口 Base 中的方法了,
* 简化了实现接口时要实现其中的方法。
*/
class BaseProxy(base: Base) : Base by base
fun main() {
val baseImpl = BaseImpl()
// 调用的是 BaseImpl 中的 say() 方法
BaseProxy(baseImpl).say()
}
// 输出结果
// BaseImpl say()
扩展函数
扩展函数可以给类额外添加成员函数,通过「类名.方法名」方式实现。
/**
* 定义扩展函数,给 File 添加 readText() 方法
*/
fun File.readText(charset: Charset): String = readBytes().toString(charset)
// 调用
fun main() {
val file = File("/Users/xing/Desktop/Android.md")
print(file.readText(Charset.forName("utf-8")))
}
Lambda 表达式
Lambda 表达式是一种匿名函数,可以在 Kotlin 中简洁地表示函数类型。它们用于实现高阶函数的功能,使得函数式编程变得更加方便。以下是对 Lambda 表达式的详细介绍以及一些示例。
Kotlin 中的 Lambda 表达式语法如下:
val lambdaName: Type = { parameters -> body }
lambdaName
是 Lambda 表达式的名称(可选)。Type
是 Lambda 表达式的类型(可选)。parameters
是输入参数。body
是表达式或语句。
示例:
- 简单的 Lambda 表达式
val square: (Int) -> Int = { x -> x * x }
println(square(5)) // 输出: 25
- 无参数的 Lambda 表达式
val greet: () -> String = { "Hello, World!" }
println(greet()) // 输出: Hello, World!
- 多参数的 Lambda 表达式
val add: (Int, Int) -> Int = { a, b -> a + b }
println(add(3, 4)) // 输出: 7
- 作为高阶函数的参数
fun operateOnNumbers(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
val result = operateOnNumbers(5, 3, add)
println(result) // 输出: 8
- 使用 Lambda 表达式作为参数
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
// 使用 Lambda 表达式过滤列表
val evenNumbers = numbers.filter { it % 2 == 0 }
println(evenNumbers) // 输出: [2, 4]
}
- 多个 Lambda 表达式
在某些情况下,可以将多个 Lambda 表达式作为参数传递给高阶函数。
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
fun main() {
val sum = calculate(10, 5) { x, y -> x + y }
val difference = calculate(10, 5) { x, y -> x - y }
println("Sum: $sum") // 输出: Sum: 15
println("Difference: $difference") // 输出: Difference: 5
}
lambda 表达式具有如下特性:
- 类型推断:Kotlin 可以自动推断 Lambda 表达式的类型,因此可以省略参数类型。
- 单个参数的简化:如果 Lambda 表达式只有一个参数,可以省略参数名称,使用
it
作为默认名称。
val double = { it: Int -> it * 2 }
println(double(4)) // 输出: 8
- 多行 Lambda 表达式:如果 Lambda 表达式体包含多行代码,需要使用
{}
包围,并且最后一行是返回值。
注解
Java 和 Kotlin 的注解机制相似,都是通过定义注解类型并在代码中使用,而 TypeScript 则通过装饰器实现类似的功能。它们的共同点在于都可以用于提供元数据,而不同之处在于实现机制、语法和使用场景。
语言 | 作用 | 举例 |
---|---|---|
TypeScript | TypeScript 中没有直接的注解机制,但可以通过装饰器(decorators)实现类似的功能。装饰器通常用于类和类成员,并提供元数据。#### 作用 |
- 元数据:为类、方法、属性提供元数据。
- 框架支持:常用于 Angular 等框架进行依赖注入和元编程。
|
function MyAnnotation(value: string) {
return function (target: any) {
target.annotationValue = value;
};
}
@MyAnnotation("Hello, TypeScript!")
class MyClass {
static getAnnotation() {
return (this as any).annotationValue;
}
}
console.log(MyClass.getAnnotation()); // 输出: Hello, TypeScript!
|
|Java |元数据:为代码元素(如类、方法、字段等)提供额外信息。运行时反射:可以在运行时通过反射获取注解信息。编译时检查:一些注解可以用于编译器进行静态检查。框架支持:许多框架(如 Spring)使用注解来进行依赖注入和配置。 |
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
String value();
}
@MyAnnotation("Hello, Java!")
public class MyClass {
public static void main(String[] args) {
MyAnnotation annotation = MyClass.class.getAnnotation(MyAnnotation.class);
System.out.println(annotation.value()); // 输出: Hello, Java!
}
}
|
|Kotlin |元数据:与 Java 类似,Kotlin 的注解用于提供元数据。运行时反射:Kotlin 的注解也可以在运行时通过反射获取。简化语法:Kotlin 的注解语法更简洁。 |
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class MyAnnotation(val value: String)
@MyAnnotation("Hello, Kotlin!")
class MyClass {
companion object {
@JvmStatic
fun main(args: Array<String>) {
val annotation = MyClass::class.annotations.find { it is MyAnnotation } as MyAnnotation
println(annotation.value) // 输出: Hello, Kotlin!
}
}
}
|
反射
反射是一种强大的编程特性,允许程序在运行时检查和操作其自身的结构和行为。通过反射,开发者可以动态地获取类的信息、方法、字段等,并在运行时创建和操作对象。
Java 和 Kotlin 都提供了比较完整的反射机制,允许开发者在运行时操作类型信息,而 TypeScript 的反射能力相对较弱,主要依赖 JavaScript 的特性和装饰器来实现。
语言 | 说明 | 举例 |
---|---|---|
TypeScript | 没有内置反射 API:TypeScript 本身没有完整的反射机制,但可以通过装饰器和元数据实现某种程度的反射。运行时类型信息:可以使用 typeof 和 instanceof 来检查类型。与 JavaScript 结合:TypeScript 的反射能力依赖于 JavaScript 的反射机制。 |
function MyDecorator(target: any, propertyKey: string) {
console.log(`Decorated method: ${propertyKey}`);
}
class ReflectionExample {
@MyDecorator
sayHello() {
console.log("Hello, TypeScript!");
}
}
const example = new ReflectionExample();
example.sayHello(); // 输出: Decorated method: sayHello
// 输出: Hello, TypeScript!
|
|Java |完整的反射机制:Java 提供了完整的反射 API,可以检查类、方法、字段等。运行时类型信息:允许在运行时获取类型信息和修改对象的行为。动态代理:可以创建动态代理以实现 AOP(面向切面编程)。 |
import java.lang.reflect.Method;
public class ReflectionExample {
public void sayHello() {
System.out.println("Hello, Java!");
}
public static void main(String[] args) throws Exception {
ReflectionExample example = new ReflectionExample();
Method method = example.getClass().getMethod("sayHello");
method.invoke(example); // 输出: Hello, Java!
}
}
|
|Kotlin |Kotlin 反射库:Kotlin 提供了反射支持,语法更简洁。类型安全:Kotlin 的反射 API 设计更加注重类型安全。扩展功能:可以通过扩展函数轻松操作 Kotlin 特性,如扩展属性和函数。 |
import kotlin.reflect.full.declaredFunctions
class ReflectionExample {
fun sayHello() {
println("Hello, Kotlin!")
}
}
fun main() {
val example = ReflectionExample()
val kClass = example::class
val function = kClass.declaredFunctions.find { it.name == "sayHello" }
function?.call(example) // 输出: Hello, Kotlin!
}
|
开发感受
Kotlin 是高级的 Java。
Java 和 Kotlin 很适合写 OOP 代码。Kotlin 比 Java 适合写面向过程的代码。
顺便再提一嘴 ArkTS,这玩意就是把 TS 变成了适合写 OOP 的样子,因此 TS 不是 Kotlin 的竞品,ArkTS 才是。