传智播客旗下高端IT在线教育平台|咨询热线:010-56288220

返回顶部 返回列表
269 0

[Kotlin专区] Kotlin凭什么吸引你

[复制链接]

63

主题

95

帖子

504

积分

超级版主

谷子老师

Rank: 8Rank: 8

积分
504
2690 谷子老师 发表于 2017-12-22 11:02:23
本帖最后由 18603661315 于 2017-12-22 11:27 编辑

今年的谷歌I/O大会一个热点就是宣布Kotlin成为安卓开发的一级编程语言。
Kotlin简介###Kotlin是JetBrains设计并开源(最新开源版本为1.1.4)的一门静态编程语言,由于设计者是IDE著名开发商JetBrains公司,Kotlin从一开始就自带IDE 支持。Intellij IDEA 15以前和2开头的Android Studio需要安装Kotlin插件,Intellij IDEA 15以上的版本,包含15以及AS 3.0及3.0以上的版本自带Kotlin插件。Kotlin的目标平台除了JVM,安卓还可以是浏览器,这是通过将Kotlin转化为JavaScript来实现的,另外的亮点是Kotlin Native,Kotlin Native 不是 JNI 的概念,它不仅仅是要与底层的 C、C++ 交互,而且还要绕过 JVM 直接编译成机器码供系统运行,但是目前还在技术预览阶段,Kotlin团队大概40人,真是一个强大的团队。Koltin“借鉴”了很多语言比如 C# 、C++ Gosu、Scala、Java,对此Kotlin貌似并不避讳,JetBrains负责人也曾经说过“大多数语言没有他们正在寻找的特性,Scala除外”,另外Kotlin的官方文档也会有“Kotlin与C#类似”这样的字眼。Kotlin语法跟Swift很像,但事实上Kotlin比Swift早三年发表,两者不存在谁借鉴谁的问题。
Kotlin凭什么吸引你
1.Kotlin与Java 100%互操作
#####Swift与OC######
说起Kotlin与Java,就会想起Swift与OC。根据了解,现在国内一些大公司依然使用OC或者Swift与OC混编的方式开发IOS。这其中涉及很多原因,如早期版本的Swift编译速度和运行速度慢,导致用户觉得应用卡,并且用Swift开发打包后的安装包比用OC的大;再比如Swift的版本更新太快,不太稳定,开发者不得不花时间去适配到最新的Swift。另外一个就是这里想讲的兼容性问题,Swift与OC并不能完全互操作,一些东西比如宏定义和Runtime,Swift调用不了,除此以外虽然Swift调用OC比较简单,但OC里用Swift比较麻烦。
不管新版本的Swift是否已经解决了上述问题,我们都可以从中发现程序员的一些关注点。比如新语言的稳定性和兼容性、编译速度、运行速度还有和旧语言的兼容性问题。那Kotlin有这些问题吗?我们接着来看。
Kotlin与Java######
Kotlin的以下优点来自于官方文档。
互操作性:Kotlin 可与 Java 进行 100% 的互操作,允许在 Kotlin 应用程序中使用所有现有的 Android 库 。
兼容性:Kotlin 与 JDK 6 完全兼容,保障了 Kotlin 应用程序可以在较旧的 Android 设备上运行而无任何问题
运行速度:Kotlin 应用程序的运行速度与 Java 差不多。 随着 Kotlin 对内联函数的支持,使用 lambda 表达式的代码通常比用 Java 写的代码运行得更快。
apk大小:在实际应用程序中,Kotlin 开发的apk比Java开发的apk增加不到 100K 的大小。
编译时长:Kotlin 支持高效的增量编译,所以对于清理构建会有额外的开销,增量构建通常与 Java 一样快或者更快(增量编译只重新编译代码中更改的部分)
学习曲线:对于 Java 开发人员,Kotlin 入门很容易。包含在 Kotlin 插件中的自动 Java 到 Kotlin 的转换器有助于迈出第一步。
另外,版本问题:Kotlin已经有稳定版本并长期向后兼容,开源的最新版本为1.1.4并且无论是在Kotlin工程里使用Java,还是Java工程里使用Kotlin,都很简单。
我们先来看看Java工程里使用Kotlin(使用带有Kotlin插件的Android Studio2.3)
MainActivity.java

  1. <div>package com.example.lingo.javawithkotlin;

  2. import android.content.Intent;
  3. import android.support.v7.app.AppCompatActivity;
  4. import android.os.Bundle;

  5. import com.example.javawithkotlin.MyService;

  6. public class MainActivity extends AppCompatActivity {

  7.    @Override
  8.     protected void onCreate(Bundle savedInstanceState) {
  9.         super.onCreate(savedInstanceState);
  10.         setContentView(R.layout.activity_main);

  11.     }
  12.     public void startService(View v){
  13.         Intent intent=new Intent(MainActivity.this,MyService.class);
  14.         startService(intent);
  15.     }

  16.     public void stopService(View v){
  17.         Intent intent=new Intent(MainActivity.this,MyService.class);
  18.         stopService(intent);
  19.     }
  20. }
  21. </div>
