澳门新蒲京娱乐


你的语言上榜没

关于SQLSTATE 57017的解决

本次入门就毫无放弃了澳门新蒲京娱乐:,到底哪个人越来越快

把一个Java应用程序转换为Kotlin,编译时间要多久?

写在文前

本文将展示在Android中会遇到的实际问题,并且使用Kotlin怎么去解决它们。一些Android开发者在处理异步、数据库或者处理Activity中非常冗长的listener时发现了很多的问题。通过一个个真实的场景,我们一边解决问题一边学习Kotlin的特性。

这是关于Kotlin的一系列文章。分为三个部分。
第一部分讨论了从Java转换到Kotlin。第二部分是我对Kotlin的看法。

快速上手

如果不知道如何在Kotlin中写一个相当简单的Java表达式。这里有一个简单的诀窍,就是在AndroidStudio的Java文件中编写一段代码,然后将其粘贴到kt文件中,它会自动转换为Kotlin。

在前面的文章中, 我讨论了把Android 应用从Java 100%转换为Kotlin 。
Kotlin代码比Java的简洁,更易于维护,所以我认为转换是值得的。
但有些人不想试用Kotlin,因为他们担心它编译可能没有Java快。
这个关注点绝对是正确的,如果变得编译很慢,没有人愿意转换他们的代码。
所以,让我们编译Lock
App试一下
,然后我把它转换成Kotlin。 我不会试图比较一行代码的编译速度;
相反,我将尝试回答将代码从Java转换为Kotlin是否会影响其总体构建的时间。

Kotlin优势

  1. 它更加易表现:这是它最重要的优点之一。你可以编写少得多的代码。

  2. 它更加安全:Kotlin是空安全的,也就是说在我们编译时期就处理了各种null的情况,避免了执行时异常。你可以节约很多调试空指针异常的时间,解决掉null引发的bug。

  3. 它可以扩展函数:这意味着,就算我们没有权限去访问这个类中的代码,我们也可以扩展这个类的更多的特性。

  4. 它是函数式的:Kotlin是基于面向对象的语言。但是就如其他很多现代的语言那样,它使用了很多函数式编程的概念,比如,使用lambda表达式来更方便地解决问题。其中一个很棒的特性就是Collections的处理方式。我稍后会进行介绍。

  5. 它是高度互操作性的:你可以继续使用所有用Java写的代码和库,甚至可以在一个项目中使用Kotlin和Java两种语言混合编程。一行Java一行Kotlin,别提有多风骚了。

我如何测试构建时间

我写了一个shell来重复执行gradle。 所有测试连续进行10次。
该项目的每个场景之前clean,并使用Gradle daemon ,daemon之前停止一次。
本文中的所有测试都在运行于3.4 GHz的Intel Core
i7-6700上,使用32GB的DDR4内存和三星850 Pro SSD。 源代码是用Gradle
2.14.1构建的。

详细实例

1. 易表现和简洁性

通过Kotlin,可以更容易地避免模版代码,因为大部分的典型情况都在语言中默认覆盖实现了。

举个例子,在Java中,如果我们要典型的数据类,我们需要去编写(至少生成)这些代码:

public class User{
    private long id;
    private String name;
    private String url;
    private String mbid;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getMbid() {
        return mbid;
    }

    public void setMbid(String mbid) {
        this.mbid = mbid;
    }

    @Override 
    public String toString() {
        return "User{" +
          "id=" + id +
          ", name='" + name + '\'' +
          ", url='" + url + '\'' +
          ", mbid='" + mbid + '\'' +
          '}';
    }
}

我们在不使用第三方框架的基础上,需要大量的set get方法和复写基础方法。

而使用Kotlin,我们只需要通过data关键字:

data class User(
    var id: Long,
    var name: String,
    var url: String,
    var mbid: String)

这个数据类,它会自动生成所有属性和它们的访问器, 并自动生成相应的
equals、hashcode、toString 方法。

