Kotlin到底比Java好在哪,我们不得不说的事

Kotlin开发 系列目录

  1. Kotlin到底比Java好在哪,我们不得不说的事

博客创建时间:2020.07.07
博客更新时间:2021.02.24


Kotlin优势

kotlin更安全
这是它最重要的优势,空引用由类型系统控制,你不会再遇到NullPointerException。这个空指针异常就像一个隐藏的定时炸弹,指不定啥时候就炸了。

在Kotlin中调用Java代码,Java代码中需要使用上非空注解。

代码简洁
使用Lambda 表达式,大量节省末班代码,特别是重复多余的findViewById。都说越少的代码越能减少错误。

函数式支持
使用高阶函数,可以将其作为返回值或参数值使用,特别方便。
什么是高阶函数呢?就是以函数作为参数,又以函数作为返回值就是高阶函数,而每个函数又对应着Lambda表达式。
常见的高阶函数map()、forEach()、flatMap()、reduce()、fold()、filter()、takeWhile()、let()、apply()

扩展函数
Kotlin同C#类似,能够扩展一个类的新功能而无需继承该类或使用像装饰者这样的任何类型的设计模式。Kotlin支持扩展函数和扩展属性。
1) 表达式可以直接作为函数返回值
2)扩展函数: fun 类名.扩展方法名(参数列表){方法体} 或者 fun 类名.扩展方法名(参数列表) = 表达式

fun MyClass.add(a:Int,b:Int) = a+b

3)扩展属性:var 类名.扩展属性 / 对应其他方法

var MyClass.d: String
    get() = "sdasda"
    set(value){d = value}//MyClass的扩展成员变量d

Kotlin中没有静态成员
对应的解决方案是伴生类,调用方式和Java的静态成员调用方式一样。

class Test {
    companion object{
        val Name="Name"
    }
}

与java可互相调用
Kotlin的标准库更多的是对Java库的扩展,但是在Kotlin中可以使用Android库。在Kotlin和Java代码中可以互相调用,完美无障碍。


Kotlin与Java区别

  1. 基本类型类似,不在存在拆箱装箱过程了
  2. 变量的声明只有var可变变量和val只读变量,Kotlin 能自动聪明的判断变量的数据类型
  3. Java中支持char>int >long自动转化,为了降低不必要的错误Kotlin中不支持
  4. Java中的原始类是Object,而Kotlin中确实Any,Any方法中只有equals(),hashCode,toString三个方法。
  5. Java当中的类型转换是指存在继承关系的,子类可以向上自动转型为父类,而父类转子类则需要强制转换。而Kotlin中的类型转换是使用as关键字
  6. Java中存在多个相同类名,引用需要用包名.类名,在Kotlin中则使用如下
import A.Student as AS//将A包下的Student类取外号为"AS"
import B.Studnet as BS//将B包下的Student类取外号为"BS"
  1. Kotlin有独特的区间类,名字叫做Range。用整型区间举例:
var intRange:IntRange = 0..1024//区间为[0,1024],注意是全闭的
var intRange2:IntRange = 0 until 1024//区间为[0,1024),注意是半闭半开的,包头不包尾

println("50是否存在与区间[0,1024]:"+intRange.contains(50))
println("-3是否存在与区间[0,1024):"+intRange.contains(-3))

//遍历区间
for(i in intRange){
    println(i)
}
  1. Kotlin中一般声明数组开始用intArrayOf和arrayOf来声明

  2. Kotlin的权限修饰符没有了default,对应的改成了internal

  3. 在Kotlin中使用"“等价Java 中的equals()方法。使用”=“来等价Java中的”==",比较两个引用是否是同一个对象。

  4. Kotlin中没有了Switch语句,用when来替代,它更加强大,它的判断值可以是表达式。

  5. Kotlin中构造器需要使用constructor这个关键字,或者有个特殊默认的构造方式,这种叫做主构造函数

class Student(var name:String,var age:Int)//构造器的特殊写法
  1. Kotlin中从属于类的函数叫做"成员方法",不从属于类的叫做函数,如一下参数是以方法块的形式表现,这个就是"函数"

  2. Kotlin的默认访问权限是public,而非Java中默认的访问权限default。
    final:是Kotlin的默认修饰符。如果你想你的类能被继承你需要使用open来修饰。
    data:这个修饰符很好用,也很有特色。用data修饰后:
    1)会自动将所有成员用operator声明,自动为成员生成getter和setter。
    2)自动重写了equals()/hashCode()和toString()方法
    3)data类没有无参构造函数,这感觉是个坑,所以Kotlin官方给出了解决方案,就是使用NoArg(无参构造函数)和AllOpen(可被继承)来解决问题。

  3. 在Kotlin的接口中可以实现方法不报错,如下在Java中会报错,但是在Kotlin中不会报错。

