kotlin简介

kotlin语言简介

kotlin是一种静态类型编程语言,和Java一样也是基于jvm的,但是比Java更简洁,更安全,与Java有高度互操作性,可以在项目中同时使用kotlin和Java编程。kotlin也已经是Android的官方开发语言了。

在android studio项目中使用kotlin

androidstudio3.0已经自带了kotlin插件,3.0以下版本要自己安装kotlin插件。安装方法:File|Settings|Plugins,搜索Kotlin并安装插件,插件安装后,还要在build.gradle文件中配置才能在项目中使用kotlin

在要使用kotlin的module的build.gradle文件中添加如下配置即可

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}

buildscript {
    ext.kotlin_version = '1.1.3-2'
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

基本类型

在 Kotlin 中,所有东西都是对象,在这个意义上讲所以我们可以在任何变量上调用成员函数和属性。有些类型是内置的,因为他们的实现是优化过的。但是用户看起来他们就像普通的类。
1.数字
kotlin提供了如下内置类型来表示数字(和java差不多)
Double 64
Float 32
Long 64
Int 32
Short 16
Byte 8

每个数字类型支持如下的转换:
-toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char

2.字符
字符用 Char 类型表示。它们不能直接当作数字

fun check(c: Char) {
    if (c == 1) { // 错误:类型不兼容
        // ……
    }
}

3.数组
数组在 Kotlin 中使用 Array 类来表示,它定义了 get 和 set 函数(按照运算符重载约定这会转变为 [])和 size 属性,以及一些其他有用的成员函数:

class Array<T> private constructor() {
    val size: Int
    operator fun get(index: Int): T
    operator fun set(index: Int, value: T): Unit

    operator fun iterator(): Iterator<T>
    // ……
}

我们可以使用库函数 arrayOf() 来创建一个数组并传递元素值给它,这样 arrayOf(1, 2, 3) 创建了 array [1, 2, 3]。

4.字符串
字符串用 String 类型表示。字符串是不可变的。 字符串的元素——字符可以使用索引运算符访问: s[i]。 可以用 for 循环迭代字符串:

for (c in str) {
    println(c)
}

5.字符串模板
字符串可以包含模板表达式 ,即一些小段代码,会求值并把结果合并到字符串中。 模板表达式以美元符($)开头,由一个简单的名字构成:

val i = 10
val s = "i = $i" // 求值结果为 "i = 10"

或者用花括号扩起来的任意表达式:

val s = "abc"
val str = "$s.length is ${s.length}" // 求值结果为 "abc.length is 3"

kotlin语法

具体的语法细节太多了,就不一一讲解了,这个写代码的时候相关语法自己查看官网。下面主要是结合实际开发讲解一些常用的语法。
下面是一个简单的fragment基类

abstract class BaseFragment : Fragment() {
    protected lateinit var rootView: View
    protected val retrofitService: RemindApi by lazy { RetrofitConfig.retrofit.create(RemindApi::class.java)}

    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        rootView = inflater?.inflate(getLayoutId(), container, false) ?:
                LayoutInflater.from(context.applicationContext).inflate(getLayoutId(), container, false)
        return rootView
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        initView()
    }

    abstract fun getLayoutId(): Int
    abstract fun initView()
}

1.可见性修饰符

kotlin中有四种可见性修饰符: public ,internal, protected ,private

package
// file name: example.kt
package foo

fun baz() {}
class Bar {}

函数, 类, 属性, 接口可以直接定义在“top-level" ,也就是直接定义在一个包文件中,对应他们的可见性描述如下:
1.如果没有指定任何可见性修饰符,默认是public,也就是所有的地方都可见
2.private : 只是在声明它的文件内可见
3.internal : 在同样的module内可见
4.protected : 不可用的在”top-level"声明中

classes and interfaces
open class Outer {
    private val a = 1
    protected open val b = 2
    internal val c = 3
    val d = 4  // public by default
}

在类和接口中,可见性修饰符的作用和在package中一样,除了protected是可以用的

module

an IntelliJ IDEA module;
a Maven project;
a Gradle source set;
a set of files compiled with one invocation of the Ant task.