空口无凭,我们验证一下:

首先建立一个kt文件,新建一个简单的User类:

data class User(var name: String)

这时候在命令行使用kotlinc编译,得到一个class文件,反编译成Java文件,可以看到:

public final class User {
   @NotNull
   private String name;

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final void setName(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.name = var1;
   }

   public User(@NotNull String name) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
      this.name = name;
   }

  // 解构声明
   @NotNull
   public final String component1() {
      return this.name;
   }

   @NotNull
   public final User copy(@NotNull String name) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      return new User(name);
   }

   // $FF: synthetic method
   // $FF: bridge method
   @NotNull
   public static User copy$default(User var0, String var1, int var2, Object var3) {
      if((var2 & 1) != 0) {
         var1 = var0.name;
      }

      return var0.copy(var1);
   }

   public String toString() {
      return "User(name=" + this.name + ")";
   }

   public int hashCode() {
      return this.name != null?this.name.hashCode():0;
   }

   public boolean equals(Object var1) {
      if(this != var1) {
         if(var1 instanceof User) {
            User var2 = (User)var1;
            if(Intrinsics.areEqual(this.name, var2.name)) {
               return true;
            }
         }

         return false;
      } else {
         return true;
      }
   }
}

事实说明在kotlin中 data 修饰符 = java中 private + getter + setter +
toString + equals + hashCode

2. 空安全

当我们使用Java开发的时候,如果我们不想遇到NullPointerException,我们就需要在每次使用它之前,不停地去判断它是否为null。

而Kotlin是空安全的,我们通过一个安全调用操作符?来明确地指定一个对象是否能为空。

我们可以像这样去写:

// 这里不能通过编译. User对象不能是null
var notNullUser: User= null

// User可以是 null
var user: User? = null

// 无法编译, user可能是null,我们需要进行处理
user.print()

// 只要在user != null时才会打印
user?.print()

// 使用Elvis操作符来给定一个在是null的情况下的替代值
val name = user?.name ?: "empty"

/** 
如果user为可空类型,又一定要调用它的成员函数和变量,可以用!!操作符
两种可能,要么正确返回name,要么抛出空指针异常
当user为null,你不想返回null,而是抛出一个空指针异常,你就可以使用它。
*/
var name = user!!.name

3. 扩展方法

我们可以给任何类添加函数(View,Context等)。比起Java的继承机制,更加简洁和优雅。举个例子,我们可以给fragment增加一个显示toast的函数:

fun Fragment.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) { 
    Toast.makeText(getActivity(), message, duration).show()
}

我们现在可以这么做:

fragment.toast("Hello world!")

此处duration已经赋了默认值,所以这个参数可传可不传。

包括扩展属性,可以直接 类名.属性名:类型

注意:Kotlin 的方法扩展并不是真正修改了对应的类文件,而是在编译器和
IDE 方面做了处理。使我们看起来像是扩展了方法。

4. 函数式支持

  • Collections迭代

Kotlin使用lambda表达式来更方便地解决问题。体现最好的就是Collections的处理方式。

list.map(
  println(it) //it表示迭代的对象
)

查看源码,我们可以看到实际上map就是一个扩展方法,给所有可以迭代的集合提供该方法,map方法接收的参数是一个lambda表达式,类型为T,返回值为R类型(意味着任意类型),那这里T类型实际上就是list的元素类型。

map方法源码.png

甚至于可以

list.map(::println)

::表示方法或类的引用。为什么可以直接传方法引用呢?

我们看看println方法源码,可以看到println接收一个Any类也就是任意类型,而且返回值为空(Kotlin中空类型为Unit类,此处源码省略了返回值类型声明),所以完全符合map方法的要求。

println方法源码.png

注:类似于RxJava对数组的处理,Kotlin也提供了flatMap方法,具体可以自己了解。

  • 事件

