孤立对象是只有一个object关键字修饰的对象,该对象会编译成两个class文件,一个是以孤立对象的名字命名的class, 一个是以孤立对象的名字后面加上一个$ 字符命名的class, 这个class又叫做虚构类, 源码中的孤立对象中的字段和方法,都被编译成以孤立对象的名字命名的class中静态方法, 这些静态方法都会访问单例的虚构类对象。虚构类市传统意义上的单例模式, 并且在类初始化的时候, 就会创建唯一的对象, 源码中的所有字段和方法都会在虚构类中有相对应的成员.
伴生类和伴生对象. 所谓伴生对象,也是一个Scala中的单例对象,使用object关键字修饰, 除此之外,还有一个使用class关键字定义的同名类, 这个类和单例对象存在于同一个文件中,这个类就叫做这个单例对象的伴生类,相对来说, 这个单例对象叫做伴生类的伴生对象.
伴生类和伴生对象的简单例子:
class Test{
var field = "field"
def doSomeThing = println("do something")
}
object Test{
val a = "a string"
def printString = println(a)
}
此时多了一个伴生类, 伴生类中有一个字段field和一个方法doSomething.
编译这个文件, 同时生成了两个class, 一个Test.class和一个Test$.class, 这个Test $.class叫做虚构类.
下面来编译虚构类(Test $.class).
和上一篇的文章来对比一下, 发现虚构类没有任何变化, 源码中的单例对象(伴生对象)中的字段和方法都在虚构类中有相对应的字段和方法,并且会为单例对象中的字段生成相应的方法。要说明的重点的是: 虽然在这个示例中加入了伴生类, 并且伴生类中也有字段和方法,但是这个字段和方法并没有对应出现的虚构类中. 这也就说明, 虚构类中的信息只和单例对象(伴生对象)有关,单例对象(伴生对象)的伴生类不会影响虚构类中的内容.
下面反编译Test.class.
单例对象(伴生对象)中的每个字段或者方法,都对应Test类中的一个静态同名方法.从反编译的结果就可以看到,这些静态方法仍然存在. 这些方法如下:
public static void printString()
{
Test..MODULE$.printString();
}
public static String a()
{
return Test..MODULE$.a();
}
除了这两个静态方法之外, Test类中还存在一些其他的字段和方法, 这些字段和方法都是成员方法,而不是静态的。这些字段和方法如下:
public String field()
{
return this.field;
}
public void field_$eq(String x$1)
{
this.field = x$1;
}
private String field = "field";
public void doSomeThing()
{
Predef..MODULE$.println("do something");
}
这些字段和方法是和伴生类中的字段和方法相对应的.其中会为字段field添加相关方法public void field_&eq(String x$1) 和
public String field(). 为字段添加同名getter()方法和xxx_&eq这样的setter()方法. 是Scala编译器的默认行为。
总结一下:
1.伴生类中定义的字段和方法, 对应同名class类(如上例子中的Test.class)中的成员字段和成员方法
2. 伴生对象中的定义的字段和方法, 对应同名类(如上例子中的Test.class)中的静态方法,所以可以认为Scala中的object关键字是静态的另一个表示方式, 只是Scala将这些静态的东西也封装成了对象
3.伴生对象中定义的字段和方法,对应虚构类(Test$.class)中的成员字段和方法
4.同名类(如上例子中的Test.class)中的静态方法,会访问单例的虚构类对象, 将相关的逻辑调用到虚构类中的成员方法中.由于虚构类是单例的, 所以可以保证伴生对象中的字段都是唯一的. 也就是说虚构类的单例性,保证了 伴生对象(即scala中的object修饰的单例对象)中信息的唯一性.
5.我们在编写scala代码时,访问伴生类的内容,需要创建对象来访问, 访问伴生对象时, 将其内部所有内容直接作为静态, 通过类名. 的方式调用即可.
6.在scala中并没有真正静态的概念, 因为看起来像静态调用访问和调用的地方, 实际上是通过伴生对象自己的静态单例对象来访问和调用的.
注意下面的例子:
//伴生类
class Worker{
var name : String = "张少军"
def show() = {
println("hello scala")
}
def aa = "printn"
}
// 伴生对象
object Worker{
var x = 110
def apply(): Worker = new Worker()
def haha() = {
println("哈哈哈哈")
var zhangshaojun = "张少军"
println(Worker.x)
def test() = {
println("hello world")
}
}
}
编译生成两个class文件, 一个Worker.class, 一个是Worker$.class
先反编译Worker.class
再编译Worker $.class
可以看出伴生类中定义的字段和方法, 在同名类中(Worker.class)中有着相对应的字段和方法,在虚构类中不会出现相对应的字段和方法. 伴生对象中定义的字段和方法,对应虚构类(Worker$.class)中的成员字段和方法. 伴生对象中的定义的字段和方法, 对应同名类(如上例子中的Worker.class)中的静态方法.
注意的是: 在伴生对象中在haha方法里定义了一个test方法. 方法里还有方法, 方法的嵌套, 从反编译中可以看出的是, test方法并没有出现在同名类(Worker.class)中, 而是出现在虚构类(Worker $.class)中, 而且还是静态私有方法. 调用该方法直接通过方法名的形式来调用。 在haha方法里定义了zhangshaojun变量, 类型为String. 值为"张少军"。 从反编译中可以看出的是: 该变量并没有出现在同名类(Worker.class)中, 而是出现在虚构类中test方法里面, 并没有出现在haha方法外面, 该变量也没有类似于getter方法(public String zhangshaojun() )和setter方法(public void zhangshaojun_ $eq(int zhangshaojun$1) ) , zhangshaojun变量只出现在方法体内部. 只在haha方法局部有效.
总结一下:
1.在伴生对象中如果在方法里出现字段, 那么在同名类中没有对应的静态方法, 只会出现在虚构类中方法里的内部,在虚构类中没有对应的getter方法和setter方法.
2.在伴生对象中如果在方法里出现方法, 那么在同名类中没有对应的静态方法, 只会出现在虚构类中。 并且还是静态私有方法.
在上一篇学习Scala:伴生对象和伴生类之间的关系(一)后面知道了, Scala是如何实现对单例对象的调用的.首先写一个Scala的入口类
从图中可以看出由此可见, 在Main $中的成员方法main中,直接调用了Test $.MODULE $.printString()方法, 而绕过了Test类,这也是合理的, 因为只有Test $才处理相关逻辑. Test.class里的代码压根就没有执行过. 为什么?? 我向一位博主请教了一下。他的回答是这样的: 虽然在这个代码中不用用到,但是在其他场景可能会用到或者在更复杂的调用逻辑里,可能就用到了, 也有可能是scala编译器生成的冗余代码。 我想应该是这样的!!
相关内容可以看下面的文章:
https://blog.csdn.net/u012443641/article/details/89921243
https://blog.csdn.net/zhangjg_blog/article/details/22760957
https://blog.csdn.net/zhangjg_blog/article/details/23376465
https://blog.csdn.net/zhangjg_blog/article/details/23462695
https://blog.csdn.net/zhangjg_blog/article/details/22821047
https://blog.csdn.net/zhangjg_blog/article/details/22903473
来源:CSDN
作者:Dusk Till Dawn
链接:https://blog.csdn.net/qq_44160357/article/details/103752216