工作中偶然发现 Scala 构造方法中的参数, 无论是否有 val/var 修饰都可以顺利编译运行, 如下:
- class AA(name: String)
- class BB(val name: String)
那么两者的区别在哪里呢? 对于 case class 呢? 其区别又在哪里? 其应用场景又在哪里呢? 下面就辨析一下如下几个类的区别
- class AA(name: String)
- class BB(val name: String)
- class CC(var name: String)
- class DD(private val name: String)
- class EE(private[this] val name: String)
- case class FF(name: String)
- case class GG(val name: String)
- case class HH(var name: String)
- case class II(private val name: String)
- case class JJ(private[this] val name: String)
单纯的从代码中来看, 发现不了什么区别, 只是简单的多了一个 val 的修饰符. 为了一探究竟, 先对源码进行编译, 然后通过 javap 对其 class 文件进行反编译, 查看其与源码的区别.
一, 普通类构造器中 val/var 存在和不存在的区别
源码:
- class AA(name: String)
- class BB(val name: String)
- class CC(var name: String)
反编译结果:
- Compiled from "Test.scala"
- public class AA {
- public AA(java.lang.String);
- }
- Compiled from "Test.scala"
- public class BB {
- private final java.lang.String name;
- public java.lang.String name();
- public BB(java.lang.String);
- }
- Compiled from "Test.scala"
- public class CC {
- private java.lang.String name;
- public java.lang.String name();
- public void name_$eq(java.lang.String);
- public CC(java.lang.String);
- }
结论: 构造器中 val 修饰的参数, 编译之后会在该类中添加一个 private final 的全局常量, 同时提供一个用于获取该常量的 public 方法, 无设置方法.
构造器中 var 修饰的参数, 编译之后会在该类中添加一个 private 的全局变量, 同时提供两个获取和设置该变量值的 public 方法.
构造器中无修饰符的参数, 则该参数属于构造函数内的局部参数, 仅仅在该构造函数内部访问.
二, 普通类构造器中 private 和 private[this] 修饰参数的区别
源码:
- class DD(private val name: String)
- class EE(private[this] val name: String)
反编译结果:
- Compiled from "Test.scala"
- public class DD {
- private final java.lang.String name;
- private java.lang.String name();
- public DD(java.lang.String);
- }
- Compiled from "Test.scala"
- public class EE {
- public EE(java.lang.String);
- }
结论: private 修饰的构造器参数, 编译之后会在该类中添加一个 private final 的全局常量, 同时提供一个 private 的访问该参数的方法. 即该参数可在该类范围内访问.
private[this] 修饰的构造器参数, 不存在全局变量, 只能在该构造方法中访问, 在该类中无法访问. 同 class AA(name: String)
注意: Scala 整个类体就是其构造函数, 所以, 站在 Scala 角度看, private[this] 修饰的构造器参数能够在整个类中访问, 而站在 Java 角度看, 该参数仅仅能够在构造函数中访问, 在类中无法访问. 而站在 Scala 角度看, private[this] 和 private 的主要区别在于, private[this] 修饰的参数无法通过 e.name 的方式访问, 即使在该类的内部. 注意下图:
图中, 在 EE#show 中是无法访问 that.name 的, 即使 that 的类型本身就是 EE 也不行的. 这才是 private[this] 和 private 在 Scala 中的主要区别.
三, 普通 class 和 case class 的区别
源码:
1 case class FF(name: String)
编译之后会发现在文件夹下面多出两个 class 文件, 一个为 FF.class, 另一个为 FF$.class 文件. 对两个文件进行反编译
反编译结果:
- Compiled from "Test.scala"
- public class FF implements scala.Product,scala.Serializable {
- //private final 修饰的 name 常量
- private final java.lang.String name;
- //public 修饰获取 name 的方法, 可用于外部访问
- public java.lang.String name();
- //public 修饰的构造函数
- public FF(java.lang.String);
- public static scala.Option<java.lang.String> unapply(FF);
- public static FF apply(java.lang.String);
- public static <A> scala.Function1<java.lang.String, A> andThen(scala.Function1<FF, A>);
- public static <A> scala.Function1<A, FF> compose(scala.Function1<A, java.lang.String>);
- public FF copy(java.lang.String);
- public java.lang.String copy$default$1();
- public java.lang.String productPrefix();
- public int productArity();
- public java.lang.Object productElement(int);
- public scala.collection.Iterator<java.lang.Object> productIterator();
- public boolean canEqual(java.lang.Object);
- public int hashCode();
- public java.lang.String toString();
- public boolean equals(java.lang.Object);
- }
- Compiled from "Test.scala"
- public final class FF$ extends scala.runtime.AbstractFunction1<java.lang.String, FF> implements scala.Serializable {
- // 静态的 FF$ 对象
- public static FF$ MODULE$;
- // 构造函数为 private
- private FF$();
- // 返回 FF 对象的 apply 方法
- public FF apply(java.lang.String);
- public static {};
- public final java.lang.String toString();
- public scala.Option<java.lang.String> unapply(FF);
- private java.lang.Object readResolve();
- public java.lang.Object apply(java.lang.Object);
- }
分析:
先看 FF.class
1, 对比 class AA(name: String) 的结果来看, case class 自动实现了 scala.Product,scala.Serializable 两个特质 (接口), 因此, case class 类中自然也会实现这两个特质中的抽象方法 / 覆写一些方法 (11~22 行).
- public class AA
- public class FF implements scala.Product,scala.Serializable
2, 对比 public class BB, 在类内部都具有一个全局常量 name 和一个获取该常量的 public 方法, 同时两者都具有一个 public 的构造函数.
3, 实现了一些特质中的一些方法, 覆写了 Java Object 中的一些方法. 例如: andThen() toString() hashCode() 等
再看 FF$
1, 有一个 public static 修饰名为 MODULE$ 的 FF$ 对象.
2, 构造器被私有化, 用 private 修饰.
3, 组合 1, 2, 两点可知, FF$ 是一个单例类. 其矢志不渝就是 Scala 中 FF 类的伴生对象.
结论:
Scala 编译器在对 case class 进行编译的时候做了特殊处理, 扩展了其方法和功能, 加入了 scala.Product,scala.Serializable 的特性, 同时为其提供了该类的伴生对象. 另外, 对于 case class 构造器参数, 其默认以 public 修饰, 可允许外部调用.
四, 其他
上面几个对比理解了, 下面这几个类之间的区别也就可以举一反三了.
- case class GG(val name: String)
- case class HH(var name: String)
- case class II(private val name: String)
- case class JJ(private[this] val name: String)
- =========================================
- =========================================
- -----end
来源: https://www.cnblogs.com/PerkinsZhu/p/9307460.html