interface JieKou{
    fun A(){
       //方法体a
       println("接口里面的方法,我是方法体")
    }
}
  1. 在class的内部类中,需要个加上inner 这个关键字,否则这个类默认是静态的。非静态内部类的this关键字冲突可以使用"this@外部类类名"来区分。
var ab  = A.AB()   //实例化A类中的静态内部类AB
var ac  = A().AC() //实例化A类中的非静态内部类AC

A a=new A();
A.AC ac =a.new AC();

A.AC ac=new A.AC();
  1. 包级函数,在Kotlin 的类名之外的函数,为之包级函数。包级函数的所有函数(包括扩展函数)都是静态的,在编译的时候呢,Kotlin就把所有的包级函数都放在了一个类当中,Java可以通过此类名来访问到所有的包级函数,如Test.getMessage()
//文件名  Test.kt
class Student(var name:String,var age:Int){
}

fun getMessage(){//这个扩展方法被编译Test.kt中的静态方法public static getMessage(Student student)
    println("")
}

  1. Kotlin中的通配符不是“?”,而是" * “。”?"被拿来用于判断非空了
  2. 正则表达式,除了Java中传统的方式外,还有新的方式就是使用
var results = Regex(geShi).split(str)//Regex为Kotlin中处理正则表达式的工具类
  1. IO流中使用"use"自动关闭读写文件

Kotlin的优缺点

Kotlin的有些优点在前面已经说过,这里补充Android中使用Kotlin和Java做出对比的优缺点
优点

  1. Kotlin支持多平台和Jetpack Compose UI ,而Java不支持的,对于Android开发请尽快熟悉使用Jetpack提供的各种支持

缺点
2. 使用Kotlin的编译器速度相对Java会稍微慢一点,这点正在不断优化改进,相信不断优化后期后速度相差不会太大


Kotlin知识点

伴生类

这个特性我需要单独来讲解说明,在Kotlin中没有static,我们在伴生类中的变量和方法相当于静态变量和静态方法。

  companion object {//静态方法和变量都应该写在这里面
        var  a:Int =  3;//静态变量
        fun get():Int{
            return a
        }
    }

Kotlin中的几种注解

1.@JavaFiled:将属性编译为Java变量

2.@JvmStatic:将对象的方法编译成Java静态方法,通常应用与伴生对象

3.@JvmOverloads:默认参数生成重载方法

4.@file:JvmName:指定Kotlin文件编译后的类名

内联函数

let
let扩展函数的实际上是一个作用域函数,当你需要去定义一个变量在一个特定的作用域范围内,let函数的是一个不错的选择;let函数另一个作用就是可以避免写一些判断null的操作。

//1)判断object为null的操作
object?.let{//表示object不为null的条件下,才会去执行let函数体
   it.todo() //在函数体内使用it替代object对象去访问其公有的属性和方法
   1000
}

在函数块内可以通过 it 指代该对象。返回值为函数块的最后一行或指定return表达式。

它的使用场景如下:
场景一: 最常用的场景就是使用let函数处理需要针对一个可null的对象统一做判空处理。
场景二: 然后就是需要去明确一个变量所处特定的作用域范围内可以使用


with
with函数和前面的几个函数使用方式略有不同,因为它不是以扩展的形式存在的。它是将某对象作为函数的参数,在函数块内可以通过 this 指代该对象。返回值为函数块的最后一行或指定return表达式。
如下两代码是等价的:

val result = with(user, {
        println("my name is $name, I am $age years old, my phone number is $phoneNum")
        1000
})

val result = with(user) {
        println("my name is $name, I am $age years old, my phone number is $phoneNum")
        1000
}

使用场景
适用于调用同一个类的多个方法时,可以省去类名重复,直接调用类的方法即可,经常用于Android中RecyclerView中onBinderViewHolder中,数据model的属性映射到UI上。

override fun onBindViewHolder(holder: ViewHolder, position: Int){
   val item = getItem(position)?: return
   
   with(item){
      holder.tvNewsTitle.text = StringUtils.trimToEmpty(titleEn)
	   holder.tvNewsSummary.text = StringUtils.trimToEmpty(summary)
	   holder.tvExtraInf.text = "难度:$gradeInfo | 单词数:$length | 读后感: $numReviews"
       ...   
   
   }

}

run
run函数实际上可以说是let和with两个函数的结合体,run函数只接收一个lambda函数为参数,以闭包形式返回,返回值为最后一行的值或者指定的return的表达式。

override fun onBindViewHolder(holder: ViewHolder, position: Int){
   
  getItem(position)?.run{
      holder.tvNewsTitle.text = StringUtils.trimToEmpty(titleEn)
	   holder.tvNewsSummary.text = StringUtils.trimToEmpty(summary)
	   holder.tvExtraInf = "难度:$gradeInfo | 单词数:$length | 读后感: $numReviews"
       ...   
   }
}

