Scala学习——对象
Scala中对象本质上可以拥有类的所有特质,甚至可以扩展其他类或特质。但有一个例外:你不能提供构造器参数
1.单例对象
在Scala中没有静态方法和静态字段,但是我们可以用object语法来达到相同的目的。对象定义了某个类的单个实例:
object Util{ private val str = "Hello World"; def out(){ println(str); } }
在解释器中运行:
可以看到,其效果与Java中的静态方法一样。
对象的构造器在该对象第一次被使用时调用。如果一个对象从未被使用,器构造器也不会执行。
注:Scala的编译器可以编译txt格式的scala代码。
那么,为什么object可以有这样类似于Java中的静态的效果呢?我们来看下Util对象编译后的class文件:
我们将Util对象的代码进行编译,之后生成了两个class文件Util.class 和 Util$.class ,我们分别查看他们都是什么:
首先Util.class中我们看到有一个静态方法out
Util$.class是一个单例模式的类,里面有我们Scala的Util对象的str成员,由于str为val,所以Util$.class这里只有str的读方法str()
我们来反编译Util.class与Util$.class,这样可以更加直观
这里Util.class的out()方法调用Util$.class的out()方法
注:Scala的对象编译完成后,会生成对应的Java class。其中方法都是静态方法,非静态成员对应到生成的单例类中。
说明:因为Scala对象是单例的这一特性,因此在程序中任何需要使用单例的地方,你都可以用Scala对象实现。
通过上面一系列的分析,我们清楚了Scala如何通过对象,来实现Java中静态变量的效果,因为他的底层就是通过java的静态方法实现的。
2.伴生对象
在Java或C++中,你通常会用到既有静态放大又有普通方法的类。在Scala中,你可以通过类和与类同名的“伴生对象”来达到相同的目的。
伴生对象要求类名和object名称相同,并且在同一个Scala文件中定义。
我们来定义一个伴生对象:
class Car{ def stop(){ println("stop..."); } } object Car{ def run(){ println("run...") } }
接下来我们分别执行类的方法和伴生对象的方法,对比差异。
我们看到伴生对象中的run()方法可以不用实例化直接运行(那当然,他是静态的方法),二类中的stop()方法不能直接运行,需要先实例化才能运行。
接下来我们看下上面的代码编译后是什么结构。
同样生成了Car.class与Car$.class,我们分别用javap命令看下这两个class的结构
在Car.class中出现了两个方法,一个是静态方法run(),也就是我们在伴生对象中定义的方法。另一个是普通方法stop(),使我们在类中定义的方法
Car$.class还是一个单例,由于我们的代码中没有定义普通成员,因此这里很干净。
反编译class文件:
说明:伴生对象可以被访问,但并不在作用于当中。举例来说,类中必须通过 对象名.方法 的方式去访问,而不是直接调用对象名。
3.扩展类或特质的对象
给我的感觉就是Java中的继承。下面来用代码演示对象扩展类(对象一样):
我们定义了一个抽象的Animal类 和一个 继承了Animal类的对象Dog,Dog重写了Animal类中的未定义方法。
上面的演示我们得知:
- Scala中抽象类的定义和Java一样用abstract关键字,继承和Java一样用extends关键字
- 和Java一样抽象类不能被实例化
4.apply方法
我们通常会在对象中定义一个apply方法。当余姚如下表达式时,apply方法就会被调用:
Object(参数1...参数N)
通常,这样一个Apply方法返回的是一个伴生对象。
举例来说,Array数组对象定义了apply方法,让我们可以以这样的表达式返回一个数组对象
Array(1,2,3,4,5)
为什么不用关键字呢?因为对于嵌套表达式而言省去new关键字会方便很多,例如:
Array(Array(1,2),Array(2,3))
注意:Array(100)和new Array(100)很容易搞混。前一个表达式时调用了apply方法,返回了一个单元素(整数100)的Array[Int];而第二个表达式调用的是构造器this(100),结果是Array[Nothing],包含100个null元素
apply方法需要定义在对象中,如果类需要定义apply方法,则需要定义在它的伴生对象中。
5.应用程序对象
每个Scala程序都必须从一个对象的main方法开始,这个方法的类型为Array[String] => Unit:
object Hello{ def main(args: Array[String]) { println("Hello World") } }
由于Java中main是静态的方法,因此Scala中的main方法必须定义在对象中或伴生对象中。
将上面的代码编译后执行:
或者直接运行scala文件
Scala中除了使用main方法外,还有另一种方式实现相同的功能,通过扩展App特质,将代码写入构造方法体内:
object Hello extends App{ println("Hello World") }
再次编译效果相同
6.枚举
和Java或C++不同,Scala中并没有枚举类型。不过标准类库提供了一个Enumeration助手类,可以用于产出枚举。
我们可以通过下面3种方式构造枚举:
//统一构建 object MyEnum extends Enumeration{ val Red,Yellow,Green = Value } //与上面一样,只不过分开写 object MyEnum extends Enumeration{ val Red = Value; val Yellow = Value; val Green = Value; } //Value方法是一个重载方法,可以通过它定义枚举值得id,对应值等 object MyEnum extends Enumeration{ val Red = Value(0,"red");//设置枚举的id和对应值 val Yellow = Value(10);//设置枚举的id,值默认与成员同名 val Green = Value("gre");//设置枚举对应值,id默认前一个枚举的id+1 }
定义完成后,我们就可以用MyEnum.Red、MyEnum.Yellow来引用枚举值了。
注:枚举的类型是MyEnum.Value而不是MyEnum,后者是握有这些值的对象。
有人推荐引入一个类型别名,不过这样需要与import一起用才会有意义:
object MyEnum extends Enumeration{ type MyEnum = Value val Red,Yellow,Green = Value } import MyEnum._ def doWhat(color:MyEnum) = { if(color == Red) "stop" else "go" }
枚举值得ID可以通过方法id返回,名称通过toString方法返回。
通过values方法返回枚举值的集合:
for(c <- MyEnum.values) println(c.toString + " " + c.id)
最后你可以通过ID或者名称来进行查找定位枚举值对象:
MyEnum(0) MyEnum.withName("Red")