今天继续之前字节面试的另外一个问题 --- 伴生对象。
----------分界线:面试问题----------
请描述一下,Scala中伴生对象编译成.class文件后与伴生类的关系。
----------分界线:背景知识----------
在开始面试问题的详解前,我们需要先了解以下的背景知识:
-
Scala的编译过程大致分为几步?
-
什么是伴生对象及伴生类?
-
什么时候存在伴生对象?
1. Scala的编译过程大致分为几步?
Scala是Java包装的一种语言;相对Java来说写法要简化很多。Scala的编译也是基于JVM的。下图[Ref -1]是Scala与Java编译过程的一个简易比较。后面会专门找一期写Scala的编译过程;当前仅需要知道一点: .scala文件跟.java的编译过程相似,通过各自的编译器都会被编译成.class文件。伴生类A会被编译成A.class,其伴生对象会被编译成A$.class。
2. 什么是伴生对象以及伴生类?
简要来说,在同一个.scala文件中,如果一个class类与一个object单例类完全同名,那么这个object就是这个class的伴生对象,而这个class就是这个object的伴生类。反之,若一个object没有伴生类(与它同名的class),则这个object被称为孤立对象。
那么伴生对象有什么用?它又有什么特点呢?
熟悉Scala语法的小伙伴可能注意到了。与Java不同,Scala中没有static关键字,而为了达到与static一样的效果,Scala向我们展示了一种新的方法,就是object,object里面的属性跟方法可以理解为就是static的。
1. 从语法角度来讲,所谓的伴生对象其实就是类的静态方法和成员的集合
2. 从技术角度来讲,scala 还是没有生成静态的内容,只不过是将伴生对象生成了一个新的类,实现属性和方法的调用。
3. 从底层原理看,伴生对象实现静态特性是依赖于 public static final MODULE$ 实现的。
伴生类跟伴生对象的一个特点是它们之间的私有成员(除了被用private[this]修饰的外)是可以相互访问的。
3. 什么时候存在伴生对象?
这个问题,不多解释直接测试给结论。
Scala中存在的几种类型,class/object/case class。这三种类型中,哪些默认存在伴生对象/伴生类呢?
我们首先先依次定义以下三个类型然后在sbt中编译
object CompanionTestObject {
var objectVal: Int = 1
val objectVar: Int = 2
def objectFunc: Int = objectVar + objectVal
}
class CompanionTestClass {
var classVal: Int = 1
val classVar: Int = 2
def classFunc: Int = classVar + classVal
}
case class CompanionTestCaseClass(var caseVar: Int = 1, caseVal: Int = 2) {
def caseFunc: Int = caseVar + caseVal
}
编译后(Intellij一般默认将.class文件输出到target\scala-2.13\classes\),我们可以看到有以下.class文件
文章之前提到过,伴生对象的.class会有$的尾缀。所以可以得出结论:
object跟case class在编译后会同时生成伴生类跟伴生对象,一般的class则不会默认生成伴生对象。
----------分界线:以下是实际面试问题解析----------
请描述一下,Scala中伴生对象编译成.class文件后与伴生类的关系。
先给结论:
Scala 的伴生对象和伴生类,从编译的角度看是独立的 。只是它们用了共同的名字。两者实际上只是共享了一个类名,而伴生对象替伴生类保存着一些 “静态”变量和方法,充当着仓库的作用 。
为了理解上述结论,再来做一个实验。我们对背景问题3的代码稍作修改,给CompanionTestClass 加一个伴生对象,然后查看反编译的结果。
class CompanionTestClass {
var classVal: Int = 1
val classVar: Int = 2
def classFunc: Int = classVar + classVal
}
object CompanionTestClass { // 新增CompanionTestClass的伴生对象
def classCompanionDef(): Unit = println("this is a companion for a class")
}
反编译.class的结果如下:
伴生类CompanionTestClass.class
package test;
import scala.reflect.*;
@ScalaSignature(bytes = "\u0006\u0005E2A\u0001D\u0007\u0001!!)q\u0003\u0001C\u00011!91\u0004\u0001a\u0001\n\u0003a\u0002b\u0002\u0011\u0001\u000