Scala学习——对象

duanhengbin 2018-10-07

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类中的未定义方法。

上面的演示我们得知:

  1. Scala中抽象类的定义和Java一样用abstract关键字,继承和Java一样用extends关键字
  2. 和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")

Scala学习——对象

相关推荐

kekeromer / 0评论 2020-04-30