文章目录

  • 前言
  • kotlin与java区别
  • 语法
  • 基本类型
  • 变量定义
  • 函数定义
  • 规避空指针
  • 关键字不同
  • 类区别
  • 区别一:kotlin文件
  • 区别二:类
  • 类格式
  • 数据类
  • 接口
  • 内部类
  • 构造方法
  • 子类
  • 匿名内部类
  • 类静态成员
  • 单例类
  • 变量和函数区别
  • 函数的格式
  • if..else..表达式
  • `?:`表达式
  • 范围
  • 集合
  • `kotlin`的特性
  • 扩展
  • 委托
  • 参考资料


前言

Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,由 JetBrains 设计开发并开源。
在Google I/O 2017中,Google 宣布 Kotlin 成为 Android 官方开发语言。其中原委我觉得跟Google与Oracle持续多年的Java侵权案脱不开关系。目前,有很多厂商android开发采用Kotlin.
kotlin和java,可以相互调用,完全兼容。
自己接触kotlin不到一个月,基本上是对照着java学的,文中有错误的地方还请指出。

kotlin与java区别

先总体说一下kotlin的特点,

  • 能在编译前规避空指针问题,当然是以更复杂的代码作为代价。
  • kotlin不是基于类的,方法不一定在类中。我觉得kotlin是基于文件的,我们可以把方法变量定义在文件中。这点跟java有很大不同,长期用java刚接触这类代码很不舒服。
  • 隐士调用setter, getter方法。当我们访问一个变量,或者给一个变量赋值时,实际上是调用gettersetter方法。这点我觉得比java要好很多,而且kotlin,提供变量的getter, setter方法的默认实现,代码上比java要少很多。
  • 代码会更美观。像SAM conversions(一种匿名函数),相比java会减少一层缩进。
    我觉得kotlin是占在巨人的肩膀上,类似于java,多做了一些优化。下面就具体介绍两者区别。

语法

先说下kotlin语法,可以参考 from java to kotlin 对比着 java理解,效果比较好。
这里简单介绍语法,深入的可以看教程。传送门

基本类型

ByteShortIntLongFloatDouble 像对应java少了Char这是一个独立的数据类型。Kotlin是完全面向对象的,没有Java类的有包装类,基本类型之分。

变量定义

关键字var(可变变量) val(常量),格式。
kotlin中的类属性,默认是没有初始化的(这点跟java不同,java默认值是0值)

var hello : String? = "Hello" //完整格式
    var world = "world" //自动识别类型,world为String
    lateinit var view : View //延时初始化

要善于使用kotlin的自动识别类型,代码会精简很多。
String?表示可以为空null ,理解为:这是一种新类型,String不可以为空。也是kotlin可以在编译期就能检测空指针异常的秘密武器之一。另外,一种防止空指针是!!?调用.
对于自己控制初始化的变量,可以加lateinit,表示由自己控制变量的初始化。

class Foo {
    lateinit var lateInitVar: String
    fun checkInit() {
        if(this::lateInitVar.isInitialized){  //重要,this::前缀是必须的。
         //如果已经初始化了,返回true  
     }
    }
}

函数定义

方法默认就是 public 的,
override 函数的可见性是继承自父类的
函数可以有默认值。

fun sayHi(name: String = "world") = println("Hi " + name)

//调用
sayHi("kaixue.io")
sayHi() // 使用了默认值 "world"
sayHi(name="wang") //命名参数

规避空指针

kotlin默认的类型,如 String默认是不可为空,如需为空,需要在类型后面加问号,如String? 在调用时进行判断。如果变量不确定,要使用!!抛出异常,?返回空指针,不抛异常。

val length = text?.length //如果text为空,返回null
    val ages = age!!.toInt()  //抛出空指针异常
    val ages1 = age?.toInt()  //不做处理返回null
    val ages2 = age?.toInt() ?: -1 //空指针时,返回默认值

关键字不同

for循环遍历跟java不同

