语言对比学习-Kotlin

语言对比学习-Kotlin

Kotlin介绍

Kotlin是由JetBrains开发的针对JVM、Android和浏览器的静态编程语言,目前,在Apache组织的许可下已经开源。
使用Kotlin,开发者可以很方便地开发移动Android应用、服务器程序和JavaScript程序。
在开发Kotlin之前,JetBrains团队一直使用Java来创建他们的IDE以及使用Java进行业务逻辑开发。之所以开发Kotlin,是因为JetBrains 的工程师们在使用Java开发应用程序的过程中,发现了大量的问题。
为了提升开发效率以及解决使用Java开发带来的问题,在借鉴了Scala、Groovy等语言后,他们决定开发一款致力于解决Java问题的编程语言Kotlin。
2010年,JetBrains着手开发Kotlin项目。2016年推出正式文档版本V1.0
2017年Google将Kotlin列为Android开发的官方支持语言

Kotlin可以说是改良的Java
一门实用且高效的编程语言
简洁、高效

  • 类型推导
  • 放弃了static关键字
  • 引入了一些特殊类,比如data class、sealed class

总体而言,Kotlin旨在成为一门提升Java生产力的更好的编程语言,它拥有简洁的表达能力、强大的工具支持。同时保持着非常快速的编译能力。

生动形象的比喻
JVM平台是一个滑雪世界
Java是双滑板

Kotlin是刻滑板
保留原有双板的滑雪习惯,但同时依旧可以做某个程度的深粉雪滑雪

Scala是单滑板
将两只脚都站着一块板上来滑行的滑雪方式,可以用更优雅的姿势获得更快的速度

什么是深粉雪?
深粉雪特指由于连夜剧烈降雪而形成的,深度1米以上,雪质松软,没被霍霍过,距离雪停时间小于24小时的粉状雪。

了解深粉雪,掌握相关的滑行技术是每个希望直滑的雪友的必修课,能够自如的在深粉雪中滑行才能真正享受其中的乐趣。

Kotlin特性
跨平台开发能力

因为Kotlin是基于JVM开发的,所以它同时具备了Android 开发、Web浏览器开发、原生Native开发的能力

100%兼容Java

要注意非空类型的兼容,避免异常

空指针安全
语言简洁,学习成本低
支持Lambda表达式
类型推断

一、Hello World

1.1、环境搭建
1.2、Hello World
fun main(args: Array<String>) {
    print("hello kotlin")
}

二、Kotlin基础

语句不需要;结尾,加;也无所谓

根类型
Any类型是所有Kotlin非空类型的超类。但Any不能持有null值,当需要持有任何值的变量包括null值,必须使用Any?
Any只包含toString、equals和hashCode。所有Kotlin的这些方法都是从Any中继承来得。但Any不能使用使用其他Object的方法(如:wait和notify)

1、数据类型

Byte、Short、Int、Long、Float、Double、Char、Boolean
都是大写

1.1、定义变量

变量类型后置,即变量名在前,变量类型在后。例如 str:String

类型推断
var

val
变量初始化之后不可再次赋值

const
只能修饰val,不能修饰var类型变量
const 只允许在top-level级别和object(伴随对象也是obejct)中声明

const val 修饰的属性相当于java中的public final static修饰的常量,可以通过类名直接访问。
val 修饰的属性相当于java中private final static修饰的常量,由于可见行为private,所以只能通过生成getter方法访问。
出于性能考虑,使用const val方式可以避免频繁函数调用。

1.2、常量/单例
class SingleTon {
    companion object {
        
        private const val STR = "str"
        
        fun getInstance() = Holder.instance
    }
    
    private object Holder {
        val instance = SingleTon()
    }
}

1.3、null安全

Kotlin支持2种类型,一种是非空类型,另一种是可空类型。可空类型需要带有?表示符号

1.4、类型转换

as (不安全的类型转换操作符)
as? (安全的类型转换操作)
为了避免抛出异常,你可以使用 安全的 类型转换操作符 as?,当类型转换失败时,它会返回 null,但不会抛出异常崩溃

