[关闭]
@MiloXia 2016-04-01T12:37:32.000000Z 字数 7226 阅读 4025

Scala 反编译

scala


val 关键字

就是 加 final
所有final成员都是在类初始化(加载)时,或者构造函数里执行的
scala 把其加入在构造函数中 (如果放在类加载,会异常复杂,因为object的实现)
坑:[http://blog.iamzsx.me/show.html?id=674002 ]

class Foo {
val foo = 10
val bar = foo
}

class Bar extends Foo {
override val foo = 20
}

object Main extends App {
println(new Bar(). bar)
}

这段代码输出结果是0,而不是10或20,这是为什么呢?用javap看了一眼编译出来的代码才发现了原因。为了简单起见,我把代码翻译成相应的Java代码:

  1. public class Foo {
  2. private final int foo;
  3. private final int bar;
  4. public int foo() {
  5. return foo ;
  6. }
  7. public int bar() {
  8. return bar ;
  9. }
  10. public Foo() {
  11. foo = 10;
  12. bar = foo();
  13. }
  14. }
  15. public class Bar extends Foo {
  16. private final int foo;
  17. public int foo() {
  18. return foo ;
  19. }
  20. public Bar() {
  21. super();
  22. foo = 20;
  23. }
  24. public static void main(String[] args) {
  25. System.out.println(new Bar().bar());
  26. }
  27. }

关键的地方在L16,这个时候bar的值是通过foo()来获得的,foo()这个时候是Bar.foo(),因此foo()返回的是Bar.foo的值。这个时候,Bar.foo还没有被初始化,所以它的值还是0,bar就被设置为0了。

所有val a = 1 字段都会生成

类初始化顺序

父类静态成员&静态代码块 -> 子类静态成员&静态代码块 -> 父类成员初始化&父类非静态代码块 -> 父类构造函数 -> 子类成员初始化&子类非静态代码块 -> 子类构造函数
其中静态初始化发生在类加载中 非静态初始化在时

  1. private final int a; //注意private 并且初始值为0 obj的话为null
  2. public int a() { return a}
  3. 构造函数中初始化a = 1

因为字段私有,所以所有子类都会考贝一份这样的代码, 又因父类先初始化
这就造成了 val 初始化坑
用lazy 或者改成def 以及早期成员定义(new {val a = ..} with Trait)能解

object 单例

object XXX {} 会编译出两个类 XXX.class 和 XXX$.class

XXX$.class 为主类

  1. public final class XXX$ extends java.lang.Object implements scala.ScalaObject{
  2. public static final XXX$ MODULE$;
  3. public static {};
  4. Code:
  5. 0: new #10; //class Singleton$
  6. 3: invokespecial #13; //Method "<init>":()V
  7. 6: return
  8. public void method(); //object里的方法
  9. ...
  10. }

MODULE$是这个类唯一的实例,这个实例是在static块创建出来的
(类加载时执行) 木有在多加载器的情况下,基本上线程安全
XXX.method 会被 翻译为XXX$.MODULE$.method
这个类里根本没有构造函数
根本就没有机会为这个类创建对象

在Java代码里,如果我们想实现Singleton必须显式声明出一个private的构造函数。而scalac绕过了javac直接生成字节码,它给出了一个用Java语言无法实现的Singleton方案。

XXX.class 就是个代理(XXX.method 会被 翻译为XXX$.MODULE$.method)

  1. public final class XXX extends java.lang.Object{
  2. public static final void method();
  3. Code:
  4. ...
  5. }

more [http://www.blogbus.com/dreamhead-logs/58331783.html ]

object 伴生对象

class XXX 和 object XXX 会被合并(scala2.8之后版本)
object XXX 定义的方法会被放到 XXX.class 以static method形式
static method 和 Singleton一样 会委托MODULE$.method
对加载来说:伴生对象object 加载时会导致class也被加载 (吗??)
more [http://www.blogbus.com/dreamhead-logs/60217908.html ]

trait

trait XXX 会被编译为XXX.class和XXX$class.class
前者是interface,后者为class

  1. trait TestTrait1 {
  2. def foo1() = {println ("foo1")};
  3. }
  4. //编译为
  5. public interface TestTrait1 extends scala.ScalaObject {
  6. public abstract void foo1();
  7. }
  8. //和
  9. public abstract class TestTrait1$class extends java.lang.Object {
  10. public static void foo1(TestTrait1 $this) {
  11. Predef..MODULE$.println("foo1");
  12. }
  13. public static void $init$(TestTrait1 $this){
  14. }

一个trait会编译成一个接口和一个实现类,而trait的实现会放在类中当做静态方法

  1. //再看实现类
  2. class X extends TestTrait1
  3. //编译为
  4. public class X implements TestTrait1 {
  5. public void foo1() {
  6. TestTrait1$class.foo1(this);
  7. }
  8. public X(){
  9. TestTrait1$class.$init$(this);
  10. }
  11. }

X在调用静态方法时把this传了进去,抽象类中的静态方法可能会依赖于各个实例不同的状态,所以需要把this传递进去 如

  1. trait TestTrait2 {
  2. val foo = ""
  3. def foo1() = {println ("foo1"+foo)};
  4. }
  5. class X2 extends TestTrait2 {override val foo = "nice."}
  6. //编译为
  7. public abstract interface TestTrait2 {
  8. public abstract void objsets$TestTrait2$_setter_$foo_$eq(String paramString);
  9. public abstract String foo();
  10. public abstract void foo1();
  11. }
  12. public abstract class TestTrait2$class
  13. {
  14. public static void foo1(TestTrait2 $this)
  15. {
  16. Predef..MODULE$.println(new StringBuilder()
  17. .append("foo1")
  18. .append($this.foo()).toString()); //this 在这用
  19. }
  20. public static void $init$(TestTrait2 $this) { //val foo = ""
  21. $this.objsets$TestTrait2$_setter_$foo_$eq("");
  22. }
  23. }
  24. public class X2 implements TestTrait2 {
  25. private final String foo; //标准的val
  26. public void objsets$TestTrait2$_setter_$foo_$eq(String x$1){
  27. }
  28. public void foo1() {
  29. TestTrait2$class.foo1(this);
  30. }
  31. public String foo() { return this.foo; }
  32. public X2(){
  33. TestTrait2$class.$init$(this);//初始化TestTrait2
  34. this.foo = "nice.";
  35. }
  36. }

参考 [http://blog.csdn.net/cuipengfei1/article/details/17465139 ]

lazy

Java中想要做到延迟加载,常规的做法是大抵是这样的:

  1. private String str = null;
  2. public String getStr() {
  3. if (str == null) {
  4. str = getStrFromWebService();
  5. }
  6. return str;
  7. }

scala

  1. lazy val str = getStrFromWebService()

其实Scala编译器做的事情和我们手动做的区别不大

  1. private String str;
  2. private volatile boolean bitmap$0;
  3. private String str$lzycompute() {
  4. synchronized (this) { //加锁的
  5. if (!this.bitmap$0) {
  6. this.str = getStrFromWebService();
  7. this.bitmap$0 = true;
  8. }
  9. return this.str;
  10. }
  11. }
  12. public String str() {
  13. return this.bitmap$0 ? this.str : str$lzycompute();
  14. }

双重检测锁(DCL):先通过状态标记变量(做一次位与操作)判断是否已经初始化过
之后版本用byte做bitmap$0,安位标记检查即可,最大8个,大于8个用int,这样减少标记字段数

注: 如果lazy val的初始化依赖外部var变量,此时最好把var 设置为@volatile var

type

一个类型的type alias,类似于这样的:type t = x。编译器将在所有使用到t的地方把t替换为x。

对于一种操作的type alias,编译器将会根据参数列表和返回值类型的不同将其替换为对应的Function0,Function1,Function2 …… 一直到Function22

  1. type TwoToOne = (String, Int) => Double
  2. def twoToOneImpl: TwoToOne = (str, i) => 1
  3. //变为
  4. public Function2<String, Object, Object> twoToOneImpl(){
  5. return new Hello..anonfun.twoToOneImpl.1(this);
  6. }

partial application

局部应用,或者叫做柯里化 (其实有差别)
所谓柯里化就是指把一个接受多个参数的函数的一部分参数写死,剩下的一部分由调用者提供
java只能这么写

  1. public String greet(String greeting, String name) {
  2. return greeting + " " + name;
  3. }
  4. public String sayHello(String name) {
  5. return greet("Hello", name);
  6. }
  7. public String greetXiaoMing(String greeting) {
  8. return greet(greeting, "Xiao Ming");
  9. }

greet用来给某个不确定的人打个不确定的招呼
sayHello用来给某个不确定的人说一句固定的Hello
greetXiaoMing用来给一个固定的人小明打一个不确定的招呼
Scala来表达

  1. def greet(greeting: String, name: String) = greeting + " " + name
  2. def sayHello = greet("hello", _: String)
  3. def greetXiaoMing = greet(_: String, "Xiao Ming")

只是把暂时不确定的参数用下划线指代出来
实现

  1. public String greet(String greeting, String name) {
  2. return new StringBuilder().append(greeting).append(" ").append(name).toString();
  3. }
  4. public Function1<String, String> sayHello() {
  5. return new AbstractFunction1() {
  6. public static final long serialVersionUID = 0L;
  7. public final String apply(String x$1) {
  8. return Hello.this.greet("hello", x$1);
  9. }
  10. };
  11. }
  12. public Function1<String, String> greetXiaoMing() {
  13. return new AbstractFunction1() {
  14. public static final long serialVersionUID = 0L;
  15. public final String apply(String x$2) {
  16. return Hello.this.greet(x$2, "Xiao Ming");
  17. }
  18. };
  19. }

可以看到sayHello和greetXiaoMing并不是返回String的,它们返回的是Function1 of String, String。也就是说直接调用它们俩是得不到我们想要的结果的,必须把这个Function1上的apply再调一下才行
实际上正是如此,这段代码:

  1. sayHello("world")
  2. greetXiaoMing("Ni Hao")
  3. //编译为
  4. sayHello().apply("world");
  5. greetXiaoMing().apply("Ni Hao");

partial application还可以有另一种稍微另类一些的写法 curry
def greet(greeting: String)(name: String) = greeting + " " + name
def sayHello = greet("hello")()
def greetXiaoMing = greet(
: String)("Xiao Ming")

Function composition

  1. def sayHi(name: String) = "Hi, " + name
  2. def sayBye(str: String) = str + ", bye"
  3. val names = List("world", "tom", "xiao ming")
  4. names.map {
  5. name => sayBye(sayHi(name))
  6. }

按照eager evaluation的规则,先运行sayHi,然后把结果传入sayBye,最后得到一个我们想要的结果
我们真正想要的是一个链式操作,一个pipe:把数据用某种操作进行处理,然后把处理后的结果传递给第二个操作继续处理。类似于这样:a.pipe(b),或者是这样:a | b
而Scala的function composition正是做这件事的

  1. names.map(sayHi _ andThen sayBye)

andThen 只是定义在Function1上的一个方法而已

  1. final List names =
  2. List$.MODULE$.apply((Seq)Predef$.MODULE$.wrapRefArray(
  3. (Object[])new String[] { "world", "tom", "xiao ming" }));
  4. return (List)names.map(((Function1)new Serializable() {
  5. public static final long serialVersionUID = 0L;
  6. public final String apply(final String name) {
  7. return Hello.this.sayHi(name);
  8. }
  9. }).andThen((Function1)new Serializable() {
  10. public static final long serialVersionUID = 0L;
  11. public final String apply(final String str) {
  12. return Hello.this.sayBye(str);
  13. }
  14. }), List$.MODULE$.canBuildFrom());

sayHi和sayBye都是被包到了Functoin1里面,调一下第一个Function1的andThen方法,把第二个Function1传进去,会返回一个新的Function1。这个返回的新的Function1就是我们想要的链式操作了。

for

本质上就是map和flatMap
意义:
不仅在于更短的代码,还在于它提高了信噪比,给我们提供了更加简化的思考模型

case class

case classes mix in scala.Product
scala.Product 提供了productIterator 和 productElement方法 这两个方法不走反射

  1. case class Person(name: String, age: Int, education: Option[String])
  2. val joe = Person(name = "Joe S. Ixpack", age = 37, education = None)
  3. scala> val elements = joe.productIterator.toList
  4. elements: List[Any] = List(Joe S. Ixpack, 37, None)
  5. scala> joe.productElement(1)
  6. res4: Any = 37
  7. scala> joe.productArity //参数列表个数
  8. res6: Int = 3

还有实现了apply和unapply的伴生对象

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注