Scala学习笔记-伴生对象于孤立对象

Scala-伴生对象于孤立对象
Scala学习笔记-伴生对象于孤立对象_第1张图片

Scala虽然是基于JVM构建的,但与Java还是有很多区别,其实一个重要的区别是Scala比Java更面向对象(纯面向对象),最明显的用例是scala中所有的操作符都是方法(操作符面向的使用者是对象)。


伴生对象/孤立对象也是scala作为纯面向对象语言的一种体现。

孤立对象

先看一个例子

object Test{
    var a = "helloworld"
    def helloworld(): Unit = {
        println("helloworld")
    }
}

使用时,它给人的感觉类似下面的代码

public class Test {
    public static String a = "helloworld";
    public static void helloworld() {
        System.out.println("helloworld");
    }
}

所以,我们可以向下面的方式访问a字段或helloworld()方法

Test.a
Test.helloworld()

这会给人以上的错误,但实际情况并非如此,此处的Test实际上是一个对象(全局单例),看下面的例子

object Test{
    var a = "helloworld"

    def helloworld(): Unit = {
        println("helloworld")
    } 

    def main(args: Array[String]) {
        println(Test)
    }
}

在REPL中运行结果如下:

D:\> scalac Test.scala
D:\> scala Test
Test$@3eb25e1a
D:\>

到此,总结一下,对于object Test{...},我们在使用Test.fieldTest.method时,Test实际上是一个全局单例的对象,而不一个类,下面通过反编译的class文件看一下具体细节

scala源码

object Test{
    var a = "helloworld"

    def helloworld(): Unit = {
        println("helloworld")
    } 
}

编译后的Java代码

虚构类:

import scala.Predef.;

public final class Test$ {
  public static final  MODULE$;
  private String a;

  static
  {
    new ();
  }

  public String a()
  {
    return this.a; } 
  public void a_$eq(String x$1) { this.a = x$1; }

  public void helloworld() {
    Predef..MODULE$.println("helloworld");
  }
  private Test$() { MODULE$ = this;

    this.a = "helloworld";
  }
}

伴生类

public final class Test
{
  public static void helloworld()
  {
    Test..MODULE$.helloworld();
  }

  public static void a_$eq(String paramString)
  {
    Test..MODULE$.a_$eq(paramString);
  }

  public static String a()
  {
    return Test..MODULE$.a();
  }
}

从上面有缺陷的反编译代码中我们可以看出,在Test上的所有调用最后都作用到单例对象MODULE$上,整个逻辑机构如下

调入入口(Test.xxxx)
          ↓
伴生类(static方法)
          ↓
虚构类(单例对象MODULE$)



伴生对象

带有伴生类的孤立对象叫做伴生对象,伴生类与伴生对象有如下关系:

  • 伴生类源码与伴生对象源码必须在同一个.scala文件中

  • 伴生类与伴生对象名称必须相同


下面为孤立对象Test创建一个伴生类

import scala.beans.BeanProperty
class Test{
    @BeanProperty var name = "zhangsan"
    def func1() {
        println("func1")
    }
}

object Test{
    var a = "helloworld"

    def helloworld(): Unit = {
        println("helloworld")
    } 
}


有了前面的基础,直接分析编译后的源码

虚构类:

import scala.Predef.;

public final class Test$ {
  public static final  MODULE$;
  private String a;

  static
  {
    new ();
  }

  public String a()
  {
    return this.a; } 
  public void a_$eq(String x$1) { this.a = x$1; }

  public void helloworld() {
    Predef..MODULE$.println("helloworld");
  }
  private Test$() { MODULE$ = this;

    this.a = "helloworld";
  }
}

伴生类:

public class Test
{
  private String name = "zhangsan";

  public static void helloworld() { Test..MODULE$.helloworld(); } 
  public static void a_$eq(String paramString) { Test..MODULE$.a_$eq(paramString); } 
  public static String a() { return Test..MODULE$.a(); } 
  public String name() { return this.name; } 
  public void name_$eq(String x$1) { this.name = x$1; } 
  public void setName(String x$1) { this.name = x$1; } 
  public void func1() {
    Predef..MODULE$.println("func1");
  }

  public String getName()
  {
    return name();
  }
}

从上述反编译后的代码来看,对于同时具有伴生类和伴生对象的scala代码,编译后,仍然是两个class文件,在伴生对象中的属性和方法仍然按照前面的规则被编译,但在伴生类中的属性和方法有所不同,它被放到伴生类的class文件中,并且是非静态的,这就意味着这些属性和方法是实例相关的,这是符合预期的。

那么,对于Test来说

val test1 = new Test
val test2 = new Test

test1 != test2

对象test1test2是不同的示例,但是

Test.equals(Test) Test == Test

都为true,因为此处Test被指向了全局唯一的单例对象MODULE$

总结一下

  • 伴生对象的属性、方法仍指向全局单例对象MODULE$

  • 伴生类的属性、方法被定义为对象相关的。

  • 伴生对象通常被用在单例对象或工具方法的容器

你可能感兴趣的:(scala)