toInt
toLong
toFloat
toDouble
toString

2、运算符

安全调用运算符:?.
Elvis运算符:?:
非空断言:!!
安全转换:as?
let函数
let函数将调用它的对象变成lambda表达式的参数。配合安全调度运算符可以把调用let函数的可空对象,转变成非空类型。然后在let函数中调用一系列对该可空类型的操作。
当需要检查多个值是否为null时,不建议使用嵌套的let调用来处理,建议使用一个if语句对这些值进行一次性检查。
延迟初始化lateinit

https://juejin.cn/post/6844903879209926663

3、条件表达式
4、循环语句

if else
for
while
do while
when:对java的switch的一个升级

when (x) {
    0, 1 -> print("x == 0 or x == 1")
       2 -> print("x == 2")
    else -> print("otherwise")
}
5、集合

Kotlin没有自己的集合库,完全依赖Java标准库中的集合类,并通过扩展函数增加特性来增强集合。意味着Kotlin与Java交互时,永远不需要包装或者转换这些集合对象,大大增强与Java的互操作性。

Kotlin与Java最大的不同之一就是:Kotlin将集合分为只读集合和可变集合。这种区别源自最基础的集合接口:kotlin.collections.Collection。该接口可以对集合进行一些基本操作,但无任何添加和移除元素的方法。

只有实现 kotlin.collections.MutableCollection 接口才可以修改集合的数据。MutableCollection 接口继承自 Collection,并提供添加、移除和清空集合元素的方法。当一个函数接收 Collection,而不是 MutableCollection,即意味着函数不对集合做修改操作

List listOf mutableList、arrayListOf
Set setOf mutableSetOf、hashSetOf、linkedSetOf、sortedSetOf
Map mapOf mutableMapOf、hashMapOf、linkeMapOf、sortedMapOf

集合使用的注意事项

  • 优先使用只读集合,只有在需要修改集合的情况下才使用可变集合。
  • 只读集合不一定是不可变的。如果你使用的变量是只读接口的类型,该变量可能引用的是一个可变集合。因为只读接口Collection是所有集合的"基类"
  • 只读集合并不总是线程安全的。如果需要在多线程环境中处理数据,必须使用支持并发访问的数据结构。

https://juejin.cn/post/6844903875242098695

6、类
  • 创建对象不需要new关键字

  • Kotlin和Java的可见性修饰符相似,同样可以使用public、protected和private修饰符。但Kotlin默认可见性是public,而Java默认可见性是包私有

  • Kotlin中并没有包私有这种可见性,Kotlin提供了一个新的修饰符:internal,表示“只在模块内部可见”。模块是指一组一起编译的Kotlin文件。可能是一个Gradle项目,可能是一个Idea模块。internal可见性的优势在于它提供了对模块实现细节的封装。

  • Kotlin允许在顶层声明中使用private修饰符,其中包括类声明,方法声明和属性声明,但这些声明只能在声明它们的文件中可见。

6.1、构造函数

分主构造和次构造之分

主构造申明在类名的后面,若这个主构造被可见性修饰符或注解修饰,则这个主构造无法省略constructor修饰符

//无法省略constructor
class Customer public @Inject constructor(name: String) { ... }

...
//可以省略constructor
class Customer(name: String){}

在主构造函数中无法写初始化代码,但可以声明属性(属性可以有默认值,当然也可以在类体内初始化声明),而属性的初始化代码可以放到init()代码块里,而且在init代码块里可以使用在主构造里声明的属性

class Customer(name: String) {
    init {
        val customerKey = name.toUpperCase()
    }
}

构造方法的参数也可以设置为默认参数,当所有构造方法的参数都是默认参数时,编译器会生成一个额外的不带参数的构造方法来使用所有的默认值。

次构造函数

class Person {
    constructor(parent: Person) {
        parent.children.add(this)
    }
}

如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数 用 this 关键字即可:

class Person(val name: String) {
    constructor(name: String, parent: Person) : this(name) {
        parent.children.add(this)
    }
}

