Kotlin开发宝典

【Kotlin】三、变量和属性

2017-12-05  本文已影响10人  FynnJason

上一篇:【Kotlin】二、编写你的第一个类

前言

在Kotlin中,一切都是对象。没有像Java中那样的原始基本类型。这个是非常有帮
助的,因为我们可以使用一致的方式来处理所有的可用的类型。

基本类型

当然,像integer,float或者boolean等类型仍然存在,但是它们全部都会作为对象
存在的。基本类型的名字和它们工作方式都是与Java非常相似的,但是有一些不同
之处你可能需要考虑到:

val i:Int=7
val d: Double = i.toDouble()
val c:Char='c'
val i: Int = c.toInt()
// Java
int bitwiseOr = FLAG1 | FLAG2;
int bitwiseAnd = FLAG1 & FLAG2;
// Kotlin
val bitwiseOr = FLAG1 or FLAG2
val bitwiseAnd = FLAG1 and FLAG2

还有很多其他的位操作符,比如 sh1 , shs , ushr , xor 或 inv 。当我
们需要的时候,可以在Kotlin官网查看。

val i = 12 // An Int
val iHex = 0x0f // 一个十六进制的Int类型
val l = 3L // A Long
val d = 3.5 // A Double
val f = 3.5F // A Float
val s = "Example"
val c = s[2] // 这是一个字符'a'
// 迭代String
val s = "Example"
for(c in s){
    print(c)
}

变量

变量可以很简单地定义成可变( var )和不可变( val )的变量。这个与Java中使
用的 final 很相似。但是不可变在Kotlin(和其它很多现代语言)中是一个很重要
的概念。

一个不可变对象意味着它在实例化之后就不能再去改变它的状态了。如果你需要一
个这个对象修改之后的版本,那就会再创建一个新的对象。这个让编程更加具有健
壮性和预估性。在Java中,大部分的对象是可变的,那就意味着任何可以访问它这
个对象的代码都可以去修改它,从而影响整个程序的其它地方。

不可变对象也可以说是线程安全的,因为它们无法去改变,也不需要去定义访问控
制,因为所有线程访问到的对象都是同一个。

所以在Kotlin中,如果我们想使用不可变性,我们编码时思考的方式需要有一些改
变。一个重要的概念是:尽可能地使用 val 。除了个别情况(特别是在Android
中,有很多类我们是不会去直接调用构造函数的),大多数时候是可以的。

之前提到的另一件事是我们通常不需要去指明类的类型,它会自动从后面分配的语
句中推断出来,这样可以让代码更加清晰和快速修改。我们在前面已经有了一些例
子:

val s = "Example" // A String
val i = 23 // An Int
val actionBar = supportActionBar // An ActionBar in an Activity

如果我们需要使用更多的范型类型,则需要指定:

val a: Any = 23
val c: Context = activity

属性

属性与Java中的字段是相同的,但是更加强大。属性做的事情是字段加上getter加
上setter。我们通过一个例子来比较他们的不同之处。这是Java中字段安全访问和
修改所需要的代码:

public class Person {
    private String name;
    public String getName() {
        return name;
    }
public void setName(String name) {
        this.name = name;
    }
}
···
Person person = new Person();
person.setName("name");
String name = person.getName();

在Kotlin中,只需要一个属性就可以了:

public class Person {
    var name: String = ""
}

···

val person = Person()
person.name = "name"
val name = person.name

如果没有任何指定,属性会默认使用getter和setter。当然它也可以修改为你自定义
的代码,并且不修改存在的代码:

public classs Person {
    var name: String = ""
        get() = field.toUpperCase()
        set(value){
            field = "Name: $value"
        }
}

如果需要在getter和setter中访问这个属性自身的值,它需要创建一个 backing
field 。可以使用 field 这个预留字段来访问,它会被编译器找到正在使用的并
自动创建。需要注意的是,如果我们直接调用了属性,那我们会使用setter和getter
而不是直接访问这个属性。 backing field 只能在属性访问器内访问。

就如在前面章节提到的,当操作Java代码的时候,Kotlin将允许使用属性的语法去
访问在Java文件中定义的getter/setter方法。编译器会直接链接到它原始的
getter/setter方法。所以当我们直接访问属性的时候不会有性能开销。

Anko是什么?

Anko是JetBrains开发的一个强大的库。它主要的目的是用来替代以前XML的方式
来使用代码生成UI布局。这是一个很有趣的特性,我推荐你可以尝试下,但是我在
这个项目中暂时不使用它。对于我(可能是由于多年的UI绘制经验)来说使用XML
更容易一些,但是你会喜欢那种方式的。

然而,这个不是我们能在这个库中得到的唯一一个功能。Anko包含了很多的非常有
帮助的函数和属性来避免让你写很多的模版代码。我们将会通过本书见到很多例
子,但是你应该快速地认识到这个库帮你解决了什么样的问题。

尽管Anko是非常有帮助的,但是我建议你要理解这个背后到底做了什么。你可以在
任何时候使用 ctrl + 点击 (Windows)或者 cmd + 点击 (Mac)的方式跳转
到Anko的源代码。Anko的实现方式对学习大部分的Kotlin语言都是非常有帮助的。

开始使用Anko

在之前,让我们来使用Anko来简化一些代码。就像你将看到的,任何时候你使用了
Anko库中的某些东西,它们都会以属性名、方法等方式被导入。这是因为Anko使
用了扩展函数在Android框架中增加了一些新的功能。我们将会在以后看到扩展函
数是什么,怎么去编写它。