//拿到值
for (item in collection) print(item)
//或者只是拿到索引
for (i in array.indices) {
    print(array[i])
}
//索引和值
for ((index, value) in array.withIndex()) {
    println("the element at $index is $value")
}

when很像java中的switch

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

is关键字as 用于类型转换
判断类型,相当于java中的instanceof

if (object is Car) {
   var car = object // smart casting
}

// if object is null
if (object is Car?) {
   var car = object // smart casting, car will be null
}

类区别

区别一:kotlin文件

在java中,方法和变量必须在类中定义,在kotlin中,方法可以定义在文件中,文件中可以多个类共存。
习惯了java,这种类型看着很不习惯。
例如:新建一个文件KotlinUtils

package mypackage

import android.util.Log
import java.text.SimpleDateFormat
import java.util.*

/**
 * Created by xxx
 */

val DEFAULT_SDF = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())

fun getCurTimeString(format: SimpleDateFormat): String {
    return date2String(Date(), format)
}

fun date2String(time: Date, format: SimpleDateFormat = DEFAULT_SDF): String {
    return format.format(time)
}

open class Animal(val sex: Int, val kind : String) {

}

class Human( sex: Int,  kind : String, val id: String) : Animal(sex, kind) {

    constructor(sex: Int, kind: String) : this(sex, kind, "0000")

    init {
        Log.i("TAG", "i am called when constructor")
    }
}

解析如下

  • 包名可以不和文件目录对应。kotlin的类一样如此,要java要求包名一定要和目录名对应。这个如果滥用也会使代码比较乱,至于是不是好处见仁见智。
  • 这个相当于java的静态类,静态方法,静态成员,当然java调用时,相当于有一个文件名+Kt的类,调用形式KotlinUtilsKt.date2String(new Date(), KotlinUtilsKt.getDEFAULT_SDF());
  • 文件中可以创建类,方法,成员。比较灵活。
  • date2String是有默认参数的方法,默认值参数只对kotlin有效,不服务于java

区别二:类

先上例子,再解析不同。

类格式

创建一个Java Animal

public class Animal {
    private int sex;
    private String kind;
    public JavaAnimal(int sex, String kind) {
        this.sex = sex;
        this.kind = kind;
    }

    public int getSex() {
        return sex;
    }

    public void setSex(int sex) {
        this.sex = sex;
    }

    public String getKind() {
        return kind;
    }

    public void setKind(String kind) {
        this.kind = kind;
    }

    public void run() {
        Log.i("TAG", "sex="+sex+", kind="+kind+" start run");
    }
}

kotlin的Animal

open class Animal(val sex: Int, val kind : String) {
    fun run() {
        Log.i("TAG", "sex=$sex,kind=$kind, start run")
    }
}

解析
kotlin的类默认是不可继承的,如果需要被继承,需要加open关键字,相当于java的 final class
构造方法书写不同,kotlin的主构造方法,在类声明时指定。参数中带有val var 关键字的表示类的成员变量。

数据类
data class Developer(var name: String, var age: Int)

会自动的从主构造函数中根据所有声明的属性提取以下函数equals()/hashCode() toString() componentN() copy()

接口

接口可以提供默认实现,且接口可以有变量。且子类要提供变量的初始化。

内部类

kotlin中,直接定义在内部的类。属于嵌套类 (大致相当于java static class),这个嵌套类是属于类的。
如果要实现java中的内部类 在kotlin中需要inner关键字

class Outer {                  // 外部类
    private val bar: Int = 1
    class Nested {             // 嵌套类,相当于java中的static class
        fun foo() = 2
    }
}

class Outer2 {
    private val bar: Int = 1
    var v = "成员属性"
    /**嵌套内部类**/
    inner class Inner2 {
        fun foo() = bar  // 访问外部类成员
        fun innerTest() {
            var o = this@Outer2 //获取外部类的成员变量
            println("内部类可以引用外部类的成员,例如:" + o.v)
        }
    }
}

