文章目录
1 变量和函数
1.1 变量
kotlin中定义变量只允许在变量前声明两种关键字:var val
。
val是value的缩写,声明一个不可变变量,初始化赋值后不能再重新赋值,相当于java中的final或者js中的const。
var是variable的缩写,声明一个个遍变量。
疑问?
仅仅通过 val var 声明的变量,编译器如何知道变量的类型? kotlin-类型推导机制
fun main() {
val a = 10
println("a = " + a)
}
上述代码中,定义了一个不可变变量a,初始化值为10。kotlin会自动推导出变量a的类型为整型。
当我们对一个变量延时赋值的话,kotlin的类型推导就不好使了。这个时候就需要显示的声明变量类型
fun main() {
var a: Int
a = 10
println("a = " + a)
}
基本参数类型表
java基本类型 | kotlin基本类型 | 解释 |
---|---|---|
int | Int | 整型 |
long | Long | 长整型 |
short | Short | 短整型 |
float | Float | 单精度浮点 |
double | Double | 双精度浮点 |
boolean | Boolean | 布尔型 |
char | Char | 字符 |
byte | Byte | 字节 |
1.2 函数
main函数是入口函数,与java中的main相似。
函数定义的语法规则
fun methodName(param1: Int, param2: Int) :Int {
return 0
}
func是function的缩写。fun后面紧跟的是函数名。其次是参数定义,参数数量不限,声明方式是参数名:参数类型
,定义完参数后需要定义函数返回值的类型,这一步是可选的,如果函数不需要返回值则可以不写。最后是函数体。
变体
import kotlin.math.max
fun largeNumber(a: Int, b: Int): Int {
return max(a,b)
}
当函数体只有一行的时候可以这样写
fun largeNumber(a:Int, b:Int): Int = max(a,b)
fun largeNumber(a:Int, b:Int)=max(a,b)
2 逻辑控制
2.1 if条件语句
一般用法
fun largeNumber(a: Int, b:Int) :Int {
if(a>b){
return a
}else{
return b
}
}
kotlin中的if结构体可以返回值
fun largeNumber(a: Int, b: Int) :Int {
val value = if(a>b){ a }else { b }
return value
}
if语句使每个条件的最后一行代码作为返回值。
再次简写
fun largeNumber(a: Int, b: Int) :Int {
return if(a>b){a}else{b}
}
此时发现,函数体只有一行代码,因此我们可以更进一步的简写。
fun largeNumber(a: Int,b: Int) = if(a>b){a}else{b}
另外,当if结构体中的代码足够简洁,可以进一步压缩。
fun largeNumber(a: Int,b: Int) = if(a>b) a else b
2.2 when条件语句
简单来说when语句类似与switch-case,但是比switch-case更加的强大。
fun getScore(name: String) = when(name){
"a"->80
"b"->90
"c"->100
else->0
}
结构为
when(匹配变量){
匹配值->{匹配后的逻辑}
...
else->{都不匹配的逻辑}
}
当逻辑代码只有一行的时候,大括号和if结构一样可以省略,且每个逻辑结构的最后一行就是返回值。
当我们来检测参数类型的时候
fun checkNumber(num: Number){
when(num){
is Int->println("整型")
is Double->println("双精度浮点")
else -> println("其他类型")
}
}
when结构还有一种不带参数的用法,但是这种用法不常用。
fun getScore(name: String) = when{
name == "a" -> 80
name == "b" -> 70
name.startSwith("z") -> 50
else -> 0
}
2.3 循环语句
while循环和java中的while循环用法一致,但是for循环做了一些修改。
java中常用的for-i循环在kotlin中被移除了而for-each循环在kotlin中获得了增强变成了for-in循环。
kotlin中表示一个双闭区间val range = 0..10
表示创建了一个[0,10]
的闭区间。
kotlin中表示一个左闭右开的区间val range = 0 until 10
表示创建了一个[0,10)
区间。
kotlin中表示一个双闭降序区间val range = 10 downTo 1
表示创建了一个[10,1]的降区间。
遍历一个区间
fun main() {
for (i in 0..10) {
println(i)
}
}
遍历区间还可以使用步长,默认是1
fun main () {
for (i in 0 until 10 step 2){
println(i)
}
}
3 面向对象
3.1 定义对象
class Person {
var name = ""
var age = 0
fun eat(){
println(name + " eating He is age = "+age)
}
}
实例化
fun main () {
val p = Person()
p.name = "张三"
p.age = 19
p.eat()
}
3.2 继承
想要继承,那么被继承的类需要可以被继承。kotlin中创建的类在默认情况想是不可继承的。想要类可以被继承,必须用open关键字声明这个类。
open class Person {
...
}
class Student : Person() {
var sno = ""
var grade = 0
}
特殊点,被继承的类后面为什么有括号?这里涉及到主构造函数和次构造函数的内容。
kotlin中将构造函数分为两种,主构造函数和次构造函数。
3.2.1 主构造函数
主构造函数是常用的构造函数,定义的类都会默认有一个不带参数的主构造函数,当然也可以显示的指明参数。主构造函数的特点是没有函数体,直接定义在类名的后面。
class Student(val sno: String,val grade: Int) : Person() {
...
}
fun main() {
val s = Student("a123456",2)
}
如果想要在主构造函数中添加一些逻辑,那么可以在init
结构中实现。
class Student(val sno: String,val grade: Int) : Person() {
init {
println("sno is "+sno)
println("grade is "+grade)
}
...
}
Kotlin最终是编译成.class文件在jvm虚拟机中执行的。Java中要求子类中的构造函数必须可以调用父类的构造函数,因此Kotlin也需要遵守这个规定。
Student继承Person,Sutdent构造函数中必须能调用Person的构造函数,但是Student主构造函数没有函数体,没地方写调用Person主构造函数的代码,如果这段代码放在Init结构体中,也不是不可以,可是绝大多数情况下是没有必要编写init结构体的。因此Kotlin设计成在声明子类的时候,就直接调用父类构造函数,也就是加了个括号的原因。
class Student(val sno:String,val grade:Int) :Person() /* 父类主构造函数调用 */{
...
}
因此,即使父类构造无参,也需要把空括号带上。如果父类是有参构造,那么如下。
open class Person(name:String,age:Int){}
class Student(val sno:String,val grade:Int,name:String,age:Int) :Person(name,age){
...
}
/*Student本来没有name,age参数,但是父类需要name,age参数,所以子类必须加上name,age参数来提供给父类使用*/
这里需要注意,name和age参数是没有 使用 var或者val修饰的,仅作为参数传入。因为如果用var或val修饰,那么被修饰的参数将自动成为类的属性。这会导致子类和父类属性冲突,因此不修饰name,age参数,将他两个的作用域只限定到主构造函数内部。
3.2.2 次构造函数
次构造函数是constructor,可以有多个,但是主构造函数只有一个。
class Student(val son:String,val grage:Int,name:String,age:Int) : Person(name,age){
constructor(name:String,age:Int) : this("",0,name,age){
/*
接受name,age参数然后通过this关键字调用主构造函数,将sno默认为空字符串,grage默认为0
*/
}
constructor(): this("",0){
/*
不接受任何参数,通过this关键字调用了第一个次构造。次构造函数可以相互调用。
*/
}
}
// 因此有三种实例化方式
val s1 = Student() // 次构造函数2
val s2 = Student("a123",1) // 次构造函数1
val s3 = Student("a123",1,"张三",18) // 主构造函数
特殊情况:主构造函数是可以没有函数体的,但是次构造函数有函数体,所以会出现某个类,没有显示的声明主构造函数,仅声明了次构造函数。
class Student: Person {
constructor(name:String,age:Int):super(name,age){
...
}
}
当子类没有显示声明主构造函数的时候,也就不需要在继承父类的时候加括号了。只有在这个情况下是可以不加括号的。不加括号的话,需要在次构造函数中调用父类的构造方法,在次构造函数中可以使用super关键字直接调用父类的构造方法。
3.3 接口
kotlin中接口的概念和java中的一样,用于丰富类来实现多态编程的。因为Java是单继承,只能继承一个父类,但是可以实现任意多的接口,kotlin也是这样。
interface Study {
fun readBooks()
fun doHomeworks()
...
}
实现方式
class Student(name:String,age:Int):Person(name.age),Study{
override fun readBooks(){
println(name + "readBooks")
}
override fun doHomeworks(){
println(name + "doHomeworks")
}
}
例子
/*Person*/
open class Person(val name: String, val age: Int) {
}
/*Study*/
interface Study {
fun readBooks()
fun doHomeWorks(){
println("这里是对doHomeworks方法的默认实现")
}
}
/*Sutdent*/
class Student(name: String, age: Int) : Person(name, age), Study {
override fun readBooks() {
println("name=$name readBooks")
}
}
/*Start*/
fun doStudy(study: Study) {
study.readBooks()
}
fun main() {
val s = Student("张三", 16)
doStudy(s)
}
/*
结果:name=张三 readBooks
*/
这里有个要点,来体现多态。
s变量可以直接调用readBooks()方法,但是这里将其传入到doStudy函数中,而doStudy函数需要个Study类型的参数。因为Student实现了Study接口,所以Student可以转换为Study类型,来供doStudy函数使用,这就是面向接口编程,称之为多态。
3.4 函数的可见性修饰
修饰符 | java | kotlin |
---|---|---|
public | 所有类可见 | 默认 所有类可见 |
private | 当前类可见 | 当前类可见 |
protected | 当前类,子类,同一包路径下可见 | 当前类,子类可见 |
default | 同一包路径下可见(默认不写default) | 无 |
internal | 无 | 统一模块中可见 |
3.5 数据类与单例类
数据类是用于数据映射的,在常用的MVC,MVP,MVVM等架构中,M表示的就是数据类。
通常数据类需要重写equals(),hashCode(),toString()方法的。
例如java中
public class CellPhone{
String brand;
double price;
public CellPhone(String brand,double price){
this.brand = brand;
this.pricee = price;
}
@Override
public boolean equles(Object o){
if(o instanceof CellPhone){
CellPhone other = (CellPone) o;
return other.brand.equles(brand) && other.price == price;
}else{
return false;
}
}
@Override
public int hashCode(){
retrun brand.hashCode() + (int)price;
}
@Override
public String toString(){
return String.format("CellPhone(brand=%s,price=%s)",brand,price);
}
}
在Kotlin中实现:
data class CellPhone(val brand:String,val price:Double)
一行代码实现data类,当类中没有任何代码时,大括号都可以省略掉。
val phone1 = CellPhone("小米", 2990.0)
val phone2 = CellPhone("小米", 2990.0)
println(phone1==phone2)
// 结果 => true
如果类声明的时候没有用data修饰,那么结果肯定是不相等的。
单例类 既单例模式,java中常见的单例写法为。
public class Singleton {
private static Singleton instanc;
public synchronized static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
private Singleton(){}
public say(){
...
}
}
//使用
Singleton s = Singleton.getInstance();
s.say();
在kotlin中声明单例模式只需要将class替换成object即可
object Singleton{
fun say(){
...
}
}
使用起来也很直接
Singleton.say()
用法和java中的静态方法调用,但是内部其实创建了个单例。
4 lambda
lambda编程在kotlin中极其强大,可以说是kotlin的灵魂所在。
4.1 集合的创建和遍历
创建一个含有多种水果的集合
val list = ArrayList<String>()
list.add("苹果")
list.add("橘子")
list.add("梨")
简化创建方法
val list = listOf("苹果","橘子","梨")
使用for-in遍历集合
for(i in list){
println(i)
}
注意:listOf创建的是只读集合,不可进行增删改。
如果我们需要一个可变的集合,需要用mutableListOf("a","b","c")
上述是List类型集合的用法,Set类型的用法与上述一致
val set = setOf("a","b","c")
set集合不可有重复的元素,重复的元素会被替换掉。
Map集合的创建
val map = HashMap<String,Int>()
map.put("a",1)
map.put("b",2)
map["c"] = 3
val number = map["a"]
简写可以只用mapOf及mutableMapOf
val map = mapOf("a" to 1, "b" to 2, "c" to 3)
for((a,b) in map){
println("name:$a number: $b")
}
4.2 集合的函数式API
对比不使用函数是API和使用函数式API
val list = listOf("a111","b2222","c33333")
var maxLengthItem = ""
for (item in list){
if(item.length>maxLengthItem){
maxLengthItem = item
}
}
println(maxLengthItem)
val list = listOf("a111","b2222","c33333")
val maxLengthItem = list.maxBy{it.length}
println(maxLengthItem)
lambda函数结构
{param1:param1Type,param2:param2Type->body}
val list = listOf("a111","b2222","c33333")
val lambda = {item:String->item.length}
val maxLengthItem = list.maxBy(lambda)
// 一般可简化为
val maxLengthItem = list.maxBy({item:String->item.length})
// Kotlin 当lambad为函数最后一个参数时候可以将其移到括号后面
val maxLengthItem = list.maxBy(){item:String->item.length}
// 如果移到括号后面后,括号里没有别的参数那么括号也可以省略
val maxLengthItem = list.maxBy{item:String->item.length}
// 由于类型推导机制存在,可以把类型省略掉
val maxLengthItem = list.maxBy{item->item.length}
// lambda函数只有一个参数的时候,可以不显示声明参数,直接用it代替
val maxLengthItem = list.maxBy{it.length}
map函数
可以将集合中的每个元素映射成为另外的元素返回一个新集合。
val list = listOf("a111","b2222","c33333")
val newList = list.map{it.toUpperCase()}
filter函数
可以从集合中过滤
val list = listOf("a111","b2222","c33333")
val newList = list.filter{it.length<=5}
// 可以配合map使用
val newList = list.filter{it.length<=5}
.map{it.toUpperCase()}
all和any函数
all函数用于判断集合中是否均满足条件,any判断集合中是否存在元素满足条件,返回的是布尔值。
val list = listOf("a111","b2222","c33333")
val isAll = map.all{it.length>=5}
val isAny = map.any{it.length>=5}
应用
java
new Thread(new Runnable(){
@Override
public void run(){
System.out.println(" thread is running ");
}
}).start();
kotlin
Thread(object : Runnable {
override fun run(){
println("thread is running")
}
}).start()
kotlin没有new 关键字,这里使用匿名类的使用改用object关键字,相比较java并无简化之处。
Thread(Runnable{
println("thread is running")
}).start()
因为runnable中只有一个待实现的方法,所以不用显示的声明重写run(),kotlin会明白lambda表达式中的逻辑就是在run中实现的内容。
另外,如果一个java方法的参数列表中只有一个java单抽象接口参数,可以将接口名称省略。
Thread()方法只有一个参数Runnable,而且Runnable又是java接口参数,所以可以继续压缩代码。
Thread({println("Thread is running")}).start()
// lambda作为参数,且只有一个参数可以省略括号
Thread{println("thread is running")}.start()
5 Kotlin处理空指针
编译时控制正检查机制。杜绝空指针异常的出现。
在java中
public void doStudy(Study:s){
s.readBooks();
}
这个函数虽然很简单,但是s参数如果为空则会导致程序出现空指针错误。
在kotlin中
fun doStydy(s:Study){
s.readBooks()
}
这里则不会出现空指针异常,这是因为kotlin的函数,默认参数不可为空。也就是说,kotlin将空指针异常提前到编译时期。
如果我们程序需要某些参数可以为空,则需要显示声明参数可为空,只是这种情况下,需要自己在函数体中处理空指针问题。
fun doStudy(s:Study?){
// 一般用法
if(s!=null){
s.readBooks()
}
// 简写为
a?.readBooks()
}
参数类型后面加问号?
表示参数可空。当使用参数的时候a?.readBooks()
表示当a不为空的时候执行后面的逻辑。
还有一种表达式
val response = if(a!=null){
a
}else{
b
}
// 简写为
val response = a?:b
例子:
fun getTextLength(text:String ?) :Int{
if(text ! =null){
return text.length
}else{
return 0
}
}
// 简写
fun getTextLength(text:String?)=text?.length?:0
// 解释
/*
1. 首先要使用text.length,就要判断text是否为空
2. text?.lenght 在text不为空的时候 返回text.length的结果
3. text为空的时候,text?.length 然乎null
4. 触发 ?:0 返回0
*/
注意,kotlin的前置空指针判断功能,不是那么智能。有时候在业务内部解决了空指针异常,可是在编译的时候,编译器并不能识别到我们的业务逻辑,会导致编译失败。
var content:String? = "HELLO"
fun main(){
if(content != null){
printUpperCase()
}
}
fun printUpperCase(){
val upperCase = content.toUpperCase()
println(upperCase)
}
/**
虽然main中进行了非空判断,但是 printUpperCase函数并不知道外部已经进行过非空检查,所以编译无法通过,
如果想要编译通过,就需要在printUpperCase函数中进行非空断言
*/
fun printUpperCase(){
val upperCase = ontent!!.toUpperCase()
printlen(upperCase)
}
params!!
表示非常确信params不为空,如果运行时候params出现了空,则抛出空指针异常。
kotlin中的一个特殊函数
let
不是关键字也不是操作符,却是一个函数。
obj.let{
obj2->业务
}
obj.let会将obj作为参数传递到后方的{lambda}
中,obj2就是obj,他俩是同一个对象。
将doStudy函数改成let方式调用。
fun doStudy(s:Study?){
study?.let{
stu->
stu.readBooks()
}
}
// 由于lambda函数仅有一个参数可以省略声明参数,并用it代替
fun doStudy(s:Study?){
study?.let{
it.readBook()
it.doHomeworks()
}
}
注意:let函数是可以处理全局变量的判空问题的,if却做不到
var s:Study? = null
fun doStudy(){
if(s!=null){
s.readBooks() // 错误
}
// let写法
s?.let{
it.readBooks() // 真确
}
}
7 字符串内嵌
字符串内嵌 : ${obj.name}
字符串内嵌 : $name
val brand = "Samsung"
val price = 1299.99
println("Cellphone(brand=$brand, price=$price)")
8 函数默认值
fun printParams(num: Int, str: String = "hello") {
println("num is $num , str is $str")
}
fun printParams(num: Int = 100, str: String) {
println("num is $num , str is $str")
}
fun main(){
printParams("hello") // 错误,需要用键值传参
parintParams(str="hello")
}
9 标准函数和静态方法
9.1 标准函数 with、run、apply
便准函数既kotlin的Standard.kt中定义的函数,在kotlin代码的任意文职都可自由的调用标准函数。
9.1.1 with函数
with有两个参数,参数一可以是任意类型的对象,参数二是个lambda表达式,with函数会在lambda表达式中提供第一个参数对象的上下文,并使用lambda表达式中的最后一行代码作为返回值返回。
val result = with(obj){
// 上下文
"value" //返回值
}
作用是,可以在连续调用同一个对象的多个方法时,简写代码。
val list = listOf("a","b","c","d")
val bulider = StringBuilder()
bulider.append("start say .\n")
for(item in list){
builder.append(item).append("\n")
}
builder.append("say over")
val result = builder.toString()
println(result)
这里我们调用了很多builder.append
,使用with来简写代码。
val list = listOf("a","b","c","d")
val result = with(StringBuilder()){
append("start say . \n")
for(item in list){
append(item).append("\n")
}
append("say over")
toString()//最后一行作为返回值,将StringBuilder转为字符串
}
println(result)
9.1.2 run函数
用法与上述with函数基本一致,区别在于,run函数不会直接调用,直接收一个lambda函数作为参数,并在lambda表达式中提供上下文。
val result = obj.run{
// obj上下文
"value" // 最后一行代码返回值
}
使用run来简写say代码
val list = listOf("a","b","c","d")
val result = StringBuilder().run{
append("start say . \n")
for(item in list){
append(item).append("\n")
}
append("say over")
toString()//最后一行作为返回值,将StringBuilder转为字符串
}
println(result)
9.1.3 apply函数
apply函数调用方式与run函数一致,但是apply函数无法指定返回值,因为操作的是对象本身,with和run返回的是新对象。
obj.apply{
// 上下文
}
println(obj)
val list = listOf("a","b","c","d")
val result = StringBuilder().allpy{
append("start say . \n")
for(item in list){
append(item).append("\n")
}
append("say over")
}
println(result.toString())
9.2 定义静态方法
静态方法在java中也叫做类方法,不需要实例化类对象,直接通过类名就可以调用。
java中定义静态方法
public class Util {
public static String simple(){
retrun "simple"
}
}
// 调用
Util.simple()
在kotlin中,设计上弱化了静态方法,而是使用单例来实现相应功能。
object Util {
fun simple() :String {
return "simple"
}
}
//
Util.simple()
这里实现了一个类似java的调用,但是用的是单例,虽然 fun simple
并不是静态方法,但是单例可以让类中的方法都具备静态方法的调用方式。
如果我们想在一个普通类中实现某个或某些静态方法,需要用到companion object
class Util {
fun doSomething(){
println("doSomething")
}
// 创建内部伴生类,相当于将单例嵌入到Util类内,调用Util.simple(),调用半生对象的方法
companion object {
fun simple():String{
return "simple"
}
}
}
虽然没有真正意义上的静态方法,但是这种实现方式已经足够在形式上模仿静态方法使用了,如果非要实现一个正真意义上的静态方法,需要注解和顶层方法。
class Util {
fun doSomething(){
println("doSomething")
}
companion object {
@JvmStatic
fun simple():String{
return "simple"
}
}
}
被注解的方法将是正真意义上的静态方法,可以在java中以静态方法的方式调用。此注释只可以添加在单例类和伴生类的方法上。
顶层方法,就是不在类内的方法,例如man方法。只需要创建kotlin文件,不需要创建kotlin类。然后在其中直接定义方法。
顶层方法被创建后,可以在任意文件,任意位置,不用在乎包名,之际调用。
如果在java中想调用kotlin中创建的顶层方法,那么就需要使用FileName.functionName()
方式来调用了,java会将存放顶层方法的文件直接识别成一个类。
10 延时初始化
kotlin的空指针检查机制虽然可以保证程序运行中,杜绝出现控制正错误。可以当么类中存在很多全局变量时候,我们为了保证kotlin判空语法检查,需要进行很多判空操作。
class MainActivity : AppCompatActivity(),View.OnClickListener {
private var adapter:MsgAdapter? = null
override fun onCreate(savedInstanceStat:Bundle?){
...
adapter = MsgAdapter(msgList)
...
}
override fun onClick(v:View?){
...
adapter?.notifyItemInserted(msgList.size-1)
...
}
}
首先定义了一个全局便令adapter,因为我们需要在onCreate中初始化此变量,所以需要将其定义为可为空。
在onClick中使用adapter变量时,我们可以确定此变量肯定不为空。但是还需尽心adapter?.
否则编译不通过。
如果这种变量多了的时候,就需要对每个变量进行判空处理。
laterinit
可以延迟变量初始化。
class MainActivity : AppCompatActivity(), View.OnClickListener {
private lateinit var adapter: MsgAdapter
override fun onCreate(savedInstanceState: Bundle?) {
...
adapter = MsgAdapter(msgList)
...
}
override fun onClick(v: View?) {
...
adapter.notifyItemInserted(msgList.size - 1)
...
}
}
延迟初始化后,就不需要对全局变量设置null了,这样adapter就是不可空全局变量了,下面的函数在使用的时候便不用在进行手动判空。
这样也留出一处风险,如果在adapter变量还没有初始化的时候就使用了adapter变量,就会触发ninitializedPropertyAccessException
变量未初始化的异常。
当然,我们也可以手动判断延迟初始化的变量是否已经初始化。
class MainActivity : AppCompatActivity(), View.OnClickListener {
private lateinit var adapter: MsgAdapter
override fun onCreate(savedInstanceState: Bundle?) {
...
if (!::adapter.isInitialized) {
adapter = MsgAdapter(msgList)
}
...
}
}
有时候我们为了避免某个变量重复初始化,也可以使用延迟初始化功能。
11 密封类
密封类特点:可继承,当在when语句中传入一个密封类变量作为条件时,Kotlin编译器会自动检查该密封类有哪些子类,并强制要求你将每一个子类所对应
的条件全部处理 ,否则编译不通过。
声明关键字sealed class ClassName
注意:密封类及其所有子类必须写在一个顶层文件中。
sealed class Result
class Success(val msg:String):Result()
class Failure(val error:Exception):Result()
fun getResultMsg(result:Result)=when(result){
is Success -> result.msg
is Failure -> "error message ${result.error.message}"
// 不需要else
}
如果在顶层文件中添加了一个UnKnown类,那么就需要在getResult函数中处理UnKnown类,否则编译不通过。
在android的Adapter中使用
顶层定义
sealed class MsgViewHolder(view:View) : RecyclerView.ViewHolder(view)
class LeftViewHolder(view:View):MsgViewHolder(view){
val leftMsg:TextView = view.findViewById(R.id.leftMsg)
}
class RightViewHolder(view:View):MsgViewHolder(view){
val rightMsg:TextView = view.findViewById(R.id.rightMsg)
}
adapter中使用
class MsgAdapter(val msgList:List<Msg>):RecyclerView.Adapter<MsgViewHolder>(){
...
override fun onBindViewHolder(holder:MsgViewHolder,position:Int){
val msg = msgList[position]
when(holder){
is LeftViewHolder -> holder.leftMsg = msg.content
is RightViewHolder -> holder.rightMsg = msg.content
}
}
}
12 扩展函数
在不修改一个类源码的情况下,任然可以为某个类添加自定义方法。
如果我们想将统计字符串中所有字母的数量。
我们会定义个Util
object StringUtil {
fun lettersCount(str: String): Int {
var count = 0
for (char in str) {
if (char.isLetter()) {
count++
}
}
return count
}
}
//
val str = "ABC123xyz!@#"
val count = StringUtil.lettersCount(str)
如果我们扩展String类,使其具备lattersCount方法
fun String.lettersCount():Int{
var count = 0
for (char in this) {
if (char.isLetter()) {
count++
}
}
return count
}
//
val count = "ABC123xyz!@#".lettersCount()
这样就很方便,如果这个方法定义成为顶层方法,那么我们在项目的任意一个位置都可以调用。
13 运算符重载
在kotlin中通过重载运算符或者关键字,可以拓展用法。
在kotlin中,重载运算符可以使任意两个对象进行运算。不如两个Money对象相加。
class ObjName {
operator fun plus(obj: ObjName):ObjName{
//处理逻辑
}
}
// 使用
val o1 = ObjName()
val o2 = ObjName()
var o3 = o1 + o2
Money例子
class Money(val value:Int){
operator plus(obj:Money):Money{
val sum = value + obj.value
return Money(sum)
}
// 继续重载
operator plus(newValue:Int):Money{
val sum = value + newValue
return Money(sum)
}
}
val m1 = Money(1)
val m2 = Money(2)
var m3:Money = m1+m2
println(m3.value)
// 结果为3
m3 = m2+1
println(m3.value)
// 结果为3
生成随机长度的字符出
// 一般方法
fun getRandomLengthString(str:String):String{
val n = (1..20).random()
val builder = StringBuilder()
repeat(n){
builder.append(str)
}
return builder.toString()
}
结合扩展函数和运算符重载实现
// 扩展
operator fun String.times(n:Int):String{
val builder = StringBuilder()
repeat(n){
builder.append(this)
}
return builder.toString()
}
// 精简代码
operator fun Stirng.times(n:Int)=repeat(n)
val value = "abc"
println(value * 3)
// 结果=》 abcabcabc
// 随机
fun getRandomLengthString(str:String) = str*(1..20).random()
14 高阶函数详解
14.1 定义高阶函数
如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数是高阶函数。
要点:
- 以函数作为参数
- 返回值是一个函数
参数为函数?
参数类型有整型、布尔型等字段类型,在Kotlin中增加了个类型:函数类型。我们在定义函数的时候,将参数定义为函数类型或者返回值定义为函数类型,那么定义的这个函数就是高阶函数。
规则例如:
(String, Int) -> Unit
//
fun example(func:(String,Int)->Unit){
func("hello",100)
}
例如我们定义一个根据两个数计算另一个数的函数,计算方法不确定
// 高阶函数
fun num1AndNum2(num1:Int,num2:Int,operation:(Int,Int)->Int):Int{
val response = operation(num1,num2)
return response
}
// 匹配函数
fun plus(num1:Int,num2:Int)=num1+num2
fun minus(num1:Int,num2:Int)=num1-num2
//调用
fun main(){
val num1 = 100
val num2 = 10
val result1 = num1AndNum2(num1,num2,::plus)
val result2 = num2AndNum2(num1,num2,::miuns)
}
当函数作为参数传入的时候要使用::name
的方式。
还是上面的函数,我们将匹配函数使用lambda
定义
fun main(){
val num1 = 100
val num2 = 10
val result1 = num1AndNum2(num1,num2,{a,b->a+b})
val result2 = num1AndNum2(num1,num2,{a,b->a-b})
}
回顾上述的扩展函数我们给StringBuilder扩展一个build方法,这个方法为高阶函数,参数是函数,返回值是StringBuilder对象。
fun StringBuilder.build(block:StringBuilder.()->Unit):StringBuilder{
block()
retrun this
}
这里在定义函数参数的时候用到了类名.()-Unit
的方式。这种方式是完整的方式。表示这个函数类型的参数是定义在StringBuilder
这个类中的,这样做的好处是当我们调用build函数传入lambda
的时候,会自动拥有StringBuilder
的上下文,集合式API-apply就是这样实现的。
// 使用
fun main(){
val list = listOf("a","b","c")
// 只有一个参数,且参数是个lambda可以省略函数调用的括号
val result = StringBuilder().build {
// build函数的参数是个函数,且函数使用 className. 声明,那么此函数可以直接拿到StringBuilder上下文,直接使用append
append("start say: \n")
for(item in list){
append(item).append("\n")
}
append("over")
}
println(result)
}
14.2 内联函数
分析高阶函数实现的原理
在Java中并没有高阶函数这个概念。将上述num1AndNum2编译为Java:
public static int num1AndNum2(int num1,int num2,Function operation) {
int result = (int) operation.invoke(num1,num2);
return result;
}
public static void main(){
int a = 100;
int b = 10;
int result = num1AndNum2(a,b,new Function(){
@override
public Integer invoke(Integer a,Integer b){
return a+b;
}
})
}
这种转换每次传入lambda都会创建个匿名内部类,会造成额外开销,所以kotlin提供了内联函数功能,可以将创建匿名类的开销省去。
内联函数用法非常简单,只需要在定义高阶函数的时候加上inline
关键字声明即可
inline fun num1AndNum2(num1:Int,num2:Int,operation:(Int,Int)->Unit)=operation(num1,num2)
这种inline优化了什么?
kotlin会将inline中的代码在编译的时候自动替换到调用的地方:
// 原始kotlin
fun main(){
val num1 = 100
val num2 = 10
val result1 = num1AndNum2(num1,num2,{a,b->a+b})
}
// 替换为新kotlin
fun main(){
val num1 = 100
val num2 = 10
val result1 = num1+num2
}
//再进行编译.class文件
高阶函数定义中说了,参数中有函数类型的参数就是高阶函数,所以可能存在多个函数类型参数的情况。在这种使用如果使用inline声明高阶函数,将会把所有lambda表达式内联。
如果我们仅想部分内联:
inline fun funcName(block1:()->Unit,noinline block2:()->Unit){}
这样block2匿名函数就不会被内联了。
内联函数可以减少消耗,但是也有限制:内联函数在编译的时候会被替换,所以内联函数就不属于真正的函数了,没有真正函数类型的参数属性,而非内联函数类型参数可以自由的传递给其他函数,因此它是真正的参数,内联函数类型的参数只允许传递给另外一个内联函数。
另外还有个重要区别:内联函数引用的lambda表达式中可以使用return关键字来进行函数返回,而非内联函数只能进行局部返回。
fun printString(str:String,block:(String)->Unit){
println("11111")
block(str)
println("2222")
}
fun main(){
println("0000")
val str = ""
printString(str){
s->
println("aaaaaa")
if(s.isEmpty())retrun@printString
println(s)
println("bbbbbb")
}
println("33333")
}
# 结果
0000
11111
aaaaa
2222
33333
# bbbbbb没输出因为内部return@pringString
这里lambda函数不允许世界使用return关键字,所以使用return@函数名
的方式来停止执行lambda函数体剩余的部分。这就是局部返回,仅仅停止了block内部,而block外部还可以继续执行。
如果内联
fun printString(str:String,block:(String)->Unit){
println("11111")
block(str)
println("2222")
}
fun main(){
println("0000")
val str = ""
printString(str){
s->
println("aaaaaa")
if(s.isEmpty())retrun
println(s)
println("bbbbbb")
}
println("33333")
}
# 结果
0000
11111
aaaaa
# 往后都没了
直接打断整个高阶函数。并且main也被打断。
理论上大多数函数都是可以内联的,但是在有些情况下,内联会产生错误。
inline fun runRunnable(block:()->Unit){
val runnable = Runnable {
block()
}
runable.run()
}
发现内联后提示错误block()
解释:
内联之后,lambda表达式将不再具备参数属性,Runnable实例化过程中需要一个参数类型的函数,所以block不符合要求,因此错误。
冲突主要在于内联函数可以使用return直接返回,而Runnable的lambda允许使用return返回
也就是说,如果我们在高阶函数的函数体中,创建了另一个lambda或者匿名类的实现(Runnable就是匿名类的实现),此时我们把高阶函数内联就必定会出错。
这种情况下使用 crossinline
可解决
inline fun runRunnable(crossinline block:()->Unit){
val runnable = Runnable{
block()
}
runnable.run()
}
crossinline表示约定 block 一定不会使用retrun关键字进行返回,这样冲突就不存在了。