2.类

完整的类申明如下
class Person public @Inject constructor(name: String)  {
    init{
    }
    constructor(parent: Person) :this(""){
    }
    constructor(a: Int) :this(Person()){
    }
}

类申明中,类名称的后面会跟一个主构造函数,constructor关键字加上括号里面的参数。构造器的前面可以加可见性修饰符和注解,如果没有加可见性修饰符和注解,constructor关键字也可以省略不写。
主构造器后面只能带参数,不能包含代码,初始化的代码可以写在初始化块里面,也就是写在上面的init关键字后面的代码块中。
除了主构造函数,还可以添加更多的构造函数,其余的构造函数必须直接或间接的委托到主构造函数,通过this关键字

类的继承

在kotlin中,类默认是不能被继承的,如果要能被继承,在 class前面加关键字“open”,“abstract”关键字修饰的class也是可以继承的,不用再加"open"
所有的类都有一个共同的超类 Any. Any不是java中的Object

open class Base {
    open fun v() {}
    fun nv() {}
}
class Derived : Base() {
    override fun v() {}
}

类继承的语法是,在类的后面跟一个 ” :“,冒号后面跟基类的名字,必须显示的指定基类的构造函数,就算是默认的无参数构造函数也要写括号。基类中的方法如果想要能被重写,方法前面也必须加“open"修饰

各种特殊的类

1.data class

data class BaseResponse(var status: String, var message: String)

data class 主要是用来定义实体类的,data class 会自动生成一些方法
equals()/hashCode(),
toString() 格式: "BaseResponse(status="0", message="success")",
componentN()
copy()

2.Object expressions and Declarations

1)创建匿名类对象
object 关键字可以用来创建一个匿名类对象,这个匿名类对象可以继承自一个或多个type(一个超类,多个接口)

interface B {...}

val ab: A = object : B {
   val y = 15
}

2)创建单例
单例是一个非常有用的设计模式,kotlin提供了一种非常简单的方法创建单例

object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ...
    }

    val allDataProviders: Collection<DataProvider>
        get() = // ...
}

使用方法如下: 直接使用名字就可以引用到单例对象

DataProviderManager.registerDataProvider(...)

3)Companion Objects
声明在类里面的object可以加companion关键字

class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}

声明在companion object中的对象可以直接通过类名调用

val instance = MyClass.create()

看起来好像是和java中的静态成员差不多,但是在运行时,他们仍然是一个具体对象的实例成员。
通过加@JvmStatic 注解,在Jvm中可以将他们变成真正的静态方法和字段

class ObjectTest {
    companion object  comObject{
        @JvmStatic
        var name = "ss"   //在java中声明 private static String name
        var age = "20"  //在java中声明 private String age
        const val address = "shanghai"  //在java中声明 public static final String address

        @JvmStatic
        fun show() {
        }
    }
}

在java中使用

public class Test {
    public Test(){
        String name = ObjectTest.getName();
        String age = ObjectTest.comObject.getAge();
        String address = ObjectTest.address;
        ObjectTest.show();
    }
}

3.Generics

class Box<T>(t: T) {
    var value = t
}
List<in T>             //对应Java中:  List<? super T>
List<out T>            //对应Java中:  List<? extends T>
List<T: Any>           //对应Java中:  List<T extends Object>

4.sealed class
5.enum class
6.nested class

3.Properties and Fields

1)属性的声明
kotlin中的类可以有属性,属性可以声明为可变的用var关键字,或声明为只读的用val关键字。
声明一个属性的完整的语法如下:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]

var stringRepresentation: String
    get() = this.toString()
    set(value) {
        setDataFromString(value)
    }

initializer, getter 和 setter 都是可选的,属性类型也是可选的,如果类型可以推导出来从initializer(或从getter 的返回类型)
如果想修改getter 或 setter 的可见性, 但不想改变他们的默认实现,可以定义方法但是不定义它的body

var setterVisibility: String = "abc"
    private set // the setter is private and has the default implementation

