Scala之Object的具体使用(小结)
一、前言
前面学习了scala的methods,接着学习scala中的object
二、object
object在scala有两种含义,在java中,其代表一个类的实例,而在scala中,其还是一个关键字,本篇首先将会把object当成一个类的实例看待,展示如何将对象从一种类型转化为另一种类型,之后将展示如何创建单例对象,scala中还存在包对象,在scala中,经常有如下定义
type throwable = java.lang.throwable type exception = java.lang.exception type error = java.lang.error type seq[+a] = scala.collection.seq[a] val seq = scala.collection.seq
使用type定义可以使得代码更为简洁,可使用伴生对象来创建静态方法,并且伴生对象可以使得在创建类对象时不需要使用new关键字,如下所示
val siblings = list(person("kim"), person("julia"), person("kenny"))
2.1 对象转化
1. 问题描述
你需要将一个类的实例从一种类型转化为另一种类型,如动态创建对象
2. 解决方案
使用asinstanceof方法进行类型转化,如下的lookup方法返回的对象将给转化为recognizer对象
val recognizer = cm.lookup("recognizer").asinstanceof[recognizer]
以上代码在java中如下
recognizer recognizer = (recognizer)cm.lookup("recognizer");
asinstanceof方法是定义在any类中的,所以任何类中都可以使用该方法
3. 讨论
在动态编程中,经常需要从一个类转化为另一个类,如在spring框架中使用applicationcontext文件来初始化bean
// open/read the application context file val ctx = new classpathxmlapplicationcontext("applicationcontext.xml") // instantiate our dog and cat objects from the application context val dog = ctx.getbean("dog").asinstanceof[animal] val cat = ctx.getbean("cat").asinstanceof[animal]
在进行数字类型转化时,也可以使用asinstanceof方法
当需要与java进行交互时,也可以使用asinstanceof方法
val objects = array("a", 1) val arrayofobject = objects.asinstanceof[array[object]] ajavaclass.sendobjects(arrayofobject)
与java类似,类型转化可能会抛出classcastexception异常
可以使用try/catch来解决此问题
2.2 与java的.class对应的方法
1. 问题描述
当一个api需要你传递class对象,在java中,你可以使用.class,但是在scala则行不通
2. 解决方案
使用scala的classof方法,如下所示
val info = new dataline.info(classof[targetdataline], null)
在java中则使用如下
info = new dataline.info(targetdataline.class, null);
classof方法定义在predef对象中,因此可以在没有import情况直接使用
3. 讨论
该方法可以让你开始学习反射,如下示例可以访问string类的方法
2.3 确定对象的类
1. 问题描述
在scala中,你不需要显示的声明类型,你偶尔也想要打印一个对象的类或类型以明白scala的工作机制
2. 解决方案
你可以使用对象的getclass方法来确定scala为你自动赋值的类型,如当需要了解可变参数的工作流程时,可以调用getclass方法,得知不同情况,类型也不相同
def printall(numbers: int*) { println("class: " + numbers.getclass) }
当使用多个参数调用printall方法和不使用参数调用printall方法,其结果不相同
当处理scala的xml库时,该方法非常有效,可以知道在不同情形下所处理的类,如下,<p>标签下包含了一个子类
当在<p>标签中添加<br/>标签后,其结果如下
3. 讨论
若在ide中无法得知对象的类型,可以使用getclass方法来获取对象类型
2.4 使用对象启动应用
1. 问题描述
你想要使用main方法来启动一个应用,或者为脚本提供一个入口
2. 解决方案
启动应用有两种方法,其一让类继承app,其二是定义一个对象并定义main方法
对于第一种方法,其通用做法如下
object hello extends app { println("hello, world") }
此时object内的语句会自动执行,第二种方法是定义main方法
object hello2 { def main(args: array[string]) { println("hello, world") } }
3. 讨论
上述两种方法中,都是通过object来启动应用的
2.5 使用object创建单例
1. 问题描述
你想要创建一个单例对象
2. 解决方案
使用object关键字来创建单例对象
object hello2 { def main(args: array[string]) { println("hello, world") } }
由于cashregister被定义为成object,因此仅仅只有一个实例,被调用的方法就相当于java中的静态方法,调用如下
object main extends app { cashregister.open cashregister.close }
在创建工具方法时,该方法同样有效
import java.util.calendar import java.text.simpledateformat object dateutils { // as "thursday, november 29" def getcurrentdate: string = getcurrentdatetime("eeee, mmmm d") // as "6:20 p.m." def getcurrenttime: string = getcurrentdatetime("k:m aa") // a common function used by other date/time functions private def getcurrentdatetime(datetimeformat: string): string = { val dateformat = new simpledateformat(datetimeformat) val cal = calendar.getinstance() dateformat.format(cal.gettime()) } }
由于方法是定义在object中,因此可以直接使用dateutils来调用这些方法,如同java中调用静态方法
dateutils.getcurrentdate dateutils.getcurrenttime
在使用actors时,单例对象可以产生可重用的消息,如果你有可以接受和发送消息的actor,你可以使用如下方法创建单例
case object startmessage case object stopmessage
这些对象将会被作为消息,并且可被传递到actor中
inputvalve ! stopmessage outputvalve ! stopmessage
3. 讨论
当使用伴生对象时,一个类就既可以有非静态方法又可以有静态方法
2.6 使用伴生对象创建静态成员
1. 问题描述
你想要为一个类创建实例方法和类方法,但是scala中没有static关键字
2. 解决方案
在class中定义非静态成员,在object中定义静态成员,对象与类要有相同的名字并且位于同一个文件中,该对象称为伴生对象
使用该方法可以让你创建静态成员(字段和方法)
// pizza class class pizza (var crusttype: string) { override def tostring = "crust type is " + crusttype } // companion object object pizza { val crust_type_thin = "thin" val crust_type_thick = "thick" def getfoo = "foo" }
pizza类和pizza对象在同一个文件中(pizza.scala),pizza对象中的成员等效于java类中的静态成员
println(pizza.crust_type_thin) println(pizza.getfoo)
你也可按照常规方法创建pizza对象
var p = new pizza(pizza.crust_type_thick) println(p)
3. 讨论
class和object具有相同的名称并且在同一个文件中,class中定义的是非静态成员,object中定义的是静态成员
class和其伴生对象可以互相访问对方的私有成员,如下面object的double方法可以访问class中的私有变量secret
class foo { private val secret = 2 } object foo { // access the private class field 'secret' def double(foo: foo) = foo.secret * 2 } object driver extends app { val f = new foo println(foo.double(f)) // prints 4 }
如下的class类中的非静态方法可以访问伴生对象中的静态私有变量
class foo { // access the private object field 'obj' def printobj { println(s"i can see ${foo.obj}") } } object foo { private val obj = "foo's object" } object driver extends app { val f = new foo f.printobj }
2.7 将常用代码放在包对象中
1. 问题描述
你想要使方法、字段和其他代码处于包级别,而不需要class或者object
2. 解决方案
将代码放置在包对象下面,如将你的代码放置在package.scala文件中,例如,如果你想要代码被com.hust.grid.leesf.model包下所有类可用,那么创建一个位于com/hust/grid/leesf/model目录下的package.scala文件,在package.scala中,在包声明中移除model,并且以其作为名字来创建包,大致如下
package com.hust.grid.leesf package object model {
其他代码放置在model中,如下所示
package com.hust.grid.leesf package object model { // field val magic_num = 42 // method def echo(a: any) { println(a) } // enumeration object margin extends enumeration { type margin = value val top, bottom, left, right = value } // type definition type mutablemap[k, v] = scala.collection.mutable.map[k, v] val mutablemap = scala.collection.mutable.map }
此时,在com.hust.grid.leesf.model包下面类、对象、接口等可以随意访问上述定义的字段、方法等
package com.hust.grid.leesf.model object maindriver extends app { // access our method, constant, and enumeration echo("hello, world") echo(magic_num) echo(margin.left) // use our mutablemap type (scala.collection.mutable.map) val mm = mutablemap("name" -> "al") mm += ("password" -> "123") for ((k,v) <- mm) printf("key: %s, value: %s\n", k, v) }
3. 讨论
最疑惑的是将package对象放在哪里,其包名和对象名
如果你想要让你的代码在com.hust.grid.leesf.model包中可见,那么将package.scala放在com/hust/grid/leesf/model目录下,而在package.scala中,其包名应该如下
package com.hust.grid.leesf
然后使用model作为对象名
package object model {
最后大致如下
package com.hust.grid.leesf package object model {
包中可以存放枚举类型、常量和隐式转换
2.8 不使用new关键字来创建对象实例
1. 问题描述
当不使用new关键字来创建对象时,scala代码会显得相对简洁,如下所示
val a = array(person("john"), person("paul"))
2. 解决方案
有两种方式
- 为类创建伴生对象,然后定义apply方法,其签名与构造方法签名相同
- 将类定义为case类
为person对象定义了伴生对象,然后定义apply方法并接受参数
class person { var name: string = _ } object person { def apply(name: string): person = { var p = new person p.name = name p } }
现在你可以不使用new关键字来创建person对象了
val dawn = person("dawn") val a = array(person("dan"), person("elijah"))
scala编译器会对伴生对象中的apply进行特殊处理,让你不使用new关键字即可创建对象
将类定义为case类,并且接受相应参数
case class person (var name: string)
现在可以采用如下方法创建对象
val p = person("fred flinstone")
scala会为case类的伴生对象创建apply方法
3. 讨论
编译器会对伴生对象的apply做特殊处理,这是scala的语法糖
val p = person("fred flinstone")
上述代码会被转化为如下代码
val p = person.apply("fred flinstone")
apply方法是工厂方法,scala的此语法糖让你不用new关键字即可创建对象
可以在伴生对象中创建多个apply方法,这样相当于多个构造函数
class person { var name = "" var age = 0 } object person { // a one-arg constructor def apply(name: string): person = { var p = new person p.name = name p } // a two-arg constructor def apply(name: string, age: int): person = { var p = new person p.name = name p.age = age p } }
可以使用如下方法创建对象
val fred = person("fred") val john = person("john", 42)
为了给case类创建多个构造函数,需要知道case类背后的逻辑
当使用scala编译器编译case类时,你会发生其生成了两个文件,person$.class和person.class文件,当使用javap反编译person$.class文件时,其输出如下
其包含了一个返回person对象的apply方法
public person apply(java.lang.string);
string对应的是case类中的name
case class person (var name: string)
使用javap命令可以看到在person.class中为name生成的getter和setter函数
在如下代码中,存在case类和apply方法
// want accessor and mutator methods for the name and age fields case class person (var name: string, var age: int) // define two auxiliary constructors object person { def apply() = new person("<no name>", 0) def apply(name: string) = new person(name, 0) }
由于name和age都是var的,所以会生成getter和setter,在object中定义了两个apply函数,因此可以使用如下三种方式来生成person对象
object test extends app { val a = person() val b = person("al") val c = person("william shatner", 82) println(a) println(b) println(c) // test the mutator methods a.name = "leonard nimoy" a.age = 82 println(a) }
其结果如下
person(<no name>,0) person(al,0) person(william shatner,82) person(leonard nimoy,82)
2.9 使用apply来实现工厂方法
1. 问题描述
为了让子类声明应该创建哪种类型的对象,并且只在一处能够创建对象,你想要实现工厂方法
2. 解决方案
可以使用伴生对象的apply方法来实现工厂方法,你可将工厂实现算法放置在apply方法中
假设你想要创建一个animal工厂,并且返回cat和dog,在animal类的伴生对象中实现apply方法,你就可以使用如下方式创建不同对象
val cat = animal("cat") // creates a cat val dog = animal("dog") // creates a dog
首先需要创建一个animal的trait
trait animal { def speak }
然后在相同文件中创建伴生对象,创建实现animal的类,一个合适的apply方法
object animal { private class dog extends animal { override def speak { println("woof") } } private class cat extends animal { override def speak { println("meow") } } // the factory method def apply(s: string): animal = { if (s == "dog") new dog else new cat } }
然后就可以使用如下语句创建不同对象
val cat = animal("cat") // creates a cat val dog = animal("dog") // creates a dog
3. 讨论
如果不使用apply方法来实现工厂方法,也可以使用如下的getanimal方法来实现上述功能
// an alternative factory method (use one or the other) def getanimal(s: string): animal = { if (s == "dog") return new dog else return new cat }
然后可以使用如下方法创建不同对象
val cat = animal.getanimal("cat") // returns a cat val dog = animal.getanimal("dog") // returns a dog
以上两种方法都是可行的
三、总结
本篇学习了scala中的object及其相应的用法,其在scala的实际编程中应用也是非常广泛。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
下一篇: 面试中遇到的java逃逸分析问题