美文网首页
相比Java Kotlin 的优缺点

相比Java Kotlin 的优缺点

作者: 風清雲少 | 来源:发表于2020-09-22 21:26 被阅读0次

1. 空安全

在 Java 不用强制我们处理空对象,所以常常会导致 NullPointerException 空指针出现,现在 Kotlin 对空对象进行了限定,必须在编译时处理对象是否为空的情况,不然会编译不通过

在对象不可空的情况下,可以直接使用这个对象

var string: String?
fun getString() : String {
    return "123"
}

在对象可空的情况下,必须要判断对象是否为空

fun getString() : String? {
    return null
}

// 如果不想判断是否为空,可以直接这样,如果 text 对象为空,则会报空指针异常,一般情况下不推荐这样使用
val text = getText()
print(text!!.length)

// 还有一种更好的处理方式,如果 text 对象为空则不会报错,但是 text.length 的结果会等于 null
val text = getText()
print(text?.length)

安全的类型转换
如果对象不是目标类型,那么常规类型转换可能会导致 ClassCastException 。 另一个选择是使用安全的类型转换,如果尝试转换不成功则返回null

val aInt: Int? = a as? Int
  • Elvis 操作符

Elvis 操作符?:
val l = b?.length ?: -1
如果?: 左侧表达式非空,elvis 操作符就返回其左侧表达式,否则返回右侧表达式。 请注意,当且仅当左侧为空时,才会对右侧表达式求值。
请注意,因为 throw 和 return 在 Kotlin 中都是表达式,所以它们也可以用在 elvis 操作符右侧。这可能会非常方便,例如,检查函数参数:

val var const

val 初始化的值不可修改
var 初始化后可以修改,根据推断出得类型进行修改
const 修饰常量

 var s="s"
 s = 1 //The integer literal does not conform to the expected type String
  • 延时加载
//可以在实际使用中进行初始化,但是不出初始化直接使用会抛异常
lateinit var str1: String  
str1 = "123"
println(str1)
// 在第一次使用的时候进行初始化。
val str2: String by lazy {
        "dd"
    }
println(str2)

强制 setter 和 getter

熟悉Java的同学,一定知道Java的规范写法中会提倡使用 setter 和 getter 的写法,而不是直接对熟悉进行访问。这样做的好处就不赘述了。只是这样写的话,会带来很多的样板代码,会在项目中出现大量的 setXXX 和 getXXX 样式的代码。而Kotlin则是直接在语法层面解决了这个问题。
Kotlin中把对熟悉的访问强制使用setter 和 getter,那怕看起来像是直接读取一个成员变量,其实本质上还是访问的它的 getter 方法。这样就直接封死了直接操作成员变量的路。
这样就能完美避免很多 setXXX 和 getXXX 的方法了,需要一个属性,“直接访问“就是了

Android 中简化findViewById

private Button mButton;
...
mButton = findViewById(R.id.button);

// butterkinfe
@BindView(R.id.button)
Button mButton;

// Databinding
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.button.setText("DataBinding");
binding.include_layout_id.textView.setText("文本")

//Kotlin
button.setText("Kotlin")
或
button.text="Kotlin"

区间和数列

Kotlin 可通过调用 kotlin.ranges 包中的 rangeTo() 函数及其操作符形式的 .. 轻松地创建两个值的区间。 通常,rangeTo() 会辅以 in!in 函数。

if (i in 1..4) {  // 等同于 1 <= i && i <= 4
    print(i)
}

1.rangeTo(10).forEach{
     println(it)
}

整数类型区间(IntRangeLongRangeCharRange)还有一个拓展特性:可以对其进行迭代。 这些区间也是相应整数类型的等差数列。 这种区间通常用于 for 循环中的迭代。

for (i in 1..4) print(i)

要反向迭代数字,请使用 downTo 函数而不是 ..

for (i in 4 downTo 1) print(i)

也可以通过任意步长(不一定为 1 )迭代数字。 这是通过 step 函数完成的。

for (i in 1..8 step 2) print(i)
println()
for (i in 8 downTo 1 step 2) print(i)