如果一个非抽象类没有声明任何(主或次)构造函数,它会有一个生成的 不带参数的主构造函数。构造函数的可见性是 public。如果你不希望你的类 有一个公有构造函数,你需要声明一个带有非默认可见性的空的主构造函数

Kotlin中类和方法默认是final,Java类和方法默认是open的。

当你允许一个类存在子类时,需要使用open修饰符修改这个类。如果想一个方法能被子类重写,也需要使用open修饰符修饰。

6.2、创建实例对象

Kotlin里没有new关键字,直接Test()来实例化对象

6.3、继承+实现接口

Kotlin加个":"就行了,之后的类和接口用逗号隔开

    constructor(context: Context):this (context,null){

    }

    constructor(context: Context,atts: AttributeSet?) : super (context,atts){
        init(context,atts)
    }
    
or
//这里初始化了3个构造函数
class EmptyErrLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = -1) : FrameLayout(context, attrs, defStyleAttr) {...}

@JvmOverloads:表示如果一个方法有N个参数,其中M个具有默认值,则会产生M个重载:第一个采用N-1个参数(除了最后一个参数之外的所有参数),第二个采用N-2个参数,同上。就像上面的例子,构造里有3个参数,有两个参数具有默认值(attrs: AttributeSet? = null, defStyleAttr: Int = -1),所以会产生这个构造函数的2个重载

6.4、几种特殊的类

嵌套类
一个类可以在单独的代码文件中定义,也可以在另一个类内部定义,后一种情况叫作嵌套类
Java的嵌套类允许访问外部类的成员,而Kotlin的嵌套类不允许访问外部类的成员

内部类
既然Kotlin限制了嵌套类不能访问外部类的成员,那还有什么办法可以实现此功能呢?
针对该问题,Kotlin另外增加了关键字inner表示内部,把inner加在嵌套类的class前面,于是嵌套类华丽的变为了内部类,这个内部类比起嵌套类的好处是能够访问外部类的成员

枚举类

enum Season {
	SPRING,SUMMER,AUTUMN,WINTER
}

密封类
为解决枚举值判断的多余分支问题,Kotlin提出了密封类的概念,密封类就像是一种更加严格的枚举类,它内部有且仅有自身的实例对象,所以是一个有限的自身实例集合。
或者说,密封类采用了嵌套类的手段,它的嵌套类全部由自身派生而来,仿佛一个家谱,明明白白地列出某人有长子、次子、三子、幺子。
密封类的每个嵌套类都必须继承该类
定义密封类的时候,需要在该类的class前面加上关键字sealed作为标记
有了密封类,外部使用when就无须指定else分支了

数据类data class

  • 自动声明与构造函数入参同名的属性字段
  • 自动实现每个属性字段的get/set方法
  • 自动提供equals方法,用于比较两个数据对象是否相等
  • 字段提供copy方法,允许完整复制某个数据对象,也可在复制后单独修改某几个字段的值
  • 自动提供toString方法,用户打印数据对象中保存的所有字段值

用Kotlin写的Bean类(默认public修饰),被定义为final类,不可被继承
在Kotlin中,当你声明属性的时候,也就声明了对应的访问器(即get和set)
val属性只有一个getter,var属性既有 getter 和 setter

模板类/泛型类
一般在类名后面补充形如“”或者"<A,B>"这样的表达式,表示此处的参数类型待定,要等创建类实例时再确定具体的参数类型

7、抽象类
8、接口

Kotlin接口可以包含抽象方法以及非抽象方法的实现(类似Java 8的默认方法)
接口也可以定义属性。声明的属性可以是抽象的,也可以是提供具体访问器实现的(即不算抽象的)。

interface MyInterface {

	//抽象属性
    var length:Int
	//提供访问器的属性
    val name:String
        get() = ""

    //抽象方法
    fun test()
    //非抽象方法(即提供默认实现方法)
    fun test2() {
    }
}

Kotlin使用 : 替代Java中的extends 和 implements 关键字。
Kotlin和Java一样,一个类可以实现任意多个接口,但是只能继承一个类。
接口中抽象的方法和抽象属性,实现接口的类必须对其提供具体的实现。
对于在接口中提供默认实现的接口方法和提供具体访问器的属性,可以对其进行覆盖,重新实现方法和提供新的访问器实现。