2)Backing Fields
kotlin中的类不能有fields.但是,在使用自定义settergetter必须要有一个field,为此,kotlin提供了一个自定义的field, 通过使用field标识符引用

如果没有这个 Backing field, 在自定义的setter和getter中直接访问counter可能会导致无限递归调用,如下的代码会导致set()的递归调用

var age = "20"
    set(value) {
        age = value+"test"
    }

正确的写法如下

var age = "20"
    set(value) {
        field = value+"test"
    }

3)Compile-Time Constants
在属性的前面加一个const修饰符,可以将属性变成编译时常量,只有满足如下条件的属性才能在前面加const:

  • top-level属性 或 一个 object的成员属性
  • 这个属性有一个初始化值,值类型为String或primitive type
  • 属性没有自定义的getter

这样的属性可以用在注解中

const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
  1. Late-Initialized Properties
    通常,一个声明为非空类型的属性必须在构造函数中被初始化,但是这样非常不方便,有时候我们想在特定的时候才赋值,所以,kotlin提供了延迟初始化属性的方法,在属性声明中加 lateinit 修饰符
public class MyTest {
    lateinit var subject: TestSubject
}
  • 使用 lateinit 修饰的属性不能有自定义的gettersetter
  • 属性不能是原始类型
  • 属性必须声明在一个类的body里面(不能在主构造函数中)

使用 lateinit 修饰后,如果属性在使用前没有被初始化,会抛出异常。

3.Functions

下面是一个函数的定义

fun double(x: Int=1,y: Int=2): Int {
    return x*y
}

声明函数用fun关键字,函数的返回值跟在参数列表后面。参数可以有默认值。
如果一个函数没有返回任何值,它的返回类型是Unit

1.单表达式函数:如果一个函数返回一个单个表达式,则括号可以省略,函数body指定在一个“=”后面

fun double(x: Int): Int = x * 2

2.泛型函数

fun <T> singletonList(item: T): List<T> {
    // ...
}

3.Higher-Order Functions and Lambdas
高阶函数:用函数作为参数,或返回一个函数的函数

fun <T> lock(lock: Lock, body: () -> T): T {
    lock.lock()
    try {
        return body()
    }
    finally {
        lock.unlock()
    }
}

lambda表达式 和 匿名函数: 是一个函数字面量, 一个没有声明的函数,但是可以作为一个表达式被传递
lambda表达式如下

val sum = { x: Int, y: Int -> x + y }

如果lambda表达式只有一个参数,可以这样写

ints.filter { it > 0 } // this literal is of type '(it: Int) -> Boolean'

lambda的返回值:可以使用return显示返回一个值,否则,最后一个表达式的值被返回

ints.filter {
    val shouldFilter = it > 0 
    shouldFilter
}

ints.filter {
    val shouldFilter = it > 0 
    return@filter shouldFilter
}

在lambda中要提前返回,终止后面的语句执行,要用return @tag, 必须指明返回的地方,直接写一个return会报错。

匿名函数的定义如下

fun(x: Int, y: Int): Int {
    return x + y
}

4.Inline Functions
内联函数:在函数的前面加一个inline修饰符

inline fun lock<T>(lock: Lock, body: () -> T): T {
    // ...
}

使用高阶函数会带来一个运行时消耗。每一个函数都是一个对象,使用内联函数后,不会生成一个函数对象,编译器会直接将使用函数的地方替换成函数的body内的代码

Non-local return
在kotlin中,我们只能用return标识退出一个命名函数或一个匿名函数,退出一个lambda要用label。但是如果lambda是作为一个inline函数的参数,则可以直接用return退出

    fun show(){
        test { return@test }
        testInline { return }
    }
    fun test(body: ()->Unit){
    }

    inline fun testInline(body: ()->Unit){
    }

4.扩展

Kotlin 同 C# 和 Gosu 类似,能够扩展一个类的新功能而无需继承该类或使用像装饰者这样的任何类型的设计模式。 这通过叫做 扩展 的特殊声明完成。Kotlin 支持 扩展函数 和 扩展属性。
声明一个扩展函数,我们需要用一个 接收者类型 也就是被扩展的类型来作为他的前缀

