自从实习结束后直到如今将近一年多的时间再也没有用过kotlin, 在今年五月份I/O大会上,Google再次明确了Kotlin在Android开发中的地位,并毫无悬念的将这个“后生晚辈”定为官方“首选”语言,这使得Kotlin不得不被“传统java 开发者”重视,他们是时候改拥抱变化了,因此咱们项目主要技术负责人如今勉强接受你们使用Kotlin开发意愿(实在不容易~)。java
对荒废了一年的语言如今从新拾起感受又熟悉又陌生,但心里仍是很兴奋的,最近准备系统地整理一下Kotlin相关内容, 但愿能在短期内从新掌握,今天主要从新梳理一下Kotlin基础语法。编程
Kotlin安装与配置这里省略(网上不少教程,AndroidStudio3.x版本默认已支持Kotlin插件)数组
Kotlin 的基本数据类型包括 Byte、Short、Int、Float、Long、Double 、Boolean, 在java中基本数据类型一共有八种分别包括整形:byte、short、int、long,浮点型:float、double , 字符型 char 以及布尔型 boolean,不一样于 Java 的是,Kotlin 字符不属于数值类型,是一个独立的数据类型, 你们能够看出Kotlin的基本数据类型和java不同,Kotlin 中其实没有基础数据类型,只有封装的对象类型,你每定义的一个变量,其实 Kotlin 帮你封装了一个对象,这样能够保证不会出现空指针, 对应Java中八大基本数据类型的对象类型、java在使用基础数据类型时候能够经过装箱操做封装成对象;安全
当须要可空引用时,像数字、字符会被装箱。装箱操做不会保留同一性。bash
布尔用 Boolean 类型表示,它有两个值:true 和 false。当须要可空引用布尔会被装箱。闭包
数组用Array类实现,具备size属性、get、setf方法,因为使用 [] 重载了 get 和 set 方法,因此咱们能够经过下标很方便的获取或者设置数组对应位置的值。java中素组具备length属性以及使用[]经过下标方式访问属性;app
数组的建立两种方式:一种是使用函数arrayOf();另一种是使用工厂函数。以下所示,咱们分别是两种方式建立了两个数组:编程语言
fun main(args: Array<String>) {
//[1,2,3]
val a = arrayOf(1, 2, 3)
//读取数组内容
println(a[0]) // 输出结果:1
println(b[1]) // 输出结果:2
}
val x: IntArray = intArrayOf(1, 2, 3
复制代码
除了类Array,还有ByteArray, ShortArray, IntArray,LongArray等用来表示各个类型的数组,省去了装箱操做,所以效率更高,其用法同Array同样:ide
和java同样,String属于不可变的,Kotlin能够经过[]很方便访问对应下标字符,java中经过chatAt方法或者subString等方式获取对应字符,Kotlin中String支持遍历形式访问其中的字符,这一点很使用;函数
for (c in str) {
println(c)
}复制代码
另外Kotlin 支持三个引号 """ 扩起来的字符串,支持多行字符串,好比:
fun main(args: Array<String>) {
val text = """多行字符串 多行字符串"""
println(text) // 输出
}复制代码
String 能够经过 trim(),trimEnd(),trimStart(),trimMargin() 等方法来删除多余的空白。
java中能够经过隐式类型转换,数值大的类型能够转换成数据小的类型,可是这样每每会丢失精度,在kotlin中因为不一样的表示方式,较小类型并非较大类型的子类型,较小的类型不能隐式转换为较大的类型。 这意味着在不进行显式转换的状况下咱们不能把 Byte 型值赋给一个 Int 变量,
val b: Byte = 1 // OK, 字面值是静态检测的
val i: Int = b.toInt() // OK复制代码
每种数据类型都有下面的这些方法,能够转化为其它的类型:
toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char复制代码
val
定义,只能为其赋值一次,val a: Int = 1 // 当即赋值 (非空属性必须在定义时候初始化,)
val b = 2 // 自动推断出 `Int` 类型 (非空属性必须在定义时候初始化,)
复制代码
var
关键字:var x = 5 // 自动推断出 `Int` 类型 (非空属性必须在定义时候初始化,)
x += 1
复制代码
Kotlin语法支持类型自动推断,在声明变量或者常量的时候能够不用指定其类型,编译器在编译时候会为咱们指定其类型;
非空属性必须在定义的时候初始化,kotlin提供了一种能够延迟初始化的方案,使用 lateinit 关键字描述属性:
var count:Int?=null //可空属性能够在后面赋值
count=5复制代码
lateinit var name : String 非空属性使用延迟初始化
函数定义使用fun关键字,参数格式为 参数:类型 ,最后函数返回值类型,以下
fun sum( a:Int, b:Int):Int{return a+b}复制代码
亦能够函数表达式声明函数
fun sum( a:Int, b:Int)= a+b // 自动类型推断或者 fun sum(a:Int,b:Int):Int=a+b 复制代码
无返回值的函数定义(相似Java中的void):
fun printSum(a: Int, b: Int): Unit {
print(a + b)
}
复制代码
Unit
返回类型能够省略:
public fun printSum(a: Int, b: Int) {
print(a + b)
}
复制代码
可变长参数用vararg关键字进行修饰:
fun print(vararg v:Int){
for(a in v){
println("$a")
}
} 复制代码
$ 表示一个变量名或者变量值
$varName 表示变量值
${varName.fun()} 表示变量的方法返回值:
var a = 1
// 模板中的简单名称:
val s1 = "a is $a"
a = 2
// 模板中的任意表达式:
val s2 = "${s1.replace("is", "was")}, but now is $a"
// 运行结果:a was 1, but now is 2复制代码
看一个经常使用if表达式:
fun value(a:Int, b :Int):Int {
if(a>b) {
return a+b
}else{
return a-b
}
}复制代码
经过条件表达式能够:
fun value(a:Int,b:Int)=if(a>b) a+b else a-b
Kotlin的空安全设计对于声明可为空的参数,在使用时要进行空判断处理,有两种处理方式,字段后加!!像Java同样抛出空异常,另外一种字段后加?可不作处理返回值为 null或配合?:作空判断处理
//类型后面加?表示可为空
var age: String? = "23"
//抛出空指针异常
val ages = age!!.toInt()
//不作处理返回 null
val ages1 = age?.toInt()
//age为空返回-1
val ages2 = age?.toInt() ?: -1复制代码
当一个引用可能为 null 值时, 对应的类型声明必须明确地标记为可为 null。
Kotlin 的NULL机制旨在消除来自代码空引用的危险,这在java语言中属于最多见的陷阱之一,也就是访问空引用的成员会致使空引用异常。常常会抛出 NullPointerException
或简称 NPE
。
在Kotlin中常常会有一些链式调用用法,安全调用在链式调用中颇有用,如:person?.class?.countName 若是调用链中任何一个属性值出现null状况,调用链会直接返回null,
后面属性不会出现NPE异常。
若是相对非null值执行某个操做,能够结合let操做符一块儿使用:
val listWithNulls: List<String?> = listOf("Kotlin", null)
for (item in listWithNulls) {
item?.let {
println(it) // 输出 Kotlin 并忽略 null
}
}复制代码
一样安全调用也能够出如今赋值的左侧,若是调用链中的任何一个接收者为空都会跳过赋值,而右侧的表达式根本不会求值:
person?.class?.mathTeacher = TeachManager.getTeacher()复制代码
7. Elvis 操做符
当咱们有一个可空的引用 r
时,咱们能够说“若是 r
非空,我使用它;不然使用某个非空的值 x
”:
val l: Int = if (b != null) b.length else -1
或者
fun value(a:Int,b:Int)=if(a>b) a+b else a-b
//条件表达式用法
复制代码
除了完整的 if-表达式,这还能够经过 Elvis 操做符表达,写做 ?:
:
val l = b?.length ?: -1复制代码
若是 ?:
左侧表达式非空,elvis 操做符就返回其左侧表达式,不然返回右侧表达式。 请注意,当且仅当左侧为空时,才会对右侧表达式求值。
这种Elvis用法相似java语言中三元操做符;
public int value(int a,int b){return a>b?a+b :a-b}
等同于:
fun value(a:Int,b:Int)=if(a>b) a+b else a-b
复制代码
val l = b?.length ?: -1
等同于:
final int l= b!=null? b.length : -1 复制代码
若是对象不是目标类型,那么常规类型转换可能会致使 ClassCastException
,
val aInt: Int? = a as Int复制代码
为了不类型转成异常,另外一个选择是使用安全的类型转换,若是尝试转换不成功则返回 null:
val aInt: Int? = a as? Int
或者
val sInt :Int?= if(a is Int) a as Int else null
或者
val sInt :Int?= if(a is Int) a else null复制代码
is 运算符检测一个表达式是否某类型的一个实例(相似于Java中的instanceof关键字), 若是一个不可变的局部变量或属性已经判断出为某类型,那么检测后的分支中能够直接看成该类型使用,无需显式转换:
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// 作过类型判断之后,obj会被系统自动转换为String类型
return obj.length
}
// 这里的obj仍然是Any类型的引用
return null
}
或者
fun getStringLength(obj: Any): Int? {
if (obj !is String)
return null
// 在这个分支中, `obj` 的类型会被自动转换为 `String`
return obj.length
}复制代码
若是你有一个可空类型元素的集合,而且想要过滤掉空元素,你可使用 filterNotNull
来实现:
val listOf = listOf<Int?>(1, 2, null, 4)复制代码
val intList: List<Int> = listOf.filterNotNull()复制代码
区间表达式由具备操做符形式 .. 的 rangeTo 函数辅以 in 和 !in 造成。
使用 in 运算符来检测某个数字是否在指定区间内:
for (i in 1..4) print(i) // 输出“1234”
for (i in 4..1) print(i) // 什么都不输出
if (i in 1..10) { // 等同于 1 <= i && i <= 10
println(i)
}
// 使用 step 指定步长
for (i in 1..4 step 2) print(i) // 输出“13”
for (i in 4 downTo 1 step 2) print(i) // 输出“42”
// 使用 until 函数排除结束元素
for (i in 1 until 10) { // i in [1, 10) 排除了 10
println(i)
}
val list = listOf("a", "b", "c")
if (-1 !in 0..list.lastIndex) {
println("-1 is out of range")
}
if (list.size !in list.indices) {
println("list size is out of valid list indices range, too")
}
复制代码
for 循环能够对任何提供迭代器(iterator)的对象进行遍历,语法以下:
一样,kotlin的for循环中使用的也是in操做符,
val items = listOf("dog", "cat", "pig")
for (item in items) {
println(item)
}
复制代码
或者经过索引
val items = listOf("apple", "banana", "kiwifruit")
for (index in items.indices) {
println("item at $index is ${items[index]}")
}
val array=arrayOf("a","b","c")
for (i in array.indices) {
print(array[i])
}复制代码
when 将它的参数和全部的分支条件顺序比较,直到某个分支知足条件,
when 既能够被当作表达式使用也能够被当作语句使用。若是它被当作表达式,符合条件的分支的值就是整个表达式的值,若是当作语句使用, 则忽略个别分支的值。
when 相似java语言的 switch 操做符。其最简单的形式以下:
fun describe(obj: Any): String =
when (obj) {
1 -> "One"
"Hello" -> "Greeting"
is Long -> "Long"
!is String -> "Not a string"
else -> "Unknown"
}
复制代码
在Kotlin中Any类是全部类的超类,相似java中的Object;
在 when 中,else 同 switch 的 default。若是其余分支都不知足条件将会求值 else 分支。若是不少分支须要用相同的方式处理,则能够把多个分支条件放在一块儿,用逗号分隔。
when 也能够用来取代 if-else if链。 若是不提供参数,全部的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支:
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}复制代码
val fruits = listOf("banana", "avocado", "apple", "kiwifruit")
fruits.filter { it.startsWith("a") }
.sortedBy { it }
.map { it.toUpperCase() }
.forEach { println(it) }
结果:APPLE AVOCADO
复制代码
类的修饰符包括 classModifier 和_accessModifier_:
classModifier: 类属性修饰符,标示类自己特性。
abstract // 抽象类
final // 类不可继承,默认属性
enum // 枚举类
open // 类可继承,类默认是final的
annotation // 注解类复制代码
accessModifier: 访问权限修饰符
private // 仅在同一个文件中可见
protected // 同一个文件中或子类可见
public // 全部调用的地方均可见
internal // 同一个模块中可见复制代码
Koltin 中的类能够有一个 主构造器,以及一个或多个次构造器,主构造器是类头部的一部分,位于类名称以后:
class Person constructor(firstName: String) {}复制代码
若是主构造器没有任何注解,也没有任何可见度修饰符,那么constructor关键字能够省略。
class Person(firstName: String) {
}复制代码
主构造器中不能包含任何代码,初始化代码能够放在初始化代码段中,初始化代码段使用 init 关键字做为前缀。
class Person constructor(firstName: String) {
init {
firstName="Scus" //主构造器的参数能够在初始化代码段中使用
}
}复制代码
另外能够经过主构造器来定义属性并初始化属性值(能够是var或val):
class People(val firstName: String, val lastName: String) {
//...
}复制代码
若是一个非抽象类没有声明构造函数(主构造函数或次构造函数),它会产生一个没有参数的构造函数。构造函数是 public 。若是你不想你的类有公共的构造函数,你就得声明一个空的主构造函数:
class DontCreateMe private constructor () {
}复制代码
类也能够有二级构造函数,须要加前缀 constructor:
class Person(val name: String) {
constructor (name: String, age:Int) : this(name) {
// 初始化...
}
}复制代码
抽象是面向对象编程的特征之一,类自己,或类中的部分红员,均可以声明为abstract的。抽象成员在类中不存在具体的实现。
注意:无需对抽象类或抽象成员标注open注解。
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}复制代码
咱们能够把类嵌套在其余类中,看如下实例:
class Outer { // 外部类
private val bar: Int = 1
class Nested { // 嵌套类
fun foo() = 2
}
}复制代码
使用对象表达式来建立匿名内部类:
test.setInterFace(object : TestInterFace {
override fun test() {
println("对象表达式建立匿名内部类的实例")
}
})
复制代码
内部类使用 inner 关键字来表示。
内部类会带有一个对外部类的对象的引用,因此内部类能够访问外部类成员属性和成员函数。
class Outer {
private val bar: Int = 1
var v = "成员属性"
/**嵌套内部类**/
inner class Inner {
fun foo() = bar // 访问外部类成员
fun innerTest() {
var o = this@Outer //获取外部类的成员变量
println("内部类能够引用外部类的成员,例如:" + o.v)
}
}
}
复制代码
Kotlin 能够建立一个只包含数据的类,关键字为 data:
data class User(val name: String, val age: Int)复制代码
密封类用来表示受限的类继承结构:当一个值为有限几种的类型, 而不能有任何其余类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合 也是受限的,但每一个枚举常量只存在一个实例,而密封类 的一个子类能够有可包含状态的多个实例。
声明一个密封类,使用 sealed 修饰类,密封类能够有子类,可是全部的子类都必需要内嵌在密封类中。
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
fun eval(expr: Expr): Double = when (expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
}复制代码
interface MyInterface {
fun bar() // 未实现
fun foo() {//已实现
println("foo")
}
}复制代码
class Child : MyInterface {
override fun bar() {
// 方法体
}
override fun fool() {
// 方法体
}
}复制代码
interface MyInterface{
var name:String //name 属性, 抽象的
}
class MyImpl:MyInterface{
override var name: String = "runoob" //重写属性
}复制代码
kotlin 中全部类都继承该 Any 类,它是全部类的超类,对于没有超类型声明的类是默认超类:
若是一个类要被继承,可使用 open 关键字进行修饰。
open class Base(p: Int) // 定义基类
class Derived(p: Int) : Base(p)复制代码
注意:Any 不是 java.lang.Object。
若是子类有主构造函数, 则基类必须在主构造函数中当即初始化。
open class Person(var name : String, var age : Int){// 基类
}
class Student(name : String, age : Int, var no : String, var score : Int) : Person(name, age) {
}复制代码
若是子类没有主构造函数,则必须在每个二级构造函数中用 super 关键字初始化基类,或者在代理另外一个构造函数。初始化基类时,能够调用基类的不一样构造方法。
class Student : Person {
constructor(ctx: Context) : super(ctx) {
}
constructor(ctx: Context, attrs: AttributeSet) : super(ctx,attrs) {
}
}复制代码
在基类中,使用fun声明函数时,此函数默认为final修饰,不能被子类重写。若是容许子类重写该函数,那么就要手动添加 open 修饰它,。
Kotlin 使用 object 关键字来声明一个对象。
Kotlin 中咱们能够方便的经过对象声明来得到一个单例。
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ……
}
val allDataProviders: Collection<DataProvider>
get() = // ……
}复制代码
引用该对象,咱们直接使用其名称便可:
DataProviderManager.registerDataProvider(……)复制代码
固然你也能够定义一个变量来获取获取这个对象,当你定义两个不一样的变量来获取这个对象时,你会发现你并不能获得两个不一样的变量,也就是说经过这种方式,咱们得到一个单例:如
object Site {
var url:String = ""
val name: String = "菜鸟教程"
}
fun main(args: Array<String>) {
var s1 = Site
var s2 = Site
s1.url = "www.runoob.com"
println(s1.url)
println(s2.url)
}
// 输出结果都为"www.runnoob.com"
复制代码
类内部的对象声明能够用 companion 关键字标记,这样它就与外部类关联在一块儿,咱们就能够直接经过外部类访问到对象的内部元素。
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
val instance = MyClass.create() // 访问到对象的内部元素复制代码
咱们能够省略掉该对象的对象名,而后使用 Companion 替代须要声明的对象名:
class MyClass {
companion object {
}
}
复制代码
注意:一个类里面只能声明一个内部关联对象,即关键字 companion 只能使用一次。
伴生对象的成员看起来像java的静态成员,但在运行时他们仍然是真实对象的实例成员。