复制代码
activity_main.xml
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.     xmlns:app="http://schemas.android.com/apk/res-auto"
  4.     xmlns:tools="http://schemas.android.com/tools"
  5.     android:layout_width="match_parent"
  6.     android:layout_height="match_parent"
  7.     tools:context="com.example.lingo.javawithkotlin.MainActivity">

  8.     <Button
  9.         android:layout_width="wrap_content"
  10.         android:layout_height="wrap_content"
  11.         android:text="启动service"
  12.         android:textAllCaps="false"
  13.         android:onClick="startService"
  14.         android:layout_marginLeft="8dp"
  15.         app:layout_constraintLeft_toLeftOf="parent"
  16.         android:layout_marginRight="8dp"
  17.         app:layout_constraintRight_toRightOf="parent"
  18.         android:id="@+id/button2"
  19.         app:layout_constraintHorizontal_bias="0.501"
  20.         android:layout_marginBottom="16dp"
  21.         app:layout_constraintBottom_toTopOf="@+id/guideline" />

  22.     <Button
  23.         android:layout_width="wrap_content"
  24.         android:layout_height="wrap_content"
  25.         android:text="停止service"
  26.         android:onClick="stopService"
  27.         android:textAllCaps="false"
  28.         android:layout_marginLeft="8dp"
  29.         app:layout_constraintLeft_toLeftOf="parent"
  30.         android:layout_marginRight="8dp"
  31.         app:layout_constraintRight_toRightOf="parent"
  32.         app:layout_constraintHorizontal_bias="0.501"
  33.         app:layout_constraintTop_toTopOf="@+id/guideline"
  34.         android:layout_marginTop="16dp" />

  35.     <android.support.constraint.Guideline
  36.         android:layout_width="wrap_content"
  37.         android:layout_height="wrap_content"
  38.         android:id="@+id/guideline"
  39.         android:orientation="horizontal"
  40.         app:layout_constraintGuide_percent="0.5" />

  41. </android.support.constraint.ConstraintLayout>
复制代码
我们在Java工程里扔进一个别的项目的MyService.kt
MyService.kt(Kotlin文件)
  1. package com.example.javawithkotlin

  2. import android.app.Service
  3. import android.content.Intent
  4. import android.os.IBinder
  5. import android.util.Log


  6. class MyService(): Service(){
  7.     override fun onBind(intent: Intent?): IBinder? {
  8.         Log.d("MyService","onBind,currentThreadID="+Thread.currentThread().id)
  9.         return  null
  10.     }

  11.     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
  12.         Log.d("MyService","onStartCommand,currentThreadID="+Thread.currentThread().id)
  13.         return super.onStartCommand(intent, flags, startId)
  14.     }

  15.     override fun onCreate() {
  16.         super.onCreate()
  17.         Log.d("MyService","onCreate,currentThreadID="+Thread.currentThread().id)
  18.     }

  19.     override fun onDestroy() {
  20.         super.onDestroy()
  21.         Log.d("MyService","onDestroy,currentThreadID="+Thread.currentThread().id)
  22.     }

  23. }
