Kotlin "谈" "弹" "潭"
本篇针对使用Java的Android开发者,快速入手Kotlin,期间可能啰啰嗦嗦稀里糊涂缓缓乎乎穿插一些我中过的坑。这里不讲Kotlin异步并发(协程)、不讲Kotlin反射,如果你是来看它们的。那我现在也木有。
目录
一、为什么要学习kotlin
二、基本使用
1、Java写法和Kotlin的写法对比
2、基本类型
- Kotlin中数字类型
- 运算符号
- 布尔值
- 数组
3、基本表达式
- 表达式
- 流程控制
- when使你力腕狂澜
- 使用表达式比语句更加安全
- for循环的奥义
4、各种类
- 类、接口基本概念
- 伴生对象
- 单例类
- 匿名内部类
- 数据类
5、各种函数
- lambda表达式
- 内联函数
- 扩展函数
- 方法默认参数
6、各种集合
- List
- Set
- Map
- 集合操作
- 惰性求值和序列
7、函数方法值类型
8、高阶函数
9、空安全
10、关于设计模式和Kotlin
- 工厂模式
- 观察者和代理模式
11、快速对比Java代码必备技能
三、参考
一、为什么要学习kotlin
1、Google推荐
现在的新特性文档以及例子有一些是只有kotlin的了2、更加简洁的语法减少啰嗦代码
3、都是基于JVM编译成字节码,与Java基本兼容基本没有太大问题,因此可以用java写的很多库,生态强大
4、强大的语言特性
二、基本使用
1、Java写法和Kotlin的写法对比
2、基本类型
Kotlin中所有的东西都是对象,包括基本类型,一般来说数字、字符、布尔值、数组与字符串是组成一门语言的基本数据类型。
Kotlin中数字类型
Type | Bit width |
---|---|
Double | 64 |
Float | 32 |
Long | 64 |
Int | 32 |
Short | 16 |
Byte | 8 |
每个数字类型支持如下的显示转换,比如
- toByte(): Byte
- toShort(): Short
- toInt(): Int
- toLong(): Long
运算符号
- shl(bits) – 有符号左移 (Java 的 <<)
- shr(bits) – 有符号右移 (Java 的 >>)
- ushr(bits) – 无符号右移 (Java 的 >>>)
- and(bits) – 位与
- or(bits) – 位或
- xor(bits) – 位异或
- inv() – 位非
布尔值
Boolean 类型,有两个值true 与 false
数组
数组在 Kotlin 中使用 Array 类来表示,它定义了 get 与 set 函数(按照运算符重载约定这会转变为 [])以及 size 属性
比如说
val persons = Array<Person>(3) {
Person("2")
Person("3")
Person("4")
}
persons.size
当然,kotlin也支持简单的基本原生基本类型,无装箱开箱的开销的ByteArray、 ShortArray、IntArray 等等
val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]
3、基本表达式
表达式
什么是表达式呢,在Java中,以;结尾的一段代码,即为一个表达式。
setContentView(R.layout.activity_main)
public static final int CAT_DRIVERS = 20;
Log.e(TAG, "猫司机的腿数量: " + catCount * 3);
这个也是kotlin中表达式的概念。
流程控制
Kotlin的流程控制和Java的基本相同,最大的区别是Kotlin没有switch语句,kotlin中是更加强大的when语句。而且kotlin的if语句和when语句可以当做表达式来使用,就跟Java中的三目运算符一样。
Java:
String name = isOne ? "是一个人" : "是半个人";
可以说when是Java中if和switch的一次强大的联合。比如说,可以有这种写法
var single3 = when (single) {
0, 1 -> aLog("single == 0 or single == 1")
else -> aLog("其他")
}
if和while都是和java一样的,区别在于if可以当做表达式来使用,比如
private val single2 = if (single >= 3) {
aLog("大于等于3")
} else {
aLog("小于3")
}
when使你力腕狂澜
如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔:
when (x) {
0, 1 -> print("x == 0 or x == 1")
else -> print("otherwise")
}
我们可以用任意表达式(而不只是常量)作为分支条件
when (x) {
parseInt(s) -> print("s encodes x")
else -> print("s does not encode x")
}
我们也可以检测一个值在(in)或者不在(!in)一个区间或者集合中:
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
另一种可能性是检测一个值是(is)或者不是(!is)一个特定类型的值。注意: 由于智能转换,你可以访问该类型的方法与属性而无需任何额外的检测。
fun hasPrefix(x: Any) = when(x) {
is String -> x.startsWith("prefix")
else -> false
}
when 也可以用来取代 if-else if链。 如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支:
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}
这些用法,可以说一个when让你不再写出混乱的if嵌套。
使用表达式比语句更加安全
先看一段Java代码
private void dididada(boolean needInit) {
String a = null;
if(needInit){
a = "a is Dog that not girlfriend";
}
Log.e(TAG, a.toString());
}
这段代码可能有潜在的问题,比如说a必须在if语句外部声明,它被初始化为null。这段代码中,我们忽略了else分支,如果needInit为false,那么会导致a.toString()空指针异常问题。并且如果needInit的值时很深的路径传递过来的,那么可能会导致这个问题更容易被忽略。
如果,你用了表达式
比如你这样子写了
fun dididada(boolean needInit) {
String a = if(needInit){
"a is Dog that not girlfriend"
} else{
""
}
Log.e(TAG, a.toString())
}
实际上,可以更简化点
fun dididada(boolean needInit) {
String a = if(needInit) "a is Dog that not girlfriend" else ""
Log.e(TAG, a.toString())
}
因为表达式强制需要你写出else语句,也就不再存在上面说的漏掉else的问题了。
for循环的奥义
上面加when的时候出现的..关键字和in关键字这些,..这个叫做区间,比如说1..4代表从1到4的数列。比如说
if (i in 1..4) { // 等同于 1 <= i && i <= 4
print(i)
}
for (i in 1..4) {
print(i)
}
输出:1,2,3,4
如果你要倒序呢,你可以这样
for (i in 4 downTo 1) {
print(i)
}
输出:4,3,2,1
downTo关键字代表反向遍历,如果你想输出一个等差数列,你可以用step关键字
for (i in 1..8 step 2){
print(i)
}
输出:1,3,5,7
用Java写出类似的逻辑,你需要
for (int i = 1; i <= 8; i += 2) {
print(i)
}
可见Kotlin相对于Java的简洁
刚才上面所说的区间都是左闭右也闭的,你还可以使用until来代替..实现左闭右开区间,就比如
for (i in 1 until 10) { // i in [1, 10), 10 is excluded
print(i)
}
4、各种类
类、接口基本概念
kotlin中类、接口的声明跟Java中是一致的
class Pig { ... }
interface Motion {
fun run()
}
如果你要继承一个类或者实现一个接口,都可以使用:符号,另外接口想继承另外一个接口,也是可以使用:来达到目的,可以说是统一extends和implement关键字,有利有弊。
比如Pig继承Animal类和实现Motion接口
class Animal {
var name:String?
}
inteface Motion {
fun run()
}
class Pig : Animal, Motion{
override fun run() {
val pigName = name
}
}
kotlin的类的声明有一个很重要的概念是主构造函数和次构造函数,主构造函数是类头的一部分,它跟在类名的后面,比如
class Pig constructor(name: String) { ... }
你甚至可以省略主构造器的constructor,就像这样
class Pig(name: String) { ... }
什么情况可以省略呢,比如说你不需要给name添加注解或修改可见性的时候,那比如说,如果你想在主构造器初始化的时候在里面写一串初始化逻辑,要怎么样呢?用Java的时候你应该是这样的
class Pig {
public Pig(String name) {
//初始化逻辑
}
}
但是现在kotlin使用主构造器初始化的话,你在也看不到可爱的构造器方法了
莫慌,你还可以这样,kotlin中提供了一种init代码块,它会在调用构造方法的时候按照在类中的init块的顺序从上往下执行,比如
class Pig(name: String) {
//第一个init块
init {
println("${name}")
}
//第二个init块
init {
println("名字长度:${name.length}")
}
}
执行的时候
Pig("你是猪猪猪", 5)
输出:
你是猪猪猪
名字长度:5
次构造器就跟Java中差不多了,但是不同的地方在于必须要有constructor关键字来声明
class Pig(name:String) {
constructor(name: String, length:Int):this(name) {
println("次构造器")
}
如果同时有主构造器和次构造器以及init块,他们的执行顺序是什么样的呢?实际上,init块会作为主构造器的一部分。比如
class Pig(name:String) {
//第一个init块
init {
println("${name}")
}
constructor(name: String, length:Int):this(name) {
println("次构造器")
}
//第二个init块
init {
println("名字长度:${name.length}")
}
}
执行的时候
Pig("你是猪猪猪")
输出:
1.你是猪猪猪
2.名字长度:5
3.次构造器
可见,不管你的init块在哪里,init块会先执行完毕
伴生对象
kotlin中没有静态内部类的概念,取而代之的是伴生对象这货
class Pig {
companion object Factory {
fun create(): pig = Pig()
}
}
该伴生对象的成员可通过只使用类名作为限定符来调用,比如说
kotlin中调用
val instance = Pig.create()
Java中调用
Pig pig = Pig.Companion.create()
可见,伴生对象可以让我们轻松实现工厂模式,造猪运动
单例类
平时用到最多的类就是单例类,kotlin自带单例特性,比如
object Demo{
fun dadada(){
}
}
使用的时候,只需要写Demo.dadada()就能使用这个单例类的方法,当然不要有误解,这个不是静态类的静态方法。反编译kotlin代码
public final class Demo {
public static final Demo INSTANCE;
public final void dadada() {
}
static {
Demo var0 = new Demo();
INSTANCE = var0;
}
}
可以看到这个是Java中静态内部类版本的单例写法。
当然,如果你想自己写单例,你可以用Java那套自己去撸。比较高端的用法,由于Kotlin有委托属性的概念,可以用lazy属性来写一个懒加载的单例类
class Pig private constructor() {
companion object {
val instance: Pig by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
Pig()
}
}
}
等同于Java中的双重校验单例
public class Pig {
private volatile static Pig instance;
private Pig(){}
public static Pig getInstance(){
if(instance==null){
synchronized (Pig.class){
if(instance==null){
instance=new Pig();
}
}
}
return instance;
}
}
更多单例的实现可以去Kotlin下的5种单例模式查看
匿名内部类
在Android中,匿名内部类是必不可少的,比如button的点击事件、各个监听事件的事件回调等等。
kotlin中的匿名内部类是要以object开头的,比如Java中的
mExpandMenu.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
}
});
kotlin的写法
view.setOnClickListener(object: View.OnClickListener {
override fun onClick(v: View?) {
//onClick代码块
}
})
你可以更精简
view.setOnClickListener {
//onClick代码块
}
数据类
我们经常会使用到一些只保存数据的类,java中需要自己去写属性和get和set方法,在kotlin中,可以使用数据类来避免模版代码。
data class User(val name: String, val age: Int)
编译器自动从主构造函数中声明的所有属性导出以下成员:
- equals()/hashCode() 对;
- toString() 格式是 "User(name=John, age=42)";
- componentN() 函数 按声明顺序对应于所有属性;
- copy() 函数
前三包含了set、get方法和基本的类方法,copy函数实际上是
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
name: String = this.name这种写法是函数默认值写法,后面会单独介绍。
又new了一个对象出来,达到复制的目的。
data class数据类能让我们少写bean类很多代码。
5、各种函数
lambda表达式
上面有个地方其实有提到过kotlin的lambda表达式
view.setOnClickListener(object: View.OnClickListener {
override fun onClick(v: View?) {
//onClick代码块
}
})
你可以更精简
view.setOnClickListener {
//onClick代码块
}
精简后的其实就是lambda表达式本身,下面我们来看一个例子
val list = listOf<String>()
val filterList = list.filter { it == "pig" }
创建一个List然后过滤出来数据时pig的元素
来看一下filter的源码
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
for (element in this) if (predicate(element)) destination.add(element)
return destination
}
可以看见filter接收的是一个(T) -> Boolean类型的predicate参数,其实它就是lambda类型(Kotlin中所有的东西都是对象,包括这个玩意),表示接收一个T类型的参数返回一个Boolean类型lambda表达式。这里的predicate会一直往下传,直到真正的过滤函数filterTo中,这里写的比较简洁,可以尝试还原,加上大括号
for (element in this) {
if (predicate(element)) {
destination.add(element)
}
}
return destination
其实就是遍历该集合内容,条件是传进来的lambda表达式,我们现在外层传进来的是it == "pig",再度还原,得到如下代码,当元素等于pig的时候,将元素添加到返回的集合中,最后返回新的集合。
for (element in this) {
if (element == "pig") {
destination.add(element)
}
}
return destination
Kotlin中提供了简洁的语法去定义函数的类型,大致如下,当然还有很多变种
() -> Unit//表示无参数无返回值的Lambda表达式类型
(T) -> Unit//表示接收一个T类型参数,无返回值的Lambda表达式类型,这种其实就是上面说的filter
(T) -> R//表示接收一个T类型参数,返回一个R类型值的Lambda表达式类型
(T, P) -> R//表示接收一个T类型和P类型的参数,返回一个R类型值的Lambda表达式类型
(T, (P,Q) -> S) -> R//表示接收一个T类型参数和一个接收P、Q类型两个参数并返回一个S类型的值的Lambda表达式类型参数,返回一个R类型值的Lambda表达式类型
lamda函数在实际使用中,上述列了2种使用场景,很明显是更加简洁易懂,不啰嗦(当然,前提是得知道原理,不然一脸懵),但是也有可能造成调试bug成本增加(难以定位问题代码)。各有利弊
内联函数
上面的filter函数中,有个地方用了inline关键字,其实这个就是内联函数
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
内联函数其实是为了优化lambda开销的产生的,平时我们制造lambda的函数尽量加上inline。具体怎么优化的,可以了解下kotlin是怎么实现lambda函数(本质上是继承Lambda抽象类并且实现了FunctionBase生成的类,比如Function3)(兼容JDK 6的情况下),然后怎么对其做优化的。浅谈Kotlin语法篇之lambda编译成字节码过程完全解析(七)
扩展函数
扩展函数我个人认为是kotlin的最精辟的地方。又回到刚才讲的filter函数,它还有众多的兄弟姐妹,比如map、zip等等等等。但是他们本质上都是扩展函数
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}
fun Activity.log(content: String) {
Log.e("minminaya", content)
}
基于这种类型的(扩展的类.扩展函数名字)叫做就是扩展函数。它仅仅限于扩展对象中的方法,扩展函数不能在静态类中使用。我们可以拿这一特性来做很多事情。比如说,ImageView结合Gilde的使用、findViewById、比如工厂方法模式的扩展(后面具体讲)。
以我们项目中常用的Glide工具类为准,这里先看下ImageView这些怎么拓展。
比如说我们之前是将Glide的加载代放到一个工具类里
public void displayImage(ImageView imageView, String url, RequestOptions requestOptions) {
if (!isWithValid(imageView)) {
return;
}
Glide.with(imageView.getContext()).asBitmap().apply(requestOptions).load(parseUrl(url)).into(imageView);
}
然后使用的时候是这样子使用
GlideLoader.getInstance().displayImage(mIvShareView.getContext(), mIvShareView,
GlideLoader.wrapFile(imagePath),
mRequestOptions);
如果将displayImage作为ImageView的扩展函数
public fun ImageView.displayImage(url: String, requestOptions: RequestOptions) {
if (GlideLoader.isWithValid(this)) {
return
}
Glide.with(context).load(GlideLoader.parseUrl(url)).apply(requestOptions).into(this)
}
使用方式可以变成这样
val imageView = ImageView(this)
imageView.displayImage("url", RequestOptions())
然后再看下Activity中怎么样消除冗长的findViewById
之前的用法比如像这样
mIvLongVideoTimeTip = mRootView.findViewById(R.id.iv_long_record_time);
mLongVideoEffectOptContainer = mRootView.findViewById(R.id.long_record_opt_container);
mIvLongVideoArEffect = mRootView.findViewById(R.id.iv_selfie_camera_long_video_ar_effect);
如果用扩展函数,你可以这样
fun <T : View> Activity._view(@IdRes id: Int): T {
return findViewById(id)
}
在Activity中使用:
val tootBar = _view<Toolbar>(R.id.toolbar)
当然kotlin为我们提供了更加方便的findId插件,你只需要在gradle文件中
apply plugin: 'kotlin-android-extensions'
Activity中(比如说Activity的布局是activity_main)
import kotlinx.android.synthetic.main.activity_main.*
比如我们现在的布局xml是这样子写
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
那代码中就可以直接使用这个id来对view进行操作
toolbar.setLogo(R.mipmap.ic_launcher)
方法默认参数
意思就是,方法的参数可以指定默认的值,函数调用的时候,如果没有携带参数,那么使用函数中的默认值,以我们的常用的未重构未Builder模式之前的GuideViewHelper为例,看一下要怎么样用kotlin来重构。
public class GuideViewHelper {
@Nullable
public static View showGuideView(Activity activity, final View attachView, int layoutId, final boolean alignView,
final int arrowId) {
return showGuideView(activity, attachView, layoutId, alignView, false, arrowId);
}
@Nullable
public static View showGuideView(Activity activity, final View attachView, int layoutId, final boolean alignView,
final boolean below, final int arrowId) {
return showGuideView(activity, attachView, layoutId, alignView, below, arrowId, 0);
}
@Nullable
public static View showGuideView(Activity activity, final View attachView, int layoutId, final boolean alignView,
final boolean below, final int arrowId, final int yOffset) {
return showGuideView(activity, attachView, layoutId, alignView, below, arrowId, yOffset, null);
}
@Nullable
public static View showGuideView(Activity activity, final View attachView, int layoutId, final boolean alignView,
final boolean below, final int arrowId, final int yOffset, final IResetLocationListener resetLocationListener) {
return showGuideView(activity, attachView, layoutId, alignView, below, arrowId, yOffset, resetLocationListener, null);
}
@Nullable
public static View showGuideView(Activity activity, final View attachView, int layoutId, final boolean alignView,
final boolean below, final int arrowId, final int yOffset,
final IResetLocationListener resetLocationListener, final GuideViewAnimator guideViewAnimator) {
if (attachView == null || activity == null || activity.isFinishing()) {
return null;
}
final View contentView = getContentView(activity);
if (contentView instanceof FrameLayout) {
FrameLayout parent = (FrameLayout) contentView;
final View guideView = LayoutInflater.from(activity).inflate(layoutId, parent, false);
if (guideViewAnimator != null) {
guideView.setTag(R.id.guide_view_animator, guideViewAnimator);
}
parent.addView(guideView);
guideView.setVisibility(View.INVISIBLE);
guideView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
setGuideViewLocation(alignView, contentView, guideView, attachView, below, arrowId, yOffset, resetLocationListener);
if (guideView.getHeight() > 0) {
guideView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
}
});
return guideView;
}
return null;
}
....
}
使用kotlin的默认参数,我们可以少写很多重载方法
class GuideViewHelperFunc {
companion object {
fun showGuideView(
activity: Activity?,
attachView: View?,
layoutId: Int,
@IdRes arrowId: Int,
alignView: Boolean = false,
below: Boolean = false,
yOffset: Int = 0,
resetLocationListener: IResetLocationListener? = null,
guideViewAnimator: GuideViewAnimator? = null
): View? {
attachView?.let {
activity?.let {
if (!it.isFinishing) {
val contentView = getContentView(activity)
if (contentView is FrameLayout) {
val parent = contentView as FrameLayout?
val guideView =
LayoutInflater.from(activity).inflate(layoutId, parent, false)
if (guideViewAnimator != null) {
guideView.setTag(R.id.guide_view_animator, guideViewAnimator)
}
parent!!.addView(guideView)
guideView.visibility = View.INVISIBLE
guideView.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
setGuideViewLocation(
alignView,
contentView,
guideView,
attachView,
below,
arrowId,
yOffset,
resetLocationListener
)
if (guideView.height > 0) {
guideView.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}
})
return guideView
}
}
}
}
return null
}
fun setGuideViewLocation(
alignView: Boolean,
contentView: View?,
guideView: View,
attachView: View?,
below: Boolean,
arrowId: Int,
yOffset: Int,
resetLocationListener: IResetLocationListener?
) {
}
fun getContentView(activity: Activity?): ViewGroup? {
return activity?.findViewById(android.R.id.content)
}
}
使用的时候,我们可以使用必填的几个参数或者必传参数+选传参数,必要时,我们升值可以不按顺序写,这里还是推荐,按照顺序,并且赋值加上参数名称,类似第三种写法
val layoutId = 0x22211
val arrowId = 0x2223
val viewGroup: ViewGroup = LinearLayout(this)
//必传参数
GuideViewHelperFunc.showGuideView(this, viewGroup, layoutId, arrowId)
//必传参数+选传(按顺序)
GuideViewHelperFunc.showGuideView(
this,
viewGroup,
layoutId,
arrowId,
alignView = true,
yOffset = 20
)
//必传参数+选传(顺序打乱)
GuideViewHelperFunc.showGuideView(
this,
attachView = viewGroup,
layoutId = layoutId,
arrowId = arrowId,
alignView = true,
yOffset = 20
)
GuideViewHelperFunc.showGuideView(
this,
layoutId = layoutId,
attachView = viewGroup,
yOffset = 20,
alignView = true,
arrowId = arrowId
)
6、各种集合
Kotlin标准库提供了基本类型的实现:Set、List以及Map。一对接口代表每种集合类型:
- 只读接口:提供访问集合元素的操作【以listOf()创建的集合】
- 可变接口:具有增删查改的功能的集合【以mutableListOf<String>()创建的集合】
集合图谱:
List
比如说我们来看一下List集合相关的内容,然后其实Set和Map都是差不多是如此设计的。
创建一个只读的List
val list1= listOf<String>()
然后你看list1的相关api,会发现竟然没有add函数???
再创建一个可变的List
val list2 = mutableListOf<String>()
本质上其实是ArrayList
然后你看list2的相关api,这下正常多了吧,这才是Java中应该有的样子是吧。
可变集合其实就是Java中的List最原始的样子,而只读集合其实就是List最原始的样子去掉了增删改查的各个api。这样做,算是kotlin另辟蹊径。在某些情况下只读集合能保证多线程的稳定性。当然只读集合并不一定是真正的永不改变的,因为Kotlin设计的与Java的互操作性,而Java是没有只读集合的,那么有可能Kotlin传入Java的集合会是可变集合。
Set
set和list一样也是一对集合
val set1 = setOf<String>()
val set2 = mutableSetOf<String>()
其中,mutableSetOf本质上是LinkedHashSet。如果你想创建别的set集合。你可以看下这个,其实是和TreeSet,HashSet等等一一对应的
Map
Map和上面的List和set一样吧,有只读和可变的写法。另外更重要的是,map有些写法很特别
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
println("All keys: ${numbersMap.keys}")
println("All values: ${numbersMap.values}")
if ("key2" in numbersMap) println("Value by key \"key2\": ${numbersMap["key2"]}")
if (1 in numbersMap.values) println("The value 1 is in the map")
if (numbersMap.containsValue(1)) println("The value 1 is in the map") // 同上
无论键值对的顺序如何,包含相同键值对的两个 Map 是相等的
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
val anotherMap = mapOf("key2" to 2, "key1" to 1, "key4" to 1, "key3" to 3)
println("The maps are equal: ${numbersMap == anotherMap}")
MutableMap 是一个具有写操作的 Map 接口,可以使用该接口添加一个新的键值对或更新给定键的值
val numbersMap = mutableMapOf("one" to 1, "two" to 2)
numbersMap.put("three", 3)
numbersMap["one"] = 11
println(numbersMap)
{one=11, two=2, three=3}
mutableMapOf的默认实现是LinkedHashMap
集合操作
Kotlin中最牛逼的我个人觉得就是跟Java Stream一样的东西,但是又没有版本兼容问题。
//Java 8 Android 7.0以上
List<String> list = new ArrayList<>();
list.stream().filter(s -> TextUtils.equals(s, "biubiu"));
Kotlin 无安卓版本限制
val list = mutableListOf<String>()
list.filter { TextUtils.equals(it, "biubiu") }
甚至可以一条龙操作
list.filter { TextUtils.equals(it, "biubiu") }.map {
//map操作
}.takeWhile {
//takeWhile操作
}
再也不用写那么复杂的for循环去操作数据了。
惰性求值和序列
如果担心产生太多的集合,那可以用asSequence()和toList避免产生太多的中间list对象
list.asSequence().filter { TextUtils.equals(it, "biubiu") }.map {
//map操作
}.takeWhile {
//takeWhile操作
}.toList()
这个操作是先将集合变成序列,然后再这个序列上进行相应的操作,最后通过toList()转换为集合列表。实际使用过程中,只有调用了toList()【又叫做末端操作】,才会去真正的去求值。
【中间操作】
list.asSequence().filter { TextUtils.equals(it, "biubiu") }.map {
//map操作
}.takeWhile {
//takeWhile操作
}
7、函数方法值类型
kotlin中的方法参数中声明的变量是final类型的,不能去改变的
如果非要修改,你需要
fun driveTrain(price: Int) {
var priceTemp = price
priceTemp = 33
Log.e(TAG, "driverTrain一次的价钱:$priceTemp")
}
这样设计有利有弊,某种程度上保证了传递进来的数据不被内部"污染",但是有时候可能会增加内存的使用。
8、高阶函数
Kotlin提供了不少高端的语法特性。比如let、with、run、apply、also等我们可以用它来改善项目中的一些写法。
比如let函数,定义一个变量在特定的作用域范围内生效,返回R值。
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
比如这个是Java中Adapter中常用的操作数据的逻辑
if (holder.musicSingerTv == null) {
return;
}
holder.musicSingerTv.setText(entity.singer);
holder.musicSingerTv.setTextColor(Color.YELLOW);
如果换成kotlin,你可以这样
holder.musicSingerTv?.let {
it.setText(entity.singer);
it.setTextColor(Color.YELLOW);
}
其他另外五种,可以去看下Kotlin系列之let、with、run、apply、also函数的使用
9、空安全
Kotlin中,变量分为可空类型和非空类型。
var str1: String? = null 可空类型
var str2: String = "ddd" 非空类型
str2 = null // 如果这样写,就会报错。要是在java中,会在运行时候报错
str1 = null // 如果可控类型这样写,就不会报错
对于可空类型,为了防止空指针,你可以使用安全调用操作符?.
比如
fun driveTrain(train: Train?) {
train?.openDoor() ?: print("train为空了")
}
?:是Elvis操作符,上面的调用它的意思是说, train如果为空,那么不执行openDoor(),执行print("train为空了")
还有一个操作符是!!
fun driveTrain(train: Train?) {
train.openDoor()
}
比如现在train是可空类型,但是你非要调用它的openDoor()方法,你会发现报错了Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Train
其实是觉得当前你的用法不安全。可以加上!!,变成这样,程序执行的时候有两种可能,要么正确返回name,要么抛出空指针异常。
train!!.openDoor()
10、关于设计模式和Kotlin
工厂模式
常用于一个父类多个子类的时候,通过其来创建子类对象
比如说现在有一个汽车工厂,同时生产奥迪和五菱宏光,我们用熟悉的工厂模式来描述其业务逻辑如下:
interface ICar {
val name: String
}
class AudiCar(override val name: String) : ICar {
fun transportPig(count: Int) {
print("运猪头数:$count")
}
}
class SGMWCar(override val name: String) : ICar {
fun transportPig(count: Int) {
print("运火箭枚数:$count")
}
}
class CarFactory {
companion object {
const val CAR_TYPE_AUDI = 0x001
const val CAR_TYPE_SGMW = 0x002
}
fun produceCar(carType: Int): ICar? {
return when (carType) {
CAR_TYPE_AUDI -> AudiCar("奥迪")
CAR_TYPE_SGMW -> SGMWCar("五菱")
else -> null
}
}
}
fun main(args: Array<String>) {
val audi = CarFactory().produceCar(CarFactory.CAR_TYPE_AUDI)
audi?.transport(3)
}
这是简单的用kotlin模仿java的工厂模式,最后的创建工厂去生产车辆这里,kotlin中还可以更简化,因为kotlin天生支持单例,只需要将class改为object
object CarFactorySingleton {
const val CAR_TYPE_AUDI = 0x001
const val CAR_TYPE_SGMW = 0x002
fun produceCar(carType: Int):ICar? {
return when (carType) {
CAR_TYPE_AUDI -> AudiCar("奥迪")
CAR_TYPE_SGMW -> SGMWCar("五菱")
else -> null
}
}
}
然后使用的时候可以变得很简洁
fun main(args: Array<String>) {
val audi = CarFactorySingleton.produceCar(CarFactorySingleton.CAR_TYPE_AUDI)
audi?.transport(33)
}
kotlin支持一种叫做operator操作符重载的功能【具体看操作符重载】
比如上述的代码还可以修改为
object CarFactorySingletonOperator {
const val CAR_TYPE_AUDI = 0x001
const val CAR_TYPE_SGMW = 0x002
operator fun invoke(carType: Int): Car? {
return when (carType) {
CAR_TYPE_AUDI -> AudiCar("奥迪")
CAR_TYPE_SGMW -> SGMWCar("五菱")
else -> null
}
}
}
调用的时候直接
//运算符invoke
val sgmw = CarFactorySingletonOperator(CarFactorySingletonOperator.CAR_TYPE_SGMW)
sgmw?.transport(23)
可以看到变得更加简洁
这里的操作符的意思其实是这样的,比如CarFactorySingletonOperator() ----> CarFactorySingletonOperator.invoke(),所以上面重载运算符后可以直接变成后面那样调用了,跟直接创建一个类的实例没什么区别啦
更强大的,你还可以用上kotlin的伴生对象和操作符重载的特性去更加简洁的生成五菱宏光。
现在我们直接把生成的方法直接写到ICar中,如下
interface ICar {
val name: String
fun transport(count: Int)
companion object {
operator fun invoke(carType: Int): ICar? {
return when (carType) {
CarFactorySingletonOperator.CAR_TYPE_AUDI -> AudiCar("奥迪")
CarFactorySingletonOperator.CAR_TYPE_SGMW -> SGMWCar("五菱")
else -> null
}
}
}
}
//伴生对象直接生产工厂对象
val sgmw = ICar(CarFactory.CAR_TYPE_SGMW)
sgmw?.transport(3)
观察者和代理模式
观察者模式和代理模式是kotlin自身支持的特性,具体可以了解下委托属性的用法和实现。
11、快速对比Java代码必备技能
Kotlin工具给我们提供了很好用的反编译工具,具体操作如下
-
1、写一个kotlin版本的类和方法
2、点击Android Studio的Tools中的Show Kotlin ByteCode (装了Kotlin插件的Jertbain全家桶应该都是这样),这个时候你就能看到字节码了,
- 3、点击字节码窗口的Decompile,字节码会转化为java代码
然后就是熟悉的Java代码了