要迭代不包含其结束元素的数字区间,请使用 until 函数

for (i in 1 until 10) {       // i in [1, 10), 10被排除
    print(i)
}

更多区间和数列
https://www.kotlincn.net/docs/reference/ranges.html

更强大的when 表达式

When 表达式
when 将它的参数和所有的分支条件顺序比较,直到某个分支满足条件。

when 既可以被当做表达式使用也可以被当做语句使用。如果它被当做表达式,符合条件的分支的值就是整个表达式的值,如果当做语句使用, 则忽略个别分支的值。

when 类似其他语言的 switch 操作符。其最简单的形式如下:

when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> { // 注意这个块
        print("x 不是 1 ,也不是 2")
    }
}

在 when 中,else 同 switch 的 default。如果其他分支都不满足条件将会求值 else 分支。

如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔:

when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}

我们也可以检测一个值在(in)或者不在(!in)一个区间或者集合中:

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")
}

另一种可能性是检测一个值是(is)或者不是(!is)一个特定类型的值。注意: 由于智能转换,你可以访问该类型的方法和属性而无需 任何额外的检测。

fun hasPrefix(x: Any) = when(x) {
    is String -> x.startsWith("prefix")
    else -> false
}

when 也可以用来取代 if-else if链。 如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支:

when {
    x.isOdd() -> print("x is odd")
    x.isEven() -> print("x is even")
    else -> print("x is funny")
}

when 中使用 in 运算符来判断集合内是否包含某实例:

fun main(args: Array<String>) {
    val items = setOf("apple", "banana", "kiwi")
    when {
        "orange" in items -> println("juicy")
        "apple" in items -> println("apple is fine too")
    }
}

函数变量

在Kotlin 语法中函数是可以作为变量进行传递的

var result = fun(number1 : Int, number2 : Int) : Int {
    return number1 + number2
}
//使用
println(result(1,2))

命名参数,方法支持添加默认参数

在 Java 上,我们可能会为了扩展某个方法而进行多次重载
但是在 Kotlin 上面,我们无需进行重载,可以直接在方法上面直接定义参数的默认值

fun toast(context : Context = this, text : String, time : Int = Toast.LENGTH_SHORT) {
    Toast.makeText(context, text, time).show()
}
toast(text = "弹个吐司")
toast(this, "弹个吐司")
toast(this, "弹个吐司", Toast.LENGTH_LONG)

高阶函数

基础高阶函数

扩展函数是 Kotlin 用于简化一些代码的书写产生的,其中有 let、with、run、apply、also 五个函数
最常用的场景就是使用let函数处理需要针对一个可null的对象统一做判空处理
又或者是需要去明确一个变量所处特定的作用域范围内可以使用

  • let
 memberTv.setText(sb.append(VIP_HOST).toString());
 memberTv.setBackgroundResource(R.drawable.member_host_bg_10_shape);
 memberTv.setTextColor(mContext.getResources().getColor(R.color.text_black_color_60_1F232A));

 memberTv?.let{
   it.text = sb.append(VIP_HOST).toString();
   it.setBackgroundResource(R.drawable.member_host_bg_10_shape);
   it.setTextColor(mContext.getResources().getColor(R.color.text_black_color_60_1F232A))
}
  • with 函数

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

class User(var name: String,var age: Int)
var result = with(User("你好",10)){
  mapOf("name" to name,"age" to age)
}
println(result) // {"name":"你好","age":10}
override fun onBindViewHolder(holder: ViewHolder, position: Int){
    val item = getItem(position)?: return
    holder.nameView.text = "姓名:${item.name}"
    holder.ageView.text = "年龄:${item.age}"
}
override fun onBindViewHolder(holder: ViewHolder, position: Int){
    val item = getItem(position)?: return
    with(item){
        holder.nameView.text = "姓名:$name"
        holder.ageView.text = "年龄:$age"
    }
}
  • run函数