嵌套类

内部类

inner class TestInner{
}

object关键字
对象声明

在Java中创建单例往往需要定义一个private的构造方法,并创建一个静态属性来持有这个类的单例。

Kotlin通过对象声明将类声明和类的单一实例结合在一起。对象声明在定义的时候就立即创建,而这个初始化过程是线程安全的。

对象声明中可以包含属性、方法、初始化语句等,也支持继承类和实现接口,唯一不允许的是不能定义构造方法(包括主构造方法和从构造方法)。

对象声明不能定义在方法和内部类中,但可以定义在其他的对象声明和非内部类(例如:嵌套类)。如果需要引用该对象,直接使用其名称即可

将对象声明反编译成Java代码,其内部实现也是定义一个private的构造方法,并始终创建一个名为INSTANCE的静态属性来持有这个类的单例,而该类的初始化放在静态代码块中。

companion object伴生对象
一般情况下,使用顶层函数可以很好的替代Java中的静态函数,但顶层函数无法访问类的private成员。

当需要定义一个方法,该方法能在没有类实例的情况下,调用该类的内部方法。可以定义一个该类的对象声明,并在该对象声明中定义该方法。类内部的对象声明可以用 companion 关键字标记,这种对象叫伴生对象。

可以直接通过类名来访问该伴生对象的方法和属性,不用再显式的指明对象声明的名称,再访问该对象声明对象的方法和属性。可以像调用该类的静态函数和属性一样,不需要再关心对象声明的名称。

伴生对象的实现和对象声明类似,定义一个private的构造方法,并始终创建一个名为Companion的静态属性来持有这个类的单例,并直接对Companion静态属性进行初始化

伴生对象的扩展
扩展方法机制允许在任何地方定义某类的扩展方法,但需要该类的实例进行调用。当需要扩展一个通过类自身调用的方法时,如果该类拥有伴生对象,可以通过对伴生对象定义扩展方法

9、函数
9.1、命名参数

Kotlin在语法层上对该情况进行优化,当调用一个Kotlin定义的函数时,可以显示标明参数的名称。这种参数叫命名参数

test(array,param = "test")

既然显示的标明了参数名,也就意味着当参数名或方法名进行改变时,其显式标明的参数名或方法名也需要进行改变

当调用Java定义的函数时,不能采用命名参数,因为Java8之前不能把参数名存在,class文件中。而Kotlin需要与Java6兼容。所以,编译器不能识别出函数参数的名称

9.2、函数重载之祸

在Java中,支持对函数进行重载。这就造成多个相同名称的函数,且其参数间只有细微的差别。当调用省略部分参数的函数时,可能不清楚到底调用的是哪一个函数。(例如:Thread类拥有8个构造函数)

默认参数
而Kotlin只需要指定参数的默认值,就可以有效避免创建多个重载函数。这种带有默认值的函数参数叫做默认参数。再配合命名参数进行使用时,可以很方便的对指定参数进行赋值,从而实现重载。

@JvmOverloads 提高Kotlin与Java的交互性
Java 中没有默认参数的概念,当从Java中调用Kotlin的函数时,必须显示地传递所有参数值。为了让Java调用者能调用该方法的重载函数,可以用@JvmOverloads注解它。在编译时,编译器会从最后一个参数开始逐个省略,生成Java的重载函数。

9.3、定义函数 Kotlin 中使用 fun 关键字声明函数
修饰符(默认public)+ fun + 方法名 + (参数列表) :返回值 {
        函数体
}
9.4、表达式函数体

当单个表达式构成完整的函数体时,可以直接去掉 {} 和 return 语句,函数的返回值类型编译器也会自动推断

 fun sum(a: Int, b: Int) = a + b
 
 fun max(a: Int, b: Int ) = if (a > b) a else b
9.5、几种特殊的函数