在Java中,每次我们去声明一个点击事件,都不得不去实现一个内部类,而在Kotlin中,可以直接声明我们要做什么。

view.setOnClickListener { toast("Hello world!") }
//注:此处的toast方法是Kotlin默认已经提供的扩展方法

5. 互操作性

Kotlin调用Java和Java调用Kotlin与之前的Java
类之间调用方式没有太大差别,不详细介绍。

就举个Java调用Kotlin的小例子:

//Kotlin
class Overloads {
    fun overloaded(a: Int, b: Int = 0, c: Int = 1){
        println("$a, $b, $c")
    }
}

//Java
public class AccessToOverloads {
    public static void main(String... args) {
        Overloads overloads = new Overloads();
        overloads.overloaded(1, 2, 3);
    }
}

可以看到非常简单,这里要多介绍一个Kotlin注解@JvmOverloads。仍然定义了一个overloaded方法,加上注解后,Kotlin会自动重载成n个方法(n表示参数个数)

//Kotlin
class Overloads {
    @JvmOverloads
    fun overloaded(a: Int, b: Int = 0, c: Int = 1){
        println("$a, $b, $c")
    }
}

/**
在Java可以调用3个overloaded方法,分别是:
overloaded(a,b,c)
overloaded(a,b)
overloaded(a)
*/
public class AccessToOverloads {
    public static void main(String... args) {
        Overloads overloads = new Overloads();
        overloads.overloaded(1, 2, 3);
        overloads.overloaded(1);
        overloads.overloaded(1,3);
    }
}

6. 其他

  • 单例

首先说说单例的实现方式,在之后的实战中,将会经常接触到object这个关键字。

先看Java,在Java中,实现一个单例,我们需要:

  1. 保留一个单例对象的静态实例

  2. 提供一个类方法让外界访问唯一的实例

  3. 构造方法采用private修饰符

而在Kotlin中,一个修饰符就解决了。

object PlainOldSingleton {

}

怎么做到的?我们看看反编译的结果:

单例

可以看到写法和Java是完全一样的,又有一个新问题,在类加载的时候就初始化了实例,这种方式很糟糕,我们最好选择懒加载。那么在Kotlin中懒加载的2种实现方式如下:

class LazyNotThreadSafe {
      //方式一
    companion object{
        val instance by lazy(LazyThreadSafetyMode.NONE) {
            LazyNotThreadSafe()
        }

        //方式二,实际是Java的直译
    private var instance2: LazyNotThreadSafe? = null

        fun get() : LazyNotThreadSafe {
            if(instance2 == null){
                instance2 = LazyNotThreadSafe()
            }
            return instance2!!
        }
    }
}

如果想要实现线程安全,可以加上@Synchronized注解,这和Java中给类加上Synchronized修饰符是一样的。同样@Volatile注解和Java的Volatile修饰符作用也是一样的。

或者使用静态内部类的单例方法:

class LazyThreadSafeStaticInnerObject private constructor(){
    companion object{
        fun getInstance() = Holder.instance
    }

    private object Holder{
        val instance = LazyThreadSafeStaticInnerObject()
    }
}
  • 委托

Kotlin中,委托的实现依靠于关键字 by
by表示将抽象主题的实例(by后边的实例)保存在代理类实例的内部。

比如下面这个例子中:BaseImpl类继承于Base接口,并可以Base接口的所有的
public 方法委托给一个指定的对象。

interface Base {
    fun display()
}

class BaseImpl : Base {
    override fun display() {
        print("baseimpl display")
    }
}

class ProxyClass(base: Base) : Base by base

//程序入口
fun main(args: Array<String>) {
    var base = BaseImpl()
    var proxy = ProxyClass(base)
    proxy.display()
}
  • 泛型

在Java中,一般使用Gson库来解析Json。调用方法的时候,我们需要传入想要转成的类的Class。我们都知道Java的泛型实际上是伪泛型,对泛型支持的底层实现采用的是类型擦除的方式(只有在编译期才有)。

