https://github.com/heimashi/kotlin_tipsphp
汇总Kotlin相对于Java的优点,以及怎么用Kotlin去简洁、务实、高效、安全的开发,每一个tip都有详细的说明和案例代码,争取把每一个tip分析得清楚易懂,会不断的更新维护tips,欢迎fork进来加入咱们一块儿来维护,有问题的话欢迎提Issues。html
详见案例代码KotlinTip1java
Kotlin中的字符串基本Java中的相似,有一点区别是加入了三个引号"""来方便长篇字符的编写。 而在Java中,这些都须要转义,先看看java中的式例android
public void testString1() {
String str1 = "abc";
String str2 = "line1\n" +
"line2\n" +
"line3";
String js = "function myFunction()\n" +
"{\n" +
" document.getElementById(\"demo\").innerHTML=\"My First JavaScript Function\";\n" +
"}";
System.out.println(str1);
System.out.println(str2);
System.out.println(js);
}
复制代码
kotlin除了有单个双引号的字符串,还对字符串的增强,引入了三个引号,"""中能够包含换行、反斜杠等等特殊字符:git
/* * kotlin对字符串的增强,三个引号"""中能够包含换行、反斜杠等等特殊字符 * */
fun testString() {
val str1 = "abc"
val str2 = """line1\n line2 line3 """
val js = """ function myFunction() { document.getElementById("demo").innerHTML="My First JavaScript Function"; } """.trimIndent()
println(str1)
println(str2)
println(js)
}
复制代码
同时,Kotlin中引入了字符串模版,方便字符串的拼接,能够用$符号拼接变量和表达式程序员
/* * kotlin字符串模版,能够用$符号拼接变量和表达式 * */
fun testString2() {
val strings = arrayListOf("abc", "efd", "gfg")
println("First content is $strings")
println("First content is ${strings[0]}")
println("First content is ${if (strings.size > 0) strings[0] else "null"}")
}
复制代码
值得注意的是,在Kotlin中,美圆符号{'$'}github
/* * Kotlin中,美圆符号$是特殊字符,在字符串中不能直接显示,必须通过转义,方法1是用反斜杠,方法二是${'$'} * */
fun testString3() {
println("First content is \$strings")
println("First content is ${'$'}strings")
}
复制代码
首先,须要弄清楚一个概念语句和表达式,而后会介绍控制结构表达式的优势:简洁api
详见案例代码tip2缓存
java中,if 是语句,没有值,必须显式的return安全
/* * java中的if语句 * */
public int max(int a, int b) {
if (a > b) {
return a;
} else {
return b;
}
}
复制代码
kotlin中,if 是表达式,不是语句,由于表达式有值,能够做为值return出去
/* * kotlin中,if 是表达式,不是语句,相似于java中的三目运算符a > b ? a : b * */
fun max(a: Int, b: Int): Int {
return if (a > b) a else b
}
复制代码
上面的if中的分支最后一行语句就是该分支的值,会做为函数的返回值。这其实跟java中的三元运算符相似,
/* * java的三元运算符 * */
public int max2(int a, int b) {
return a > b ? a : b;
}
复制代码
上面是java中的三元运算符,kotlin中if是表达式有值,彻底能够替代,故kotlin中已没有三元运算符了,用if来替代。 上面的max函数还能够简化成下面的形式
/* * kotlin简化版本 * */
fun max2(a: Int, b: Int) = if (a > b) a else b
复制代码
Kotlin中的when很是强大,彻底能够取代Java中的switch和if/else,同时,when也是表达式,when的每一个分支的最后一行为当前分支的值 先看一下java中的switch
/* * java中的switch * */
public String getPoint(char grade) {
switch (grade) {
case 'A':
return "GOOD";
case 'B':
case 'C':
return "OK";
case 'D':
return "BAD";
default:
return "UN_KNOW";
}
}
复制代码
java中的switch有太多限制,咱们再看看Kotlin怎样去简化的
/* * kotlin中,when是表达式,能够取代Java 中的switch,when的每一个分支的最后一行为当前分支的值 * */
fun getPoint(grade: Char) = when (grade) {
'A' -> "GOOD"
'B', 'C' -> {
println("test when")
"OK"
}
'D' -> "BAD"
else -> "UN_KNOW"
}
复制代码
一样的,when语句还能够取代java中的if/else if,其是表达式有值,而且更佳简洁
/* * java中的if else * */
public String getPoint2(Integer point) {
if (point > 100) {
return "GOOD";
} else if (point > 60) {
return "OK";
} else if (point.hashCode() == 0x100) {
//...
return "STH";
} else {
return "UN_KNOW";
}
}
复制代码
再看看kotlin的版本,使用不带参数的when,只须要6行代码
/* * kotlin中,when是表达式,能够取代java的if/else,when的每一个分支的最后一行为当前分支的值 * */
fun getPoint2(grade: Int) = when {
grade > 90 -> "GOOD"
grade > 60 -> "OK"
grade.hashCode() == 0x100 -> "STH"
else -> "UN_KNOW"
}
复制代码
Kotlin的函数更加好调用,主要是表如今两个方面:1,显式的标示参数名,能够方便代码阅读;2,函数能够有默认参数值,能够大大减小Java中的函数重载。 例如如今须要实现一个工具函数,打印列表的内容: 详见案例代码KotlinTip3
/* * 打印列表的内容 * */
fun <T> joinToString(collection: Collection<T>, separator: String, prefix: String, postfix: String): String {
val result = StringBuilder(prefix)
for ((index, element) in collection.withIndex()) {
if (index > 0) result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
/* * 测试 * */
fun printList() {
val list = listOf(2, 4, 0)
// 不标明参数名
println(joinToString(list, " - ", "[", "]"))
// 显式的标明参数名称
println(joinToString(list, separator = " - ", prefix = "[", postfix = "]"))
}
复制代码
如上面的代码所示,函数joinToString想要打印列表的内容,须要传入四个参数:列表、分隔符、前缀和后缀。 因为参数不少,在后续使用该函数的时候不是很直观的知道每一个参数是干什么用的,这时候能够显式的标明参数名称,增长代码可读性。 同时,定义函数的时候还能够给函数默认的参数,以下所示:
/* * 打印列表的内容,带有默认的参数,能够避免java的函数重载 * */
fun <T> joinToString2(collection: Collection<T>, separator: String = ", ", prefix: String = "", postfix: String = ""): String {
val result = StringBuilder(prefix)
for ((index, element) in collection.withIndex()) {
if (index > 0) result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
/* * 测试 * */
fun printList3() {
val list = listOf(2, 4, 0)
println(joinToString2(list, " - "))
println(joinToString2(list, " , ", "["))
}
复制代码
这样有了默认参数后,在使用函数时,若是不传入该参数,默认会使用默认的值,这样能够避免Java中大量的函数重载。
扩展函数和属性是Kotlin很是方便实用的一个功能,它可让咱们随意的扩展第三方的库,你若是以为别人给的SDK的api很差用,或者不能知足你的需求,这时候你能够用扩展函数彻底去自定义。 例如String类中,咱们想获取最后一个字符,String中没有这样的直接函数,你能够用.后声明这样一个扩展函数: 详见案例代码KotlinTip4
/* * 扩展函数 * */
fun String.lastChar(): Char = this.get(this.length - 1)
/* * 测试 * */
fun testFunExtension() {
val str = "test extension fun";
println(str.lastChar())
}
复制代码
这样定义好lastChar()函数后,以后只须要import进来后,就能够用String类直接调用该函数了,跟调用它本身的方法没有区别。这样能够避免重复代码和一些静态工具类,并且代码更加简洁明了。 例如咱们能够改造上面tip3中的打印列表内容的函数:
/* * 用扩展函数改造Tip3中的列表打印内容函数 * */
fun <T> Collection<T>.joinToString3(separator: String = ", ", prefix: String = "", postfix: String = ""): String {
val result = StringBuilder(prefix)
for ((index, element) in withIndex()) {
if (index > 0) result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
fun printList4() {
val list = listOf(2, 4, 0)
println(list.joinToString3("/"))
}
复制代码
除了扩展函数,还能够扩展属性,例如我想实现String和StringBuilder经过属性去直接得到最后字符:
/* * 扩展属性 lastChar获取String的最后一个字符 * */
val String.lastChar: Char
get() = get(length - 1)
/* * 扩展属性 lastChar获取StringBuilder的最后一个字符 * */
var StringBuilder.lastChar: Char
get() = get(length - 1)
set(value: Char) {
setCharAt(length - 1, value)
}
/* * 测试 * */
fun testExtension() {
val s = "abc"
println(s.lastChar)
val sb = StringBuilder("abc")
println(sb.lastChar)
}
复制代码
定义好扩展属性后,以后只需import完了就跟使用本身的属性同样方便了。
在Kotlin中要理解一些语法,只要认识到Kotlin语言最后须要编译为class字节码,Java也是编译为class执行,也就是能够大体理解为Kotlin须要转成Java同样的语法结构, Kotlin就是一种强大的语法糖而已,Java不具有的功能Kotlin也不能越界的。
咱们看看将上面的扩展函数转成Java后的代码
/* * 扩展函数会转化为一个静态的函数,同时这个静态函数的第一个参数就是该类的实例对象 * */
public static final char lastChar(@NotNull String $receiver) {
Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
return $receiver.charAt($receiver.length() - 1);
}
/* * 获取的扩展属性会转化为一个静态的get函数,同时这个静态函数的第一个参数就是该类的实例对象 * */
public static final char getLastChar(@NotNull StringBuilder $receiver) {
Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
return $receiver.charAt($receiver.length() - 1);
}
/* * 设置的扩展属性会转化为一个静态的set函数,同时这个静态函数的第一个参数就是该类的实例对象 * */
public static final void setLastChar(@NotNull StringBuilder $receiver, char value) {
Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
$receiver.setCharAt($receiver.length() - 1, value);
}
复制代码
查看上面的代码可知:对于扩展函数,转化为Java的时候其实就是一个静态的函数,同时这个静态函数的第一个参数就是该类的实例对象,这样把类的实例传入函数之后,函数内部就能够访问到类的公有方法。 对于扩展属性也相似,获取的扩展属性会转化为一个静态的get函数,同时这个静态函数的第一个参数就是该类的实例对象,设置的扩展属性会转化为一个静态的set函数,同时这个静态函数的第一个参数就是该类的实例对象。 函数内部能够访问公有的方法和属性。顶层的扩展函数是static的,不能被override
从上面转换的源码其实能够看到扩展函数和扩展属性适用的地方和缺陷:
open class C
class D : C()
fun C.foo() = "c"
fun D.foo() = "d"
/* * https://kotlinlang.org/docs/reference/extensions.html * Extensions do not actually modify classes they extend. By defining an extension, you do not insert new members into a class, * but merely make new functions callable with the dot-notation on variables of this type. Extension functions are * dispatched statically. * */
fun printFoo(c: C) {
println(c.foo())
}
fun testStatically() {
printFoo(C()) // print c
printFoo(D()) // also print c
}
复制代码
上面的案例中即便调用printFoo(D())仍是打印出c,而不是d。转成java中会看到下面的代码,D类型在调用的时候会强制转换为C类型:public static final String foo(@NotNull C $receiver) {
Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
return "c";
}
public static final String foo(@NotNull D $receiver) {
Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
return "d";
}
public static final void printFoo(@NotNull C c) {
Intrinsics.checkParameterIsNotNull(c, "c");
String var1 = foo(c);
System.out.println(var1);
}
public static final void testStatically() {
printFoo(new C());
printFoo((C)(new D()));
}
复制代码
声明扩展函数做为类的成员变量
open class D {
}
class D1 : D() {
}
open class C {
open fun D.foo() {
println("D.foo in C")
}
open fun D1.foo() {
println("D1.foo in C")
}
fun caller(d: D) {
d.foo() // call the extension function
}
}
class C1 : C() {
override fun D.foo() {
println("D.foo in C1")
}
override fun D1.foo() {
println("D1.foo in C1")
}
}
fun testAsMembers() {
C().caller(D()) // prints "D.foo in C"
C1().caller(D()) // prints "D.foo in C1" - dispatch receiver is resolved virtually
C().caller(D1()) // prints "D.foo in C" - extension receiver is resolved statically
C1().caller(D1()) // prints "D.foo in C1"
}
复制代码
函数caller的类型是D,即便调用C().caller(D1()),打印的结果仍是D.foo in C,而不是D1.foo in C,不是运行时来动态决定类型,成员扩展函数申明为open, 一旦在子类中被override,就调用不到在父类中的扩展函数,在子类中的做用域内的只能访问到override后的函数,不能像普通函数override那样经过super关键字访问了。
/* * show toast in activity * */
fun Activity.toast(msg: String) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}
val Context.inputMethodManager: InputMethodManager?
get() = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
/* * hide soft input * */
fun Context.hideSoftInput(view: View) {
inputMethodManager?.hideSoftInputFromWindow(view.windowToken, 0)
}
/** * screen width in pixels */
val Context.screenWidth
get() = resources.displayMetrics.widthPixels
/** * screen height in pixels */
val Context.screenHeight
get() = resources.displayMetrics.heightPixels
/** * returns dip(dp) dimension value in pixels * @param value dp */
fun Context.dip2px(value: Int): Int = (value * resources.displayMetrics.density).toInt()
复制代码
懒初始化是指推迟一个变量的初始化时机,变量在使用的时候才去实例化,这样会更加的高效。由于咱们一般会遇到这样的状况,一个变量直到使用时才须要被初始化,或者仅仅是它的初始化依赖于某些没法当即得到的上下文。 详见案例代码KotlinTip5
/* * 懒初始化api实例 * */
val purchasingApi: PurchasingApi by lazy {
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(MoshiConverterFactory.create())
.build()
retrofit.create(PurchasingApi::class.java)
}
复制代码
像上面的代码,retrofit生成的api实例会在首次使用到的时候才去实例化。须要注意的是by lazy通常只能修饰val不变的对象,不能修饰var可变对象。
class User(var name: String, var age: Int)
/* * 懒初始化by lazy * */
val user1: User by lazy {
User("jack", 15)
}
复制代码
另外,对于var的变量,若是类型是非空的,是必须初始化的,否则编译不经过,这时候须要用到lateinit延迟初始化,使用的时候再去实例化。
/* * 延迟初始化lateinit * */
lateinit var user2: User
fun testLateInit() {
user2 = User("Lily", 14)
}
复制代码
在Android的View中,会有不少代码是在声明一个View,而后经过findViewById后从xml中实例化赋值给对应的View。在kotlin中能够彻底解放出来了,利用kotlin-android-extensions插件,不用再手写findViewById。步骤以下: 详见案例代码KotlinTip6
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
<TextView android:id="@+id/tip6Tv" android:layout_width="match_parent" android:layout_height="wrap_content" />
<ImageView android:id="@+id/tip6Img" android:layout_width="match_parent" android:layout_height="wrap_content" />
<Button android:id="@+id/tip6Btn" android:layout_width="match_parent" android:layout_height="wrap_content" />
</LinearLayout>
复制代码
import com.sw.kotlin.tips.R
/* * 导入插件生成的View * */
import kotlinx.android.synthetic.main.activity_tip6.*
class KotlinTip6 : Activity() {
/* * 自动根据layout的id生成对应的view * */
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_tip6)
tip6Tv.text = "Auto find view for TextView"
tip6Img.setImageBitmap(null)
tip6Btn.setOnClickListener {
test()
}
}
private fun test(){
tip6Tv.text = "update"
}
}
复制代码
像上面代码这样,Activity里的三个View自动生成了,不用再去声明,而后findViewById,而后转型赋值,是否是减小了不少不必的代码,让代码很是的干净。
要看原理仍是将上面的代码转为java语言来理解,参照tips4提供的方式转换为以下的java代码:
public final class KotlinTip6 extends Activity {
private HashMap _$_findViewCache;
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2131296284);
TextView var10000 = (TextView)this._$_findCachedViewById(id.tip6Tv);
Intrinsics.checkExpressionValueIsNotNull(var10000, "tip6Tv");
var10000.setText((CharSequence)"Auto find view for TextView");
((ImageView)this._$_findCachedViewById(id.tip6Img)).setImageBitmap((Bitmap)null);
((Button)this._$_findCachedViewById(id.tip6Btn)).setOnClickListener((OnClickListener)(new OnClickListener() {
public final void onClick(View it) {
KotlinTip6.this.test();
}
}));
}
private final void test() {
TextView var10000 = (TextView)this._$_findCachedViewById(id.tip6Tv);
Intrinsics.checkExpressionValueIsNotNull(var10000, "tip6Tv");
var10000.setText((CharSequence) "update");
}
public View _$_findCachedViewById(int var1) {
if (this._$_findViewCache == null) {
this._$_findViewCache = new HashMap();
}
View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1));
if (var2 == null) {
var2 = this.findViewById(var1);
this._$_findViewCache.put(Integer.valueOf(var1), var2);
}
return var2;
}
public void _$_clearFindViewByIdCache() {
if (this._$_findViewCache != null) {
this._$_findViewCache.clear();
}
}
}
复制代码
如上面的代码所示,在编译阶段,插件会帮咱们生成视图缓存,视图由一个Hashmap结构的_$_findViewCache变量缓存, 会根据对应的id先从缓存里查找,缓存没命中再去真正调用findViewById查找出来,再存在HashMap中。
在fragment中也相似,有一点区别,例子以下:
class Tip6Fragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater?.inflate(R.layout.fragment_tip6, container, false)
/* * 这时候不能在onCreateView方法里用view,须要在onViewCreate里,原理是插件用了getView来findViewById * */
// tip6Tv.text = "test2"
return view
}
/* * 须要在onViewCreate里,原理是插件用了getView来findViewById * */
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tip6Tv.text = "test"
}
}
复制代码
如上所示,Fragment须要注意,不能在onCreateView方法里用view,否则会出现空指针异常,须要在onViewCreate里,原理是插件用了getView来findViewById, 咱们看看将上面的代码转成java后的代码:
public final class Tip6Fragment extends Fragment {
private HashMap _$_findViewCache;
@Nullable
public View onCreateView(@Nullable LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater != null?inflater.inflate(2131296286, container, false):null;
return view;
}
public void onViewCreated(@Nullable View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
TextView var10000 = (TextView)this._$_findCachedViewById(id.tip6Tv);
Intrinsics.checkExpressionValueIsNotNull(var10000, "tip6Tv");
var10000.setText((CharSequence)"test");
}
public View _$_findCachedViewById(int var1) {
if (this._$_findViewCache == null) {
this._$_findViewCache = new HashMap();
}
View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1));
if (var2 == null) {
View var10000 = this.getView();
if (var10000 == null) {
return null;
}
var2 = var10000.findViewById(var1);
this._$_findViewCache.put(Integer.valueOf(var1), var2);
}
return var2;
}
public void _$_clearFindViewByIdCache() {
if (this._$_findViewCache != null) {
this._$_findViewCache.clear();
}
}
// $FF: synthetic method
public void onDestroyView() {
super.onDestroyView();
this._$_clearFindViewByIdCache();
}
}
复制代码
跟Activity中相似,会有一个View的HashMap,关键不一样的地方在__clearFindViewByIdCache方法清掉缓存。
Kotlin中提供了函数的嵌套,在函数内部还能够定义新的函数。这样咱们能够在函数中嵌套这些提早的函数,来抽取重复代码。以下面的案例所示: 详见案例代码KotlinTip7
class User(val id: Int, val name: String, val address: String, val email: String)
fun saveUser(user: User) {
if (user.name.isEmpty()) {
throw IllegalArgumentException("Can't save user ${user.id}: empty Name")
}
if (user.address.isEmpty()) {
throw IllegalArgumentException("Can't save user ${user.id}: empty Address")
}
if (user.email.isEmpty()) {
throw IllegalArgumentException("Can't save user ${user.id}: empty Email")
}
// save to db ...
}
复制代码
上面的代码在判断name、address等是否为空的处理其实很相似。这时候,咱们能够利用在函数内部嵌套的声明一个通用的判空函数将相同的代码抽取到一块儿:
/* * 利用局部函数抽取相同的逻辑,去除重复的代码 * */
fun saveUser2(user: User) {
fun validate(value: String, fildName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException("Can't save user ${user.id}: empty $fildName")
}
}
validate(user.name, "Name")
validate(user.address, "Address")
validate(user.email, "Email")
// save to db ...
}
复制代码
除了利用嵌套函数去抽取,此时,其实也能够用扩展函数来抽取,以下所示:
/* * 利用扩展函数抽取逻辑 * */
fun User.validateAll() {
fun validate(value: String, fildName: String) {
if (value.isEmpty()) {
throw IllegalArgumentException("Can't save user $id: empty $fildName")
}
}
validate(name, "Name")
validate(address, "Address")
validate(email, "Email")
}
fun saveUser3(user: User) {
user.validateAll()
// save to db ...
}
复制代码
在java中要声明一个model类须要实现不少的代码,首先须要将变量声明为private,而后须要实现get和set方法,还要实现对应的hashcode equals toString方法等,以下所示: 详见案例代码Tip8
public static class User {
private String name;
private int age;
private int gender;
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getGender() {
return gender;
}
public void setGender(int gender) {
this.gender = gender;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
", address='" + address + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (age != user.age) return false;
if (gender != user.gender) return false;
if (name != null ? !name.equals(user.name) : user.name != null) return false;
return address != null ? address.equals(user.address) : user.address == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
result = 31 * result + gender;
result = 31 * result + (address != null ? address.hashCode() : 0);
return result;
}
}
复制代码
这段代码Java须要70行左右,而若是用kotlin,只须要一行代码就能够作到。
/* * Kotlin会为类的参数自动实现get set方法 * */
class User(val name: String, val age: Int, val gender: Int, var address: String)
/* * 用data关键词来声明一个数据类,除了会自动实现get set,还会自动生成equals hashcode toString * */
data class User2(val name: String, val age: Int, val gender: Int, var address: String)
复制代码
对于Kotlin中的类,会为它的参数自动实现get set方法。而若是加上data关键字,还会自动生成equals hashcode toString。原理其实数据类中的大部分代码都是模版代码,Kotlin聪明的将这个模版代码的实现放在了编译器处理的阶段。
经过继承的实现容易致使脆弱性,例如若是须要修改其余类的一些行为,这时候Java中的一种策略是采用装饰器模式:建立一个新类,实现与原始类同样的接口并将原来的类的实例做为一个成员变量。 与原始类拥有相同行为的方法不用修改,只须要直接转发给原始类的实例。以下所示: 详见案例代码KotlinTip9
/* * 常见的装饰器模式,为了修改部分的函数,却须要实现全部的接口函数 * */
class CountingSet<T>(val innerSet: MutableCollection<T> = HashSet<T>()) : MutableCollection<T> {
var objectAdded = 0
override val size: Int
get() = innerSet.size
/* * 须要修改的方法 * */
override fun add(element: T): Boolean {
objectAdded++
return innerSet.add(element)
}
/* * 须要修改的方法 * */
override fun addAll(elements: Collection<T>): Boolean {
objectAdded += elements.size
return innerSet.addAll(elements)
}
override fun contains(element: T): Boolean {
return innerSet.contains(element)
}
override fun containsAll(elements: Collection<T>): Boolean {
return innerSet.containsAll(elements)
}
override fun isEmpty(): Boolean {
return innerSet.isEmpty()
}
override fun clear() {
innerSet.clear()
}
override fun iterator(): MutableIterator<T> {
return innerSet.iterator()
}
override fun remove(element: T): Boolean {
return innerSet.remove(element)
}
override fun removeAll(elements: Collection<T>): Boolean {
return innerSet.removeAll(elements)
}
override fun retainAll(elements: Collection<T>): Boolean {
return innerSet.retainAll(elements)
}
}
复制代码
如上所示,想要修改HashSet的某些行为函数add和addAll,须要实现MutableCollection接口的全部方法,将这些方法转发给innerSet去具体的实现。虽然只须要修改其中的两个方法,其余代码都是模版代码。 只要是重复的模版代码,Kotlin这种全新的语法糖就会想办法将它放在编译阶段再去生成。 这时候能够用到类委托by关键字,以下所示:
/* * 经过by关键字将接口的实现委托给innerSet成员变量,须要修改的函数再去override就能够了 * */
class CountingSet2<T>(val innerSet: MutableCollection<T> = HashSet<T>()) : MutableCollection<T> by innerSet {
var objectAdded = 0
override fun add(element: T): Boolean {
objectAdded++
return innerSet.add(element)
}
override fun addAll(elements: Collection<T>): Boolean {
objectAdded += elements.size
return innerSet.addAll(elements)
}
}
复制代码
经过by关键字将接口的实现委托给innerSet成员变量,须要修改的函数再去override就能够了,经过类委托将10行代码就能够实现上面接近100行的功能,简洁明了,去掉了模版代码。
详见案例代码KotlinTip10 lambda表达式能够简化咱们的代码。以Android中常见的OnClickListener来讲明,在Java中咱们通常这样设置:
TextView textView = new TextView(context);
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// handle click
}
});
复制代码
Java中须要声明一个匿名内部类去处理,这种状况能够用lambda表达式来简化。
咱们来看看Kotlin中的例子:
val textView = TextView(context)
/* * 传统方式 * */
textView.setOnClickListener(object : android.view.View.OnClickListener {
override fun onClick(v: android.view.View?) {
// handle click
}
})
/* * lambda的方式 * */
textView.setOnClickListener({ v ->
{
// handle click
}
})
复制代码
当lambda的参数没有使用时能够省略,省略的时候用it来替代
/* * lambda的参数若是没有使用能够省略,省略的时候用it来替代 * */
textView.setOnClickListener({
// handle click
})
复制代码
lambda在参数的最后一个的状况能够将之提出去
/* * lambda在参数的最后一个的状况能够将之提出去 * */
textView.setOnClickListener() {
// handle click
}
复制代码
lambda提出去以后,函数若是没有其余参数括号能够省略
/* * lambda提出去以后,函数若是没有其余参数括号能够省略 * */
textView.setOnClickListener {
// handle click
}
复制代码
咱们再看看若是本身去实现一个带lambda参数的函数应该怎么去定义:
interface OnClickListener {
fun onClick()
}
class View {
var listener: OnClickListener? = null;
/* * 传统方式 * */
fun setOnClickListener(listener: OnClickListener) {
this.listener = listener
}
fun doSth() {
// some case:
listener?.onClick()
}
/* * 声明lambda方式,listener: () -> Unit * */
fun setOnClickListener(listener: () -> Unit) {
}
}
复制代码
在函数参数中须要声明lambda的类型后,再调用该函数的时候就能够传入一个lambda表达式了。
inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
复制代码
Kotlin中能够用with语句来省略同一个变量的屡次声明,例以下面的函数 详见案例代码KotlinTip11
/* *打印字母表函数,在函数内result变量在好几处有使用到 * */
fun alphabet(): String {
val result = StringBuilder()
result.append("START\n")
for (letter in 'A'..'Z') {
result.append(letter)
}
result.append("\nEND")
return result.toString()
}
复制代码
在上面的函数中,result变量出现了5次,若是用with语句,能够将这5次都不用再出现了,咱们来一步一步地看是怎么实现的:
/* * 经过with语句,将result做为参数传入,在内部就能够经过this来表示result变量了 * */
fun alphabet2(): String {
val result = StringBuilder()
return with(result) {
this.append("START\n")
for (letter in 'A'..'Z') {
this.append(letter)
}
this.append("\nEND")
this.toString()
}
}
复制代码
经过with语句,将result做为参数传入,在内部就能够经过this来表示result变量了,并且这个this是能够省略的
/* * 经过with语句,将result参数做为参数,在内部this也能够省略掉 * */
fun alphabet3(): String {
val result = StringBuilder()
return with(result) {
append("START\n")
for (letter in 'A'..'Z') {
append(letter)
}
append("\nEND")
toString()
}
}
复制代码
在内部this省略掉后,如今只有一个result了,这个其实也是不必的,因而出现了下面的最终版本:
/* * 经过with语句,能够直接将对象传入,省掉对象的声明 * */
fun alphabet4(): String {
return with(StringBuilder()) {
append("START\n")
for (letter in 'A'..'Z') {
append(letter)
}
append("\nEND")
toString()
}
}
复制代码
像上面这样,咱们能够把同一个变量的显式调用从5次变为0次,发现Kotlin的魅力了吧。
inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
复制代码
除了用上面的with能够简化同一个变量的屡次声明,还能够用apply关键字,咱们来改造一下tip11中的函数: 详见案例代码KotlinTip12
/* * 用apply语句简化代码,在apply的大括号里能够访问类的公有属性和方法 * */
fun alphabet5() = StringBuilder().apply {
append("START\n")
for (letter in 'A'..'Z') {
append(letter)
}
append("\nEND")
}.toString()
复制代码
像上面这样的,经过apply后,在apply的大括号里能够访问类的公有属性和方法。这在对应类的初始化是很是方便的,例以下面的例子
/* * 用apply语句简化类的初始化,在类实例化的时候,就能够经过apply把须要初始化的步骤所有实现,很是的简洁 * */
fun testApply(context: Context) {
var imgView = ImageView(context).apply {
setBackgroundColor(0)
setImageBitmap(null)
}
var textView = TextView(context).apply {
text = "content"
textSize = 20.0f
setPadding(10, 0, 0, 0)
}
var user = User().apply {
age = 15
name = "Jack"
val a = address
address = "bbb"
}
}
复制代码
在类实例化的时候,就能够经过apply把须要初始化的步骤所有实现,很是的简洁
NullPointerException是Java程序员很是头痛的一个问题,咱们知道Java 中分受检异常和非受检异常,NullPointerException是非受检异常,也就是说NullPointerException不须要显示的去catch住, 每每在运行期间,程序就可能报出一个NullPointerException而后crash掉,Kotlin做为一门高效安全的语言,它尝试在编译阶段就把空指针问题显式的检测出来,把问题留在了编译阶段,让程序更加健壮。 详见案例代码KotlinTip13
fun testNullType() {
val a: String = "aa"
/* * a是非空类型,下面的给a赋值为null将会编译不经过 * */
// a = null
a.length
/* * ?声明是可空类型,能够赋值为null * */
var b: String? = "bb"
b = null
/* * b是可空类型,直接访问可空类型将编译不经过,须要经过?.或者!!.来访问 * */
// b.length
b?.length
b!!.length
}
复制代码
/* * 不推荐这样的写法:链式的连续用!!. * */
val user = User()
user!!.name!!.subSequence(0,5)!!.length
复制代码
对应一个可空类型,每次对它的访问都须要带上?.判断
val user: User? = User()
/* * 每次访问都用用?.判断 * */
user?.name
user?.age
user?.toString()
复制代码
但这样多了不少代码,kotlin作了一些优化,
/* * 或者提早判断是否为空,若是不为空在这个分支里会自动转化为非空类型就能够直接访问了 * */
if (user != null) {
user.name
user.age
user.toString()
}
复制代码
经过if提早判断类型是否为空,若是不为空在这个分支里会自动转化为非空类型就能够直接访问了。
inline fun <T, R> T.let(block: (T) -> R): R = block(this)
复制代码
上面的代码还能够用?.let语句进行,以下所示:
/* * 经过let语句,在?.let以后,若是为空不会有任何操做,只有在非空的时候才会执行let以后的操做 * */
user?.let {
it.name
it.age
it.toString()
}
复制代码
经过let语句,在?.let以后,若是为空不会有任何操做,只有在非空的时候才会执行let以后的操做
若是值可能为空,对空值的处理可能会比较麻烦,像下面这样:
/* * 对空值的处理 * */
fun testElvis(input: String?, user: User?) {
val a: Int?
if (input == null) {
a = -1
} else {
a = input?.length
}
if (user == null) {
var newOne = User()
newOne.save()
} else {
user.save()
}
}
复制代码
Elvis操做符?:可以简化上面的操做,?:符号会在符号左边为空的状况才会进行下面的处理,不为空则不会有任何操做。跟?.let正好相反,例如咱们能够用两行代码来简化上面从操做:
/** * Elvis操做符 ?: 简化对空值的处理 */
fun testElvis2(input: String?, user: User?) {
val b = input?.length ?: -1;
user?.save() ?: User().save()
}
复制代码
Kotlin支持对运算符的重载,这对于对一些对象的操做更加灵活直观。
如下面对坐标点Point的案例说明怎么去重载运算符: 详见案例代码KotlinTip14
class Point(val x: Int, val y: Int) {
/* * plus函数重载对Point对象的加法运算符 * */
operator fun plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
/* * minus函数重载对Point对象的减法运算符 * */
operator fun minus(other: Point): Point {
return Point(x - other.x, y - other.y)
}
override fun toString(): String {
return "[x:$x, y:$y]"
}
}
复制代码
如上所示,经过plus函数重载对Point对象的加法运算符,经过minus函数重载对Point对象的减法运算符,而后就能够用+、-号对两个对象进行操做了:
fun testOperator() {
val point1 = Point(10, 10)
val point2 = Point(4, 4)
val point3 = point1 + point2
println(point3)
println(point1 - point2)
}
复制代码
val list = listOf(2, 5, 10)
/* * 传入函数来过滤 * */
println(list.filter { it > 4 })
/* * 定义函数类型 * */
val sum = { x: Int, y: Int -> x + y }
val action = { println(42) }
val sum2: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
val action2: () -> Unit = { println(42) }
复制代码
函数做为参数,即高阶函数中,函数的参数能够是一个函数类型,例如要定义一个函数,该函数根据传入的操做函数来对2和3作相应的处理。 详见案例代码KotlinTip15
/* * 定义对2和3的操做函数 * */
fun twoAndThree(operator: (Int, Int) -> Int) {
val result = operator(2, 3)
println("Result:$result")
}
fun test03() {
twoAndThree { a, b -> a + b }
twoAndThree { a, b -> a * b }
}
复制代码
operator是函数类型,函数的具体类型为(Int, Int) -> Int,即输入两个Int返回一个Int值。定义完了后就能够像上面这样使用了。 再举一个例子,实现String类的字符过滤:
/* * 函数做为参数,实现String类的字符过滤 * */
fun String.filter(predicate: (Char) -> Boolean): String {
val sb = StringBuilder()
for (index in 0 until length) {
val element = get(index)
if (predicate(element)) sb.append(element)
}
return sb.toString()
}
fun test04() {
println("12eafsfsfdbzzsa".filter { it in 'a'..'f' })
}
复制代码
像上面这样predicate是函数类型,它会根据传入的char来判断获得一个Boolean值。
函数做为返回值也很是实用,例如咱们的需求是根据不一样的快递类型返回不一样计价公式,普通快递和高级快递的计价规则不同,这时候咱们能够将计价规则函数做为返回值:
enum class Delivery {
STANDARD, EXPEDITED
}
/* * 根据不一样的运输类型返回不一样的快递方式 * */
fun getShippingCostCalculator(delivery: Delivery): (Int) -> Double {
if (delivery == Delivery.EXPEDITED) {
return { 6 + 2.1 * it }
}
return { 1.3 * it }
}
fun test05() {
val calculator1 = getShippingCostCalculator(Delivery.EXPEDITED)
val calculator2 = getShippingCostCalculator(Delivery.STANDARD)
println("Ex costs ${calculator1(5)}")
println("St costs ${calculator2(5)}")
}
复制代码
若是是普通快递,采用1.3 * it的规则计算价格,若是是高级快递按照6 + 2.1 * it计算价格,根据不一样的类型返回不一样的计价函数。
策略模式是常见的模式之一,java的例子以下。 详见案例代码Tip16
/** * 定义策略接口 */
public interface Strategy {
void doSth();
}
/** * A策略 */
public static class AStrategy implements Strategy {
@Override
public void doSth() {
System.out.println("Do A Strategy");
}
}
/** * B策略 */
public static class BStrategy implements Strategy {
@Override
public void doSth() {
System.out.println("Do B Strategy");
}
}
/** * 策略实施者 */
public static class Worker {
private Strategy strategy;
public Worker(Strategy strategy) {
this.strategy = strategy;
}
public void work() {
System.out.println("START");
if (strategy != null) {
strategy.doSth();
}
System.out.println("END");
}
}
复制代码
如上面的例子所示,有A、B两种策略,Worker根据不一样的策略作不一样的工做,使用策略时:
Worker worker1 = new Worker(new AStrategy());
Worker worker2 = new Worker(new BStrategy());
worker1.work();
worker2.work();
复制代码
在java中实现这种策略模式不免须要先定义好策略的接口,而后根据接口实现不一样的策略, 在Kotlin中彻底能够用用Lambda来简化策略模式,上面的例子用Kotlin实现:
/** * 策略实施者 * @param strategy lambda类型的策略 */
class Worker(private val strategy: () -> Unit) {
fun work() {
println("START")
strategy.invoke()
println("END")
}
}
/* * 测试 * */
fun testStrategy() {
val worker1 = Worker({
println("Do A Strategy")
})
val bStrategy = {
println("Do B Strategy")
}
val worker2 = Worker(bStrategy)
worker1.work()
worker2.work()
}
复制代码
不须要先定义策略的接口,直接把策略以lambda表达式的形式传进来就好了。