顶层函数
Kotlin认为,根本不需要创建这些无意义的类。可以直接将函数放在代码文件的顶层,不用附属于任何一个类。
Kotlin中,顶层函数属于包内成员,包内可以直接使用,包外只需要import该顶层函数,即可使用。
Kotlin和Java具有很强互操作性,如果让Java调用顶层函数该怎么调用呢?先看一下顶层函数编译成Java是public static final 函数

顶层属性
既然有顶层方法,应该也有顶层属性。和顶层函数一样,属性也可以放在文件的顶层,不附属与任何一个类。这种属性叫顶层属性

泛型函数

内联函数
前面定义泛型函数时,是把它作为一个全局函数,也就是在类Wimbledon定义,而不在类内部定义。
因为类的成员函数依赖于类,只有泛型类/模板类才能拥有成员泛型函数,而普通类是不允许定义泛型函数的,否则编译器会直接报错。
不过有个例外情况,如果参数类型都是继承自某种类型,那么允许在定义函数时指定从这个基类泛化开,凡是继承自该基类的子类,都可以作为输入参数进行函数调用,反之则无法调用函数。

举个例子 inline

内联函数在编译的时候会在调用处把该函数的内部代码直接复制一份,调用10次就会复制10份,而非普通函数那样仅仅提供一个函数的访问地址

lambda的开销
Kotlin的lambda带来简洁语法的同时,也带来了一定的性能消耗。每一个lambda表达式会被编译成一个实现FunctionN接口的匿名类。
对于捕捉变量的lambda表达式,每次调用都是创建一个新的对象,带来额外的性能开销。对于不捕捉变量的lambda表达式,只会创建一个单例的 FunctionN 实例,并在下次调用时复用。
同时 Kotnlin 编译出来的 Function 对象没有避免对基本数据类型的装箱和拆箱(因为接收的是Object类型)。这就意味着输入值或输出值涉及到基本数据类型时,会调用系统的装箱与拆箱,造成一定的性能消耗。

为了生成与Java语句一样高效的代码,Kotlin提供了内联机制。对于带有inline修饰符函数,Kotlin编译器会直接将函数实现的真实代码替换每一次的函数被调用的地方。
内联的 lambda 表达式只能在内联函数内部直接调用或者作为可内联的参数传递。否则,编译器会禁止参数被内联,并给出错误信息“Illegal usage of inline-parameter”。而且内联的函数应尽量简单,比如Kotlin标准库中的内联函数总是很小的。

lambda的性能优化
自Kotlin1.0起,每一个lambda表达式都会被编译成一个匿名类,带来额外的开销。可以使用内联函数来优化lambda带来的额外消耗。
所谓的内联函数,就是使用inline修饰的函数。在函数被使用的地方编译器并不会生成函数调用的代码,而是将函数实现的真实代码替换每一次的函数调用。Kotlin中大多数的库函数都标记成了inline。

局部函数
Kotlin中支持局部函数,所谓局部函数就是在方法中声明方法,内部方法可以获取到外部函数的参数和局部变量。可以将各个小方法定义为局部方法,即提供所需的简洁结构也无须额外的语法开销

fun daqi(person: Person){
    //需要在顶层定义,不然函数未定义无法使用
    fun personFileIsEmpty(value:String?,fileName:String){
        if (value == null){
            throw IllegalArgumentException("$fileName is null")
        }
    }
    
    personFileIsEmpty(person.age,"age")
    personFileIsEmpty(person.name,"name")
    
    //正常操作
}

局部函数的缺点是:局部函数的不能声明为内联,并且拥有局部函数的的函数也不能声明为内联

简化函数
如果一个函数的表达式比较简单,一两行代码就可以搞定,那么Kotlin就允许使用等号代替大括号

尾递归函数
函数末尾的返回值重复调用了自身函数
此时要在fun前面加上关键字tailrec,告诉编译器这是一个尾递归函数,则编译器会相应进行优化,从而提高程序性能

高阶函数
把A函数作为B函数的输入函数,故B函数被称为高阶函数,对应的A函数则为高阶函数的函数参数,又称函数变量