所以当使用Gson.fromJson(String json , Class<T> classOf)方法时,虽然传入了类型参数,当实际上这个T仍然是个Object。

而在Kotlin中,可以使用reified,告别Class。

reified的意思是具体化。作为Kotlin的一个方法泛型关键字,它代表你可以在方法体内访问泛型指定的JVM类对象。

inline fun <reified T: Any> Gson.fromJson(json: String): T{
//封装了`Gson.fromJson(String json , Class<T> classOf)`方法
    return fromJson(json, T::class.java)
}

这里需要指定T类型为Any,即Object类。

接着可以不需要传入Class,直接调用

fun main(args: Array<String>) {
    val json = "{state:0,result:'success',name:'test'}"
    var result : ReifiedBean =  Gson().fromJsonNew(json)
    println(result.name+","+result.result)
}

这要归功于inline,inline
意味着编译的时候真正要编译到调用点。那么哪个方法调用了它,参数的类型都是确定的。也就不需要传入Class了

** 7. 摆脱不必要的依赖**

Kotlin替换了许多第三方库,如ButterKnife、Google
Autovalue、Retrolambda、Lombok和一些RxJava代码。

但是也是可以100%兼容RxJava的,举个读取本地文本逐个字打印的例子。

Kotlin中使用RxJava

好了,言归正传。

普通的获取View方法,需要一个个去findViewById

普通的获取View方法

而使用Kotlin后

使用Kotlin获取View

可能有人注意到了,还是需要findViewById啊!!骗子!说好的优雅呢?完全没觉得更加简洁啊!!别急,Kotlin常用的获取控件方式不是这样的,容我介绍个Kotlin库——Anko。

测试

我想在几种常见的使用场景中运行基准:使用和不使用Gradle
daemon+clean,没有文件更改的增量编译,以及更改的文件的增量编译。
在转换之前,App Lock的Java代码有5,491个方法和12,371行代码。
改写后,这些数字下降到4,987方法和8,564行Kotlin代码。
在重写期间没有发生大的架构更改,因此在重写之前和之后测试编译时间应该很好地了解Java和Kotlin之间的构建时间的差异。

3. Kotlin库——Anko

简介
Anko是Kotlin官方开发的一个让开发Android应用更快速更简单的Kotlin库

1. 再也不用findViewById

做过Android开发的人都知道,布局文件写的多了,findViewById也是一个很大的工作量,而且还要先声明变量,在findViewById然后再强转成我们的控件,使用方式一般如下

TextView username;
username=(TextView)findViewById(R.id.user);

username.setText("我是一个TextView");

有时候写的是不是想吐,可能有些人说现在不是有一些注解的库,如butterknife,当我们使用注解时可以不用findViewById了,使用方式如下

@BindView(R.id.user)
TextView username;

username.setText("我是一个TextView");

确实是这样,使用注解后确实给我们少了一些工作量,不过这依然没有最简单化,最简单的就是我们可以直接给id为user的控件直接赋值,或许你会感觉这有点不可思议。不过Kotlin确实做到了。我们可以直接这样写

user.text="我是一个TextView"

user就是我们布局文件声明的id,.text就相当于setText(),在Kotlin语言中,我们看不到了像Java中的set/get方法了。

当我们想这样使用的时候(不用findViewById,直接使用xml控件id)
我们需要在gradle加入apply plugin: ‘kotlin-android-extensions’,需要加入下面一句代码

import kotlinx.android.synthetic.main.activity_login.*
注:activity_login就是我们的布局

import org.jetbrains.anko.toast
import org.jetbrains.anko.onClick

class Main2Activity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main2)
        my_textView.text = "kotline test"
        my_textView.textColor = Color.BLUE
        my_button.text = "Click"
        my_button.onClick { toast("aa") }
    }
}  