复制代码
之后文件头部会出现这样的提示,当然首先你的AS必须安装Kotlin插件或者直接使用Android Studio3(内嵌Kotlin):



屏幕快照 2017-07-20 下午1.57.54.png


点击Configure后弹出下面窗口,点击OK便可



屏幕快照 2017-07-20 下午3.13.36.png

AS会自动配置,需要点击Sync now,更新AS的配置
AndroidManifest.xml配置MyService



屏幕快照 2017-07-20 下午1.17.51.png

运行后分别点击“启动service”和“停止service”可以看到Java成功调用Kotlin,就是这么简单:




屏幕快照 2017-07-20 下午3.32.20.png




屏幕快照 2017-07-20 下午3.38.25.png




接下来看看在Kotlin工程里使用Java会是怎样的,这里实现与上面应用一样的效果
用Android Studio3新建一个包含Kotlin的工程



屏幕快照 2017-07-20 下午3.39.32.png

MainActivity.kt
  1. package com.example.lingo.kotlinwithjava

  2. import android.content.Intent
  3. import android.support.v7.app.AppCompatActivity
  4. import android.os.Bundle
  5. import android.view.View
  6. import com.example.lingo.kotlinwithjava.Service.MyService

  7. class MainActivity : AppCompatActivity() {

  8.     override fun onCreate(savedInstanceState: Bundle?) {
  9.         super.onCreate(savedInstanceState)
  10.         setContentView(R.layout.activity_main)
  11.     }

  12.     fun startService(v: View){
  13.         val mIntent= Intent(this@MainActivity,MyService::class.java)
  14.         startService(mIntent)
  15.     }
  16.    
  17.     fun stopService(v:View){
  18.         val mIntent= Intent(this@MainActivity,MyService::class.java)
  19.         stopService(mIntent)
  20.     }
  21. }
复制代码
activity_main.xml与上面一样
类似的,我们在这个kotlin工程里扔进一个Java文件:
MyService.java
  1. package com.example.lingo.kotlinwithjava.Service;

  2. import android.app.Service;
  3. import android.content.Intent;
  4. import android.os.IBinder;
  5. import android.support.annotation.Nullable;
  6. import android.util.Log;

  7. /**
  8. * Created by lingo on 2017/7/20.
  9. */

  10. public class MyService extends Service {
  11.     @Nullable
  12.     @Override
  13.     public IBinder onBind(Intent intent) {
  14.         Log.d("MyService","onBind,currentThreadID="+Thread.currentThread().getId());
  15.         return null;
  16.     }

  17.     @Override
  18.     public int onStartCommand(Intent intent, int flags, int startId) {
  19.         Log.d("MyService","onStartCommand,currentThreadID="+Thread.currentThread().getId());
  20.         return super.onStartCommand(intent, flags, startId);
  21.     }

  22.     @Override
  23.     public void onCreate() {
  24.         super.onCreate();
  25.         Log.d("MyService","onCreate,currentThreadID="+Thread.currentThread().getId());
  26.     }

  27.     @Override
  28.     public void onDestroy() {
  29.         super.onDestroy();
  30.         Log.d("MyService","onDestroy,currentThreadID="+Thread.currentThread().getId());
  31.     }
  32. }
复制代码
AndroidManifest.xml同样配置MyService
一样分别点击两个按钮运行,可以看到Kotlin成功调用Java



屏幕快照 2017-07-20 下午3.57.27.png




屏幕快照 2017-07-20 下午3.56.26.png


