文章目录
- 前言
- 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
方法。当我们访问一个变量,或者给一个变量赋值时,实际上是调用getter
,setter
方法。这点我觉得比java
要好很多,而且kotlin,提供变量的getter, setter方法的默认实现,代码上比java要少很多。 - 代码会更美观。像
SAM conversions
(一种匿名函数),相比java会减少一层缩进。
我觉得kotlin
是占在巨人的肩膀上,类似于java
,多做了一些优化。下面就具体介绍两者区别。
语法
先说下kotlin语法,可以参考 from java to kotlin 对比着 java理解,效果比较好。
这里简单介绍语法,深入的可以看教程。传送门
基本类型
Byte
、Short
、Int
、Long
、Float
、Double
像对应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) {
}
子类
继承需要用:
跟着父类,相当于java
的extends
,在子类中需要声明主构造函数。构造函数语句块,在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
中,可以不用写静态成员,kotlin
的top-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
, val
,override
在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 一致,另外还有独特的Sequence
在kotlin
中listOf()
创建不可变的 List
,mutableListOf()
创建可变的 List
setOf()
创建不可变的 Set
,mutableSetOf()
创建可变的 Set
mapOf()
创建不可变的 Map
,mutableMapOf()
创建可变的 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, 适用于那些无法在初始化阶段就确定属性值的场合
参考资料