实际上可以说是let和with两个函数的结合体,run函数只接收一个lambda函数为参数,以闭包形式返回,返回值为最后一行的值或者指定的return的表达式
适用于let,with函数任何场景。因为run函数是let,with两个函数结合体,准确来说它弥补了let函数在函数体内必须使用it参数替代对象,在run函数中可以像with函数一样可以省略,直接访问实例的公有属性和方法,另一方面它弥补了with函数传入对象判空问题,在run函数中可以像let函数一样做判空处理,这里还是借助 onBindViewHolder 案例进行简化

override fun onBindViewHolder(holder: ViewHolder, position: Int){
    val item = getItem(position)?: return
    holder.nameView.text = "姓名:${item.name}"
    holder.ageView.text = "年龄:${item.age}"
}
override fun onBindViewHolder(holder: ViewHolder, position: Int){
    val item = getItem(position)?: return
    item?.run {
        holder.nameView.text = "姓名:$name"
        holder.ageView.text = "年龄:$age"
    }
}
  • apply函数

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

class User(var name: String,var age: Int)
var result = User("你好",10).apply{
    name = "123"
    age = 1
}
payView = View.inflate(context, R.layout.payment_layout, this)
payView.aliPayLayout.background = getResources().getDrawable(R.drawable.gray_bg_10_shape)
payView.wxPayLayout.background = getResources().getDrawable(R.drawable.gray_bg_10_shape)
//使用 apply 函数后的代码是这样的

payView = View.inflate(context, R.layout.payment_layout, this).apply {
        aliPayLayout.background = getResources().getDrawable(R.drawable.gray_bg_10_shape)
        wxPayLayout.background = getResources().getDrawable(R.drawable.gray_bg_10_shape)
}

多层级判空问题

if (mSectionMetaData == null || mSectionMetaData.questionnaire == null || mSectionMetaData.section == null) {
    return;
}
if (mSectionMetaData.questionnaire.userProject != null) {
    renderAnalysis();
    return;
}
if (mSectionMetaData.section != null && !mSectionMetaData.section.sectionArticles.isEmpty()) {
    fetchQuestionData();
    return;
}

// kotlin

mSectionMetaData?.apply {
    //mSectionMetaData不为空的时候操作mSectionMetaData
}?.questionnaire?.apply {
    //questionnaire不为空的时候操作questionnaire
}?.section?.apply {
    //section不为空的时候操作section
}?.sectionArticle?.apply {
    //sectionArticle不为空的时候操作sectionArticl
}
  • also 函数

适用于let函数的任何场景,also函数和let很像,只是唯一的不同点就是let函数最后的返回值是最后一行的返回值而also函数的返回值是返回当前的这个对象。一般可用于多个扩展函数链式调用

  val result = "helloworld".also {
        println(it.length)
    }
    println(result) // helloworld

协程

https://www.jianshu.com/p/3af2b68da9b5

扩展功能

可以在不用继承的情况下对扩展原有类的方法,例如对 String 类进行扩展方法

 fun String.getString() :String{
      return "$this,hello"
 }

// 需要注意,handle 方法在哪个类中被定义,这种扩展只能在那个类里面才能使用
println("123".getString())
// 123,hello

通过字节码文件揭秘扩展函数的实现

 @NotNull
 public final String getString(@NotNull String $this$getString) {
      Intrinsics.checkNotNullParameter((Object)$this$getString, (String)"$this$getString");
      return $this$getString + ",hello";
 }

//调用
  String string = this.getString("123");

  • 可空接收者

注意可以为可空的接收者类型定义扩展。这样的扩展可以在对象变量上调用, 即使其值为 null,并且可以在函数体内检测 this == null,这能让你在没有检测 null 的时候调用 Kotlin 中的toString():检测发生在扩展函数的内部。

fun Any?.toString(): String {
    if (this == null) return "null"
    // 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()
    // 解析为 Any 类的成员函数
    return toString()
}
  • 扩展属性
  • 伴生对象的扩展
  • 扩展的作用域

大多数时候我们在顶层定义扩展——直接在包里:

package org.example.declarations
 
fun List<String>.getLongestString() { /*……*/}

要使用所定义包之外的一个扩展,我们需要在调用方导入它:

package org.example.usage

import org.example.declarations.getLongestString

