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.但是,在使用自定义setter或getter必须要有一个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() { ... }
- Late-Initialized Properties
通常,一个声明为非空类型的属性必须在构造函数中被初始化,但是这样非常不方便,有时候我们想在特定的时候才赋值,所以,kotlin提供了延迟初始化属性的方法,在属性声明中加 lateinit 修饰符
public class MyTest {
lateinit var subject: TestSubject
}
- 使用 lateinit 修饰的属性不能有自定义的getter或setter,
- 属性不能是原始类型
- 属性必须声明在一个类的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
Clean builds with the Gradle daemon running
Incremental builds
incremental compilation with a modified source file
总结
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