fun main(args: Array<String>) {
    val demo = Outer.Nested().foo() // 调用格式:外部类.嵌套类.嵌套类方法/属性
    println(demo)    // == 2
    
    val demo = Outer2().Inner2().foo()
    println(demo) //   1
}
构造方法

kotlin中,有主构造函数,在类声明的时候定义。跟在类型后面,如果参数有val, var修饰,表示这是类的成员变量。构造函数的语句块,放在init代码块中。
init代码块,初始化代码块,先于构造函数执行。可以理解为:他是主构造函数的部分。
kotlin的构造函数,写法是constructor+参数
还有次构造方法,必须要调用主构造函数方法或间接调用。
如下面Human
Java

public class Human extends JavaAnimal {
    private String id;
    public Human(int sex, String kind, String id) {
        super(sex, kind);
        this.id = id;
        Log.i("TAG", "i am called when constructor");
    }

    public Human(int sex, String kind) {
        this(sex, kind, null);
        Log.i("TAG", "i am called constructor 2 param");
    }

    public Human(int sex) {
        this(sex, "cat", null);
    }
}

Kotlin

class Human (sex: Int,  kind : String, val id: String) : Animal(sex, kind) {

    constructor(sex: Int, kind: String) : this(sex, kind, "0000")
    constructor(sex:Int) : this(sex, "cat") {
        //间接调用了主构造函数, 可以有自己的语句块
        Log.i("TAG", "i am called constructor 2 param")
    }

    init {
        //构造函数语句块
        Log.i("TAG", "i am called when constructor")
    }
}

写在类中的主构造函数,如果是public可以省略修饰符,如果是私有的则不可以省略,完整的写法是这样的

class Human public constructor(sex: Int,  kind : String, val id: String) : Animal(sex, kind) {
}
子类

继承需要用:跟着父类,相当于javaextends,在子类中需要声明主构造函数。构造函数语句块,在init语句块中。
注意,默认类是不可以继承的,如果需要被继承,需要加open关键字声明。
示例见,上面的Human

匿名内部类

需要用到关键字object(object的使用场景1),如下

etSrc.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void afterTextChanged(Editable editable) {
                tvContent.setText(etSrc.getText().toString());
            }
        });

kotlin语法

etSrc.addTextChangedListener(object : TextWatcher {
            override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {

            }

            override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {

            }

            override fun afterTextChanged(editable: Editable) {
                tvContent.text = etSrc.text.toString()
            }
        })
类静态成员

kotlin类有伴生对象,表示这些成员是类的成员。类似于java中的static关键字。object的使用场景2

class Human public constructor(sex: Int,  kind : String, val id: String) : Animal(sex, kind) {
    companion object{
        //伴生对象, 是类的成员
        val FEMAL = 1
        val MALE = 2
        fun isMale(sex: Int):Boolean = sex == MALE
    }

    init {
        Log.i("TAG", "i am called when constructor")
    }
}

调用

val human = Human(Human.MALE, "human", "0001")

kotlin中,可以不用写静态成员,kotlintop-level declaration顶层声明,可以将方法,变量写在类的外部,外面都可以引用。这种方式一般用来写工具类。

单例类

java中的单例类有很多种形式,例如加锁、类加载形式。 kotlin中实现单例类更加简单。直接用object修饰即可。object的使用场景3

object Earch {
    var age = 46
    fun rotation() {
        Log.i("TAG", "self rotate in one day")
    }
}

变量和函数区别

kotlin的变量使用,var 表示普通变量 val 相当于java中的final只能被赋值一次。
另外,kotlin还有const关键字,可以在编译代码时硬编码到代码使用的地方。只有基本类型和String可以用。

函数的格式

fun sum(a: Int, b: Int): Int {   // Int 参数,返回值 Int
    return a + b
}

kotlin中,方法都要用fun关键字,变量都要用var, valoverride在kotlin中为一个关键字,返回值写在后面,无返回值用Unit表示,也可以省略。

