Kotlin这门语言相对熟悉Java的开发者来讲,容易入手,kotlin提供了很多Java中没有的东西,并且相比Java来讲,更加简洁易用。官方也一直力荐Kotlin-First,所以我们有必要赶紧熟练运用起来。
Kotlin有很多优势:
- 语法简洁,代码数量减半
- 语法更加高级,增加了很多现代高级语言的特性,提升了效率
- 语言安全性方面做了很大的改变,几乎杜绝了空指针异常
- 和Java 100%兼容
- Kotlin有出色的类型推导机制
- Kotlin阉割掉了基本数据类型,全部采用引用数据类型
下面我们对Kotlin的语法做一个讲解:
- 1 基础:val和var 永远优先使用val (属于使用注意事项)
- Kotlin语法糖:当函数只有一行代码时候,kotlin允许我们不必要编写函数体,而是直接将唯一的一行代码写在函数的尾部,中间用等号连接,省略掉return,由于出色的类型推导机制,返回值类型也可以省略。
fun sum(a: Int, b: Int): Int { return a + b } //语法糖后变为 fun sum(a:Int, b:Int) = a + b
- if 逻辑控制语句:kotlin中可以将条件语句作为返回值
//kotlin类比java的语法 fun largerNumber(num1:Int, num2:Int):Int{ var result = 0 if(num1 > num2){ result = num1 } else { result = num2 } return result } //kotlin简化版写法 fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) num1 else num2
- when条件语句:可以类比java中switch,swith支持整型或者短于整型的变量,java switch在1.7加入了对String的支持,但是类型还是很少,并且每一个case还得加入break,比较麻烦,kotlin中的when支持的类型就很多,并且功能更加强大。比如查询学生成绩,如果用if语句实现:
fun getScore(name: String) = if (name == "Tom") { 87 } else if (name == "Jim") { 77 } else { 0 } //when改造后 when语句当 -> 后面的执行逻辑是一行代码时候,可以不用写{ } fun getScore(name:String) = when(name){ "Tom" -> 87 "Jim" -> 77 else -> 0 } //或者when中不传参数的话, 可扩展性更加强,比如下面这种 fun getScore(name:String) = when { name.startWith("Tom") -> 87 name == "Jim" -> 77 else -> 0 } //when还可以匹配类型 fun checkNumber(number:Number){ when (number) { is Int -> println("number is Int") is Double -> println("number is Double") else -> println("number is not support") } }
- for循环语句
val range = 0..10 //0 - 10 [0,10] val range2 = 0 until 10 //[0,10) val range3 = 0..10 step 2 //步长为2的一个区间 val range4 = 0 until 10 step 2 //步长为2的一个区间 val range5 = 10 downTo 0 step 2 //从大到小,步长为2的一个区间 for(i in range) println(i)
- 2 面向对象编程
//kotlin中类默认是不能被继承的,只有写上open的话,类才可以被继承 open class Person(private val name: String, val age: Int) class Student(private val sno: Int, name: String, age: Int) : Person(name, age) //Worker没有主构造函数,只有次级构造函数的话,就不必对Person加括号 class Worker : Person { constructor(name: String, age: Int) : super(name, age) }
- 数据类和单例类
//kotlin object Single {} //反编译后的等价的java代码如下,可以看出内部帮我们封装了单例的实现, 属于饿汉模式单例。 public final class Single { public static final Single INSTANCE; private Single() { } static { Single var0 = new Single(); INSTANCE = var0; } }
- 集合的函数式API:最后一行自动作为返回值
{参数名 :参数类型,参数名 :参数类型 -> 函数体 } //求集合中最长元素 val lambda = { fruit: String -> fruit.length } list.maxBy(lambda) //1 将lambda直接放入括号内 list.maxBy({ fruit: String -> fruit.length }) //2 最后一个参数是lambda时候,挪到括号外边 list.maxBy() { fruit: String -> fruit.length } //3 只有一个参数且是lambda时候,可以去掉括号 list.maxBy { fruit: String -> fruit.length } //4 lambda只有一个参数时候,可以去掉变量类型 list.maxBy { fruit -> fruit.length } //5 lambda只有一个参数时候,可以使用it代替 list.maxBy { it.length }
- Java函数式API的使用:条件是如果在kotlin中调用Java方法,并且该方法接收一个单抽象方法接口参数,就可以使用函数式API.
Thread(object :Runnable { override fun run() { TODO("Not yet implemented") } }).start() //符合Java函数式API, 简化为 Thread(Runnable { TODO("Not yet implemented") }).start()
- 空指针安全
- ?. 判空辅助工具,如果不为空正常调用,否则就什么也不做
- ?: 左右两边都是表达式,如果左边表达式不为空就返回左边表达式结果,否则返回右边表达式结果
- !! 非空断言符号,使用有风险
fun getLength(str:String?) = str?.length ?: 0
- 作用域运算符
- .let 参数是it, 返回值是最后一行
- .run 参数是this, 返回值是最后一行
- .apply 参数是this, 返回值是本身
- .also 参数是it, 返回值是本身
- with 参数是括号内的,返回值是最后一行
//let public inline fun <T, R> T.let(block: (T) -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block(this) } //run public inline fun <T, R> T.run(block: T.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() } //apply public inline fun <T> T.apply(block: T.() -> Unit): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block() return this } //also public inline fun <T> T.also(block: (T) -> Unit): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block(this) return this } //with public inline fun <T, R> with(receiver: T, block: T.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return receiver.block() }
- 字符串模板和缺省参数(使用简单方便,就不举例了)
- 3 定义静态方法或者顶层方法: 不加@JvmStatic的话,相当于内部调用了创建对象(单例或者伴生对象)的方法,加上之后就会在类中声明一个静态方法,才是真正的静态方法
object Utils { fun get() {} @JvmStatic fun get2() {} } class Utils2 { companion object { fun foo() {} @JvmStatic fun foo2() {} } }
- 4 延迟初始化和密封类
lateinit var adapter:MyAdapter if(::adapter.isInitialized){ ... } sealed class Result<out R> class Success<out T>(val data: T) : Result<T>() class Failure(val err: Exception) : Result<Nothing>() fun <T> getResultMsg(result: Result<T>)= when(result){ is Success -> println("success") is Failure -> println("failure") }
- 5 扩展函数和运算符重载
- 扩展函数定义在新建的String.kt, 具有全局作用域
- 运算符重载可以有很多好玩的用法
operator fun String.times(n: Int): String { val b = StringBuilder() repeat(n) { b.append(this) } return b.toString() }
-
6 高阶函数:如果一个函数接收另一个函数作为参数或者返回值类型是另一个函数的话,那么该函数就称为高阶函数。高阶函数允许让函数类型的参数来决定函数的执行逻辑。
-
Kotlin中新增了函数类型的概念,定义举例为:
(String, Int)-> Int
fun main() { num1AndNum2(1, 2, ::plus) num1AndNum2(1, 2, ::minus) } fun num1AndNum2(num1: Int, num2: Int, operator: (Int, Int) -> Int) = operator(num1, num2) fun plus(num1: Int, num2: Int) = num1 + num2 fun minus(num1: Int, num2: Int) = num1 - num2
为了方便起见,还可以使用Lambda表达式,匿名函数来调用高阶函数
-
7 内联函数:
- Java是没有高阶函数的概念的,但是kotlin是要编译成class字节码的,那Kotlin高级函数是如何转换成java支持的语法结构的?其实内部使用的是匿名类,导致每次调用一次表达式就会创建一个匿名类实例,造成额外的内存和性能开销,
- 所以使用内联函数inline 可以将使用lambda表达式带来的开销消除。原理是kotlin编译器会将内联函数中的代码在编译时候自动替换到调用它的地方,就不存在运行时的开销了。
- 非内联函数的lambda表达式是不能使用return函数返回的,只能局部返回;内联函数的lambda表达式可以函数返回,也可以局部返回(如果lambda表达式有返回值的话,只能局部返回)。如下使用inline后会报错,原因在于inline引用的lambda默认是可以使用return函数返回的,但是Runnable是一个匿名类的方式实现的,是在匿名类中调用的传入的函数类型参数,匿名类是不能进行外层调用函数返回的,所以此时是不能进行外层调用函数返回的,顶多只能对匿名类中的函数返回,所以就有了矛盾。
- 或者原因可以这样理解:我们在高阶函数中创建了另外的lambda或者匿名类,在其中调用了函数类型参数,如果此时再将高阶函数声明为内联函数的话,就会报错。使用
crossinline
就相当于协调一下,确保内联函数的lambda中不会使用return返回。
inline fun runRunnable(block: () -> Unit) { val runnable = Runnable { block()// block会报错 } runnable.run() }
-
8 类委托和委托属性
- 类委托是将一个类的实现委托给另一个类,可以大大减少代码量,只重写我们需要的方法即可。
class MySet2<T>(helperSet: HashSet<T>) : Set<T> by helperSet { override fun isEmpty() = false } fun main() { val te = Te() println(te.name) te.name = "13" println(te.name) } class Te { var name by MyDelegate("12") } class MyDelegate(var name: String) : ReadWriteProperty<Any, String> { override operator fun getValue(thisRef: Any, property: KProperty<*>): String { return name } override operator fun setValue(thisRef: Any, property: KProperty<*>, value: String) { name = value } }
- 属性委托就是将属性的具体实现委托给另一个类,下面是实现一个简单的仿lazy函数later(开发中请使用lazy函数)
import kotlin.reflect.KProperty fun <T> later(block: () -> T) = Later(block) class Later<T>(val block: () -> T) { var value: Any? = null operator fun getValue(any: Any?, prop: KProperty<*>): T { if (value == null) { value = block() } return value as T } } fun main() { val p by later { "hello" } }
-
9 infix中缀表达式 语法糖 :不能定义为顶层函数,必须是某一个类的成员函数 + 有且只有一个参数
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that) fun main() { val result = "name" to " beijing" println(result.first + result.second) }
-
10 泛型实化使得a is T 或者T::Class.java的语法成为了可能: 实化条件是inline + reified
inline fun <reified T> start(context: Context) { context.startActivity(Intent(context, T::class.java)) } inline fun <reified T> start(context: Context, block: Intent.() -> Unit) { val intent = Intent(context, T::class.java) block(intent) context.startActivity(intent) } //使用 start<NewVersionActivity>(this) { putExtra("name", "beijing") }
-
11 泛型 kotlin中默认给只读的List协变的,想要改变的话使用mutableList。
-
class A where T : Number, T : Serializable
-
泛型协变:如果定义了一个类
MyClass<T>
的泛型类, 其中A是B的子类型,同时MyClass<A>是MyClass<B>
的子类型,那么我们称MyClass在T这个泛型上是协变的。- 虽然String是Object的子类,但是
List<String>并不是List<Object>
的子类,因为如果可以的话,我们会将List<String>赋值给List<Object>
,但是我们不知道List<Object>
还会添加什么内容,所以在获取的时候就会产生类型转换的问题,为了杜绝这种隐患,就不能List<String>并不是List<Object>
的子类。如果我们在List<Object>
上只读的话,就不会出现这个问题了,加上out就可以了。 - 协变意味着参数只能出现在out位置,但是contains这个方法出现在了in位置,因为我们知道这个地方并不会改变集合中的元素,仅仅是判断集合中是否包含这个元素,可以使用@UnsafeVariance声明。
public interface List<out E> : Collection<E> { override val size: Int override fun isEmpty(): Boolean override fun contains(element: @UnsafeVariance E): Boolean override fun iterator(): Iterator<E> override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean public operator fun get(index: Int): E }
- 虽然String是Object的子类,但是
-
泛型逆变:如果定义了一个类
MyClass<T>
的泛型类, 其中A是B的子类型,同时MyClass<B>是MyClass<A>
的子类型,那么我们称MyClass在T这个泛型上是逆变的。fun main() { val value = object : Transformer<Person>{ override fun transform(name: String, age: Int): Person { return Teacher("teacher",12) } } handTransform(value) } fun handTransform(func:Transformer<Student>){ val value = func.transform("tom",19) } interface Transformer<in T> { fun transform(name: String, age: Int): @UnsafeVariance T } open class Person(val name: String) class Student(name: String, val age: Int) : Person(name) class Teacher(name: String, val age: Int) : Person(name) //为什么Comparable是逆变的,因为加入Person可以比较,那么Student肯定也可以比较,所以采用了逆变。 public interface Comparable<in T> { public operator fun compareTo(other: T): Int }
以上是对Kotlin常用的语法做的一个小结,便于以后复习和总结,内容主要来自于郭霖大神的《第一行代码 第3版》。