适用于let,with函数任何场景。因为run函数是let,with两个函数结合体,准确来说它弥补了let函数在函数体内必须使用it参数替代对象,在run函数中可以像with函数一样可以省略,直接访问实例的公有属性和方法,另一方面它弥补了with函数传入对象判空问题,在run函数中可以像let函数一样做判空处理


apply
从结构上来看apply函数和run函数很像,唯一不同点就是它们各自返回的值不一样,run函数是以闭包形式返回最后一行代码的值,而apply函数的返回的是传入对象的本身。

以下与with函数不一样,其结果值result指代的是user

fun main() {
    val user = User("Kotlin", 1, "1111111")

    val result = user.apply {
        println("my name is $name, I am $age years old, my phone number is $phoneNum")
        1000
    }
    println("result: $result")
}

使用场景
1)apply一般用于一个对象实例初始化的时候,需要对对象中的属性进行赋值。或者动态inflate出一个XML的View的时候需要给View绑定数据也会用到,这种情景非常常见。

mSheetDialogView = View.inflate(activity, R.layout.biz_exam_plan_layout_sheet_inner, null).apply{
   course_comment_tv_label.paint.isFakeBoldText = true
   course_comment_tv_score.paint.isFakeBoldText = true
   course_comment_tv_cancel.paint.isFakeBoldText = true
   course_comment_tv_confirm.paint.isFakeBoldText = true
   course_comment_seek_bar.max = 10
   course_comment_seek_bar.progress = 0

}

2) 也和let函数一样用于去进行判null。


协程

协程是Kotlin、go等语言中特有的概念,在Java中不存在协程的概念。

以单核CPU为例,我们知道同一时刻只能执行一个线程。每个线程通过CPU调度并发运行,运行的线程太多,在频繁的线程切换中,会让可执行的CPU时间段变短,运行效率变低。

协程的本质是在同一线程的运行中,并发运行不同的任务代码,不同的任务由kotlin自身来决定运行谁,该方案减少了多线程的任务切换时间消耗。

一个线程中有多个协程,协程和协程之间可以嵌套。其特性为:

  1. 可控性:协程能做到可控,其启动和停止都是代码控制
  2. 轻量级:协程非常小,占用资源比线程还少

透过现象看本质

  1. Kotlin-JVM中所谓的协程是假协程,本质上还是一套基于原生Java Thread API 的封装。和Go中的协程完全不是一个东西,不要混淆,更谈不上什么性能更好。

  2. Kotlin-JVM中所谓的协程挂起,就是开启了一个子线程去执行任务(不会阻塞原先Thread的执行。要理解对于CPU来说,在宏观上每个线程得到执行的概率都是相等的),仅此而已,没有什么其他高深的东西。

  3. Kotlin-JVM中的协程最大的价值是写起来比RxJava的线程切换还要方便。几乎就是用阻塞的写法来完成非阻塞的任务。

    OpenJDK正在做JVM的协程实现,项目名称为loom,有兴趣的同学可以查看对应资料。

  4. 对于Java来说,不管你用什么方法,只要你没有魔改JVM,那么最终你代码里start几个线程,操作系统就会创建几个线程,是1比1的关系。 同样在ART和OS中创建Thread的对应关系是1:1的。

  5. Kotlin官网中那个创建10w个Kotlin协程没有oom的例子其实有误导性,本质上那10w个Kotlin协程就是10w个并发任务仅此而已,他下面运行的就是一个单线程的线程池。你往一个线程池里面丢多少个任务都不会OOM的

    前提是你的线程池创建的时候设定了对应的拒绝策略,否则无界队列下,任务过多一定会OOM),因为在运行的始终是那几个线程。


总结

首先自己使用了Kotlin在Android 中开发已有快7个多月,根据自己的使用体验确实更偏向使用Kotlin开发,当然每个使用体验都会有不一样,适合自己就是合适的。

希望大家进行学习使用Kotlin后再进行自我体会,比较两者的优缺。学习使用肯定不是单纯的通过IDEA将Java代码转换成Kotlin,那样只是使用Kotlin语言来写Java,没有任何意义。

作为技术开发者,我们应该勇敢拥抱改变和革新,不能用原有的先入为主观念来看待问题,实践是检验一切真理的唯一标准,实践了才能有自己的收获。

希望有异议积极进行评论留言,必回。对错都是异议讨论中定论的,欢迎留言!


相关链接

  1. Kotlin到底比Java好在哪,我们不得不说的事

扩展链接:

  1. Android CameraX 使用入门
  2. ART与Dalvik、JVM之间的关系你懂了吗?

博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收藏 ^ _ ^ !

  • 13
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 27
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值