Scala 应用程序代码
Converter 应用程序的代码非常简单 — 不管用什么语言编写。当然,用 Java 编写起来非常容易,但是用 Scala 编写也同样不复杂。首先我们看看前面见过的 UI 背后的代码。
视图背后的代码
解释创建 UI 的 Scala 代码的最简单方式是先看看代码,然后走查一遍。对于任何应用程序,都是在应用程序的 AndroidManifest.xml 文件中定义应用程序的默认活动。任何 UI 背后都有一个 Activity 类,默认的 Activity 定义当应用程序初次装载时执行的 Activity 类。对于像本文这样简单的应用程序,有一个 Converter 类,清单 4 中显示了它的源代码。
清单 4. Converter 活动类
class Converter extends Activity{ import ConverterHelper._ private[this] var amountValue:EditText = null private[this] var uom:Spinner= null private[this] var convertButton:Button = null private[this] var resultValue:TextView = null override def onCreate(savedInstanceState:Bundle){ super.onCreate(savedInstanceState) setContentView(R.layout.main) uom = findViewById(R.id.uom_value).asInstanceOf[Spinner] this.setUomChoice(ENGLISH) amountValue = findViewById(R.id.amount).asInstanceOf[EditText] convertButton = findViewById(R.id.convert_button).asInstanceOf[Button] resultValue = findViewById(R.id.result_value).asInstanceOf[TextView] convertButton.setOnClickListener( () => { val unit = uom.getSelectedItem.asInstanceOf[String] val amount = parseDouble(amountValue.getText.toString) val result = UnitsConverter.convert(Measurement(unit,amount)) resultValue.setText(result) }) } override def onCreateOptionsMenu(menu:Menu) = { super.onCreateOptionsMenu(menu) menu.add(NONE, 0, 0, R.string.english_units) menu.add(NONE, 1, 1, R.string.metric_units) true } override def onMenuItemSelected(featureId:Int, item:MenuItem) = { super.onMenuItemSelected(featureId, item) setUomChoice(if (item.getItemId == 1) METRIC else ENGLISH) true } private def setUomChoice(unitOfMeasure:UnitsSystem){ if (uom == null){ uom = findViewById(R.id.uom_value).asInstanceOf[Spinner] } val arrayId = unitOfMeasure match { case METRIC => R.array.metric_units case _ => R.array.english_units } val units = new ArrayAdapter[String](this, R.layout.spinner_view, getResources.getStringArray(arrayId)) uom.setAdapter(units) } } |
我们从这个类的顶部开始。它扩展 android.app.Activity。这是一个 Java 类,但是从 Scala 中可以对 Java 类轻松地进行细分。接下来,它有一些实例变量。每个实例变量对应前面定义的一个 UI 元素。注意,每个实例变量还被限定为 private[this]。这演示了 Scala 中特有的一种访问控制级别,而 Java 语言中不存在这种访问控制。这些变量不仅是私有的,而且只属于 Converter 类的特定实例。这种级别的访问控制对于移动应用程序来说有些大材小用,但是如果您是一名 Scala 开发人员,可以放心地在 Android 应用程序上使用您熟悉的语法。
回到清单 4 中的代码,注意,我们覆盖了 onCreate 方法。这是 Activity 类中定义的方法,通常被定制的 Activity 覆盖。如果用 Java 语言编写该代码,那么应该添加一个 @Override 标注。在 Scala 中,override 是一个关键词,用于确保正确性。这样可以防止误拼方法名之类的常见错误。如果误拼了方法名,Scala 编译器将捕捉到方法名并返回一个错误。注意,在这个方法上,以及任何其他方法上,不需要声明返回类型。Scala 编译器可以轻松推断出该信息,所以不需要多此一举。
onCreate 中的大部分代码类似于 Java 语言编写的代码。但是有几点比较有趣。注意,我们使用 findViewById 方法(在 Activity 子类中定义)获得不同 UI 元素的句柄。这个方法不是类型安全的,需要进行类型转换(cast)。在 Scala 中,要进行类型转换,可使用参数化方法 asInstanceOf[T],其中 T 是要转换的类型。这种转换在功能上与 Java 语言中的转换一样。不过 Scala 有更好的语法。接下来,注意对 setUomChoice 的调用(稍后我们将详细谈到这个方法)。最后,注意上述代码获得一个在布局 XML 中创建的按钮的句柄,并添加一个单击事件处理程序。
如果用 Java 语言编写,那么必须传入 Android 接口 OnClickListener 的一个实现。这个接口只定义一个方法:onClick。实际上,您关心的只是那个方法,但是在 Java 语言中无法直接传入方法。而在 Scala 中则不同,在 Scala 中可以传入方法字面量(literal)或闭包。在这里,我们用语法 () => { ... } 表示闭包,其中方法的主体就是花括号中的内容。开始/结束括号表示一个不带参数的函数。但是,我将这个闭包传递到 Button 的一个实例上的 setOnClickListener 方法,Button 是 Android SDK 中定义的一个 Java 类。如何将 Scala 闭包传递到 Java API?我们来看看。
Android 上的函数式编程
为了理解如何让 Android API 使用函数字面量,看看 Converter 类定义的第一行。这是一条重要的语句。这是 Scala 的另一个很好的特性。您可以在代码的任何地方导入包、类等,它们的作用域限于导入它们的文件。在这里,我们导入 ConverterHelper 中的所有东西。清单 5 显示 ConverterHelper 代码。
清单 5. ConverterHelper
object ConverterHelper{ import android.view.View.OnClickListener implicit def funcToClicker(f:View => Unit):OnClickListener = new OnClickListener(){ def onClick(v:View)=f.apply(v)} implicit def funcToClicker0(f:() => Unit):OnClickListener = new OnClickListener() { def onClick(v:View)=f.apply} } |
这是一个 Scala 单例(singleton),因为它使用对象声明,而不是类声明。单例模式被直接内置在 Scala 中,可以替代 Java 语言中的静态方法或变量。在这里,这个单例存放一对函数:funcToClicker 和 funcToClicker0。这两个函数以一个函数作为输入参数,并返回 OnClickListener 的一个实例,OnClickListener 是 Android SDK 中定义的一个接口。例如,funcToClicker 被定义为以一个函数 f 为参数。这个函数 f 的类型为带一个 View 类型(Android 中的另一个类)的输入参数的函数,并返回 Unit,它是 void 在 Scala 中的对等物。然后,它返回 OnClickListener 的一个实现,在这个实现中,该接口的 onClick 方法被实现为将输入函数 f 应用到 View 参数。另一个函数 funcToClick0 也做同样的事情,只是以一个不带输入参数的函数为参数。
这两个函数(funcToClicker 和 funcToClicker0)都被定义为隐式函数(implicit)。这是 Scala 的一个方便的特性。它可以让编译器隐式地将一种类型转换成另一种类型。在这里,当编译器解析 Converter 类的 onCreate 方法时,它遇到一个 setOnClickListener 调用。这个方法需要一个 OnClickListener 实例。但是,编译器却发现一个函数。在报错并出现编译失败之前,编译器将检查是否存在隐式函数,允许将函数转换为 OnClickListener。由于确实还有这样的函数,所以它执行转换,编译成功。现在,我们理解了如何使用 Android 中的闭包,接下来更仔细地看看应用程序逻辑 — 特别是,如何执行单位转换计算。
单位转换和计算
我们回到清单 4。传入 onClickListener 的函数收到用户输入的度量单位和值。然后,它创建一个 Measurement 实例,并将该实例传递到一个 UnitsConverter 对象。清单 6 显示相应的代码。
清单 6. Measurement 和 UnitsConverter
case class Measurement(uom:String, amount:Double) object UnitsConverter{ // constants val lbToKg = 0.45359237D val ozToG = 28.3495231 val fOzToMl = 29.5735296 val galToL = 3.78541178 val milesToKm = 1.609344 val inchToCm = 2.54 def convert (measure:Measurement)= measure.uom match { case "Fahrenheit" => (5.0/9.0)*(measure.amount - 32.0) + " C" case "Pounds" => lbToKg*measure.amount + " kg" case "Ounces" => ozToG*measure.amount + " g" case "Fluid Ounces" => fOzToMl*measure.amount + " mL" case "Gallons" => galToL*measure.amount + " L" case "Miles" => milesToKm*measure.amount + " km" case "Inches" => inchToCm*measure.amount + " cm" case "Celsius" => (9.0/5.0*measure.amount + 32.0) + " F" case "Kilograms" => measure.amount/lbToKg + " lbs" case "Grams" => measure.amount/ozToG + " oz" case "Millileters" => measure.amount/fOzToMl + " fl. oz." case "Liters" => measure.amount/galToL + " gallons" case "Kilometers" => measure.amount/milesToKm + " miles" case "Centimeters" => measure.amount/inchToCm + " inches" case _ => "" } } |
Measurement 是一个 case 类。这是 Scala 中的一个方便的特性。用 “case” 修饰一个类会导致这个类生成这样一个构造函数:这个构造函数需要类的属性,以及 equals、 hashCode 和 toString 的实现。它对于像 Measurement 这样的数据结构类非常适合。它还为定义的属性(在这里就是 uom 和 amount)生成 getter 方法。也可以将那些属性定义为 vars(可变变量),然后也会生成 setter 方法。仅仅一行 Scala 代码可以做这么多事情!