1.kotlin变量
①可变变量:用var关键字,表示可读可写
可读可写 变量名 : 类型 = 初始化值
var text : String = "hello world"
var关键字的变量可重新被赋值:
text = "hi"
②不可变变量:用val关键字,表示只读,只能赋值一次
只读 变量名 : 类型 = 初始化值
val text : String = "hello world"
val关键字的变量不可重新被赋值。
无论是变量还是常量都可以没有初始化值,但是需要注意的是,在声明的变量或常量作为类里面的全局使用的时候必须进行初始化操作,否则会报错,而在函数里面则非必需。
类型推断:
在Java中,声明变量或者常量需要显性的告知所归属的类型,而在kotlin中则无需显示,可交给编译器进行判断:
var x = 5 // 系统自动推断变量类型为Int
③const关键字(编译时常量)
在Kotlin中通过val定义的变量,只有get方法,没有set方法,所以只能读不能写。但是其get方法可以复写,从而造成val定义的变量有可能会发生值变化。这时候就需要采用const关键字。在Kotlin中除了val关键字定义一个常量外,还提供了一个const关键字标识一个常量。const修饰的val变量相当于java中static final,是真正意义上的java常量。
(1)在实际编码中无法直接使用const val,而必须写在companion object代码块里:
//companion object类似于Java的static
companion object {
const val C : Int = 0
}
(2)const关键字不能修饰局部变量
const是编译时常量,所以它不能被定义在函数内部作为局部变量。如果在函数内部定义,就必须在运行时才能调用函数赋值,就不能叫编译时常量了。因此,编译时常量只能在函数之外定义,就可以就编译期间初始化了。
④range 区间
range表达式由具有操作符形式 .. 的rangeTo函数辅以 in 和 !in 形成。
for (i in 1..4) print(i) // 输出“1234”
for (i in 4..1) print(i) // 什么都不输出,因为在使用in的时候必须保证前面的数字小于后面的数字
if (i in 1..10) { // 等同于 1 <= i && i <= 10
println(i)
}
if (i !in 1..10) { // 等同于 i < 1 || 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)
}
注意:使用 ../downTo既包括前又包括后,使用until只包括前不包括后。
2.when表达式
when将它的参数和所有的分支条件顺序比较,直到某个分支满足条件。
when既可以被当做表达式使用(有返回值),也可以被当做语句使用。如果它被当做表达式,符合条件的分支的值就是整个表达式的值,如果当做语句使用, 则忽略个别分支的值。
when 类似java语言的switch操作符。
when当做表达式的例子:
val week = 2
val info = when (week) {
1 -> "今天星期一"
2 -> "今天星期二"
else -> "不是星期一 ,也不是星期二"
}
相比较Java的switch而言,when更加强大,它不仅能满足同一个类型的条件判断,同时还能满足多种类型的判断:
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
3.字符串模板
①$varName 表示变量值
val garden = "海洋公园"
val time = 6
println("今天我去${garden}玩了$time 小时")
注:如果$后面紧跟着文字,则需要使用{},如果$后面不紧跟文字(多一个空格)就可以不用{}
②${表达式} 表示表达式的返回值
val isLogin = false
println("服务器返回值:${if(isLogin) "登录成功" else "登录失败"}"
4.函数
修饰符(默认为public) fun关键字 函数名(参数名: 参数类型) : 函数返回值类型 {}
private fun methodName(age: Int, name: String):Int {
}
①有返回值的函数
fun sum(a: Int, b: Int): Int {
return a + b
}
②无返回值的函数
fun printSum(a: Int, b: Int): Unit {
print(a + b)
}
Unit可以不写,系统默认的就是Unit。Unit相当于Java里的void。
所以上面的例子也可以写成:
fun printSum(a: Int, b: Int) {
print(a + b)
}
③可变长参数函数 vararg
fun vars(vararg v:Int){
for(vt in v){
print(vt)
}
}
// 测试
fun main(args: Array<String>) {
vars(1,2,3,4,5) // 输出12345
}
④函数参数的默认参数
private fun getStu(name: String, age: Int = 18) {
println("她的名字是$name, 年龄是$age")
}
注意到这个方法有两个参数,其中第二个给了默认值,此时使用这个方法可以传入两个参数,或者传入一个参数(第二个参数使用默认值)。
getStu("lili", 15)
输出:她的名字是lili, 年龄是15
getStu("lili")
输出:她的名字是lili, 年龄是18
⑤具名函数参数
比如有一个函数,它的参数个数特别多:
private fun getStu(name: String, age: Int, sex: String, phone: String, pwd: String, score: Int, job: String, address: String) {
}
由于参数特别多,使用该函数的时候,需要按照参数顺序一个一个的传值,此时可以使用具名函数参数,就不需要考虑参数的顺序问题了:
getStu(age = 15, job = "doctor", phone = "15132458765", score = 99, address = "beijing", name = "lili", sex = "girl", pwd = "123")
5.匿名函数
匿名函数的完整格式为:
修饰符 函数名: (参数类型,参数类型) -> 返回值类型 = {参数名,参数名 -> 函数实现}
举例:
fun main(args: Array<String>) {
val sum: (Int, Int) -> Int = {x,y -> x+y}
println(sum(1,2)) // 输出 3
}
其中val sum: (Int, Int) -> Int = {x,y -> x+y}就是匿名函数,它等价于:
fun sum(x: Int, y: Int): Int {
return x + y
}
匿名函数还可以分开写:
fun main(args: Array<String>) {
//第一步:函数输入输出的声明
val sum: (Int, Int) -> Int
//第二步:对上面函数的实现,在实现里,最后一行语句代表函数的返回值,但是不能写return关键字
sum = {x,y ->
x - y
x+y //这个才是返回值
}
//第三步:调用此函数
println(sum(1,2)) // 输出 3
}
注意:
(1)匿名函数不要写return,最后一行就是返回值(隐式返回)。
(2)调用匿名函数时,可以用sum(1,2) 或者 sum.invoke(1,2)。
(3)匿名函数的函数名前面为什用val,而不是fun?匿名函数是没有函数名的,这里的函数名指的是这个匿名函数的返回值,是一个变量,所以用val。
①it关键字
在匿名函数里,如果只有一个参数,则不用显示声明参数,可以用it代替。参数多于一个时,不能使用it。
举例:
val text: (String) -> String = { "hello, $it "}
println(text("lili")) //输出 hello,lili
②匿名函数的类型推断
在匿名函数里,如果匿名函数名后面使用“:”的话,返回值的类型必须显示指定出来:
val sum: (Int, Int) -> Int = {x,y -> x+y}
此时可以使用“=”,则不用显示指明返回值的类型了,会自动推断返回类型。
使用格式为:
修饰符 函数名 = {参数名: 参数类型, 参数名: 参数类型 -> 函数实现}
举例:
val sum = {x: Int, y: Int -> x + y}
同样,{}里面最后一行是返回值。系统会根据最后一行判断返回类型的。
6.参数是函数的函数
fun login(name: String, pwd: String, responseResult: (String, Int) -> Unit) {
if(name == "aqw" && pwd == "1234") {
responseResult("success", 200)
} else {
responseResult("fail", 404)
}
}
login方法一共有三个参数,其中第三个参数是一个匿名函数的形式,这个匿名函数有两个参数,并且没有返回值。
调用login方法有三种方式:
1)login("aqw", "1234") { msg: String, code: Int ->
println("最终登录情况为:msg:$msg, code:$code")
}
2)login("aqw", "1234", { msg: String, code: Int ->
println("最终登录情况为:msg:$msg, code:$code")
})
3)login("aqw", "1234"), responseResult = { msg: String, code: Int ->
println("最终登录情况为:msg:$msg, code:$code")
})
7.内联函数inline
当函数中使用lambda作为参数时,需要声明为内联。如果不使用内联,在调用端会生成多个对象来完成lambda的调用,从而完成性能损耗。
使用内联,相当于c++里的#define宏定义,会把代码替换到调用处,调用处没有任何函数开辟、对象开辟的损耗。
inline fun login(name: String, pwd: String, responseResult: (String, Int) -> Unit) {
if(name == "aqw" && pwd == "1234") {
responseResult("success", 200)
} else {
responseResult("fail", 404)
}
}
所以,如果函数参数有lambda,尽量使用inline内联关键字,这样内部会做优化,减少函数开辟、对象开辟的损耗。
8.函数引用::
假设现在有一个函数,它的一个参数是lambda(比如上边的login函数),此时还有一个函数:
fun methodName(msg: String, code: Int) {
println("结果是: msg:$msg, code: $code")
}
现在要把methodName这个函数当做login的第三个参数传进去,因为lambda属于函数类型的对望,所以这里不能直接传函数进入,就需要用到函数引用了:
login("aqw", "1234", ::methodName)
或者
val obj = ::methodName
login("aqw", "1234", obj)
这里函数引用相当于把普通函数变成了函数类型的对象。
9.函数类型作为返回值
一个函数,它的返回值还可以是一个函数:
fun methodName(info: String): (String, Int) -> String {
println("info: $info")
return { name: String, age: Int ->
"我是返回值函数 name: $name, age: $age"
}
}
methodName是一个函数,它的参数是String,返回值是一个函数,返回值函数的参数是String,Int,返回值函数的返回值是String。
methodName的调用方式为:
val returnMethod = methodName("hello")
val value = returnMethod("lili", 18)
println("value:$value")
10.NULL检查机制
①kotlin变量默认是不可空类型,不能随意赋值为null。
举例:
var text: String = "hello"
text = null 报错,提示不能给非空变量赋值为null
②要想使用null,可以在变量声明的时候指定为可空类型:
var text: String? = "hello"
text = null
③kotlin的空安全设计
对于声明可为空的参数,在使用时候要进行空判断处理,处理的方式有两种:
(1)使用非空断言操作符,字段后加!!像Java一样抛出异常。
var age: String? = "23" //age声明为可空类型
val ages = age!!.toInt() //不管age是不是null都会执行toInt(),和java一样,抛出空指针异常。
注:如果百分百能够保证age是有值的,才可以使用!!断言,否则会有java空指针异常的风险。
(2)字段后加?在值为空的时候不做处理直接返回null或者配合?:做空判断处理。
var age: String? = "23" //age声明为可空类型
age.toInt() //报错,因为age为可空类型,即age可能为null,要想使用age,需要给出补救措施
val ages1 = age?.toInt() //补救措施一:age是可空类型,如果age真的是null,?后面这一段代码不执行,所以不会引发空指针异常 ,返回null
val ages2 = age?.toInt() ?: -1 //补救措施二:age为空返回-1
(3)使用带let的安全调用
var age: String? = "23" //age声明为可空类型
age?.let {
}
let可以把age拿进{}里面来使用,在{}里面默认有一个it,$it的值就是age的值。如果age真的是null,则{}里面的代码不会执行,也就是说,只要执行到{}里,$it肯定不是null。
(4)使用if判断null值,和java一样
var age: String? = "23"
if(age != null) {
val ages = age.toInt()
} else {
……
}
(5)空合并操作符 ?:
var age: String? = "23"
println(age ?: "age是null") //如果age是null,则会输出?:后面的语句(age是null),否则输出age本身的值(23)
6.类型检测及自动类型转换
可以使用is运算符检测一个表达式是否某类型的一个实例(类似于Java中的instanceof关键字)。
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// 做过类型判断以后,obj会被系统自动转换为String类型
return obj.length
}
}
7.基本数据类型
kotlin的基本数值类型包括Byte、Short、Int、Long、Float、Double 等。不同于Java的是,字符不属于数值类型,是一个独立的数据类型。
Kotlin中没有基础数据类型,只有封装的数字类型,每定义一个变量,Kotlin都封装了一个对象,这样可以保证不会出现空指针。数字类型也一样,所以在比较两个数字的时候,就有比较数据大小和比较两个对象是否相同的区别了。在 Kotlin 中,三个等号 === 表示比较对象地址,两个 == 表示比较两个值大小。
fun main(args: Array<String>) {
val a: Int = 10000
println(a === a) // true,值相等,对象地址相等
//经过了装箱,创建了两个不同的对象
val boxedA: Int? = a
val anotherBoxedA: Int? = a
//虽然经过了装箱,但值是相等的,都是10000
println(boxedA === anotherBoxedA) // false,值相等,对象地址不一样
println(boxedA == anotherBoxedA) // true,值相等
}
8.位操作符
对于Int和Long类型,还有一系列的位操作符可以使用,分别是:
shl(bits) – 左移位 (Java’s <<)
shr(bits) – 右移位 (Java’s >>)
ushr(bits) – 无符号右移位 (Java’s >>>)
and(bits) – 与
or(bits) – 或
xor(bits) – 异或
inv() – 反向
1.kotlin中的findViewById
在kotlin中使用findViewById绑定控件:
var button : Button = findViewById(R.id.btn_ok);
或者
可以省去findViewById步骤,直接使用控件的id来操作控件,步骤如下:
①在app的build.gradle 中添加
id 'kotlin-android-extensions'
②在Activity中直接通过View的ID调用方法:
btn_button.text = "这是我的Button"
其中,btn_button是布局文件中Button控件的id。.text = 相当于java中的.setText()方法,用于给button设置文字。
此时,Android Studio会自动导入包,如图
2.kotlin中的setOnClickListener
在android中,点击事件有三种写法:
①匿名内部类。
bt_click.setOnClickListener {
Toast.makeText(this,"点击了",Toast.LENGTH_SHORT).show();
}
和java的区别是,这里不需要new OnClicklistener ,大括号里直接写代码逻辑就行。
②Activity实现全局OnClickListener接口。
class MainActivity : AppCompatActivity(), View.OnClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bt_click.setOnClickListener(this)
}
override fun onClick(v: View?) {
when (v?.id) {
R.id.bt_click ->
Toast.makeText(this, "点击了", Toast.LENGTH_SHORT).show()
}
}
}
这种方法与java的区别是,没有implements关键字表示实现接口;在onClick方法中,when就相当于java中的switch,“:”符号改为了“->”。
③指定xml的onClick属性。
<Button
android:id="@+id/bt_click"
android:layout_width="match_parent"
android:layout_height="50dp"
android:onClick="click"
android:text="点击" />
对应的java代码:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
fun click(v: View?) {
when (v?.id) {
R.id.bt_click ->
Toast.makeText(this, "点击了", Toast.LENGTH_SHORT).show()
}
}
这个与java类似。
3.kotlin中的startActivity
①显示调用
val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)
②隐式调用
val intent = Intent("com.test.demo.second")
intent.addCategory("xxx")
startActivity(intent)
val intent2 = Intent(Intent.ACTION_VIEW)
intent2.data = Uri.parse( "https://www.baidu.com")
startActivity(intent2)
4.kotlin中的类型转换as
引用类型转换:
①引用类型的类型转换只能发生父子之间转换
②子类可以自动转换成父类的对象
③父类需要强制类型转换才可以转换成子类
如下例,window.decorView是View类型,而ViewGroup继承于View,父类转子类需要强制转化。在kotlin中使用as操作符来进行转化:
val viewGroup=window.decorView as ViewGroup
对比java的强制转换:
ViewGroup viewGroup=(ViewGroup)getWindow().getDecorView()
注:在kotlin中使用as操作符时,如果类型转换不成功会发生异常TypeCastException。因此,安全的类型转换常使用as?来解决类型转换异常问题,如果转换失败,则会返回null,但不会报错发生异常。
val y=null
val x:String=y as String
这里,由于null 不可以转换成String,因此会报错。
修改后:
val y=null
val x:String?=y as? String
5.kotlin中的空安全设计?!!
①变量的类型后面加?表示这个变量可以为null。如果该变量为null时,则不会执行该变量后面的逻辑,也不会抛出空指针异常,俗称空安全。如果该变量不为null,就会正常执行该变量后面的内容。
反例: 一个没有加?的变量被赋值为null后,会提示“Null can not be a value of a non--null type String”
正例:
②变量后面加!!表示该变量为null时会抛出空指针异常,像java语法一样空指针不安全;如果不为null,才会正常执行该变量后面的内容。
6.kotlin中接口作为参数
①第一种写法
button.setOnClickListener(object : View.OnClickListener{
override fun onClick(v: View?) {
……
}
})
这种写法体现不了kotlin语言简洁的优势,可以优化为:
button.setOnClickListener{
……
}
②接口做为参数时的各种情况,这里总结一下。
接口作为参数时参数位置一般替换为下面的写法:
{参数1,参数2 ... -> {需要运行的方法}}
在此基础上可以继续优化:
1)优化一:当接口做参数位于最后一个参数时,可以提到小括号外面;
以dailog的取消按钮为例:
dailog的取消事件如下
public Builder setNegativeButton(CharSequence text, final OnClickListener listener)
interface OnClickListener {
void onClick(DialogInterface dialog, int which);
}
因此按照“{参数1,参数2 ... -> {需要运行的方法}}”格式,写法如下:
builder.setNegativeButton("取消", {dialog, which -> dialog.dismiss()})
由于接口做参数位于最后一个参数,此时便可以提到小括号外面,如下:
builder.setNegativeButton("取消"){dialog, which -> dialog.dismiss()}
2)优化二:如果是唯一参数,甚至可以省略小括号;
以button的点击事件为例:
public void setOnClickListener(OnClickListener l)
public interface OnClickListener {
void onClick(View v);
}
在kotlin中可以写成:
button.setOnClickListener({v -> doSomething()})
按1)优化可以写成:
button.setOnClickListener(){v -> doSomething()}
由于setOnClickListener接口中只有一个参数,因此还可以继续优化,即省略小括号,如下:
button.setOnClickListener{v -> doSomething()}
3)当接口的回调方法参数只有一个时,参数和函数推导符可以省略,默认it为其参数。
还是以上面的button点击事件为例:
由于接口的回调方法onClick只有一个参数,因此可以继续优化,把参数和函数推导符省略,doSomething中可以直接使用it代替v,写法如下:
button.setOnClickListener{doSomething()}
注意:lambda表达式在java和kotlin中写法是不一样的(上面用的不是lambda表达式)。比如在java中下面的lambda表达式,在kotlin中是不行的。
button.setOnClickListener(v -> {doSomething()})
7.kotlin中的for循环
①until关键字
until关键字在使用时左侧和右侧都需要一个数值,until循环是一个左闭右开区间。
for (i in 0 until 100) {
println(i) // 输出: 0 ~ 99
}
②…
…在Kotlin中称为区间表达式,表示一个左闭右闭区间,和until关键字一样在使用的时候左侧和右侧都需要一个数值。
for (i in 0..100) {
println(i) // 输出: 0 ~ 100
}
注:…也可以直接声明成一个变量,这个变量就表示一个区间。当循环的时候,循环这个变量就会循环存储在当中的区间。
val test = 0..100
使用…创建的区间只能是一个升序区间,也就是从小到大,不能从大到小,如果想从大到小需要使用另外一个关键字downTo。
③downTo
kotlin当中降序循环使用downTo关键字,downTo关键字生成的是一个左闭右闭区间,同样它也是左右侧都需要一个数值。
for (i in 100 downTo 0) {
println(i) // 输出: 100 ~ 0
}
④step关键字
kotlin在循环的时候默认步长为1,想要改变步长可以使用step关键字。
从0循环到10设置步长为2:
for (i in 0..10 step 2) {
print("${if (i == 10) i else "$i--"}") // 输出: 0--2--4--6--8--10
}
⑤循环数组或列表
1)直接取出元素:
for (i in arrayList) {
println(i) // 输出: 1,2,3,4....2
}
2)同时循环取出索引和元素:
同时取出元素的索引需要在循环时调用容器的withIndex()方法:
for ((index, item) in arrayList.withIndex()) {
println("index: $index, item: $item") // 输出: index: 0, item: 1....index: 13, item: 2
}
3)通过索引遍历一个数组或列表
for (i in 0 until arrayList.size) {
println(arrayList[i]) // 输出: 1,2,3,4....2
}
8.kotlin中的静态
①静态类
Kotlin中使用object修饰静态类,里面的方法和变量都是静态的。被修饰的类可以使用类名.方法名的形式调用,如下:
object Util {
fun getCurrentVersion(): String {
return BuildConfig.VERSION_NAME
}
}
调用:
var version_name1 = Util.getCurrentVersion()
②静态方法
Kotlin中使用companion object修饰静态方法,可以使用类名.方法名的形式调用,如下:
class Util2 {
companion object {
fun getCurrentVersion(): String {
return BuildConfig.VERSION_NAME
}
}
}
调用:
var version_name2 = Util2.getCurrentVersion()