通俗理解Kotlin及其30大特性
文章目录
- 通俗理解Kotlin及其30大特性
- 前言
- 背景
- 编译&运行
- 字节码对比
- Java VS Kotlin
- 变量/常量
- 类型声明
- 变量初始化
- 空安全特性
- 函数
- 函数声明
- 函数参数
- 函数可变参数
- 局部函数
- 函数/属性/操作符的扩展
- 函数/属性的引用
- 操作符重载
- Lambda 表达式
- 数组/List/Map/元组
- 控制表达式
- if/when
- for/while
- try-catch
- 位运算
- 类
- 类的定义/构造/继承
- 类的访问修饰符
- 数据类
- 类的别名
- 协程
- 总结
- 在AOSP中部分使用
- 优缺点比较
前言
Kotlin是Jetbrain公司发明的编程语言之一,指在替换java。恰好Google此前和SUN公司因为java闹得打官司,所以Jetbrain机智地将Kotlin推荐给了Google Android。目前来看Kotlin在Android应用开发中起到了不错的效果。
背景
Kotlin的特色,引用官网说明:
编译&运行
本质上,kotlin语言经过kotlin编译器也是编译成java字节码,可以运行在JVM虚拟机上。
由于多了一道转化工序,所以一般来说,Kotlin的编译时间会更长一些,产生的编译文件也大一些。
字节码对比
可以使用Android Studio/IDEA的工具查看Kotlin的字节码:
-
点击菜单栏 -> Tool -> Kotlin -> Show Kotlin Bytecode,查看生成的Java字节码
-
还可以点击顶部的"Decompile"按钮查看翻译后的Java代码
java 源码:
package com.xxxx.java;public class SDK {public static int addSum(int a, int b) {System.out.println("run in java sdk!");return a+b;}
}
java 字节码:
// class version 65.0 (65)
// access flags 0x21
public class com/xxxx/java/SDK {// compiled from: SDK.java// access flags 0x1public <init>()VL0LINENUMBER 3 L0ALOAD 0INVOKESPECIAL java/lang/Object.<init> ()VRETURNL1LOCALVARIABLE this Lcom/xxxx/java/SDK; L0 L1 0MAXSTACK = 1MAXLOCALS = 1// access flags 0x9public static addSum(II)IL0LINENUMBER 5 L0GETSTATIC java/lang/System.out : Ljava/io/PrintStream;LDC "run in java sdk!"INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)VL1LINENUMBER 6 L1ILOAD 0ILOAD 1IADDIRETURNL2LOCALVARIABLE a I L0 L2 0LOCALVARIABLE b I L0 L2 1MAXSTACK = 2MAXLOCALS = 2
}
kotlin 源码:
package com.xxxx.kotlinclass SDK {}fun addSum(a:Int, b:Int):Int {println("run in kotlin sdk!")return a+b;
}
kotlin字节码:
// ================com/xxxx/kotlin/SDK.class =================
// class version 52.0 (52)
// access flags 0x31
public final class com/xxxx/kotlin/SDK {// compiled from: SDK.kt@Lkotlin/Metadata;(mv={1, 9, 0}, k=1, d1={"\u0000\u000c\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\u0008\u0002\u0018\u00002\u00020\u0001B\u0005\u00a2\u0006\u0002\u0010\u0002\u00a8\u0006\u0003"}, d2={"Lcom/xxxx/kotlin/SDK;", "", "()V", "KotlinStudy"})// access flags 0x1public <init>()VL0LINENUMBER 3 L0ALOAD 0INVOKESPECIAL java/lang/Object.<init> ()VRETURNL1LOCALVARIABLE this Lcom/xxxx/kotlin/SDK; L0 L1 0MAXSTACK = 1MAXLOCALS = 1
}// ================com/xxxx/kotlin/SDKKt.class =================
// class version 52.0 (52)
// access flags 0x31
public final class com/xxxx/kotlin/SDKKt {// compiled from: SDK.kt@Lkotlin/Metadata;(mv={1, 9, 0}, k=2, d1={"\u0000\n\n\u0000\n\u0002\u0010\u0008\n\u0002\u0008\u0003\u001a\u0016\u0010\u0000\u001a\u00020\u00012\u0006\u0010\u0002\u001a\u00020\u00012\u0006\u0010\u0003\u001a\u00020\u0001\u00a8\u0006\u0004"}, d2={"addSum", "", "a", "b", "KotlinStudy"})// access flags 0x19public final static addSum(II)IL0LINENUMBER 8 L0LDC "run in kotlin sdk!"ASTORE 2GETSTATIC java/lang/System.out : Ljava/io/PrintStream;ALOAD 2INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)VL1LINENUMBER 9 L1ILOAD 0ILOAD 1IADDIRETURNL2LOCALVARIABLE a I L0 L2 0LOCALVARIABLE b I L0 L2 1MAXSTACK = 2MAXLOCALS = 3
}
【java字节码 vs kotlin字节码】
核心函数的字节码不变,在形式上稍微有调整。
Java VS Kotlin
变量/常量
类型声明
Java:
- 使用关键字
int
、String
等来声明变量类型,例如int num = 10;
。
private static void test1() {int num=10;System.out.println("what's type of num?"+getType(num));String str="Hello, world!";System.out.println("what's type of str?"+getType(str));final int N=10;//N=11;System.out.println("N="+N);}
Kotlin:
-
使用关键字
var
或val
来声明变量,例如var num: Int = 10
或val name: String = "Kotlin"
。 -
默认支持局部变量类型推断,使用关键字
val
或var
声明变量 -
var
是可变变量,val一旦赋值不可再变,相当于java
的final
fun test1(){var num = 10println("what's type of num?${num::class.java.typeName}")var str = "Hello, world!"println("what's type of str?" + getType(str))val N = 10//N = 11println("N=$N")//const val PI = 3.14println(PI)val a: Int = 10val b: Long = a.toLong()println("what's type of b? $b-->" + getType(b))
}
Kotlin使用关键字 const
(只能用于顶层和对象声明)和 val
(只读变量)来声明常量,
const
只能修饰属性(类属性、顶层属性),不能用于局部变量,再编译期间定下来,所以它的类型只能是 String
或基本类型。
例如:
const val PI = 3.14
val name: String = "Kotlin"
Kotlin数据类型转换方式更为简洁,例如:
val a: Int = 10
val b: Long = a.toLong()
变量初始化
lateinit
是一种延迟初始化方式,必须在使用前初始化,例如:
lateinit var name: String
fun init() {name = "Kotlin"
}
lazy
是一种懒加载方式,会在第一次访问时初始化,例如:
val name: String by lazy {println("Initializing")"Kotlin"
}
lateinit var
和by lazy
都可以推迟初始化。
lateinit var
只是在编译期忽略对属性未初始化进行检查,何时初始化还需开发者自行决定。
by lazy
在被第一次调用的时候能自动初始化,做到了真正的延迟初始化的行为。
空安全特性
Java:不支持空安全特性,需要手动判断 null 值。
private static void test2() {String str=null;System.out.println(str);if ( str != null) {System.out.println(str.substring(0,1));}
}
Kotlin:支持空安全特性,使用 ?
来标记可为空的类型,例如 var name: String? = null
。
var str: String? =null;
println(str)
println(str?.substring(0,1))
println(str?.length) // 如果 str 不为 null,返回 str 的长度,否则返回 null
此外,Kotlin还支持非空断言,使用 !!
运算符,相当于回归java设计,例如:
println(str!!.length) // 如果 str 不为 null,返回 str 的长度,否则抛出 NullPointerException
运行时会报错:
Exception in thread "main" java.lang.NullPointerExceptionat com.xxxx.kotlin.KotlinMainTestKt.test2(KotlinMainTest.kt:163)at com.xxxx.kotlin.KotlinMainTestKt.main(KotlinMainTest.kt:16)
Elvis 运算符是一种处理空值的方式,可以指定一个默认值,例如
val str: String? = null
val length = str?.length ?: 0 // 如果 str 不为 null,返回 str 的长度,否则返回 0
Kotlin:安全类型转换是一种转换类型的方式,可以避免类型转换异常,例如:
val str: Any = "Kotlin"
val length = (str as? String)?.length ?: 0
函数
函数声明
Java:使用关键字 void
来声明函数的返回类型,例如 public void printName(String name) {}
。
private static String test3() {return "String";}
Kotlin:使用关键字 fun
来声明函数,例如 fun printName(name: String) {}
。
private fun test3(): String {return "String"
}
此外,
- Kotlin调用静态方法,无需带上包名,可以直接调用,这一点体现了kotlin的静态性。
- Kotlin函数调用,也无须分号结尾。
函数参数
Java:不支持函数的默认参数。
private static void test4(String name, Boolean isMale) {System.out.println("name="+name+", isMale="+isMale);}
Kotlin:支持函数的默认参数,例如 fun printName(name: String, isMale: Boolean = true) {}
。
test4("haha")fun test4(name: String, isMale: Boolean = true) {println("name=$name, isMale=$isMale")
}
此外,类的构造函数也支持默认参数:
class Person(val name: String = "Kotlin", val age: Int = 20)
Kotlin:具名参数是一种通过名称来指定函数参数的方式,可以提高代码可读性,例如:
fun printPerson(name: String, age: Int) {println("Name: $name, Age: $age")
}
printPerson(name = "Kotlin", age = 20)
fun main(){test4("kotlin",false)test4("haha") //默认参数test4( isMale = false, name="Lucas") //具名参数
}fun test4(name: String, isMale: Boolean = true) {println("name=$name, isMale=$isMale")
}
函数可变参数
Java:使用 ...
来声明可变参数,例如 public void printNames(String... names) {}
。
private static void test5(String... names) {for (int i = 0; i < names.length; i++) {System.out.println(names[i]);}}
Kotlin:使用关键字 vararg
来声明可变参数,例如 fun printNames(vararg names: String) {}
。
fun test5(vararg names: String) {for ( name in names){println(name)}
}
局部函数
函数中仍然可以嵌套函数,对于函数内代码块频繁调用的情况有用。
fun test6() {fun sum(a:Int, b:Int): Int {fun multiply(c:Int, d:Int): Int{return c*d}return multiply(a, a)+multiply(b, b);}// 平方和println(sum(1,2))
}
函数/属性/操作符的扩展
- 扩展函数是一种将函数添加到现有类中的方式
- 扩展属性是一种将属性添加到现有类中的方式
val String.lastChar: Charget() = get(length - 1)
val String.pai: Stringget() = "3.1415926"
val String.isEmail: Booleanget() = this.matches(Regex("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}"))fun test7() {fun String.addHello() = this + ", Hello!"println("Lucas".addHello());println("Lucas".lastChar);println(String().pai);println("lucas.deng@xxxx.com".isEmail);
}
- 扩展运算符是一种将数组或集合打散为参数列表的方式,例如:
fun sum(a: Int, b: Int, c: Int) = a + b + c
val list = listOf(1, 2, 3)
val result = sum(*list.toIntArray())
函数/属性的引用
-
支持属性引用,可以使用
::
运算符来引用属性 -
支持函数引用,可以使用
::
运算符来引用函数
fun test8() {class Person(val name: String) {fun printName() {println(name)}}val person = Person("Kotlin")val nameProperty = Person::nameprintln(getType(nameProperty))println(nameProperty.toString())val name = nameProperty.get(person)println(name)fun printName(name: String) {println(name)}val namePrinter = ::printNamenamePrinter("Kotlin")val c= String::classprintln(c)val d= String::toStringprintln(d("abc"))val e= String::equalsprintln(e("abc", "abc"))}
操作符重载
不是所有操作符都支持重载,支持的操作符主要是运算类的。
表达式 | 翻译为 |
---|---|
a + b | a.plus(b) |
a - b | a.minus(b) |
a * b | a.times(b) |
a / b | a.div(b) |
a % b | a.rem(b) |
a..b | a.rangeTo(b) |
a..<b | a.rangeUntil(b) |
fun test9() {data class Point(val x: Int, val y: Int) {operator fun plus(other: Point) = Point(x + other.x, y + other.y)operator fun minus(other: Point) = Point(x - other.x, y)}val p1 = Point(1, 2)val p2 = Point(3, 4)val p3 = p1 + p2println(p3)val p4 = p2 - p1println(p4)}
Lambda 表达式
Java:支持 Lambda 表达式,但写法比较繁琐。
如果只有一个显式声明的抽象方法,那么它就是一个函数接口。
java Lambda表达式只能应用于函数接口。
private static void test12(){new Thread(() -> System.out.println("Thread run().....")).start();Call call= (a,b)->{ return a+b; };System.out.println(call.plus(1,4));}static interface Call {int plus(int a, int b);// int minus(int a, int b);}
Kotlin:支持 Lambda 表达式,写法简洁,例如 val sum = { a: Int, b: Int -> a + b }
。
private fun test10() {Thread { println("Thread run().....") }.start()val sum = { a: Int, b: Int -> a + b }println(sum(1,4))
}
Java:使用 for 循环和 if 语句来实现过滤器。
Kotlin:使用 Lambda 表达式和过滤器函数来实现,例如 val nums = listOf(1, 2, 3, 4, 5).filter { it % 2 == 0 }
。
private fun test10() {Thread { println("Thread run().....") }.start()val sum = { a: Int, b: Int -> a + b }println(sum(1,4))val nums = listOf(1, 2, 3, 4, 5).filter { it % 2 == 0 }println(nums)
}
数组/List/Map/元组
数组: 不再使用[ ], 而是用Array类来代替掉,
List: 直接用listOf就可以进行初始化,直接用forEach就可以打印所有元素
Map: 直接用hashMapOf就可以进行初始化,直接用forEach就可以打印所有元素
元组:支持元组,使用 Pair
和 Triple
类来表示二元组和三元组。主要用于记录小量的二元/三元数据,而不必要重新定义一个类。
fun test11() {//数组val arrayEmpty = emptyArray<String>()val array1 = arrayOfNulls<Int>(5)val array2 = arrayOf(1, 2, 3, 4)array2.forEach {print(" $it")}println()// Listval listEmpty = emptyList<String>()val nums = listOf(1, 2, 3, 4, 5)val sum = nums.reduce { acc, i -> acc + i }for (item in nums)print(" $item")println()val map = hashMapOf("Kotlin" to 1, "Java" to 2, "Python" to 3)println(map["Kotlin"])map["Lucas"] = 23println(map["Lucas"])for ((key, value) in map) {print("$key = $value, ")}println()map.forEach {(key, value) -> print("$key = $value, ")}println()val pair = Pair("Kotlin", 20)val triple = Triple("Kotlin", 20, "male")val (name, age) = pairprintln(pair)println(triple)val triple2 = Triple("Lucas", 27, "male")var arrays = arrayOf(triple,triple2)arrays.forEach {println(it)}
}
控制表达式
if/when
Java:
使用 if-else if-else 或 switch-case 来实现条件判断。
Kotlin:
if 表达式是有返回值的,可以用来赋值,基本可以代替三目运算符,例如:
fun test12() {val a = 10val b = 20//val max = a>b? a:bval max = if (a > b) a else bprintln(max)val value = 88if (value in 1..100) {println("$value")}}
使用 when 表达式,例如:
fun test14( index : String): String {val num=10;when(index){"a"->return "A""b"->return "B""c"->return "C"}return when(num){1-> "99"2-> "88"3-> "77"else->"-1"}
}fun test13() {var num=34when(num){10 -> println("10")23 -> println("23")34 -> println("OK!")35 -> println("35")}
}
不再需要break,不然又有什么坑要踩。
for/while
Kotlin:for 循环可以遍历集合、数组等对象,例如:
val list = listOf("Kotlin", "Java", "Python")
for (item in list) {println(item)
}
Kotlin:支持 Range 表达式,例如 val nums = 1..10
。
fun test15() {for (i in 1..9) {print(i)}println()for (i in 9..1) {print(i)}println()for (i in 9 downTo 1) {print(i)}println()for (i in 1..20 step 2) {print(i)}println()for (i in 1 until 10) {print(i)}println()
}
Kotlin:while 循环可以重复执行某个代码块,例如:
var i = 0
while (i < 10) {println(i)i++
}
Kotlin:do-while 循环与 while 循环类似,但是至少会执行一次,例如:
var i = 0
do {println(i)i++
} while (i < 10)
fun test16() {var i = 0while (i < 10) {print(i)i++}println()var j = 0do {print(j)j++} while (j < 10)println()}
try-catch
Java:使用 try-catch 语句块来捕获异常。
Kotlin:不仅可以使用语句块,还能使用 try-catch 表达式来捕获异常,例如:
fun test17() {try {val str:String? = null;println(str!!.length)}catch (e:NullPointerException){println("please check your code!")}val len = try {val str:String? = null;println(str!!.length)}catch (e:NullPointerException){println("please check your code!")5}println(len)
}
位运算
Kotlin:位运算是一种对二进制位进行操作的方式,例如:
fun test18() {val a = 0b0101val b = 0b1010val c = a and b // 0b0000val d = a or b // 0b1111val e = a xor b // 0b1111val f = a.inv() // 0b1010println(c)println(d)println(e)println(f)println(Integer.toBinaryString(e))println(Integer.toBinaryString(f))}
类
类的定义/构造/继承
最简单的类定义:
class Emptyclass Anything{
}
定义Person类,并调用
open class Person //主构造Person()
{constructor(id: Int, name: String) {}constructor(id: Int, sex: Char) {}//主构造constructor() {}override fun toString(): String {return "abc"}}
fun test19() {var person:Person = Person()var person2:Person = Person(110,'C')var person3:Person = Person(10086, "Lucas")
}
Java:使用关键字 extends
来实现类的继承,例如 public class Student extends Person {}
。
private static void test7() {Student student = new Student();System.out.println(student);}static class Student extends Person {}static class Person {String name = "David";int age = 23;@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}}
Kotlin:使用关键字 :
来实现类的继承,例如 class Student : Person()
。
并且Person必须为open,定义Student类,继承至Person
class Student() : Person()
{// Kotlin 全部都是没有默认值的// Java 成员有默认值,但是方法内部没有默认值// lateinit 懒加载 没有赋值 就不能使用,否则报错lateinit var name : Stringvar age: Int = 0override fun toString(): String {return "Student(name='$name', age=$age)"}constructor(n:String) : this() {name = n}constructor(n:String, a:Int) : this() {name = nage = a}}
fun test20() {var student:Student = Student("Hello")println(student.toString())
}
实现接口:
interface Eat {fun EatMethod() : Boolean
}
class Student() : Person(), Eat
{override fun EatMethod(): Boolean {TODO("Not yet implemented")}...
}
类的访问修饰符
Java:使用关键字 public
、private
、protected
和 default
(没有修饰符)来修饰类的访问权限。
Kotlin:使用关键字 public
、private
、protected
和 internal
来修饰类的访问权限,默认为 public
。
数据类
Java:不支持数据类。
private static void test10() {User jack = new User("Jack", 23);System.out.println(jack.toString());}static class User{String username;int age;public User(String username, int age) {this.username = username;this.age = age;}}
Kotlin:
支持数据类,使用关键字 data
来声明,例如 data class User(val id: Int, val name: String, val sex: Char)
。
编译器会自动的从主构造函数中根据所有声明的属性提取以下函数:
equals()
/hashCode()
toString()
格式如"User(name=John, age=42)"
componentN() functions
对应于属性,按声明顺序排列copy()
函数
fun test21() {val user = User(99, "Lucas", 'M')val(myID, myName, mySex) = user.copy()println("myID:$myID, myName:$myName, mySex:$mySex")// _ 代表不接收val(_, myName2, _) = user.copy()println("myName2:$myName2")println(user.toString())val user1=user.copy()val user2=user.copy(name="Kotlin")println(user1.equals(user))println(user2.equals(user))}
类的别名
Java:不支持类型别名。
Kotlin:支持类型别名,使用关键字 typealias
来声明,例如:
typealias Name = String
typealias MyList = List<String>
fun test22() {fun printName(name: Name) {}val list: MyList = listOf("Kotlin", "Java", "Python")
}
此外,内部类和枚举类也和java稍有不同,可以自行查阅。
协程
Kotlin中的协程可以认为是一个“线程框架”,类似于Java中的线程池Executor,类似于Android中的Handler/AsyncTask。
Kotlin 协程的最大好处在于,你可以把运行的不同线程的代码写在同一个代码块里,可以随意切。(用看起来同步的方式写出异步代码)
总结
在AOSP中部分使用
以frameworks/base/packages/SystemUI为例,kt文件有510个java文件有1220个占比达到了1/3。
find ./frameworks/base/packages/SystemUI/src -name "*.kt" | wc -l
510
find ./frameworks/base/packages/SystemUI/src -name "*.java" | wc -l
1220
优缺点比较
Java | Kotlin | |
---|---|---|
优点 | 改进错误检测和解决的检查异常 提供详细的文档。 大量熟练的开发人员可用 大量的第 3 方库 它允许您形成标准程序和可重用代码。 它是一个多线程环境,允许您在一个程序中同时执行多个任务。 完美的表现 易于浏览的社区资料 | 使用 Kotlin 多平台框架,您可以提取一个通用代码库,同时针对所有这些代码库 Kotlin 提供了内置的 null 安全支持,这是一个救星,尤其是在 Android 上,它充满了旧的 Java 风格的 API。 它比 Java 更简洁、更具表现力,这意味着出错的空间更小。 提供用户友好且易于理解的编码规范 将大型应用程序划分为更小的层。 使用大量函数类型和专门的语言结构,如 lambda 表达式。 帮助开发者创建扩展功能 提供了一种非常简单且几乎自动化的方式来创建数据类 Kotlin 是一种静态类型语言,因此非常易于阅读和编写。 这种语言允许以各种方式交换和使用来自 Java 的信息。 在 Kotlin 中编写新代码将花费更少的时间。 部署 kotlin 代码并大规模维护它非常容易。 |
缺点 | 由于诸多限制,不太适合 Android API 设计 需要大量手动工作,这增加了潜在错误的数量 JIT 编译器使程序相对较慢。 Java 具有较高的内存和处理要求。 它不支持像指针这样的低级编程结构。 您无法控制垃圾收集,因为 Java 不提供 delete()、free() 等函数。 | 开发者社区很小,因此缺乏学习材料和专业帮助。 Java 不提供可能导致错误的检查异常的功能。 编译速度比Java慢 编译的产物比java大 Kotlin 作为一种高度声明性的语言,有时它可以帮助您在相应的 JVM 字节码中生成大量样板 |