Kotlin 朱涛-1 基础语法 变量 类型 函数 流程控制
目录
01 | Kotlin基础语法:正式开启学习之旅
开发环境
推荐安装 IntelliJ IDEA,可以通过 File -> Project Structure -> SDKs -> +
选择第三方提供的 OpenJDK 。
变量
Kotlin 当中应尽可能避免使用 var(可变变量,variable),尽可能多地使用 val(不可变变量,value),它相当于 Java 里面的 final
变量
Java 中的 field 有默认值,局部变量没有默认值
Kotlin 的所有变量都没有默认值
Kotlin 函数参数默认是 val 类型,所以参数前不需要写 val 关键字(也不能写)。
Java 中函数的参数默认是可修改的,因为默认没使用 final 修饰(但可以写)。Kotlin 这样设计的原因是,保证了参数不会被修改。
常量
Kotlin 新增了修饰常量的 const 关键字。
Kotlin 的常量必须声明在 伴生对象 或者「top-level」中,因为常量是静态的。
Kotlin 中只有基本类型和 String 类型可以声明成常量。Java 中任何类型的对象,都可以声明成常量。
Kotlin 中的常量指的是「compile-time constant 编译时常量」,它的意思是「编译器在编译的时候就知道这个东西在每个调用处的实际值」,因此可以在编译时直接把这个值硬编码到代码里使用的地方。
而非基本类型或 String 类型的变量,可以改变对象内部的值,这样这个变量就不是常量了。
public static final User user = new User(123, "abc"); // Java 的常量不是真正意义上的常量
user.name = "bqt"; // 可以改变对象内部的值
基础类型
基础类型包括数字类型、布尔类型、字符类型
,以及这些类型组成的数组
。
一切皆对象
Java 的类型系统并不是完全面向对象的,因为它存在原始类型,而原始类型并不属于对象。
- 原始类型(Primitive Types):开销小、性能高,但不是对象
- 包装类型(Wrapper Type):开销大、性能差,但它是对象,可以很好地发挥面向对象的特性
而 Kotlin 从语言设计的层面上就规避了这个问题,类型系统是完全面向对象的,Kotlin 中没有原始类型
这个概念。在 Kotlin 里,一切皆对象。
val i: Double = 1.toDouble() // 1 是对象,所以可以调用它的成员方法
空安全
Kotlin 强制要求开发者在定义变量的时候,指定这个变量是否可能为 null。可为空的变量
无法直接赋值给不可为空的变量
。
数字类型
Kotlin 和 Java 的数字类型基本是一致的,包括它们对数字字面量
的定义方式。
val int = 1 // 整数默认会被推导为 Int 类型
val long = 1234567L // 需要使用 L 后缀
val double = 13.14 // 小数默认会被推导为 Double
val float = 13.14F // 需要使用 F 后缀,也可以使用小写 f
val hexadecimal = 0XAB // 使用 0x 来代表十六进制字面量,也可以使用小写 0x ---> 16 * 10 + 11
val binary = 0B0101 // 使用 0b 来代表二进制字面量,也可以使用小写 0b ---> 1*4+1*1
println("$float, $hexadecimal, $binary") // 13.14, 171, 5
Java 可以隐式转换数字类型,而 Kotlin 必须显式转换数字类型。
目的是,让我们代码的可读性更强,将来也更容易维护。
int i = 100;
long j = i; // 在 Java 中,可以直接把 int 类型赋值给 long 类型,编译器会自动做类型转换
val i = 100
val j: Long = i // 编译器报错!在 Kotlin 里,必须显式转换
val j: Long = i.toLong() // 编译通过
布尔类型 Boolean
Kotlin 中布尔类型的变量只有两种值,分别是 true 和 false。布尔类型支持与或非
逻辑操作。
val isTrue: Boolean = true
字符类型 Char
Char 用于代表单个的字符
,比如'A'、'B'、'C',字符用单引号
括起来。
val c: Char = 'A'
val i: Int = c // 编译器报错,不支持数字类型隐式转换
val i: Int = c.toInt() // 编译通过
字符串 String
和 Java 一样,Kotlin 中的字符串也是不可变的。Kotlin 中可以使用字符串模板
。
val name = "Kotlin"
print("Hello $name!") // 直接在字符串中访问变量
print("Hello ${name}xxx!") // 有时需要使用花括号将变量括起来
Kotlin 中可以用三个双引号
来表示一个原始字符串,用于存放复杂的多行文本。
val s = """
当我们的字符串有复杂的格式时,
原始字符串非常的方便,
因为它可以做到所见即所得。
"""
print(s)
数组
- 在 Kotlin 当中,一般使用
arrayOf()
来创建数组,同时,Kotlin 编译器也会根据传入的参数进行类型推导
- Java 中的
数组和集合
的操作是不一样的,而 Kotlin 中是统一的
val arrayInt: Array<Int> = arrayOf(1, 2, 3) // 整型数组,编译成 Java 后是 Integer[]
val arrayString: Array<String> = arrayOf("apple", "pear") // 字符串数组
val intArray: IntArray = intArrayOf(1, 2, 3) // 编译成 Java 后是 int[] 数组
Array
和 IntArray 的区别是:前者会被装箱(Integer),后者不会被装箱(int)
Kotlin 中的数组 Array
是一个拥有泛型的类,但失去了协变 (covariance) 的特性,就是子类数组对象不能赋值给父类的数组变量。
val strs: Array<String> = arrayOf("a", "b", "c")
val anys: Array<Any> = strs // compile-error: Type mismatch
而这在 Java 中是可以的
String[] strs = {"a", "b", "c"};
Object[] objs = strs; // success
集合
Kotlin 和 Java 一样有三种集合类型:List、Set 和 Map,它们的含义分别如下:
- List 以固定顺序存储一组元素,元素可以重复。
- Set 存储一组互不相等的元素,通常没有固定顺序。
- Map 存储 键值对 的数据集合,键互不相等,但不同的键可以对应相同的值。
对于协变的支持与否,List 和数组刚好反过来了:
val strs: List<String> = listOf("a", "b", "c")
val anys: List<Any> = strs // success
List<String> strList = new ArrayList<>();
List<Object> objList = strList; // compile error: incompatible types
数组存在的理由:基本类型 (int[]、float[]) 的数组不用自动装箱,性能好一点。不过 Kotlin 中要用专门的基本类型数组类,IntArray FloatArray LongArray,才可以免于装箱。
Kotlin 中的 Map 除了可以使用 get/put 获取/修改对应的值,还可以使用方括号的方式。这里用到了「操作符重载」的知识,实现了和数组一样的「Positional Access Operations」
Kotlin 还引入了一个新的容器类型 Sequence,它和 Iterable 一样用来遍历一组数据并可以对每个元素进行特定的处理。
sequenceOf("a", "b", "c")
listOf("a", "b", "c").asSequence()
generateSequence(0) { it + 1 } // lambda 表达式负责生成第二个及以后的元素,it 表示前一个元素
可见性修饰符
Kotlin 相比 Java 少了一个 default 「包内可见」修饰符,多了一个 internal「module 内可见」修饰符。
Java 中没写可见性修饰符时,表示包内可见,只有在同一个 package 内可以引用。Kotlin 中如果不写可见性修饰符,就表示 public。
在 Android 的官方 sdk 中,有一些方法只想对 sdk 内可见,不想开放给用户使用。为了实现这个特性,会在方法的注释中添加一个 Javadoc 方法 @hide
,用来限制客户端访问。但这种限制不太严格,可以通过反射访问到限制的方法。
针对这个情况,Kotlin 引进了一个更为严格的可见性修饰符:internal。
这里的 module 具体指的是一组共同编译的 kotlin 文件,常见的形式有:Android Studio 里的 module(包括引入的 SDK),Maven 里的 project。
为什么弃用 default 「包内可见」修饰符:
- Kotlin 鼓励创建 top-level 函数和属性,一个源码文件可以包含多个类,使得 Kotlin 的源码结构更加扁平化,包结构不再像 Java 中那么重要。
- 为了代码的解耦和可维护性,module 越来越多、越来越小,使得 internal 已经可以满足对于代码封装的需求。
Java 中 protected 表示包内可见 + 子类可见。Kotlin 相比 Java protected 的可见范围收窄了,Kotlin 中不再有「包内可见」的概念。
- Java 中的 private 表示类中可见,作为内部类时对外部类可见(用不用 static 修饰都可见)。
- Kotlin 中的 private 表示类中或所在文件内可见,作为内部类时对外部类不可见(用不用 inner 修饰都不可见)。
Java 中一个文件只允许一个外部类,所以 class 和 interface 不允许设置为 private,因为声明 private 后无法被外部使用,这样就没有意义了。
Kotlin 允许同一个文件声明多个 class 和 top-level 的函数和属性,所以 Kotlin 中允许类和接口声明为 private,因为同个文件中的其它成员可以访问。
函数
函数声明 fun
//关键字 函数名 参数类型 返回值类型
//↓ ↓ ↓ ↓
fun helloFunction(name: String): String {
return "Hello $name !"
}
Kotlin 可以定义「top-level declaration」的函数。
顶级函数属于包,不同包名下可以定义相同方法声明的 顶级函数,使用时,IDE 会自动加上包前缀来区分。
单一表达式函数
函数体只有一行代码
- 可以省略函数体的花括号,直接使用
=
来连接 - 表达式中的
return
需要去掉 - 返回值的类型也可以省略
fun helloFunction(name: String) = "Hello $name !"
函数调用
Kotlin 支持命名参数,它允许我们在调用函数的时候传入形参的名字
,这样的代码可读性更强。
helloFunction("Kotlin")
helloFunction(name = "Kotlin") // 只能是形参的名字 name
Kotlin 支持参数默认值,在参数较多的情况下有很大的优势。
fun createUser(
name: String,
age: Int,
count: Long = 0L,
size: Int = 0,
) = println("name=$name age=$age count=$count size=$size")
createUser("bqt", 30)
createUser("bqt", 30, 10) // name=bqt age=30 count=10 size=0
createUser(age = 30, size = 10, name = "bqt")
流程控制
在 Kotlin 当中,流程控制主要有 if、when
、for、 while。
if
Kotlin 中的 if,除了可以作为程序语句(Statement),它还可以作为表达式(Expression)来使用。
val i = 1
val message = if (i > 0) "Big" else "Small"
var text: String? = null
val i = if (text != null) text.length else 0
val j = text?.length ?: 0 // Elvis 表达式
when
类似 Java 里的 switch case 语句。
val i: Int = 1
when (i) {
1 -> print("一")
2 -> print("二")
else -> print("不是一也不是二")
}
when 也可以作为表达式,为变量赋值。此时要求它里面的逻辑分支必须是完整的。
val i: Int = 1
val message = when (i) {
1 -> "一"
2 -> "二"
else -> "i 不是一也不是二" // 如果去掉这行,会报错
}
while 与 for
Kotlin 的 for 语句更多的是用于迭代
数组、集合、区间。
val array = arrayOf(1, 2, 3)
for (i in array) {}
val oneToThree = 1..3 // 代表 [1, 3],不能定义【逆序区间】
for (i in oneToThree) {}
val reverse = 6 downTo 0 step 2 // 步长为 2 的逆序区间,即 6 4 2 0
val list = listOf("a", "b", "c")
for (i in 0..list.size - 1) { println(i) } // 0 1 2 --- 不建议
for (i in list.indices) { println(i) } // 0 1 2 --- 建议
list.indices.forEach { println(it) } // 0 1 2 --- 建议
注意,逆序区间需要使用
downTo
来定义,使用..
定义的逆序区间无法遍历到任何元素。
小结
让开发者写出简洁易懂的代码:
- 支持类型推导
- 代码末尾不需要分号
- 字符串模板
- 原始字符串
- 单一表达式函数
- 函数参数支持默认值
- if 和 when 可以作为表达式
在语言设计的层面规避错误:
- 强制区分
可为空类型
和不可为空类型
,规避空指针异常 - 推崇不可变性,
- 基础类型不支持
隐式类型转换
,避免隐藏问题 - 数组访问行为与集合统一
- 函数调用支持命名参数,提高可读性
- when 表达式强制要求逻辑分支完整,避免逻辑漏洞
2018-02-11
本文来自博客园,作者:白乾涛,转载请注明原文链接:https://www.cnblogs.com/baiqiantao/p/8442691.html