扩展函数
Kotlin可以在无需继承的情况下扩展一个类的功能,然后像内部函数一样直接通过对象进行调用。扩展函数这个特性可以很平滑与现有Java代码进行集成。
声明一个扩展函数,需要用一个接收者类型也就是被扩展的类型来作为他的前缀。而调用该扩展函数的对象,叫作接收者对象。接收者对象用this表示,this可有可无。
举例DateUtil

扩展高阶函数

10、闭包

在Kotlin中,你会发现匿名函数体、Lambda(以及局部函数,object表达式)在语法上都存在{}。由这对花括号包裹的代码块如果访问了外部环境变量则被称为一个闭包。

一个闭包可以被当做参数传递或者直接使用,它可以简单地看出“访问外部环境变量的函数”。
Lambda是Kotlin中最常见的闭包形式。

11、泛型

在编译期检查类型,让类型更安全
自动类型转换
提高代码通用性

https://juejin.cn/post/6844903890542936078

12、异步-协程
13、异常

Kotlin的异常处理与Java类似

14、注解

注解是什么?简单说注解就是一种标注(标记、标识),没有具体的功能逻辑代码。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。Kotlin注解的使用和Java完全一样,声明注解类的语法略有不同。Java 注解与 Kotlin 100% 兼容。

声明Kotlin的注解
Kotlin的声明注解的语法和常规类的声明非常相似,但需要在class关键字之前加上annotation修饰符。但Kotlin编译器禁止为注解类指定类主体,因为注解类只是用来定义关联到 声明 和 表达式 的元数据的结构。

annotation class TestAnnotation

https://juejin.cn/post/6844903893822881806

15、反射-kapt

当在Kotlin中使用反射时,你会和两种不同的反射API打交道。

标准的Java反射,定义在包 java.lang.reflect 中。因为Kotlin类会被编译成普通的Java字节码,Java反射API可以完美地支持它们

Kotlin反射API,定义在包kotlin.reflect中。通过Kotlin反射API你可以访问那些在Java世界里不存在的概念,诸如属性和可空类型。Kotlin反射API并不是Java反射API的替代方案。同时,Kotlin反射API没有仅局限于Kotlin类,可以使用同样的API访问用任何JVM语言写的类
https://juejin.cn/post/6844903895076962311

16、Kotlin Jetpack Compose

MutableLiveData

17、 序列

之前探究集合的函数式Api时发现,这些函数都会遍历集合并且提早创建新集合,每一步的中间结果会被存储在新集合中。当数据量很大时,调用十分的低效

fun main(args: Array<String>) {
    val list = (1..10).toList()
    list.filter { 
        it % 2 == 0
    }.map { 
        it * it
    }.forEach {
        println(it)
    }
}

序列对每个元素逐个执行所有处理步骤,可以避免构建中间变量,提高整个集合处理链的性能。
序列也称为惰性集合,序列与Java8中的Stream很像,序列是Kotlin对流这种概念提供的实现。

Kotlin惰性集合操作的入口是 Sequence 接口。该接口只有 iterator 方法,用来从序列中获取值。

Kotlin的序列使用装饰设计模式,对集合转换的匿名Sequence对象进行动态扩展。所谓装饰设计模式就是在不继承的情况下,使类变得更强大(例如Java的I/O流)。最后在末端操作中调用Sequence的迭代器进行迭代,触发中间操作,并获取其返回值进行处理并输出。

18、委托属性

装饰设计模式
在不使用继承的情况下,扩展一个对象的功能,使该对象变得更加强大。

通常套路是:创建一个新类,新类实现与原始类一样的接口,并将原来的类的实例作为一个字段保存,与原始类拥有同样的行为(方法)。一部分行为(方法)与原始类保持一致(即直接调用原始类的行为(方法)),还有一部分行为(方法)在原始类的行为(方法)基础上进行扩展。
      装饰设计模式的缺点是需要较多的样板代码,显得比较啰嗦。例如:最原始的装饰类需要实现接口的全部方法,并在这些方法中调用原始类对象对应的方法。

Kotlin将委托作为一个语言级别的功能进行头等支持。可以利用by关键字,将新类的接口实现委托给原始类,编译器会为新类自动生成接口方法,并默认返回原始类对应的具体实现

let
apply

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值