为什么Anko不需要.setText可以直接.text呢?其实这是通过扩展函数实现的,我们看下内部的实现细节:

public var TextView.text: CharSequence        
  get() = getText()           
  set(v) = setText(v)

2. Anko Layout

通常我们使用xml文件写我们的布局,但是存在有一些缺点:如不是类型安全,不是空安全,解析xml文件消耗更多的CPU和电量等等。

而Anko
Layout可以使用DSL动态创建我们的UI,并且它比我们使用Java动态创建布局方便很多。主要是更简洁,它拥有类似xml创建布局的层级关系,能让我们更容易阅读。

 verticalLayout {
            val textView = textView("textview")
            val name = editText()
            val button=button()
                    button.onClick {
                toast("${name.text}")
            }
        }

我们在OnCreate方法中可以去掉setContentView,然后加入上面代码就可以显示如下图的效果,即一个垂直的线性布局中,放了一个TextView,一个EditText,和一个Button。并且Button中有一个点击事件,当点击时将EditText的内容以toast显示。

Anko Layout.png

在上面创建UI过程中,我们直接把创建UI的代码写在onCreate方法中了,当然,还有一种写法。我们创建一个内部类实行AnkoComponent接口,并重写createView方法,该方法返回一个View,也就是我们创建的布局。修改如下

inner class UI : AnkoComponent<LoginActivity> {
        override fun createView(ui: AnkoContext<LoginActivity>): View {
           return with(ui){
               verticalLayout {
                   val textView=textView("我是一个TextView"){
                       textSize = sp(17).toFloat()//自定义字体大小
                       textColor=context.resources.getColor(R.color.red)//自定义颜色
                   }.lparams{
                       margin=dip(10)//它表示将10dp转换为像素
                       height= dip(40)
                       width= matchParent
                   }
                   val name = editText("EditText")
                   button("Button") {
                        onClick { view ->
                            toast("Hello, ${name.text}!")
                        }
                   }
               }
           }
        }
    }

然后在onCreate方法中加一句代码,即可创建我们的布局页面了。如下

UI().setContentView(this@LoginActivity)

其中,dip(10),表示将10dp转换为像素的意思,是Anko的扩展函数,说到扩展函数,我发现Kotlin源码里大量地使用扩展函数,这也是Kotlin语言的优势之一。确实很强大,例如dip扩展(摘取View扩展)

inline fun View.dip(value: Int): Int = context.dip(value)
fun Context.dip(value: Int): Int = (value * resources.displayMetrics.density).toInt()

就如我们之前说的toast、text也是拓展函数一样

inline fun AnkoContext<*>.toast(message: CharSequence) = ctx.toast(message)
fun Context.toast(message: CharSequence) = Toast.makeText(this, message, Toast.LENGTH_SHORT).show()

但是为了界面和逻辑分离,界面还是建议使用xml,所以这里就不对Anko
Layout多做介绍了。

3. 其他方面

比如网络请求AsyncTask

 doAsync {
            //后台执行代码

            uiThread { 
            //UI线程
            toast("线程${Thread.currentThread().name}")

         }
      }

其他内容可以直接访问Anko

clean + 不用Gradle daemon Build

这是两种语言中构建时间最差的情况:从冷启动运行一个clean的构建。
对于这个测试,我禁用了Gradle daemon。
这里是十个构建所花费的时间:

澳门新蒲京娱乐 1

在这种情况下的结果是,Java构建时间平均为15.5秒,而Kotlin平均为18.5秒:增加了17%。
这对Kotlin来说并不是一个好的开始,但是大部分人不会这么编译他们的代码。

对于没有Gradle daemon 并且clean构建,Java编译比Kotlin快17%

Kotlin的缺点

尽管 Kotlin 非常棒,但是它并不完美。我列举了一些我不喜欢的部分。

相关文章

No Comments, Be The First!
近期评论
    功能
    网站地图xml地图