函数的简写

// 当函数体只有一句话

fun getScore(): Int = score

// 当返回值确定,方法返回值声明也可以省略

fun getScore() = score // return-type is Int

if…else…表达式

在kotlin没有java的.. ? .. : ..,取而代之的是if..else..表达式。
例如

//if..else..表达式
val text = if (x > 5)
              "x > 5"
           else "x <= 5"
//?:表达式
val message = text ?: ""

?:表达式

表示,如果前值不为空(null)返回前值,否则返回后值。

范围

for (i in 1..10) //[1, 10]
for (i in 1 until 10) //[1, 10)
for (i in 1..10 step 2) //(1, 3, 5, 7, 9)
for (i in 10 downTo 0) //[10, 0]

for (item in collection) //遍历集合
for ((key, value) in map) //遍历map

集合

kotlin中有,Array, List, Set, Map跟 java 一致,另外还有独特的Sequencekotlin
listOf() 创建不可变的 ListmutableListOf() 创建可变的 ListsetOf() 创建不可变的 SetmutableSetOf() 创建可变的 SetmapOf() 创建不可变的 MapmutableMapOf() 创建可变的 Map

不可变集合的意思:size不可变,内容不可变。

val listOfNumber = listOf(1, 2, 3, 4)
val keyValue = mapOf(1 to "Amit",
                     2 to "Ali",
                     3 to "Mindorks")

另外,优化点:
Kotlin 中用专门的基本类型数组类 (IntArray FloatArray LongArray) 可以免于装箱,提升效率
集合的操作

//创建
val intArray = intArrayOf(1, 2, 3)
//遍历
intArray.forEach { i ->
    print(i + " ")
}
//过滤
val newList: List = intArray.filter { i ->
    i != 1 // 过滤掉数组中等于 1 的元素
}
//映射新的集合
val newList: List = intArray.map { i ->
    i + 1 //每个元素加 1
}
// 每个元素创建新的集合,最后合并到一个集合
// [1, 2, 3]
// {"2", "a" , "3", "a", "4", "a"}
intArray.flatMap { i ->
    listOf("${i + 1}", "a") // ? 生成新集合
}

sequence
用法跟list类似,他是惰性集合操作,当用到的时候才会去执行。性能相对list更高

  • 一旦满足遍历退出的条件,就可以省略后续不必要的遍历过程。
  • List 这种实现 Iterable接口的集合类,每调用一次函数就会生成一个新的 Iterable,下一个函数再基于新的 Iterable 执行,每次函数调用产生的临时 Iterable 会导致额外的内存消耗,而 Sequence 在整个流程中只有一个。
val list = listOf(1, 2, 3, 4)
val result: List = list
    .map { i ->
        println("Map $i")
        i * 2 
    }
    .filter { i ->
        println("Filter $i")
        i % 3  == 0 
    }
println(result.first()) // 只取集合的第一个元素
//当出现满足条件的第一个元素的时候,Sequence 就不会执行后面的元素遍历了,即跳过了 4 的遍历。

kotlin的特性

扩展

java中,扩展类的方法,要继承它实现一个子类。在kotlin中提供更方便的扩展方法。

fun Int.triple(): Int {
  return this * 3
}

var result = 3.triple()

若扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数。类的扩展没有多态的特性,因为他是静态解析的。

委托

通过关键字 by
类委托

class Drived(b: Base) : Base by b
 属性委托
 class User1 {
 var userName: String by Delegate()
 }
 标准委托
 val lazyValue: String by lazy {
 println(“computed!”)
 “Hello”
 }

代码风格跟java很类似
委托 通过关键字by (类似java中的代理模式)
标准委托
延迟属性Lazy, 特点:第一次调用会执行方法,之后调用只是返回记录的结果。
可观察属性Observable,
notNull, 适用于那些无法在初始化阶段就确定属性值的场合

参考资料

菜鸟教程form java to kotlin码上开学