这篇文章会列出我认为入门须要掌握的特性,若是要想应用到项目中去的话能够先去GitHub上找一些优秀的Kotlin项目学习一下Kotlin的编程思想。html
Kotlin中文站:里面有一些参考资料以及一些推荐书籍。java
不少时候直接看下编译后的Java代码比看别人的解释容易得多。 以Android Studio为例:Tools -> Kotlin -> Show Kotlin Bytecode -> 会出现一个小窗口,点击Decompileandroid
基本语法能够看这个java-to-kotlin(这个很重要,必定要先看一下,内容并很少),里面是关于Java与Kotlin语法上的不一样,固然只看这些是不够的git
在Kotlin中你们基本上都会用lambda表达式去写代码,这里不建议直接去看Kotlin lambda表达式的语法,本身刚开始写Demo的时候编译器会给出一些警告,指出某些代码能够被转换成lambda表达式,而后让编译器去帮你转换,有了这些基本印象后再去看语法会感受比较友好,具体的语法这里就不说了,内容仍是比较多的,能够自行查找。github
private
:类内部可见编程
protected
:类内部及其子类可见安全
internal
:Module内可见bash
public
:全局可见dom
var
表示可变变量(variable),val
表示不可变变量(value),也就是后者是被final
修饰的val name = "xiaoming"
编译器会自动识别为String
类型null
的,若是想要赋值为null
在声明对象类型时须要加上?
,且可空类型不能直接调用其方法,须要使用?.
或!!
,若是为null
前者会返回null
后者会抛出空指针异常,具体以下:var str1: String = "abc"
str1 = null // 编译器会报错
var str2: String? = "abc"
str2 = null // 没问题
str1.length // 没问题
str2.length // 编译器会报错
val out = str2?.length //没问题
println(out) // 这里输出null
str2!!.length // 这里运行时抛出空指针
if (str2 != null ) {
str2.length // 没问题
}
复制代码
var
和val
都是private
的(就算显式声明了public
也会被编译成private
),编译器会自动生成getter/setter
方法(val
只有getter
),在Kotlin中能够直接使用.
来调用,编译器会本身去调用它的getter/setter
方法,如user.id = 1
会去调用user
对象的setId(1)
若是不想让成员变量被外部访问,能够显示声明变量为private
,如private val id = 1
,这样编译器就不会生成getter/setter
固然getter/setter
方法是能够被修改的,操做以下:class User{
var id = 0
get() = field - 1
set(value) {
field = value + 1
}
}
复制代码
其中field
被称为”幕后字段“指的就是当前变量,像下面这种写法是错误的,由于id = value + 1
会继续调用id
的setId(value + 1)
方法,就会产生死循环。ide
class User{
var id = 0
set(value) {
id = value + 1
}
}
复制代码
在Kotlin中不须要使用+
来拼接字符串,一个字符串中能够经过$
来获取变量值,以下
val name = "xiaoming"
println("name is $name") // 获取变量值
println("length is ${name.length}") // 使用表达式
println("${'$'}29.18") // 打印 $ 符号
复制代码
上面的代码分别会输出
name is xiaoming
length is 8
$29.18
public
的final
的(抽象函数和接口函数除外),想要不final
须要加上open
关键字修饰// 能够被重写
open fun canOverride() {}
// 没法被重写
fun cannotOverride() {}
复制代码
overrider
关键字override fun toString() = "kotlin"
复制代码
fun plus(x: Int,y: Int) : Int = x + y // 此处返回类型能够省略
fun sout(s: String) = print(s)
复制代码
fun userInfo(id: Int, name: String = "xiaoming", sex: String = "male") {
println("id: $id, name: $name, sex: $sex")
}
fun test() {
userInfo(1) // 输出:id: 1, name: xiaoming, sex: male
userInfo(2, "xiaohong") // 输出:id: 2, name: xiaohong, sex: male
userInfo(1, sex = "female") // 输出:id: 1, name: xiaoming, sex: female
}
复制代码
若是想要提供给Java使用能够加上@JvmOverloads
,这样编译成Java代码时就会生成对应重载函数
@JvmOverloads
fun userInfo(id: Int, name: String = "xiaoming", sex: String = "male") {
println("id: $id, name: $name, sex: $sex")
}
复制代码
Unit
对象,Unit
能够理解成Java的Void
,能够做为泛型对象,若是将Unit
类型的函数赋值给一个变量,编译器会给变量赋值一个Unit
单例val a = test()
复制代码
编译为Java以后就是这样的
Unit a = Unit.INSTANCE;
复制代码
Unit
直接继承于Any
类(即Java中的Object
类),只重写了toString
方法
override fun toString() = "kotlin.Unit"
复制代码
在Kotlin中函数是能够嵌套的,内部函数能够访问到外部函数的局部变量,且其自己只能被外部函数访问。
fun outFun(name: String) {
fun nestFun() {
print(name)
}
nestFun()
}
复制代码
这是Kotlin的一个很是好用的特性,它能够为一个类添加函数,在这个函数中能够访问到对象的公有属性和方法,声明完扩展函数以后,该类及其子类的对象就能够直接经过.
来调用这个函数,好比为CharSequence
类添加一个toList
方法,将字符逐个添加到一个列表中而后返回
fun CharSequence.toList(): List<Char> {
val list = ArrayList<Char>()
for (char in this) {
list.add(char)
}
return list
}
复制代码
声明完上面这段代码以后,就能够直接调用了
val s = "asdf"
val list = s.toList()
复制代码
除了扩展函数以外,Kotlin还支持扩展属性,可是扩展属性不能直接赋值,只能设置它的getter/setter
方法,实际上编译成Java代码后仍是扩展了两个方法
var StringBuilder.lastChar: Char
get() = get(length - 1)
set(value) = setCharAt(length - 1, value)
复制代码
在Kotlin中函数能够做为参数传入到另外一个函数中
fun run(block: () -> Unit) {
block()
}
复制代码
上面就是一个简单的高阶函数,能够这样使用,运行以后就会输出run,这里用到了Lambda 表达式,不懂的能够再去看下
run {
print("run")
}
复制代码
经过::
来获取某个对象的方法来传入(::
也能够用来获取变量)
val runnable = Runnable {
print("run")
}
run(runnable::run)
复制代码
经过inline
修饰的函数为内联函数
当咱们使用高阶函数时,传入的函数对象会被编译成一个对象,而后再调用该对象的方法,这样会增长内存和性能的开销,而若是使用内联函数的话就能够将方法的调用转换为语句的调用
fun run(block: () -> Unit) {
block()
}
fun testRun() {
run {
print("run")
}
}
复制代码
上面的代码编译成Java代码大体是这样的,能够看到在testRun
方法中,直接被编译成了语句调用
public final void run(@NotNull Function0 block) {
block.invoke();
}
public final void testRun() {
String var = "run";
System.out.print(var);
}
复制代码
使用内联函数能够避免产生多余的对象,可是会增长编译后代码量,因此要避免内联代码块过大的函数,若是一个函数中有包含多个函数参数,能够经过noinline
关键字来避免内联
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {}
复制代码
Any
类,至关于Java中的Object
public final
的,想要类能够被继承一样须要用open
关键字来修饰:
来表示的,中间由,
隔开且没有顺序要求open class A()
interface B
class C : A(), B
复制代码
init
代码块中实现,若是不须要对构造函数添加访问修饰符或者注解,那么constructor
关键字能够省略class Test constructor(a:Int) {
init {
println("Here is the constructor.")
println("value a= $a")
}
}
复制代码
若是只想经过构造函数给成员变量赋值的话能够直接这样
class Test(val a:Int){
...
}
复制代码
若是想让构造函数私有能够这样
class Test private constructor() {
...
}
复制代码
constructor
关键字来声明,这在Kotlin中叫作次构造函数,而声明在类名上的叫作主构造函数,次构造函数须要直接或间接的继承主构造函数,具体能够看这里class Test() {
init {
...
}
constructor(a:Int): this() {
...
}
}
复制代码
class
类,一种就是Java中Class
,另一种是Kotlin自由的KClass
,它们的获取方式也不同val kClazz = Person::class
val clazz = Person::class.java
复制代码
嵌套类编译成Java代码以后就是静态内部类
class Out {
...
class Nest {
...
}
}
val nestClass = Out.Nest()
复制代码
Kotlin中内部类须要使用inner
关键字修饰
class Out {
...
inner class Inner {
...
}
}
val innerClass = Out().Inner()
复制代码
object
关键字能够简单地建立一个单例类,其中的变量和方法均可以直接经过类名来调用
fun main(args: Array<String>) {
val string = Singleton.str;
Singleton.printMessage(string)
}
object Singleton {
val str = "Singleton"
fun printMessage(message: String) = println("$str $message")
}
复制代码
能够用来写工具类
object StringUtils {
fun isEmpty(str: String?) = str == null || str == ""
}
复制代码
Kotlin中并无static
关键字,若是须要静态变量或是静态方法的话就要使用伴生对象,使用companion object
来声明,伴生对象的命名能够省略,编译器会使用默认的命名Companion
,一个类只能拥有一个伴生对象
fun main(args: Array<String>) {
val test = Test.create()
println(Test.TAG)
}
class Test {
companion object {
val TAG = "KotlinTest"
fun create() = Test()
}
}
复制代码
上面的代码会将TAG
编译成Test
类的静态成员变量,而create()
方法其实不是一个真正的静态方法,它是属于伴生对象类的一个普通的public
成员方法,伴生对象其实是外部类的一个静态单例内部类,虽然能够直接经过Test
类来调用create()
,但其实编译事后是这样的
// kotlin code
val test = Test.create()
// java code
Test test = Test.Companion.create();
复制代码
若是想要生成一个真正的静态方法,可使用@JvmStatic
注解来实现 下面使用object
和companion object
写一个延时加载的单例类
class Singleton private constructor() {
companion object {
fun getInstance() = Holder.instance
}
object Holder {
val instance = Singleton()
}
}
复制代码
数据类相似于lombok的@Data
注解,能够自动生成toString()
,equals()
,hashcode()
,copy()
等方法,具体能够去看一下编译成的Java代码
data class User(val id:Int, val name:String, val age:Int)
复制代码
除了经常使用的getter/setter
以外还能够这样用
val user1 = User(1, "Ben", 25)
val user2 = user1.copy(id= 2, age = 23)
val (id, name, age) = user1
复制代码
第二行代码将user1
拷贝给了user2
并修改了id
和age
,第三行代码将user1
的三个数据分别赋值给了id
,name
,age
三个变量,这个被称为解构,之因此能够这样写是由于数据类还实现了componentN()
,后面在运算符重载会讲到
密封类自己是个抽象类,主要特色是它的构造方法是私有的,直接继承它的子类只能定义在密封类所在的文件中,没法定义在别的文件中,也就是说它限制了外部继承,直接子类就是肯定的那几个,因此密封类也能够理解为功能更多的枚举类,由于它能够有更多的属性以及方法
sealed class Person(val name: String, var age: Int) {
class Male(name: String, age: Int) : Person(name, age)
class Female(name: String, age: Int) : Person(name, age)
}
复制代码
Kotlin也对密封类使用when
语句也作了优化,能够不写else
,由于子类是肯定的那几个
fun test(person: Person) {
when (person) {
is Person.Male -> println("male")
is Person.Female -> println("female")
}
}
复制代码
Kotlin中的各类操做符都对应着一种方法,好比+
,-
,*
,/
分别对应着plus
,minus
,times
,div
,这些方法都是能够被重载的,编写时须要在方法前面加上operate
来修饰
fun main(args: Array<String>) {
val a = Point(1, 4)
val b = Point(2, 3)
val c = a + b
c.printPoint()
}
class Point(val x: Int, val y: Int) {
operator fun plus(another: Point): Point {
return Point(x + another.x, y + another.y)
}
fun printPoint() {
println("x= $x, y= $y")
}
}
复制代码
上面的代码运行以后会输出"x= 3, y= 7",此外还有不少操做符,这里就不列举了,前面说到的componentN()
其实也是操做符对应的方法,val (id, name, age) = user
编译以后就是
int id = user.component1();
String name = user.component2();
int age = user.component3();
复制代码
使用const val
来声明一个常量,常量只能在object
,companion object
或是类的外部声明,且只能是基本数据类型或是String
类型,由于常量要求在编译期就能肯定它的值
const val pi = 3.14
object A {
const val pi = 3.14
}
class B {
companion object {
const val pi = 3.14
}
}
复制代码
前面说过val
是不可变变量,它和常量的区别是const val
编译以后是public
的而val
是pirvate
的,访问val
只能经过它的getter
方法,而getter
方法又是能够修改的(以下),对于开发者来讲,常量应该是一个肯定的值,因此val
不是常量
object A {
val pi = 3.14
get() = field + Math.random()
}
复制代码
下面的这些仍是属于Kotlin入门的范畴,并非不重要,只是内容较多,就不详细讲了,网上相关的文章也有不少
Kotlin标准库
Kotlin标准库提供了一些好用的函数,能够看下那些函数的实现,对学习Kotlin也有很大帮助
委托/委托属性
Kotlin语言是原生支持委托的,其中还包括了委托属性
泛型
Kotlin的泛型和Java的泛型大体上是相同的,可是写法上仍是有点区别的,并且Kotlin可使用inline
和reified
来支持真泛型
协程
Kotlin是有协程库来支持协程的,协程能够理解为运行在线程中的线程,比线程更轻量,使用协程必定程度上能够简化代码
集合操做符
Kotlin为集合提供了一系列操做符(其实是集合的扩展函数),相似于RxJava,能够链式调用
与Java交互
通常使用Kotlin开发避免不了与Java交互,对于调用Java代码或是让Java调用Kotlin代码均可能会存在一些问题
Anko、ktx
Anko和ktx是为android设计的一个kotlin代码库,对于android开发能够了解一下