From Java To Kotlin, 空安全、扩展、函数、Lambda
概述(Summarize)
-
• 可以做什么?
-
• Kotlin的优点
Kotlin 是什么?
Kotlin 出自于捷克一家软件研发公司 JetBrains ,这家公司开发出很多优秀的 IDE,如 IntelliJ IDEA、DataGrip 等都是它的杰作,包括 Google 官方的 Android IDE -- Android Studio,也是 IntelliJ IDEA 的插件版。
名称取自圣彼得堡附近的一个小岛 ( Kotlin Island,和 Java一样用岛屿命名,JetBrains 在 2010 年首次推出 Kotlin 编程语言,并在次年将之开源。
-
Java字节码。也可以编译成 JavaScript,方便在没有 JVM 的设备上运行。
-
替代 Java 语言。
Java 虚拟机上运行的静态类型编程语言,被称之为 Android 世界的Swift。
可以做什么?
-
• Server-side
-
Kotlin Multiplatform Mobile is in Beta!
-
Create a multiPlatform library for JVM, JS, and Native platforms。
可以做很多方向的开发!
Android 官方开发语言从Java变为Kotlin,Java 有哪些问题?
-
• 更少的函数式编程特性:Java 语言在函数式编程方面的支持相对较弱,虽然 Java 8 引入了 Lambda 表达式和 Stream API,但是 Kotlin 语言在这方面的支持更加全面和友好。
-
• 语法繁琐,不够简洁:Java 语言比 Kotlin 语言更为冗长,需要写更多的代码来完成相同的任务,这可能会降低开发效率。
Kotlin的优点
-
• 互操作性: Kotlin 可以与 Java 混合编程,Kotlin 和 Java 可以相互调用,目标是 100% 兼容。
• 简约:使用一行代码创建一个包含 getters
、 setters
、 equals(
、 hashCode(
、 toString(
以及 copy(
的 POJO:
Kotlin 特性(Features)
-
• 类型推断(Type inference)
-
• 扩展函数 (Extension functions
-
• 字符串模板(String templates)
-
• 函数类型 (Function Type
-
• 高阶函数(Primary constructors)
-
• 类委托(Class delegation)
基本语法 (Basic Syntax )
-
• 空安全(Null Safety )
-
• 让函数更好的调用( Making functions easier to call )
-
• 参数默认值(Default arguments)
• 变量(Variables)
变量(Variables)
类型,后面跟着变量的名称和对应的值,然后以分号结尾。就像这样:
Integer price = 100;
而 Kotlin 则不一样,我们要使用val
或者是var
这样的关键字作为开头,后面跟“变量名称”,接着是“变量类型”和“赋值语句”,最后是分号结尾。就像这样:
/*
关键字 变量类型
↓ ↓ */
var price: Int = 100; /*
↑ ↑
变量名 变量值 */
在 Kotlin 里面,代码末尾的分号省略不写,就像这样:
var price = 100 // 默认推导类型为: Int
另外,由于 Kotlin 支持类型推导,大部分情况下,我们的变量类型可以省略不写,就像这样:
var price = 100 // 默认推导类型为: Int
var 声明的变量,我们叫做可变变量,它对应 Java 里的普通变量。
只读变量,它相当于 Java 里面的 final 变量。
var price = 100
price = 101
val num = 1
num = 2 // 编译器报错
var, val 反编译成 Java :
class PersonZ {
var name = "zhang"
var age = 30
val nickname: String
get( {
return if (age > 30 "laozhang" else "xiaozhang"
}
fun grow( {
age += 1
}
属性 nickname 的值并非不可变,当调用 grow( 方法时,它的值会从 "xiaozhang" 变为 "laozhang",
编译时常量
编译时确定。
val time = System.currentTimeMillis(
// 这种会报错
const val constTime = System.currentTimeMillis(
基本数据类型( Basic Data Type )
Kotlin 的基本数值类型包括 Byte、Short、Int、Long、Float、Double 等。
类型 | 位宽度 | 备注 |
---|---|---|
Double | 64 | Kotlin 没有 double |
Float | 32 | Kotlin 没有 float |
Long | 64 | Kotlin 没有 long |
Int | 32 | Kotlin 没有 int/Intege |
Short | 16 | Kotlin 没有 short |
Byte | 8 | Kotlin 没有 byte |
在 Kotlin 里,一切都是对象。
空安全(Null Safety )
val i: Double = null // 编译器报错
以上的代码并不能通过 Kotlin 编译。
是否可能为 null。
val i: Double = null // 编译器报错
val j: Double? = null // 编译通过
并且由于 Kotlin 对可能为空的变量类型做了强制区分,这就意味着,“可能为空的变量”无法直接赋值给“不可为空的变量”,反过来 “不可为空的变量” 可以赋值给“可能为空的变量” 。
var i: Double = 1.0
var j: Double? = null
i = j // 编译器报错
j = i // 编译通过
这么设计的原因是,从集合逻辑上:可能为空 包含 不可为空
var i: Double = 1.0
val j: Double? = null
if (j != null {
i = j // 编译通过
}
函数声明( Define Function )
public String helloFunction(@NotNull String name {
return "Hello " + name + " !";
}
Kotlin :
/*
关键字 函数名 参数类型 返回值类型
↓ ↓ ↓ ↓ */
fun helloFunction(name: String: String {
return "Hello $name !"
}/* ↑
花括号内为:函数体
*/
-
返回值类型,紧跟在参数的后面,这点和 Java 不一样。
• 使用了 fun 关键字来定义函数;
-
• 直接用
=
连接,变成一种类似 变量赋值的 函数形式
• return可以省略
fun helloFunton(name:String:String = "Hello $name !"
我们称之为单表达式函数
类型推导,返回值类型可以省略:
fun helloFunton(name:String:= "Hello $name !"
这样看起来就更简洁了。
让函数更好的调用( Making functions easier to call )
命名参数/具名参数 (Named arguments)
helloFunction("Kotlin"
和 Java 一样。
命名函数参数 举个例子,现在有一个函数:
fun createUser(
name: String,
age: Int,
gender: Int,
friendCount: Int,
feedCount: Int,
likeCount: Long,
commentCount: Int
{
//..
}
如果像 Java 那样调用:
createUser("Tom", 30, 1, 78, 2093, 10937, 3285
就要严格按照参数顺序传参:
-
可读性不高。
不好维护。
createUser(
name = "Tom",
age = 30,
gender = 1,
friendCount = 78,
feedCount = 2093,
likeCount = 10937,
commentCount = 3285
我们把函数的形参加了进来,形参和实参用 =
连接,建立了两者的对应关系。这样可读性更强。
feedCount也可以很方便的定位到参数。 这样易维护
参数默认值(Default arguments)
fun createUser(
name: String,
age: Int,
gender: Int = 1,
friendCount: Int = 0,
feedCount: Int = 0,
likeCount: Long = 0L,
commentCount: Int = 0
{
//..
}
gender、likeCount 等参数被赋予了默认值,当我们调用时,有些有默认值的参数就可以不传参,Kotlin编译器自动帮我们填上默认值。
createUser(
name = "Tom",
age = 30,
friendCount = 50
在 Java 当中要实现类似的逻辑,我们就必须手动定义新的“3 个参数的 createUser 函数”,或者是使用 Builder 设计模式。
Classes and Objects
-
• 抽象类 (Abstract Class
-
• 接口和实现 (Interface and implements)
-
• 数据类(Data Class )
-
• object:匿名内部类
-
• object:伴生对象
-
• 什么是扩展函数和扩展属性?
类 (Class)
Java
public class Person {
private String name;
private int age;
public Person(String name, int age {
this.name = name;
this.age = age;
}
// 属性 name 没有 setter
public String getName( {
return name;
}
public int getAge( {
return age;
}
public void setAge(int age {
this.age = age;
}
}
Class
Kotlin
class Person(val name: String, var age: Int
Kotlin 定义类,同样使用 class 关键字。
编译器会帮我们生成“构造函数”,
和Java相比 Kotlin 定义一个类足够简洁。
抽象类与继承
抽象类 (Abstract Class
abstract class Person(val name: String {
abstract fun walk(
// 省略
}
继承(Extend)
// Java 的继承
// ↓
public class MainActivity extends Activity {
@Override
void onCreate({ ... }
}
// Kotlin 的继承
// ↓
class MainActivity : AppCompatActivity( {
override fun onCreate( { ... }
}
* * *
接口和实现 (Interface and implements)
--------------------------------
Kotlin 当中的接口(interface),和 Java 也是大同小异的,它们都是通过 interface 这个关键字来定义的。
interface Behavior {
fun walk(
}
override fun walk( {
// walk
}
// ...
}
可以看到在以上的代码中,我们定义了一个新的接口 Behavior,它里面有一个需要被实现的方法 walk,然后我们在 Person 类当中实现了这个接口。
**Kotlin 的继承和接口实现语法基本上是一样的。**
* * *
Kotlin 的接口,跟 Java 最大的差异就在于,接口的方法可以有默认实现,同时,它也可以有属性。
interface Behavior {
// 接口内的可以有属性
val canWalk: Boolean
// 接口方法的默认实现
fun walk( {
if (canWalk {
// do something
}
}
}
class Person(val name: String: Behavior {
// 重写接口的属性
override val canWalk: Boolean
get( = true
}
我们在接口方法当中,为 walk( 方法提供了默认实现,如果 canWalk 为 true,才执行 walk 内部的具体行为。
Kotlin 当中的接口,被设计得更加强大了。
在 Java 1.8 版本当中,Java接口也引入了类似的特性。
* * *
嵌套类和内部类( Nested and Inner Classes )
-----------------------------------
Java 当中,最常见的嵌套类分为两种:**非静态内部类**、**静态内部类**。Kotlin 当中也有一样的概念。
class A {
class B {
}
}
以上代码中,B 类,就是 A 类里面的嵌套类。
**注意:** 无法在 B 类当中访问 A 类的属性和成员方法。
因为Kotlin 默认嵌套类(B类)是一个静态内部类
Kotlin 嵌套类反编译成 Java 代码:

----------------
Koltin 数据类,就是用于存放数据的类,等价于 **POJO** (Plain Ordinary Java Object)。要定义一个数据类,我们只需要在普通的类前面加上一个关键字 `data`,就可以把它变成一个"数据类"。
// 数据类当中,最少要有一个属性
↓
data class Person(val name: String, val age: Int
编译器会为数据类自动生成一些 POJO 常用的方法
* • getter(
* • setter(
* • equals(;
* • hashCode(;
* • toString(;
* • componentN( 函数;
* • copy(。
* * *
Koltin 数据类反编译成 Java代码:
,主要分为两种语法:
第一个是**扩展函数**,
第二个是**扩展属性**。
从语法上看,扩展**看起来**就像是我们从类的外部为它扩展了新的成员。
场景:假如我们想修改 JDK 当中的 String,想在它的基础上增加一个方法“lastElement(”来获取末尾元素,如果使用 Java,我们是无法通过常规手段实现的,因为我们没办法修改 JDK 的源代码。**任何第三方提供的 SDK,我们都无权修改**。
不过,借助 Kotlin 的扩展函数,我们就完全可以在**语义层面**,来为第三方 SDK 的类**扩展**新的成员方法和成员属性。
### 扩展函数
扩展函数,就是从类的外部扩展出来的一个函数,这个函数看起来就像是类的成员函数一样
Extension.kt
/*
① ② ③ ④
↓ ↓ ↓ ↓ */
fun String.lastElement(: Char? {
// ⑤
// ↓
if (this.isEmpty( {
return null
}
}
fun main( {
val msg = "Hello Wolrd"
// lastElement就像String的成员方法一样可以直接调用
val last = msg.lastElement( // last = d
}
* • 注释①,fun关键字,代表我们要定义一个函数。也就是说,不管是定义普通 Kotlin 函数,还是定义扩展函数,我们都需要 fun 关键字。
* • 注释②,“String.”,代表我们的扩展函数是为 String 这个类定义的。在 Kotlin 当中,它有一个名字,叫做接收者(Receiver),也就是扩展函数的接收方。
* • 注释③,lastElement(,是我们定义的扩展函数的名称。
* • 注释④,“Char?”,代表扩展函数的返回值是可能为空的 Char 类型。
* • 注释⑤,“this.”,代表“具体的 String 对象”,当我们调用 msg.lastElement( 的时候,this 就代表了 msg。
* * *
扩展函数反编译成 Java 代码:
public final class StringExtKt {
@Nullable
public static final Character lastElement(@NotNull String \(this\lastElement {
// 省略
}
}
而如果我们将上面的 StringExtKt 修改成 StringUtils,它就变成了典型的 Java 工具类
public final class StringUtils {
// 省略
}
}
public static final void main( {
Character last = StringUtils.lastElement(msg;
}
所以 Kotlin 扩展函数 **本质** 上和 Java静态方法 是一样的。
只是编译器帮我们做了很多事情,让代码写起来更简洁。
* * *
### 扩展属性
而扩展属性,则是在类的外部为它定义一个新的成员属性。
// 接收者类型
// ↓
val String.lastElement: Char?
get( = if (isEmpty( {
null
} else {
get(length - 1
}
val msg = "Hello Wolrd"
// lastElement就像String的成员属性一样可以直接调用
val last = msg.lastElement // last = d
}
* * *
扩展函数/扩展属性对比
,这样就简洁了很多。
"This is Toast".showToast(context
* * *
函数与 Lambda 表达式
==============
* • 函数类型(Function Type)
* • 函数引用 (Function reference)
* • 高阶函数(Higher-order function)
* • 匿名函数 (Anonymous function)
* • Lambda Expressions
* • 函数式(SAM)接口
* • SAM 转换
* • 高阶函数应用
* * *
函数类型(Function Type)
-------------------
函数类型(Function Type)就是**函数的**
**类型**,在 Kotlin 的世界里,函数是一等公民 既然变量可以有类型,函数也可以有类型。
// (Int, Int ->Float 这就是 add 函数的类型
// ↑ ↑ ↑
fun add(a: Int, b: Int: Float { return (a+b.toFloat( }
将第三行代码里的“ **Int** **Int** **Float**”抽出来,就可以确定该函数的类型。
将函数的“参数类型”和“返回值类型”抽象出来后,加上`()`,`->` 符号加工后,就得到了“函数类型”。
`(Int, Int ->Float` 就代表了参数类型是两个 Int,返回值类型为 Float 的函数类型。
* * *
函数引用(Function reference)
------------------------
普通的变量有引用的概念,我们可以将一个变量赋值给另一个变量,这一点,在函数上也是同样适用的,函数也有引用,并且也可以赋值给变量。
前面定义的 add 函数,赋值给另一个函数变量时,不能直接用的,
转换成 Java 代码。可以看到在 Java 里, **函数类型**被声明为**普通的接口**:一个函数类型的变量是FunctionN接口的一个实现。Kotlin标准库定义了一系列的**接口**,这些接口对应于**不同参数数量**的**函数**:`Function0<R>`(没有参数的函数)、`Function2<P1,P2,R>`(2个参数的函数)...`Function22<P1,P2 ... R>`。每个接口定义了一个`invoke(`方法,调用这个方法就会执行函数。一个**函数类型的变量**就是实现了对应的FunctionN接口的**实现类**的**实例**。实现类的`invoke(`方法包含了 **函数引用**对应的**函数**的**函数体**
反编译成 Java代码:
public final void testGaojie( {
// println( add(2, 3
float var1 = this.add(2, 3;
System.out.println(var1;
// val function: (Int, Int -> Float = ::add
Function2 function = (Function2(new Function2((GaojieFunTestthis {
// \(FF: synthetic method
// \FF: bridge method
public Object invoke(Object var1, Object var2 {
return this.invoke(((Numbervar1.intValue(, ((Numbervar2.intValue(;
}
return ((GaojieFunTestthis.receiver.add(p1, p2;
}
};
// println( function(2, 3 // 输出 5.0
float var2 = ((Numberfunction.invoke(2, 3.floatValue(;
System.out.println(var2;
// println( function.invoke(2, 3 // 输出 5.0
var2 = ((Numberfunction.invoke(2, 3.floatValue(;
System.out.println(var2;
}
* * *
高阶函数 (Higher-order function)
----------------------------
高阶函数的定义:高阶函数是将函数用作**参数**或者**返回值**的函数。
如果一个函数的**参数类型**是**函数类型**或者**返回值类型**是**函数类型**,那么这个函数就是就是高阶函数 。
或者说,如果一个函数的**参数**或者**返回值**,其中有一个是**函数**,那么这个函数就是高阶函数。
// 函数类型的变量 函数类型
// ↓ ↓
fun higherOrderAdd( a:Int,b: Int,block: (Int, Int -> Float:Float{
// 函数类型的变量
// ↓
var result = block.invoke(a,b
// 函数类型的变量
// ↓
var result2 = block(a,b
println("result:$result"
return result
}
higherOrderAdd 有一个参数是函数类型,所以它是高阶函数
* * *
匿名函数
----
匿名函数看起来跟普通函数很相似,除了它的**名字**和**参数类型**被省略了外。 匿名函数示例如下:
fun(a :Int, b :Int = a + b
上面的匿名函数是没法直接调用的,赋值给变量后才可以调用
val anonymousFunction = fun(a :Int, b :Int = a + b
fun anonymousFunctionTest( {
higherOrderAdd(2,2,::add // 函数引用
higherOrderAdd(2,2,anonymousFunction // 函数变量
higherOrderAdd(2,2,
fun (a:Int,b:Int:Float{ return (a+b.toFloat(} // 匿名函数
}
```
匿名函数**本质**上也是函数类型的对象,所以可以赋值给变量。
* * *
* * *
匿名函数不能单独声明在 ()外面,因为匿名函数是(**函数的声明**与**函数引用**合二为一)
内不能直接 声明 具名函数,因为它不是对象
接口
----------
SAM 是 Single Abstract Method 的缩写,只有一个抽象方法的接口称为**函数式接口**或 **SAM(单一抽象方法)接口**。函数式接口可以有多个非抽象成员,但**只能有一个抽象成员**。
在Java 中可以用注解@FunctionalInterface 声明一个函数式接口:
```
@FunctionalInterface
public interface Runnable {
void run(;
}
```
在 Kotlin 中可以用 fun 修饰符在 Kotlin 中声明一个函数式接口:
```
// 注意 interface 前的 fun
fun interface KRunnable {
fun invoke(
}
```
* * *
* * *
SAM 转换
------
对于函数式接口,可以通过 lambda 表达式实现 SAM 转换,从而使代码更简洁、更有可读性。
使用 lambda 表达式可以替代手动创建 实现函数式接口的类。 通过 SAM 转换,Kotlin 可以将 签名与接口的单个抽象方法的**签名匹配**的任何 **lambda 表达式**,转换成实现该接口的类的**实例**。
```
// 注意需用fun 关键字声明
fun interface Action{
fun run(str:String
}
fun runAction(action: Action{
action.run("this run"
}
fun main( {
// 创建一个 实现函数式接口 的类 的实例(匿名内部类)
val action = object :Action{
override fun run(str: String {
println(str
}
}
// 传入实例,不使用 SAM 转换
runAction(action
// 利用 Kotlin 的 SAM 转换,可以改为以下等效代码:
// 使用 Lambda表达式替代手动创建 实现函数式接口的类
runAction({
str-> println(str
}
}
* * *
* * *
fun interface InterfaceApi{
fun run(str:String
}
fun runInterface(interfaceApi: InterfaceApi{
interfaceApi.run("this run"
}
// 函数类型替代接口定义
fun factionTypeReplaceInterface(block:(String->Unit{
block("this block run"
}
//===Test
// 普通函数,参数是函数式接口对象,传 函数类型对象 也是可以的
fun testFactionTypeReplaceInterface({
val function:(String->Unit = { println(it }
runInterface(function //普通函数,参数是函数式接口对象,传 函数类型对象 也是可以的
factionTypeReplaceInterface(function
}
// 高阶函数, 参数是函数类型对象,传 是函数式接口对象 是不可以的。
fun testInterface({
val interfaceApi:InterfaceApi = object :InterfaceApi{
override fun run(str: String {
println(str
}
}
runInterface(interfaceApi
factionTypeReplaceInterface(interfaceApi// 高阶函数, 参数是函数类型对象,传 是函数式接口对象 是不可以的。
}
![图片](https://mmbiz.qpic.cn/mmbiz_jpg/RlsGJDiaDGWAfxv2W9t2s7ziccdBdDy4fMyadFZ6LWUKiaCQjnPlIIeOFvd6BvFVa59M4GcOXqoebypmYJP9Uwgzg/640?wx_fmt=jpeg "null"
普通函数,参数是函数式接口对象,传 函数类型对象 也是可以的
反过来不可以:
高阶函数,参数是函数类型对象,传 是函数式接口对象 是不可以的。
前面说的都是函数传不同的参数类型。
![图片](https://mmbiz.qpic.cn/mmbiz_jpg/RlsGJDiaDGWAfxv2W9t2s7ziccdBdDy4fMCnQxksiaeMqWq5tc9ZzbxC4IUFbY2oLMEfxLe8Pf5S2RabTbCwcXEgw/640?wx_fmt=jpeg
这张图中的三处报错都是,**类型不匹配**。
**说明:**
作为函数实参时,函数类型对象 单向代替 函数式接口对象。
但是在创建对象时,函数类型、函数式接口两种类型是泾渭分明的。
高阶函数应用
------
在Android开发时,我们经常会遇到给自定义View绑定点击事件的场景。以往通常的做法如下:
// CustomView.java
private OnContextClickListener mOnContextClickListener;
public void setOnContextClickListener(OnContextClickListener l {
mOnContextClickListener = l;
}
public interface OnContextClickListener {
void onContextClick(View v;
}
### \>
// 设置手指点击事件
customView.setOnContextClickListener(new View.OnContextClickListener( {
@Override
public void onContextClick(View v {
gotoPreview(;
}
};
看完了这两段代码之后,你有没有觉得这样的代码会很啰嗦?因为,真正逻辑只有一行代码:gotoPreview(,而实际上我们却写了 6 行代码。
* * *
### 用 Kotlin 高阶函数 改写后
//View.kt
// (View -> Unit 就是「函数类型 」
// ↑ ↑
var mOnContextClickListener: ((View -> Unit? = null
fun setOnContextClickListener(l: (View -> Unit {
mOnClickListener = l;
}
如果我们将前面Java写的例子的核心逻辑提取出来,会发现这样才是最简单明了的:
// { gotoPreview( } 就是 Lambda
// ↑
customView.setOnContextClickListener({ gotoPreview( }
Kotlin 语言的设计者是怎么做的呢?实际上他们是分成了两个部分:
* • 用函数类型替代接口定义;
* • 用 Lambda 表达式作为函数参数。
* * *
Kotlin 中引入高阶函数会带来几个好处:一个是针对**定义方**,代码中**减少**了接口类的定义;另一个是对于**调用方**来说,代码也会更加**简洁**。这样一来,就大大减少了代码量,提高了代码可读性,并通过减少类的数量,提高了代码的性能。
|
| 不使用高阶函数 | 使用高阶函数 |
| --- | --- | --- |
| 定义方 | 需要额外定义接口 | 不需要额外定义接口 |
| 调用方 | 代码繁琐 | 代码简洁清晰 |
| 性能 | 差 | 借助inline的情况,性能更高 |
* * *
最后总结
====
思考讨论
----
本文主要分享了 空安全、扩展函数、高阶函数、Lambda,
本文分享的Kotlin内容,您认为哪些特性是最有趣或最有用的?
* * *
参考文档:
-----
* • Kotlin 语言中文站
* • 《Kotlin实战》
* • 《Kotlin核心编程》
* • 《Kotlin编程权威指南》
* • 《Java 8实战》