其实在刚接触 Kotlin 的时候,我心里是拒绝的,因为看习惯了 Java 的代码,以致于看 Kotlin 代码时有一股极大的别扭感,让我不想去学习(其实在我学 C 语言的时候也是这个感受)。可是没办法呀,谁叫这货是 Android 官方开发语言呢,再加上如今的招聘要求多多少少也要会 Kotlin 了,因而只能硬着头皮上咯。java
相比较 Java ,Kotlin 没有了基本数据类型,能够看做是 Java 的基本类型包装类拿了过来部分改了名字,以下:程序员
Java | Kotlin |
---|---|
byte | Byte |
short | Short |
int | Int |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Char |
另外字符串是支持模板的,看下面:数组
fun main() {
val hello = "Hello"
println("$hello, World!")
println("length = ${hello.length}")
}
// 输出:Hello, World!
// length = 5
复制代码
Kotlin 中没有了 [] 进行数组声明或建立,8 种基本数据类型有本身的数组类型,以下:安全
Java | Kotlin |
---|---|
byte[] | ByteArray |
short[] | ShortArray |
int[] | IntArray |
long[] | LongArray |
float[] | FloatArray |
double[] | DoubleArray |
boolean[] | BooleanArray |
char[] | CharArray |
那引用类型呢?例如咱们要使用一个 String 数组,咱们须要用 Array<T>
这么一个类,泛型表明数组所属类型。同理,基本数据类型也能够用 Array<T>
来声明,如 Array<Int>
,Array<Boolean>
等等。bash
初始化既能够用它们的构造函数,也能够用 Kotlin 提供的全局函数,arrayOf()
,emptyArray()
,前者接收一个可变参数,后者则是一个空数组。闭包
public 、protected 、private 和 internal 做为 4 大访问权限,去除了 default 。ide
public 做为默认访问权限,所以不少时候咱们能够不用写上 public 。函数
protected 相较于 Java 不变。学习
private 除了能够修饰类级别,其他不变。为何能够修饰类呢?这是由于 Kotlin 中一个文件能够声明 1 个以上的类,对这些类加上 private 则表示仅能够在当前文件中使用。ui
internal 表示模块级别的访问权限,被 internal 标识则表示仅在当前模块可见,这在模块封装上是颇有帮助的。
Kotlin 中,类、字段和函数默认是 final 的,抽象类和接口除外。所以若是咱们想要它们变得可继承,须要在前面加上 open 关键字。
对于普通类,若是想要字段或者函数可继承,仅在字段或函数上加 open 是不够的,由于这时候类仍是不可继承的,所以类也须要加上 open ,这时字段和函数才真正可继承。
若是不想一个已 open 的字段或函数可继承,能够用 final 关键字进行修饰。
Kotlin 中将类型进行了后置,什么意思呢?举个栗子:
// Java 中咱们这样声明变量和函数
public final class Person {
public final String name;
public int age;
public final void talk(String str) {
int temp = 0
}
}
// Kotlin 中等价于
class Person {
val name: String? = null
var age: Int = 0
fun talk(str: String?): Unit {
var temp = 0
}
}
复制代码
首先 Kotlin 中是能够省略分号的,固然前提是你一个语句一行,若是一行有两个语句的话就不能省略了,正常状况下我们都是一行一语句的,接着来分析上面的代码。
变量:
因为类型后置,所以 Kotlin 使用关键字 val 和 var 来表示变量,val (value) 表示只读变量,在第一次赋值后就不可修改了;var (variable) 就是咱们在 Java 中经常使用的变量了。
在 Kotlin 中,成员变量是没有默认值的,须要咱们手动赋值。
另外,Kotlin 是支持类型推断的,在声明变量时,能够不用写类型,前提是有提供初始值。尽管如此,Kotlin 仍然是强类型语言,类型不匹配照样会报错,例如 var age = 0
,在这个声明中,尽管咱们没有显式提供类型,但初始值代表了这个变量就是一个 Int 变量(整型默认为 Int),所以后续咱们只能赋 Int 值。
函数:
因为类型后置,所以 Kotlin 使用关键字 fun 来表示函数,而且参数列表也遵循类型后置规则,Kotlin 中函数的参数是能够有默认值的,如:
fun setName(name: String = "aaronzzx"): Unit {
// ...
}
复制代码
这个 : Unit 是啥?Unit 至关于 Java 的 void ,: Unit 表示返回 Unit 类型,至关于没有返回值,固然更多时候咱们不须要写上这个多余的东西,上面的代码等价于:
fun setName(name: String = "aaronzzx") {
// ...
}
复制代码
函数也能够进行类型推断,这里指返回值推断,仅在简写函数时可用,举个栗子:
fun getAge(): Int {
return age;
}
// 等价于
fun getAge() = age
// 当咱们的函数只有 1 个语句时,即便没有返回值,也能够简写。
// 前提这个语句不是赋值语句,否则就 "=" 冲突了
fun function() {
operate()
}
// 等价于
fun function() = operate()
// 拥有可变参数的函数
fun function(vararg strs: String) {
for (string in strs) {
println(string)
}
}
// 参数为接口或抽象类
fun initView() {
HttpUtils.request(object: Callback {
override fun onSuccess() {
// ...
}
override fun onFailure() {
// ...
}
})
}
复制代码
Ps:你可能有发现类型后面跟了个 ?
,这个表示此变量可被赋值 null ,若是不加问号是没办法赋值 null 的,这和 Kotlin 的“空安全”有关,后面会说。
Kotlin 的构造函数在第一次看到可能会优势懵逼,先来看下与 Java 的对比:
// Java
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name) {
this.name = name;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
// Kotlin
class Person(
private var name: String? = null,
private var age: Int = 0
)
// 等价于
class Person(
name: String? = null,
age: Int = 0
) {
private var name: String? = name
private var age: Int = age
init {
// 可选
}
}
// 等价于
class Person() {
private var name: String? = null
private var age: Int = 0
constructor(name: String?): this() {
this.name = name
}
constructor(name: String?, age: Int): this() {
this.name = name
this.age = age
}
}
复制代码
在这里能够看到,Kotlin 具备碾压性的优点,对于须要重载构造函数,Kotlin 最少可用 4 行代码搞定,接下来分析。
首先 Kotlin 能够省略类体,由于主构造函数被设定在了类名上,主函数能够有参数,也能够没有。若是有参数,能够在参数名前面加关键字 val 或 var ,表示将这个参数做为成员变量,在关键字前面能够用访问权限或注解进行修饰。
init 表示主构造函数的函数体,若是没有特殊用途能够被省略。
constructor 表示次构造函数,在拥有主构造函数的状况下须要显示调用主构造函数。
在使用 Java 时,对于 NullPointerException 真的是迫不得已,一不当心就报空指针异常,而后程序就 Crash 了。所以咱们只能常常写以下的判空代码:
public String getMobile(Response response) {
String mobile = null;
if (response != null) {
Data data = response.getData();
if (data != null) {
mobile = data.getMobile();
}
}
return mobile;
}
复制代码
或许是知道 Java 程序员处于水深火热之中,Kotlin 添加了空安全这一语言特性,使用 ?
对可空变量进行标识,而没被 ?
标识的变量呢?理所固然的是不会为空了,因而咱们对于不为空的变量就不用写繁琐的判空代码了,由于它不会出现空指针异常了。
对于可为空的变量,咱们仍是须要进行判空的,可是形式不与 Java 同样,因而上面的代码能够这么写:
fun getMobile(response: Response?): String? {
return response?.data?.mobile
}
复制代码
?.
表示若是当前对象不为空就调用,与 Java 相比,简洁了好多。
即 companion object ,这是个啥呢?看下面这段代码,你应该就明白了。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val url = intent.getStringExtra(EXTRA_URL)
// ...
}
companion object {
private const val EXTRA_URL = "EXTRA_URL"
fun start(context: Context, url: String) {
val intent = Intent(context, MainActivity::class.java)
intent.putExtra(EXTRA_URL, url)
context.startActivity(intent)
}
}
}
复制代码
说白了,这东西就至关于 Java 的 static ,只是 Kotlin 中没有 static 这个概念,因此若是咱们要声明静态变量或者静态函数,就须要用到 companion object 。
另外上面的代码,可能有些地方你看起来不太明白,这里说一下。
继承与实现,统统使用 :
操做符,不一样的类或接口用 ,
隔开,而且须要显式调用父类构造函数。
首先 Kotlin 中建立对象是省略 new 关键字的,直接用构造函数便可。
Java 中的 @Override
注解在 Kotlin 中变成了关键字 override ,重写必不可少。
对于无参的 getter 函数,能够以变量名的形式书写,例如 getIntent()
就是 intent
。
const val 关键字用来声明常量,仅在 object 、companion object 和 顶层变量 中可用。
object 匿名内部类和单例关键字,若是要建立一个单例类,只需这么写。
object Singleton {
fun exec() {
// ...
}
}
class Test {
fun function() {
// 尽管看起来有点像静态调用,但这就是单例
Singleton.exec()
}
}
复制代码
顶层变量,用代码比较直观,以下:
const val TAG = "当前文件"
class MainActivity : AppCompatActivity() {
// ...
}
复制代码
顶层变量 属于文件,不属于类或接口等等。
在 Kotlin 中分支语句属于表达式,所以咱们能够这么写代码:
fun function(type: Int) {
// if-else
var str = if (type == 1) {
"Hello, World!"
} else if (type == 2) {
"Goodbye!"
} else ""
// switch ,在 Kotlin 中关键字变为 when
str = when (type) {
1 -> "Hello, World!"
2 -> "Goodbye!"
else -> ""
}
// 或者 when 不带参数
str = when {
type == 1 -> "Hello, World!"
type == 2 -> "Goodbye!"
else -> ""
}
}
复制代码
当分支语句做为表达式时,除非你的条件已经包括全部状况了,不然必定要有 else 。
while 和 do-while 相较于 Java 不变,for 有改动,举个栗子:
fun function() {
// for (int i = 0; i < 10; i++)
// 打印的 i 值 是同样的
for (i in 0..9) {
println(i)
}
for (i in 0 until 10) {
println(i)
}
// 下面这段将依次打印 10-0
for (i in 10 downTo 0) {
println (i)
}
// 还能够控制步长
// 依次打印 0, 2, 4, 6, 8, 10
for (i in 0..10 step 2) {
println(i)
}
}
复制代码
对于任何类型,咱们均可以擅自给他添加新的函数,而后就能够用这个类对象调用咱们自定义的扩展函数了,直接看代码:
fun String.suffix(suffix: String): String {
return this + suffix
}
fun function() {
val str = "Hello"
val newStr = str.suffix(", World!")
println(str)
println(newStr)
}
// 将依次输出:Hello
// Hello, World!
复制代码
高阶函数是将函数用做参数或返回值的函数。
在这里咱们会接触到一个东西:闭包, 闭包就是可以读取其余函数内部变量的函数。
class Operator {
fun add(a: Int, b: Int) = a + b
fun minus(a: Int, b: Int) = a - b
fun change( a: Int, b: Int, closure: (Int, Int) -> Int
): String {
return "${closure(a, b)}"
}
}
fun main() {
val operator = Operator()
val add: (Int, Int) -> Int = operator::add
val str1 = operator.change(2, 2, add)
val str2 = operator.change(5, 2) { a: Int, b: Int ->
a - b
}
println("str1 = $str1")
println("str2 = $str2")
}
// 输出:str1 = 4
// str2 = 3
复制代码
从 main 函数中,能够看到变量 add 拥有函数类型 (Int, Int) -> Int ,这意味着这个变量只能被函数赋值,对象名::add
表示拿到这个函数的函数类型。
接着看下面的 change 函数,这个大括号里面的代码其实就是闭包,虽然看起来好像是从新定义了一个函数,当一个函数的参数列表最后一个参数是一个闭包时,能够单独将闭包抽出来放在小括号后面,若是参数列表只有一个闭包,那么小括号均可以省略。闭包中,->
左边表示闭包的参数,类型能够省略。
闭包其实看起来有点像回调,咱们先写好代码,而后在 change 函数中进行调用。
函数类型能够没有参数,可是小括号是不能省略的,包括返回类型也不可省略,例如一个没有参数没有返回值的函数类型,能够这样表示:() -> Unit
。
另外函数类型也是可空的,举个栗子:
fun main() {
// 当加上?时,函数类型须要用括号括起来
val function: (() -> Unit)? = {
// ...
}
// 调用时须要用 invoke 进行调用
function?.invoke()
}
复制代码