package com.lee.socrates.remind.util

fun Context.showToast(message: String, duration: Int = Toast.LENGTH_LONG) {
    Toast.makeText(this, message, duration).show()
}

扩展函数中的this关键字指的是接收对象(也就是函数扩展到的那个接收类对象)

扩展是静态解析的

扩展不能真正的修改他们所扩展的类。通过定义一个扩展,你并没有在一个类中插入新成员, 仅仅是可以通过该类型的变量用点表达式去调用这个新函数。
扩展函数是静态分发的,即他们不是根据接收者类型的虚方法。 这意味着调用的扩展函数是由函数调用所在的表达式的类型来决定的, 而不是由表达式运行时求值结果决定的。例如:

open class C
class D: C()
fun C.foo() = "c"
fun D.foo() = "d"
fun printFoo(c: C) {
    println(c.foo())
}
printFoo(D())

这个例子会输出 "c",因为调用的扩展函数只取决于参数 c 的声明类型,该类型是 C 类。

如果一个类定义有一个成员函数和一个扩展函数,而这两个函数又有相同的接收者类型、相同的名字并且都适用给定的参数,这种情况总是取成员函数

kotlin的android扩展

项目中使用kotlin的android扩展的方法,在build.gradle文件中加入下面的代码

apply plugin: 'kotlin-android-extensions'

使用kotlin的android扩展可以省略写很多样板代码的时间
例如:不需要再写findViewById()
布局中有如下view

    <android.support.v7.widget.AppCompatButton
        android:id="@+id/btnLogin"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="24dp"
        android:layout_marginTop="24dp"
        android:padding="12dp"
        android:text="Login"/>

    <TextView
        android:id="@+id/linkSignUp"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="24dp"
        android:gravity="center"
        android:text="No account yet? Create one"
        android:textSize="16dip"/>

在程序中使用这两个view的时候,直接通过view定义的id的名字就可以引用到view对象,完全不需要写findViewById()

        btnLogin.setOnClickListener {
            if (!inputEmail.text.toString().validateUserName(context)) {
                return@setOnClickListener
            }
            if (!inputPassword.text.toString().validatePassword(context)) {
                return@setOnClickListener
            }
            login()
        }
        linkSignUp.setOnClickListener {
            ARouter.getInstance().navigation(activity, "register")
        }

anko

anko是一个用Kotlin 写的安卓开发的DSL (Domain-Specific Language) 组件,可以很方便的用代码写布局

已有的项目中使用kotlin

在已有的项目中引入kotlin是很方便的,kotlin和java是100%可以互操作的。
kotlin插件提供了直接将java文件转换成kotlin文件的功能,但是有时候转换后的文件可能会有语法错误,要自己手动去修复错误。
使用方法 Ctrl+Alt+A 调出搜索Action的框,搜索”Convert java file to kotlin file“的action,点击这个action后,java文件就自动转换成kotlin文件了。
这个有点要注意的是,插件没有提供Kotlin文件转换成Java文件的功能。

对编译和运行的影响

kotlin也是基于Jvm的语言,运行速度和Java都差不多。
对编译的影响,参考网上一篇文章Kotlin vs Java: Compilation speed

Clean builds with no Gradle daemon

1-VM3VcA0f0XdjnTWDjOqgQg.png

Clean builds with the Gradle daemon running


1-l9HeNTP1a2EFCNUsuMlFow.png

Incremental builds

1-at-xrjHBLoSOzpGHzobUAA.png

incremental compilation with a modified source file


1-X0JgS3Go9vsMcwCe_95gWA.png

总结
clean之后编译java速度更快,大概快10%-15%,但是对android开发来说,只有偶尔会clean project.
如果开了Gradle daemon,增量编译的时候,kotlin甚至有更快的编译速度,所以引入kotlin对编译速度不会有太坏的影响。

一个kotlin的demo,github地址:
https://github.com/leesocrates/remind

参考连接
官网
kotlin和java编译速度比较
Learn Kotlin while developing an Android App

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容