2.Kotlin拥有函数式编程特性
Koltin是面对对象语言,同时拥有函数式编程特性(不符合纯函数的条件)
这种特性体现在Kotlin里函数是一等公民,可以作为参数和返回值,也可以使用Lambda表达式、匿名函数、函数引用或者属性引用作为函数类型参数的实参和返回值,函数也可以直接写在最外层
例如:
fun A(lambdaString)->Int){
}
调用时:A{s->s.get(0)
s.length}
Java难道不具备函数式编程的特性?不,Java8其实也具有Lambda表达式和函数式接口,可以说也是具备函数式编程特性的,但是,首先Java8的Lambda表达式语法上没有kotlin简洁、并且仅限用于函数式接口类型,其次从 Android Studio 2.4 Preview 4和gradle plugin 2.4.0-alpha4版本开始,android才原生支持 Java 8的部分特性,Lambda表达式和函数式接口已经支持了,但从目前来看,Java8更多特性(例如Stream API)只有在API 24以后(对应7.0以上的设备)的设备支持。
3.Kotlin更简洁
实现相同功能,Kotlin需要程序员编写的代码量比Java少,数据类、Lambda表达式、反射等都能减少程序员编写的代码量。
拿数据类来说,我们经常创建一些只保存数据的类。
比如用Java创建一个学生数据类:

  1. public class student{
  2.   private String name;
  3.   private String sex;
  4.   private int studentID;
  5. public student(String name,String sex,int studentID){
  6.         this.name=name;
  7.         this.sex=sex;
  8.         this.studentID=studentID;
  9. }
  10.   public String getName(){
  11.           return name;
  12.   }
  13.   public void setName(String name){
  14.          this.name=name;
  15. }
  16. public String getSex(){
  17.         return sex;
  18. }
  19. public void  setSex(String sex){
  20.         this.sex=sex;
  21. }
  22. public int getStudentID(){
  23.      return studentID;
  24. }
  25. public void setStudentID(int studentID){
  26.      this.studentID=studentID;
  27. }
复制代码
而Kotlin对应的代码为:
d
  1. ata class student( var name:String, var sex:String,var studentID:Int)
  2. //其实不等价,Koltin的这一行代码不仅实现了上述Java代码的功能,还实现了几个函数,包括componentN(),这个函数是解构声明(把一个对象解构成很多变量)的关键
复制代码

用法:val mStudent=student("Lingo","女",190)mStudent.name="XX"//调用name的set访问器,注意属性和字段的不同,变量对应的存储空间存储的是字段,属性对应操作字段的方法。外界能直接访问的是属性
4.Kotlin更安全
许多编程语言(包括 Java)中最常见的陷阱之一是访问空引用的成员,导致空引用异常(NullPointerException),图灵奖得主Tony Hoare称发明空引用是他所犯的十亿美元的错误,可见空引用是不容小觑的错误。
  1. <p>Kotlin中有非空类型和可空类型的概念:

  2. var a: String = "abc"

  3. a = null // 编译错误

  4. val l = a.length//编译通过,因为a不可能为null,所以这个调用不会抛出NullPointer Exception错误,编译器认为这个调用是安全的</p>
  5. <p>var b: String? = "abc"

  6. b = null // ok

  7. val l = b.length//编译器抛错,提醒这个b可能为空。</p>
复制代码
在第二种情况中如何让b.length通过编译?

  1. <p>1.常规的在条件中检查 null:val l = if (b != null) b.length else -1(Kotlin中if是表达式,会返回分支的最后一个表达式的值作为if的返回值)</p>
  2. <ol start="2">
  3. <li><p>?.表达式

  4. 例如:val l=b?.length

  5. 表示如果 b 非空,就返回 b.length,否则返回 null

  6. 甚至是链式调用:

  7. class Student(var school:School?)

  8. class School(var president:String)

  9. val student:Student?=Student(School(“某校长名字”))

  10. val president=student?.school?.president//president存储学生所属学校的校长名

  11. 只要任意?左侧表达式的值为空就返回空,不然相当于返回

  12. student.school.president</p></li>
  13. <li><p>?:表达式

  14. 例如:val l = b?.length ?: -1//等价于val l= if (b != null) b.length else -1

  15. 如果 ?: 左侧表达式非空,就返回其左侧表达式,否则返回右侧表达式。

  16. 请注意,当且仅当左侧为空时,才会对右侧表达式求值。</p></li>
  17. <li><p>!!操作符强行让编译器在这一行代码不报错,运行时抛出的NPE错误责任由程序员承担

  18. 例如:val l = b!!.length</p></li>
  19. </ol>
  20. <p>除此之外,Koltin还提供了安全的类型转换

  21. 例如:val b: Int? = a as? Int

  22. as是Kotlin的类型转换关键字,as?表示如果转换不成功则返回null</p>
复制代码
5.Kotlin更灵活
5.1闭包
Kotlin有闭包的概念,但不同于Swift,Swift的闭包类似于kotlin的Lambda表达式,那Kotlin里的闭包是什么呢?
Kotlin的闭包是指外部作用域中声明的变量,Kotlin对闭包的访问与Java对闭包的访问不同,Koltin更加灵活。
看下面的例子:

  1. class OuterClass{
  2.        fun click(){
  3.              var clickCount = 0
  4.              val bt= Button(this)
  5.             //addView代码省略
  6.             bt.setOnClickListener( object: View.OnClickListener{
  7.                  override fun onClick(v: View?) {
  8.                       clickCount++
  9.             }
  10.         } )
  11.       }
  12. }
复制代码
对应Java代码:
  1. <div>public class OuterClass{
  2. public void click(){
  3.     final int clickCount=0;
  4.     Button bt=new Buttton(this);
  5.   //addView代码省略
  6.     bt.setOnClickListener(new View.OnClickListener{
  7.            @override
  8.            public  void onClick(View v) {
  9.                clickCount++;//这一行编译不过
  10.             }
  11.         } );
  12. }</div>
复制代码
这是一段给Button注册点击监听器的代码,当点击事件发生时让clickCount的值+1,你可能很清楚这在Java的语法里是行不通的。Java必须将clickCount声明为final,才可以在onClick方法里调用,并且不可以改动值,也就是说在Java里clickCount++这一行会编译不过。
第一个问题:为什么在Java里要将clickCount声明为final才能在匿名内部类中访问?为什么即使添加final也只能访问不能修改clickCount的值?
在匿名内部类里访问闭包,从逻辑角度看貌似很正常,但从Java语言的角度考虑,这比较麻烦。想想这里匿名内部类是一个接口实例,它确实持有外部类实例的引用,但是怎么通过这个外部类实例去访问到外部类一个方法里的局部变量?Java的做法是把clickCount拷贝一份,作为匿名内部类实例的数据成员,虽然对于编译器是两个变量但对于程序员来说是一份,值应该一直一样,那如何保持拷贝的值与clickCount一直一致呢?索性,Java将闭包声明为fianl,这样clickCount和拷贝后的值就不可变了,当然就一直一致,简单粗暴。另外Java8中不用显示将clickCount声明为final,编译器会替你做。
第二个问题:如果clickCount声明为外部类的成员,为什么不加final也可被内部类访问?
因为内部类持有外部类对象的引用。
第三个问题:上述Kotlin代码如何成立?为什么clickCount++不会报错?
通过将Kotlin编译为字节码然后再反编译我看到这段Kotlin代码对应的Java代码:

  1. final IntRef clickCount = new IntRef();
  2.          clickCount.element = 0;
  3.          bt.setOnClickListener((OnClickListener)(new OnClickListener() {
  4.             public void onClick(@Nullable View v) {
  5.                int var2 = clickCount.element++;
  6.             }
  7.          }));
  8.          clickCount = null;
  9.       }
复制代码
IntRef是什么?我们进源代码看看:
  1. public static final class IntRef implements Serializable {
  2.         public int element;

  3.         @Override
  4.         public String toString() {
  5.             return String.valueOf(element);
  6.         }
  7.     }
复制代码
再清楚不过了,实际上Kotlin将匿名内部类需要的访问的闭包一一封装成这样的类,int类型对应IntRef,Short对应ShortRef等。IntRef只有一个字段,字段存储的值就是0
所以Koltin中val clickCount=0实际上的代码是:
  1. final val clickCount=IntRef()
  2. clickCount.element=0
复制代码
由于声明为final,编译器将拷贝一份这个实例的引用作为匿名内部类的成员,这里的final是指该成员和clickCount会一直保持对这个IntRef实例的引用,但是这个IntRef实例本身可以变,它的数据成员element的值变了,可见Kotlin在闭包的处理方面是基于Java的。
5.2扩展######
Kotlin允许扩展功能能够扩展一个类的新功能而无需继承该类,可以是扩展函数也可以是扩展属性,通过定义一个扩展,你并没有在一个类中插入新成员, 仅仅是可以通过变量(类型为这个类)用点表达式去调用这个新函数。
扩展函数
fun String.foo():Int{        return length    }调用
  1. println("abc".foo())//结果为3
  2. <span style="color: rgb(47, 47, 47); font-family: -apple-system, " sf="" ui="" text",="" arial,="" "pingfang="" sc",="" "hiragino="" sans="" gb",="" "microsoft="" yahei",="" "wenquanyi="" micro="" hei",="" sans-serif;="" font-size:="" 16px;="" white-space:="" normal;="" background-color:="" rgb(247,="" 247,="" 247);"="">上述代码扩展了String的一个函数,并且可以在扩展函数内调用扩展接收者(“.”左边)的属性或者方法</span>
复制代码
扩展属性
fun main(args: Array<String>) {    println("abc".mlastIndex)//结果为2}val String.mlastIndex: Int    get() = length - 15.3反射######Java8中的方法引用也是很强大的,类似于Koltin里的函数引用,但Kotlin并不只有函数引用,属性引用也支持
函数引用
通过“::函数名”的形式可以把一个函数当做一个“值”来传递,这个“值”的类型是函数类型,例如
  1. fun isOdd(x: Int) = x % 2 != 0
  2. val numbers = listOf(1, 2, 3)
  3. println(numbers.filter(::isOdd)) // 输出 [1, 3]
复制代码
fiter的原型:
  1. public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {

  2. return filterTo(ArrayList<T>(), predicate)

  3. }

  4. ::isOdd的类型为(Int)->Boolean
复制代码
构造函数的引用
构造函数的引用也可归为函数引用,在Java8也有相同的概念
  1. class Foo(var p:String)

  2. fun function(factory: (String) -> Foo) {
  3.   ...
  4. }
  5. 调用时function(::Foo)
复制代码

function函数的参数factory与Foo构造函数接受相同参数并且同样返回Foo类型的对象。
属性引用
  1. var x = 1
  2. val strs = listOf("a", "bc", "def")
  3.         println(strs.map(String::length)) // 输出 [1, 2, 3]
复制代码
String::length对应的类型不是Int,虽然String实例调用length是Int,String::length对应的类型为(String)->Int(更准确是KMutableProperty1<String,Int>),而对于属性x,::x对应的类型为()->Int(更准确是KMutableProperty0<Int>)
查看源码就能清楚了:
  1. public interface KMutableProperty1<T, R> : KProperty1<T, R>, KMutableProperty<R>
复制代码


  1. <p>public interface KProperty1<T, out R> : KProperty<R>, (T) -> R</p>
复制代码

  1. public interface KMutableProperty0<R> : KProperty0<R>, KMutableProperty<R>
复制代码

  1. public interface KProperty0<out R> : KProperty<R>, () -> R
复制代码
5.4控制流
Kotlin中的控制流相对于Java来说更灵活,像if、when既是控制流也是表达式,举个例子:

  1. <pre class="hljs undefined"><code>fun main(args: Array<String>) {
  2.     val x:Any=3
  3.     val result=when(x) {
  4.         is String -> x.length
  5.         is Int->x+1
  6.         else ->x
  7.     }
  8.     println(result)//结果为4
  9. }</code></pre>
复制代码

Kotlin的when相当于Java的switch+break,但是功能更加强大,"->"左边是分支条件,可以是任意表达式(不只常量),上述代码中的is表示判断x的类型,相当于Java的instanceof,每个分支最后一行的值为返回值
















回复

您需要登录后才可以回帖 登录 | 立即注册