Scala 剖析 02: Val,Var和def

Scala 剖析 02: Val,Var和def,第1张

目录

前言

Scala 的类型定义

val 定义不可变变量

var 定义可变变量

def 定义变量


前言

scala 同 java 一样,运行在 JVM 上.其语法的灵活和易用可以理解为都是在 java 语言的基础上封装了丰富的语法糖.那scala 不同的语言特性"翻译"为 java 又是什么样子,Scala 剖析系列将会从零开始进行探索.

本文主要讲述了 scala 中变量定义部分和 java 的异同点.

Scala 的类型定义

众所周知,scala 中,类型定义分可变(var)与不可变(val)两种,但其实也可以像定义方法一样使用 def 来定义变量.也就是说以下三种定义变量的情况都是合法的

object Test {
    val immutableValue1 = 0
    var mutableValue1 = 0
    def defValue = 0
}

那这三种定义方式具体有什么异同呢?

val 定义不可变变量

首先看 val 定义变量的情况.原始代码如下

class Test {
    val immutableValue1 = 0
}

class 文件的反编译结果如下

public class Test {
    private final int immutableValue1 = 0;
    public int immutableValue1() {
        return this.immutableValue1;
    }
}

反编译结果中显示 immutableValue1 被显式的定义为了 final,并且提供了该变量的 getter.

看到这里,有些伙伴会脱口而出,scala 的 val 变量对应了 java 中的常量,因为反编译的结果都写了是以 final 修饰的,包括在之前的面试中很多求职者也是这么回答的.但从现在的反编译结果看,这个回答显然是片面的.

首先 immutableValue1 是以 private 修饰的,其他类在使用时只能通过同名的 getter 获取值而无法直接访问.这种访问方式上的差异主要原因在于 Java 中 Final 修饰的常量字段是不允许被继承的,而 scala 中 val 修饰的字段可以被继承.通过提供 private 变量和public 的 getter 这种方式可以很好的避免了"不可变"和"可继承"的冲突.

我们来看一个 val 变量被继承的例子.

class Father { val immutableValue1 = 0}
class Son extends Father {}

object Test{
    def main(args: Array[String]): Unit = {
        val son = new Son
        son.getClass.getMethods.foreach(println)
    }
}

打印 son 中全部的方法名如下

//继承自 Father 的方法
public int com.lwx.scala.test.Father.immutableValue1()

//继承自 Object 的方法
public final void java.lang.Object.wait(long,int)
public final native void java.lang.Object.wait(long)
public final void java.lang.Object.wait()
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

所以以后跟别人讨论 val 和 var 区别的问题,请把这个区别一并也说上.显得你很细.[旺财]

var 定义可变变量

有了 val 的编译经验,其实我们可以大体推断出 var 的具体实现.

val 是通过私有成员变量只提供 getter 的形式达到了不可变的效果,那 var 一定就是同时提供了 setter 和 getter.反编译之后的结果也如同我们的设想一样,getter的方法名还是同字段名,setter 的名字是"方法名_$eq"不做过多赘述.

有一个小的 tips 大家了解一下就好:当变量定义在class 中时,遵循私有化成员变量提供 public 的 getter 和 setter 的情况,当定义在 object 中时,变量会以相同的方式声明在 object 对应的 xxx$类(object)中,同时在 xxx 类(class)提供静态的 getter 和 setter.

scala 代码:

class Test{
    val immutableVal1 = 0;
}

object Test{
    val immutableVal2 = 0;
}

Test$.class 反编译后代码:

public final class Test$ {
    public static Test$ MODULE$;
    private final int immutableVal2;

    public int immutableVal2() {
        return this.immutableVal2;
    }

    private Test$() {
        MODULE$ = this;
        this.immutableVal2 = 0;
    }
}

可以看到类中定义了静态的本类实例和常规的 getter ,并不是静态的 getter.

Test.class 反编译后代码:

public class Test {
    private final int immutableVal1 = 0;

    public static int immutableVal2() {
        return Test$.MODULE$.immutableVal2();
    }

    public int immutableVal1() {
        return this.immutableVal1;
    }
}

object 中定义的变量的"静态"体现在类定义中的静态方法上,成员变量本身没有除了 private final 没有其他额外修饰.var 声明的变量雷同,只是多了 setter 方法的定义.

def 定义变量

在类体重,如果我们定义了名为 a 的变量,那么就不可以再定义名为 a 的方法. IDEA会提示 a 在这个领域内已经被定义了.

有了前边的铺垫,相信原因大家都能想到是字段 a 的 getter 和方法 a 重名了,所以方法名为 a 不合法.所以我们可以理解为 scala 中字段的定义和方法的定义其实是雷同的,因为编译为字节码时进行了一系列的封装.

前文提到了用 def 可以定义变量,而且还没有报错.那 def 定义变量又该如何解释?我们来看下代码

scala 代码:

object Test{
    def a = 0
    println(a)
}

反编译后的代码:

import scala.runtime.BoxesRunTime;

public final class Test$ {
    public static Test$ MODULE$;
    public int a() {
        return 0;
    }

    private Test$() {
        MODULE$ = this;
        scala.Predef$.MODULE$.println(BoxesRunTime.boxToInteger(a()));
    }
}

方法简单粗暴,相当于没有定义类变量,直接定义了一个方法,返回值就是等号右边的值.相当于 def a = 0 本质上还是定义了一个方法,只不过使用起来太方便给人一种好像是变量的错觉.

反编译结果里的BoxesRunTime.boxToInteger在Predef 类中有出现过,目的是为了避免类型Java 基础类型在类型转换时的隐患.

所以我们在平时编程时,将 def 当做定义变量的关键字也不是不行.但需要注意它本质上还是一个方法,在使用上存在一定的区别. 看如下代码

scala代码:

object Test{
    def main(args: Array[String]): Unit = {

        println("===def a ===")
        def a = Random.nextInt(10)
        println(a)
        println(a)
        println(a)

        println("===val b ===")
        val b = Random.nextInt(10)
        println(b)
        println(b)
        println(b)
    }
}

执行结果如下:

===def a ===
8
4
3

===val b ===
5
5
5

def 定义的 a 每次结果都不同,相当于每次都执行了随机取值,而 val 定义的 b 每次值都一样.

进一步印证了 a 是函数 b 是变量的观点,但在日常编程中还是很可能被忽视.

以上就是关于 scala 中 val,var 和 def 的一些探索,可以说细节拉满.感谢您的耐心阅读.

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/725217.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-26
下一篇 2022-04-26

发表评论

登录后才能评论

评论列表(0条)

保存