在 MainActivity:onCreate ,一个Anko扩展函数可以被用来简化获取一个
RecyclerView:

val forecastList: RecyclerView = find(R.id.forecast_list)

我们现在还不能使用库中更多的东西,但是Anko能帮助我们简化代码,比如,实例
化Intent,Activity之间的跳转,Fragment的创建,数据库的访问,Alert的创建……
我们将会在实现这个App的过程中学习到很多有趣的例子。

扩展函数

扩展函数数是指在一个类上增加一种新的行为,甚至我们没有这个类代码的访问权
限。这是一个在缺少有用函数的类上扩展的方法。在Java中,通常会实现很多带有
static方法的工具类。Kotlin中扩展函数的一个优势是我们不需要在调用方法的时候
把整个对象当作参数传入。扩展函数表现得就像是属于这个类的一样,而且我们可
以使用 this 关键字和调用所有public方法。

举个例子,我们可以创建一个toast函数,这个函数不需要传入任何context,它可以
被任何Context或者它的子类调用,比如Activity或者Service:

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

这个方法可以在Activity内部直接调用:

toast("Hello world!")
toast("Hello world!", Toast.LENGTH_LONG)

当然,Anko已经包括了自己的toast扩展函数,跟上面这个很相似。Anko提供了一
些针对 CharSequence 和 resource 的函数,还有两个不同的toast和longToast方
法:

toast("Hello world!")
longToast(R.id.hello_world)

扩展函数也可以是一个属性。所以我们可以通过相似的方法来扩展属性。下面的例
子展示了使用他自己的getter/setter生成一个属性的方式。Kotlin由于互操作性的特
性已经提供了这个属性,但理解扩展属性背后的思想是一个很不错的练习:

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

扩展函数并不是真正地修改了原来的类,它是以静态导入的方式来实现的。扩展函
数可以被声明在任何文件中,因此有个通用的实践是把一系列有关的函数放在一个
新建的文件里。

这是Anko功能背后的魔法。现在通过以上,你也可以自己创建你的魔法。

执行一个请求

对于感受我们要实现的想法而言,我们目前的文本是很好开始,但是现在是时候去
请求一些显示在RecyclerView上的真正的数据了。我们将会使用OpenWeatherMap
API来获取数据,还有一些普通类来现实这个请求。多亏Kotlin非常强大的互操作
性,你可以使用任何你想使用的库,比如用Retrofit来执行服务器请求。当只是执行
一个简单的API请求,我们可以不使用任何第三方库来简单地实现。

而且,如你所见,Kotlin提供了一些扩展函数来让请求变得更简单。首先,我们要
创建一个新的Request类:

public class Request(val url: String) {
    public fun run() {
        val forecastJsonStr = URL(url).readText()
        Log.d(javaClass.simpleName, forecastJsonStr)
    }
}

我们的请求很简单地接收一个url,然后读取结果并在logcat上打印json。实现非常
简单,因为我们使用 readText ,这是Kotlin标准库中的扩展函数。这个方法不推
荐结果很大的响应,但是在我们这个例子中已经足够好了。

如果你用这些代码去比较Java,你会发现我们仅使用标准库就节省了大量的代码。
比如 HttpURLConnection 、 BufferedReader 和需要达到相同效果所必要的迭
代结果,管理连接状态、reader等部分的代码。很明显,这些就是场景背后函数所
作的事情,但是我们却不用关心。

为了可以执行请求,App必须要有Internet权限。所以需要
在 AndroidManifest.xml 中添加:

<uses-permission android:name="android.permission.INTERNET" />

在主线程以外执行请求

如你所知,HTTP请求不被允许在主线程中执行,否则它会抛出异常。这是因为阻
塞住UI线程是一个非常差的体验。Android中通用的做法是使用 AsyncTask ,但是
这些类是非常丑陋的,并且使用它们无任何副作用地实现功能也是非常困难的。如
果你使用不小心, AsyncTasks 会非常危险,因为当运行到 postExecute 时,如
果Activity已经被销毁了,这里就会崩溃。

Anko提供了非常简单的DSL来处理异步任务,它满足大部分的需求。它提供了一个
基本的 async 函数用于在其它线程执行代码,也可以选择通过调用 uiThread 的
方式回到主线程。在子线程中执行请求如下这么简单:

async() {
    Request(url).run()
    uiThread { longToast("Request performed") }
}

UIThread 有一个很不错的一点就是可以依赖于调用者。如果它是被一
个 Activity 调用的,那么如果 activity.isFinishing() 返回 true ,
则 uiThread 不会执行,这样就不会在Activity销毁的时候遇到崩溃的情况了。
假如你想使用 Future 来工作, async 返回一个Java Future 。而且如果你需
要一个返回结果的 Future ,你可以使用 asyncResult 。

真的很简单,对吧?而且比 AsyncTasks 更加具有可读性。现在,我仅仅给请求
发送了一个url,来测试我们是否可以正确接收内容,这样我们才能在Activity中把它
画出来。我很快会讲到怎么去进行json解析和转换成app中的数据类,但是在我们
继续之前,学习什么是数据类也是很重要的。

检查代码并审查url请求和包结构的代码。你可以运行app并且确保你可以在打印的
json日志和请求完毕之后的toast。

上一篇 下一篇

猜你喜欢

热点阅读