fun main() {
    val list = listOf("red", "green", "blue")
    list.getLongestString()
}

类型推断

var a = 1;
var s = "123";
  • 类型检测及自动类型转换

我们可以使用 is 运算符检测一个表达式是否某类型的一个实例(类似于Java中的instanceof关键字)。

fun getStringLength(obj: Any): Int? {
  if (obj is String) {
    // 做过类型判断以后,obj会被系统自动转换为String类型
    return obj.length 
  }

  //在这里还有一种方法,与Java中instanceof不同,使用!is
  // if (obj !is String){
  //   // XXX
  // }

  // 这里的obj仍然是Any类型的引用
  return null
}

lambda 表达式

缺点

  1. 与Java结合使用的空安全
    上面的章节提到了Kotlin中很好的特性“空安全”,这个特性用起来稍有不慎就会有坑,比如说在和Java库结合使用的时候,Kotlin和Java的互相调用,你会发现稍不留神就会出现一个 KotlinNullPointerException。
    这种问题就是在声明了非空的Kotin方法中,接收到了来自Java库传进来的一个null。毕竟在Java中没有这样类似的空类型检测,所以无法保证传递给Kotlin 的是非空的。
    所以在Kotlin和Java相互调用的时候一定要万分小心。

  2. 语法层面抛弃了static类型
    Kotlin里面抛弃了Java里面的 static 关键字。反而是提出了类似的 companion object 来完成静态成员的功能。
    这个怎么说呢,可以理解Kotlin是为了实现 “一切皆对象” 的设计目标,如果保留了静态成员就达不到这个目标了。
    但是这个粗暴地去掉了 static 关键字,也还是有很多不方便的地方的,这样就无法定义一些类级别的成员了,而伴生对象本质上还是一些成员对象,也无法做到和Class相关。

  3. 抛弃了 Checked Exception
    Java 要求你必须在函数头部写上“throws FileNotFoundException”,否则它就不能编译。这个声明表示函数在某些情况下,会抛出 FileNotFoundException 这个异常。由于编译器看到了这个声明,它会严格检查你对 foo 函数的用法。在调用 foo 的时候,你必须使用 try-catch 处理这个异常,或者在调用的函数头部也声明 “throws FileNotFoundException”,把这个异常传递给上一层调用者。
    Kotlin 里面抛弃了对 Checked Exception。
    Kotlin 也在官网给出了一些解释,大概的意思是Checked Exception 在项目小的时候确实能够提升效率和代码质量,但是在大型项目中却会降低代码质量。

void foo(string filename) throws FileNotFoundException
{
  if (...)
  {
    throw new FileNotFoundException();
  }
  ...
}

还有些不同之处

在java中允许创建任意的子类并重写方法任意的方法,除非显示的使用了final关键字进行标注。
而在kotlin的世界里面则不是这样,在kotlin中它所有的类默认都是final的,那么就意味着不能被继承,而且在类中所有的方法也是默认是final的,那么就是kotlin的方法默认也不能被重写。那么想在kotlin中继承父类应该怎么做呢?
以下内容摘自《Kotlin 实战》,它解释了默认类为 final 的意义:

类默认为 final 带来了一个重要的好处就是这使得在大量场景中的智能转 换成为可能 。 正如我们在 2.3.5 节中提到的,智能转换只能在进行类型检查后没 有改变过的变量上起作用 。 对于一个类来说,这意味着智能转换只能在 val 类 型并且没有自定义访问器的类属性上使用 。这个前提意味着属性必须是 final 的 , 否则如果一个子类可以重写属性并定义一个自定义的访问器将会打破智能转换 的关键前提。 因为属性默认是 final 的,可以在大多数属性上不加思考地使用 智能转换,这提高了你的代码表现力 。

open class Person{
  open fun user(name: String){
  }
}
open class People:Person(){
  overide fun user(name:String){
      super.user(name)
  }
}
  • Kotlin里不需要“;”

相关文章

网友评论

      本文标题:相比Java Kotlin 的优缺点

      本文链接:https://www.haomeiwen.com/subject/tfalyktx.html