科特林岛(Котлин)是一座俄罗斯的岛屿,位于圣彼得堡以西约30千米处,形状狭长,东西长度约14千米,南北宽度约2千米,面积有16平方千米,扼守俄国进入芬兰湾的水道。科特林岛上建有喀琅施塔得市,为圣彼得堡下辖的城市。java
Kotlin是一门以kotlin岛名命名的现代化编程语言。它是一种针对Java平台的静态类型的新编程语言。专一于与Java代码的互操做性,几乎能够应用于现今使用Java的任何地方:服务端开发、Android应用等等。Kotlin能够很好的和全部现存的java库和框架一块儿工做,且性能与Java旗鼓至关。git
谷歌开发者社区作过一个问卷调查,大概有40%的Android开发者已使用过Kotlin。github
Kotlin与Java同样是一门静态类型编程语言,在编译期就已经肯定全部表达式的类型。可是与Java不一样的一点是Kotlin不须要在源码中显示的每一个声明变量的类型,其所拥有的 类型推导 特性使编译器经过上下文推断变量的类型。 以下编程
var xOld: Int = 1
var x = 1
var yOld: String = "string"
var y = "string"
复制代码
在讨论Kotlin中的函数式编程前,咱们先了解一下函数式编程的核心概念:数组
- 头等函数:把函数看成值来使用和传递,可使用变量保存它,也能够将其做为其余函数的参数来进行传递,或者将其做为函数的返回值。
- 不可变性:使用不可变对象,保证该对象的状态在建立以后不会再变化。
- 无反作用:使用纯函数,此类函数在输入相同参数时会输出一样的结果,且不会改变其余变量的状态,也不会与外界有任何交互。
因为Kotlin的首要目标是提供一种更简介、高效、安全的替代java的语言,因此其拥有java的面向对象特性。同时其丰富的特性集也让其支持函数式编程的风格。主要特性以下:安全
示例代码:bash
fun main(args: Array<String>) {
var hello: () -> Unit = { print("Hello world") }
test(hello)
}
fun test(f: () -> Unit) {
f.invoke()
}
>>>
Hello world
复制代码
fun main(args: Array<String>) {
var square: (Int) -> Int = { it * it }
test(10, square)
}
fun test(i: Int, f: (Int) -> Int) {
print(f(i))
}
>>>
100
复制代码
//数据类
data class User(val name:String,val age:Int)
复制代码
Kotlin彻底开源,能够自由使用,采用Apache2许可证;开发过程彻底公开在Github上。架构
推荐的开发工具:IntelliJ IDEA、Android Studio、Eclipse。框架
Kotlin 不是一门哲学性、研究性语言而是一门实用语言,它的的诞生是为了解决当前编程世界的许多问题,它的特性也是依据与许多开发者遇到的场景而选择的。Kotlin开发团队对于Kotlin能帮助解决实际项目问题的特性颇有自信。编程语言
Kotlin也没有强制使用某种特定的编程风格和范式。因为其设计者是JetBrains,Kotlin的开发工具IntelliJ IDEA的插件和编译器乃是同步开发,且在设计语言特性时就考虑到了对工具的支持,因此毋庸置疑,Kotlin的开发工具对于开发者来讲是及其有好的。
在咱们编写Kotlin过程当中,良好的IDE会发现那些能够用更简洁的结构来替换的通用代码模式,咱们同时也能够经过研究IDE使用的语言特性,将其应用到本身的代码中。
Java中常见的代码在getter
、setter
以及将构造函数的参数赋值给变量的操做都被设置为隐式的。
var name:String = ""
var name: String
get() {
return name
}
set(value) {
name = value
}
复制代码
class Person(name: String){
init {
print(name)
}
}
class Student{
constructor(name: String){
print(name)
}
}
复制代码
Kotlin丰富的标准库也能够代替不少没必要要的冗长的代码。
例:以下所示的list,需求为:输出其中第一个包含字母 b
的item,咱们来看看经过Java和Kotlin的分别是如何实现的。
List<String> list = new ArrayList<>();
list.add("a");
...
list.add("a");
list.add("ab");
复制代码
Java
for (String s : list) {
if (s.contains("b")) {
System.out.print(s);
break;
}
}
复制代码
Kotlin
print(list.find { it.contains("b")})
复制代码
仅需一行代码就轻松搞定了。
还有一个细节须要提醒你们,与不少现代语言同样,Kotlin中 没有;
号。
越简洁的代码写起来花的时间越短,更重要的是,读起来耗费的时间更短,便于提升你的生产力, 使你更快的达到目标。
一般,咱们声称一门语言是安全的,安全的含义是它的设计能够防止程序出现某些类型的错误。Kotlin借鉴于Java,提供了一些设计,使其使用起来更加安全。
?
,将其用来定义变量是否为 null
val s: String= "" //不可为空
val s1: String? = null //可为空
复制代码
if (value is String){
print(value.toUpperCase())
}
复制代码
在Java项目中添加Kotlin代码时,不会影响咱们的任何Java代码。同时,咱们能够在Kotlin中使用Java的方法、库。能够继承Java的类,实现Java的接口,在Kotlin上使用Java的注解。
另外,Kotlin的互操做性使Java代码像调用其余Java类和方法同样轻松的调用Kotlin代码,在项目任何地方均可以混用Java和Kotlin。
Kotlin最大程度的使用Java库,使其互操做性更强。好比,Kotlin没有本身的集合库,它彻底依赖的Java标准库,并使用额外的函数来扩展集合功能,使它们在Kotlin中能够更加便捷的使用。
Kotlin的工具也对互操做性提供了全面支持。能够编译任意混合的Java和Kotlin源码。两种源文件自由切换,重构某一种语言时,在另一种语言中也会获得正确的更新。
两种语言能够随意拼接,就像呼吸同样天然。
在Java中声明变量时:
int a = 10
复制代码
在Kotlin中声明变量时,参数的名称和类型使用:
分隔,类型在:
以后,稍后咱们会看到函数声明也是如此。
val a: Int = 10 //显示指定变量类型
val a = 10 //类型声明省略
复制代码
在Kotlin中全部的变量必须有初始值,若是没有也必须经过延迟初始化或者懒加载方式,在调用变量以前完成赋值操做。
lateinit var name: String
复制代码
val name by lazy { "liuxi" }
复制代码
若是你细心的话,会发如今上述的例子中出现了两种声明变量的关键字:
val
声明变量,在须要的时候再使用 var
声明变量Kotlin为咱们提供了更便捷的字符串拼接方式供咱们使用
val name = "liuxi"
fun age() = 12
val log = "name: $name age: ${age()}"
>>>
name: liuxi age: 12
复制代码
咱们先来看一下Kotlin中的Hello world。
fun main(args: Array<String>){ //main 是函数名,()内部是参数列表, {}内部是函数体
print("Hello world")
}
复制代码
Kotlin使用fun
关键字来定义函数。
接下来咱们在看一下带返回类型的函数:
fun max(a: Int,b: Int): Int{
retun if (a > b) a else b
}
复制代码
函数的返回类型被定义在 :
以后
Kotlin中 if 是有结果值的表达式,不是语句,同时被用来替换掉Java中三元运算符 (boolean值) ? (表达式1) :(表达式2)。
表达式和语句的区别是表达式有值,且能够做为另外一个表达式的一部分来使用。
若是一个函数的函数体是由单个表达式构成的,那么这个函数能够去掉外层的括号{}
和return
,在函数声明和表达式中间用=
链接。
fun max(a: Int,b: Int) = if(a > b) a else b
//=后面的内容能够被称做表达式体
复制代码
借助于Kotlin优秀的类型推导特性,在上述范例中,咱们也省略掉了对函数返回类型的声明。
从Kotlin到Java,不少概念都是相似的。Kotlin 为了让函数更简洁易读,借鉴Java并作出了不少改,基本以下:
咱们先来看看在Kotlin 中命名参数,默认参数值的使用
命名参数指的是在调用Kotlin定义的函数是,能够显示的代表一些参数的名称。固然若是一个函数由多个参数组成,为了不混淆,在你给其中一个参数之名名称以后,其以后的全部参数都须要标明名称。
fun createAccount(account: String,password: String,desc: String){
...
}
create("liuxi","123456","cool")//常规使用
create("liuxi",password = "123456", desc = "cool") //使用命名参数的方式调用
复制代码
默认参数值,顾名思义就是一个参数支持声明默认值,用于避免重载的函数。
好比上述的createAccount()
函数中的desc
是一个非必要参数,这时咱们就能够给其设置默认值。
fun createAccount(account: String,password: String,desc: String = ""){
...
}
createAccount("liuxi","123456","cool")
createAccount("liuxi","123456")//当用户不想输入这个参数时,咱们能够直接最后一个参数省略掉。
复制代码
固然,默认参数值的写法在Java中是不被支持的,调用此方法时仍需显式的指定全部参数。为了Java也能够体会到此方法的便捷性,咱们可使用@JvmOverloads
注解此方法。这样在Java中会自动生成此方法的重载方法。
顶层函数和属性的诞生是为了消除Java中静态工具类的写法。即Java中各式各样的Util工具类。
在Kotlin中,咱们能够将一些函数和属性(它们的功能类型使得它们很难归属到某一个具体的类中)直接写在代码文件(.kt)的顶层,不属于任何的类。这些函数和属性依旧是包内的成员。若是从包外想访问它,则须要Import。 咱们来建立一个Log.kt
文件
package com.liuxi.fun
val KEY = "123456"
fun log(msg:String){ //不须要放置在类体中
Log.d("liuxi_test", msg)
}
复制代码
在Kotlin中使用此方法就比较便捷:
log("测试顶层函数")
复制代码
而在Java中,这个文件会被编译成一个以文件名命名的类,顶层函数会被编译成这个类的静态函数。咱们也能够利用@JvmName
注解去自定义类的名称。
/*Java*/
package com.liuxi.fun
public class LogKt{
public static final String KEY = "123456"
public static void log(String msg){
Log.d("liuxi_test", msg)
}
}
复制代码
Kotlin的一个特点就是能够平滑的与现有代码集成。当咱们在项目中遇到Java和Kotlin混合使用的时候,为了便捷的使用一些基于Java的功能,咱们能够利用扩展函数来对其进行优化。
理论上来讲,扩展函数就是类的成员函数,只不过它定义在类的外面。咱们来看一个TextView的扩展函数:结合SpannableString
给TextView
赋值包含一个Image的文本:
/*文件名SpanHelper.kt*/
/**
* 将目标String的替换为Image
*
* @param mainBody
* @param targetBody
* @param drawableRes
* @return
*/
fun TextView.setTextWithImageSpan(mainBody: String?, targetBody: String?, drawableRes: Int) {
if (mainBody.isNullOrEmpty() || targetBody.isNullOrEmpty() || drawableRes <= 0) {
return
}
val spannableStr = SpannableString(mainBody)
val start = mainBody.indexOf(targetBody)
val end = start + targetBody.length
spannableStr.setSpan(ImageSpan(context, drawableRes), start, end, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)
text = spannableStr
}
复制代码
此扩展方法的调用示例:
textView.setTextWithImageSpan("酷酷的", "酷酷", R.drawable.icon_diamond_small)
复制代码
简短的一行代码,咱们就实现了给TextView设置一段包含图片的文本。这个功能真的很酷~~~
接下来,咱们来看一下在Java中如何调用扩展函数。
SpanHelperKt.setTextWithImageSpan(textView, "酷酷的", "酷酷", R.drawable.icon_diamond_small)
复制代码
由于这个函数被声明为顶层函数,因此它被编译成为静态函数。textView
被当作静态函数的第一个参数来使用。
扩展函数不存在重写,Kotlin会把其看成静态函数来看待。
接下来咱们来看一下扩展属性:
//文件名IntUtil.Kt
val Int.square: Int
get() = toInt() * toInt()
fun main(args:Array<String>){
print(5.square)
}
>>> 25
复制代码
与扩展函数同样,扩展属性也像是接收者的一个普通成员属性同样。可是必须定义 getter
函数。由于没有支持的字段,因此没有默认的getter
。同理由于没有存储的地方,因此也没法初始化。我的感受扩展属性和扩展函数相似,可是使用的便捷性比扩展函数略逊一筹。
//扩展属性在Java中的调用
IntUtilKt.getSquare(5)
>>> 25
复制代码
一种支持在函数内部再定义一个函数的语法:
fun createAccount(name:String,password:String){
fun lengthNotEnough(string:String) = string.length > 10
if(lengthNotEnough(name) return
if(lengthNotEnough(password) return
}
复制代码
这个功能的意义在于解决代码重复问题,尽量的保持清晰的代码结构,提升可读性。
小知识点:局部函数能够访问外层函数的参数。
以下是Collections.kt
中的一个方法
public fun <T> listOf(vararg elements: T): List<T> = if (elements.size > 0) elements.asList() else emptyList()
复制代码
vararg
修饰符在Kotlin中被用来声明能够接受任意数量的参数。
一个颇有趣的语法糖。 一下是我随手写的一个例子
infix fun TextView.copy(view: TextView) {
this.text = view.text
}
复制代码
咱们使用infix
添加在只有一个参数的函数前面。上述例子是在一个扩展函数前面添加上了infix
。 使用范例以下:
val textView1 = ...
val textView2 = ...
textView1 copy textView2 //中缀调用式写法,这样textView2的内容就被复制到了textView1 中
textView1.copy(textView2)//常规写法,与中缀调用式写法等价。
复制代码
中缀调用是一种特殊的函数调用,在使用过程当中没有添加额外的分隔符,函数名称(copy)直接放置在目标对象和参数之间。
Kotlin中的集合与Java的类相同,可是对扩展函数、内联函数等对API作了大量的扩展,此处就再也不一一阐述了。
Kotlin中和Java中同样,使用关键字 class
声明类
class Person{
}
复制代码
若一个类没有类体,可上述花括号能够省略
class Person
复制代码
另外一个须要注意的地方是:Kotlin中类声明中没有了public
。在Kotlin中,类默认为public和final的
如你所知,在Java中,一个类能够声明一个或者多个构造函数,在Kotlin中一样如此。可是Kotlin对构造函数作了一些修改,引入的主构造方法(在类体外声明,为了简洁的初始化类的操做而被创造出来)和从构造方法(与Java构造方法相似,在类体中声明)并为此新增了两个关键字 init
和 constructor
。
关键字constructor
用来声明 构造方法 。
下面是一个用Java式思想和kotlin定义的新语法写出来的Person类
class Person{
val name: String
constructor(name: String){
this.name = name
}
}
复制代码
接下来咱们对其引入主构造函数的概念和 init
关键字:
class Person constructor(name: String){ //带一个参数的主构造方法
val name: String
init { //初始化语句块
this.name = name
}
}
复制代码
如上代码所示,init
用来标示初始化语句块,在类被建立的时候被执行,也可搭配主构造h函数使用。一个类中也能够声明多个初始化语句块,它们按照出如今类体中的顺序执行。
接下来,咱们在对Person类进行简化:
class Person constructor(name: String){
val name: String = name
}
复制代码
主构造函数的参数同样可使用 var
或 val
声明(从构造函数不能够),咱们可使用以下方式继续简化咱们类的属性定义:
//val 表示相应的属性会用构造方法的参数来初始化
class Person constructor(val name: String)
复制代码
若是主构造函数没有注解或者可见性修饰符,则咱们能够将constructor
省略。
class Person (val name: String)
复制代码
在 Kotlin 中的一个类能够有一个主构造函数以及一个或多个从构造函数。当一个类有主构造函数时,其类体中声明的从构造函数须要经过直接或者间接的方式委托给主构造函数,此时就须要使用到this
关键字。
class Person(val name: String = "liuxi"){
constructor(name: String,age: Int): this(name)
constructor(name: String,age: Int,gender:String): this(name, age)
}
复制代码
若是你想你的类不被其余代码实例化,则能够给构造函数添加可见型修饰符private
。
class Person private constructor()
复制代码
val p = Person("liuxi")
复制代码
注:Kotlin中声明对象不须要使用 new
关键字
咱们先来认识几个基础概念:
接口 : 使用 inerface
定义
interface OnClickListener{
fun onClick()
fun test(){ //带有默认方法体的方法
print("Hello")
}
}
复制代码
抽象类: 使用abstract
修饰
abstract class Game{
abstract fun play()
open fun record(){ //关键字open用来表示这个方法能够被重写,open也能够被用来修饰类。
print("score")
}
fun test(){
print("test")
}
}
复制代码
接下来咱们来展现一下如何继承一个抽象类
class A: Game(){
override fun play() = print("Go play")
override fun record(){
super.record() //super 关键字用来标示对父类或者接口的默认方法体的引用,相似于Java
}
}
复制代码
接下来咱们在让类A
实现接口OnClickListener
:
class A: Game(), OnClickListener{
override fun play() = print("Go play")
override fun record(){
super.record() //super 关键字用来标示对父类或者接口的默认方法体的引用,相似于Java
}
override fun onClick()
override fun test(){
super.test()
}
}
复制代码
如上所示,不管是继承仍是实现接口,咱们都统一经过 :
来实现,没有使用Java中的 extends
和 implements
。
其中还须要注意下类的继承,和接口的实现有一个区别,被继承的类名后带了()
,它值得是类的构造方法。若是你的类声明了主构造方法且含有参数,则子类也被强制要求实现主构造方法。此定义相似于Java的子类必须实现父类构造方法,且将父类构造所须要的参数传递给父类。
open class A(name:String) //一个类若是非抽象类,则默认是final,不能够被继承,可是用open修饰后就能够了。
class B(name: String): A(name)
复制代码
open
和 final
是两个互斥定义的关键字。
还有一个细节也须要咱们注意,当你override
父类或者接口的方法后,它们默认是open的,若是你不但愿你的子类再去修改它,须要用final
修饰
class A: Game(), OnClickListener{
final override fun play() = print("Go play")
...
}
复制代码
数据类
data class Person(val name)
复制代码
内部类和嵌套类
Java:
class A{
public String name = "liuxi";
static class B{
void test(){
System.out.print(name);//此操做被拒绝
}
}
class C{
void test(){
System.out.print(name);
}
}
}
复制代码
Kotlin:
class A{
var name = "liuxi"
class B{
fun test(){ //为嵌套类
//print(name),此操做被拒绝,不持有外部类的实例,至关于Java 中的 static class B
}
}
inner class C{
fun test(){ //为嵌套类
print(name) //持有外部类的实例
}
}
}
复制代码
密封类 : 定义受限的类继承结构。这是为了解决when结构必需要实现一个else分支(相似于Java switch case中的default)而提出的解决方。给父类添加 sealed
修饰符且全部的直接子类必须嵌套在父类中。
sealed class Children{
class Boy: Children()
class Girl:Children()
}
复制代码
object
关键字object
关键字在Kotlin中有主要有三个使用场景,其核心理念是:object
定义一个类同时建立一个实例。 接下来,看一下三个场景各是什么:
object
关键字,经过一句话去定义一个类和一个该类的变量。在编译过程当中,该变量的名字始终都为INSTANCE
。object Utils{ //因为类在定义的时候就当即建立了该类的变量,因此该类不被容许拥有构造函数,除此以外,其余地方都和普通类同样。
fun test(){
...
}
val TEST = "test"
}
复制代码
没有在Java中的各类样式书写的单例模式,Kotlin仅需一行代码,轻松搞定。
kotlin中调用以下:
Utils.test()
Utils.TEST
复制代码
在Java中调用以下:
Utils.INSTANCE.test()
Utils.INSTANCE.TEST
复制代码
须要经过.INSTANCE
引用对象声明中的方法,相似于咱们在Java单例模式中习惯去建立的mInstance
Kotlin没有保留Java中的static
关键字,为了补足静态函数和属性的功能,Kotlin提出了伴生对象,顶层函数和属性(也叫包级函数和属性),咱们先来说解伴生对象,后者在以后会被提到。
伴生对象是一个声明在类中的普通对象,能够拥有名字,也能够实现接口或者有扩展函数或属性。
class Person(val name: String){
companion object Loader{
fun test(){
...
}
val type:String = "simple"
}
}
复制代码
如上范例所示:companion
关键字被用来标记伴生对象,伴生对象的名字能够被省略。简易写法以下:
class Person{
companion object{
...
}
}
复制代码
咱们能够把伴生对象看成Java中的静态对象来看待,被定义为伴生对象后,咱们就能够经过容器类的名称来获取这个对象的调用了。示例以下:
Person.companion.test()
复制代码
object
来实现匿名内部类object
在类体内部,能够被用来声明匿名对象 (替代了Java中的匿名内部类用法)。
常见使用场景有:将一个接口或者抽象类声明为匿名对象。
以下是将咱们常见的 OnClikListener
声明为匿名对象:
view.setOnClickListener(object: OnClickListener{
override onClick(v:View){
}
})
复制代码
Java中使用swich
语句来完成对一个值可能出现的不一样状况的做出对应处理。在Kotlin中咱们使用when来处理。它比switch拥有更多的使用场景,功能更增强大,使用的也更加频繁,且还能够和智能转换 完美的结合使用。
when和if同样,都是表达式,能够拥有返回值。
举个例子来讲:当下有一个需求,咱们须要根据不一样vip等级返回不一样的drawable,供给ImageView使用。
val imageView: ImageView = ...
val level: Int = ...
var imageView = imageView.setImageResource(
when (level) {
1 -> R.drawable.icon_level_1
2 -> R.drawable.icon_level_2
3 -> R.drawable.icon_level_3
4 -> R.drawable.icon_level_4
5 -> R.drawable.icon_level_5
else -> {
R.drawable.icon_level_default
}
})
复制代码
when
语句对于level
的不一样状况,when
返回不一样分支上的值交给setImageResource()
去使用。
值得一提的是,when
支持在一个分支上合并多个选项,好比咱们对上述例子作出修改,将一、2定义为上,3定义为中,四、5定义为下,咱们使用 when
写出的代码以下:
...
when (level) {
1,2 -> R.drawable.icon_level_top
3 -> R.drawable.icon_level_middle
4,5 -> R.drawable.icon_level_bottom
else -> {
R.drawable.icon_level_default
}
}
复制代码
when
比switch
强大的地方在于: 前者容许使用任何对象做为分支条件,甚至包括无参。可是后者必须使用枚举常量、字符串或者数字字面值。
open class BaseActivity{
...
}
class MainActivity: BaseActivity(){
...
fun mainPrint(){
print("main")
}
}
class SettingActivity: BaseActivity(){
...
}
复制代码
fun getTitle(activity: BaseActivity):String = when(activity){
is MainActivity -> {
activity.mainPrint()
"main" //if 和 when 均可以使用代码块做为分支体,在示例状况下,代码块中最后一个表达式就是返回的结果
}
is SettingActivity -> "Setting"
else -> {
""
}
}
复制代码
is
:用来检查判断一个变量是不是某种类型,检查事后编译器会自动帮你把该变量转换成检查的类型,你能够直接调用该类型下的方法和变量,此功能能够被称做 智能转换 。它替咱们省略掉了Java 中的instanceOf
检查后的类型强制转换这一步,类型判断和类型转换二合一的操做也使咱们的代码更加安全。固然,若是你仍然须要显式的类型转换,Kotlin提供了as
来表示到特定类型的显示转换。
var a = activity as Maintivity
复制代码
接下来咱们从一个例子来认识一下 when
的无参写法。
咱们须要根据两人团中成员的性别分别给出类型定义,如果都为男性则称做男团、都为女性则成为女团、男女混合则称做二人转。
/*常量*/
const val BOY = 0
const val GIRL = 1
const val BOY_GROUP = "男团"
const val GIRL_GROUP = "女团"
const val MIX_GROUP = "二人转"
复制代码
咱们先给出 if
语句的写法:
fun getGroupName(gender1: Int, gender2: Int) =
if (gender1 != gender2) {
MIX_GROUP
} else if (gender1 == BOY) {
BOY_GROUP
} else {
GIRL_GROUP
}
复制代码
咱们先给出 when
语句的写法:
fun getGroupName2(gender1: Int, gender2: Int) =
when {
gender1 != gender2 -> MIX_GROUP
gender1 == BOY -> BOY_GROUP
else -> GIRL_GROUP
}
复制代码
when
语句的功能特别丰富,相似于Java中的switch
可是功能更增强大。
可空性 是Kotlin类型系统中帮你避免NullPointerException错误的特性。
在Java中咱们遇到过太多太多的 java.lang.NullPointerException
,Kotlin解决这类问题的方法是将运行时的异常转换为编译时的异常。经过支持做为类型学系统的一部分的可控性,编译器就能在编译期间发现不少潜在的错误,从而减小运行时抛出空指针异常的可能性。
以下是一个获取字符串长度的方法
fun getLength(str: String?){ //咱们须要在那些可能为null的实参后面加上问号来标记它
if(str == null){
return 0
}
return str.length
}
复制代码
问号 ?
能够加在任何类型的后面,来表示这个类型的变量能够存储null
引用。相反没有被 ?
标记的变量不能存储null
引用。若是你对一个不接收null
引用的参数赋值为null
,程序会直接抛出异常 Null can not be a value of a non-null type
。
可空类型和非空类型在Kotlin中有着很严格的区分。一旦你的变量被定义为可空类型,对它的操做也将收到限制,你将没法调用它的方法,除非你能证实它不为空;也不能将它赋值给非空类型的变量。也不能将可空类型的参数传给拥有非空类型参数的函数。
那么咱们能够对可空类型参数作什么呢?咱们对于可空类型参数,最重要的就是和null进行比较。一旦你进行了比较操做,编译器就会记住。而且在这次比较发生的做用域内把这个值看成非空来对待(咱们就能够调用非空类型下的参数和函数了),如上述例子。
可空的和非空的对象在运行时没有什么区别。可空类型并非非空类型的包装。全部的检查都发生在编译器。即便用Kotlin的可空类型在运行时不会带来额外的开销。
?.
安全调用运算符?.
容许把一次null
检查和一次方法调用合并成一个操做。
str?.toUpperCase()
if(str != null) str.toUpperCase() else null //等效于str?.toUpperCase()
复制代码
安全运算调符只会调用非空值的方法。 安全调用符的结果类型也是可空的。在上述例子中返回类型为
String?
?:
在引入可空性概念后,就表明了Kotlin中会出现不少默认值为null
的可空类型。为了不默认值weinull
带来的麻烦,Kotlin提供了 Elvis运算符 ?:
(null合并运算符)
下面展现了它是怎么使用的:
fun toStr(s: String?){
val str = s ?: "" // 若是s为null,则赋值为""
var length = s?.length ?: 0 //常见使用场景,和安全调用运算符搭配使用
}
复制代码
!!
一个很暴力的语法!非空断言是最简单直率的处理可空类型的工具。它使用!!
双叹号表示。
var length = s!!.length
复制代码
当一个使用非空断言的变量为null时,会显式的抛出空指针异常。除非你对你的逻辑颇有自信,不要轻易使用这个工具。 常见使用场景是你在一个函数使用一个值时对其进行了非空判断,而在另外一个函数中使用这个值时,不想重复进行非空判断,这时你就可使用非空断言。
let
函数let
函数让可空表达式的处理变得更加容易。它会把调用它的对象变成一个lambda
表达式。
和安全调用运算符一块儿使用时,lambda
中的it
为该对象的非空值,若是该对象为null
则什么都不会发生,不为空时会执行lambda表达式
str?.let{ print(it.length)}
/* 如下是为了增长可读性,简单格式化后的写法*/
str?.let{ it->
print(it.length)
}
/*常规写法的等效语句*/
if(str != null){
print(it.length)
}
复制代码
let
生成的lambda
语句中的it
表明着调用对象的非空值
本文到此也基本告一段落了,写下这边文章的主要目的是想让更多的对Kotlin有兴趣的开发人员、在Kotlin和Java中徘徊纠结的人们还有那些对Kotlin抱有各类观念的人简单了解一下Kotlin,激发起你们学习Kotlin的兴趣,也抛开对Kotlin的偏见。看完此篇文章你应该也能够简单上手Kotlin了,快快写起来吧~
文章的定义是入门讲解,所以不少地方讲解的不够完善,也不够详细。但愿那些对Kotlin有兴趣的人在学习Kotlin过程当中最好仍是可以系统的学习一边Kotlin的基础知识以及进阶语法。文章绝大部分知识点参照于《Kotlin实战》,推荐入门学者翻阅。
一些想写但没加入到文章中的知识点(有些部分是我的掌握不熟练,怕描述不许确,还有一点是基于篇幅考虑,怕写的太长吓跑新人)
- Kotlin中的lambda表达式,这部分的内容十分丰富,还有不少简单便捷的语法糖。
- Kotlin 基本数据类型,有点懒就没写,但愿能够自行翻阅。
- Kotlin中的集合和数组 以及迭代相关知识(for 、区间、数列、map迭代、in关键字)。
- Kotlin的高阶函数和泛型。
- 协程(coroutines)是在Kotlin1.3版本中正式发布,必定要学习~
欢迎你们多多讨论,讲的晦涩或描述的有问题的地方也恳请你们指正,一块儿进步~~~~///(^v^)\~~~