scala class

 主构造器

Scala 声明主构造函数的过程和 Java 区别很大; Java 中构造器函数的定义一目了然, 由于Scala 的设计者认为每敲一个键都是珍贵的, 所以把 Scala 主构造器的定义和类的定义交织在一起, 导致 Scala 的主构造器没法像 Java 的构造器那样清晰了。Scala 之父 Martin Odersky 建议我们这样来看待主构造器, "在 Scala 中, 类也接受参数, 就像方法一样"。

主构造器结构

先来说明 Scala 类一个术语的定义, 字段(Filed), 对应于 Java 中成员变量, 不过又有不同之处, Scala 中字段还对应一组 setter/getter 方法, 现在有疑问的话, 可以先当成员变量理解, 看到后面就懂了.

在 Scala 中, 每个类都有主构造器, 有如下的结构

  1. 主构造器的定义和类的定义交织在一起, 主构造器的参数被编译成字段;
  2. 主构造器会执行类定义中的所有语句;
  3. 如果类名后没有参数, 即该类具备一个无参主构造器, 这样的一个构造器仅仅简单的执行类体的所有语句而已

示例说明

我们来看一个简单的 Flower 类, Flower 类体由 3 个字段, 1 个方法定义和调用语句, 以及 2 个 println 语句构成。

class Flower(val name: String, var color: String) {
  
  println("constructor start")
  
  var number = 10
  def showMessage = println(s"$number $color $name")
  showMessage
  
  println("constructor finish")
}

object Test extends App {
  new Flower("lilac", "purple") // lilac 丁香花 
}

/*输出
constructor start
10 purple lilac
constructor finish 
*/

我们先来看看看上面的 Flower 类. 它有以下特点:

1、定义 Flower 类时, 我们直接在类名后加上了参数列表, 即主构造器的参数列表,即上面说的第 1 个特点,。

    主构造器的这些由 val 或 var 定义的参数列表会成为 Flower 类的字段(成员变量); 接着, 我故意把 Flower 的数量 number 字段定义在类体里面, 是为了说明我们既可以选择将字段定义在主构造器参数中, 也可以将字段定义在类体中, 字段定义在主构造器参数中的好处就是能够在 new 一个对象的时候, 能将传入参数给字段赋值. 并且我们定义了一个方法 showMessage, 并且调用了它;

2、主构造器会执行类定义中的所有语句

     在测试语句中, 我们使用 new Flower("lilac", "purple") 调用了类的主构造器, 从 Flower 类调用主构造器初始化的输出结果可以看出 Scala 主构造器的 第 2 个特点, 即主构造器会执行类定义中的所有语句, 从语句 println("constructor start")到类定义的最后一条语句 println("constructor finish").

其实主构造器的第一条语句是主构造器参数的所在字段的赋值语句, 具体看下面的反编译之后的构造器部分).

然后我们看看背后发生了什么? 这里使用 JAD 工具反编译结果如下

Flower类反编译代码

import scala.Predef;
import scala.StringContext;
import scala.collection.Seq;
import scala.collection.mutable.WrappedArray;
import scala.reflect.ScalaSignature;
import scala.runtime.BoxesRunTime;

// 此处删去了 @ScalaSignature(bytes = XXXX)

public class Flower {
    private final String name;
    private String color;
    private int number;

    public String name() {
        return this.name;
    }

    public String color() {
        return this.color;
    }

    public void color_$eq(String x$1) {
        this.color = x$1;
    }

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

    public void number_$eq(int x$1) {
        this.number = x$1;
    }

    public void showMessage() {
        Predef..MODULE$.println((Object)new StringContext((Seq)Predef..MODULE$.wrapRefArray((Object[])new String[]{"", " ", " ", ""})).s((Seq)Predef..MODULE$.genericWrapArray((Object)new Object[]{BoxesRunTime.boxToInteger((int)this.number()), this.color(), this.name()})));
    }

    public Flower(String name, String color) {
        this.name = name;
        this.color = color;
        Predef..MODULE$.println((Object)" constructor start");
        this.number = 10;
        this.showMessage();
        Predef..MODULE$.println((Object)"constructor  finish");
    }
}

上面反编译的结果, 如果不纠结细节, 从总体上来看还是很清晰的. 现在我们来分析一下

Flower类反编译代码解析

首先, 我们定义类 Flower 时, 并没有声明类的可见性, 反编译的结果 Flower 类的可见性是 public. 这是因为在 Scala 中, 类的默认可见性就是 public, 且 Scala 源文件中可以包含多个类, 这些类可见性都是 public.

然后, 我们来看看主构造器 public Flower(String name, String color), 可以看出主构造器会执行类中定义中的所有语句, 说明Scala 主构造器包含了整个类定义, 和类的定义交织在一起;

        Predef..MODULE$.println((Object)" constructor start");
        this.number = 10;
        this.showMessage();
        Predef..MODULE$.println((Object)"constructor  finish");

然后我们看到反编译代码类的前 3 行是:

private final String name;
private String color;
private int number;

可以看出主构造器参数花名 name 和颜色 color 都自动变成了反编译类中的成员变量(这里没有说是字段是因为在反编译代码很接近 Java 源代码, 比如说 name, 只是一个成员变量). 你一定发现了反编译代码中还有一些方法, 下面我们就来说说这些方法. 不过在说这些方法之前, 我们来回忆一下字段, 之前说过 Scala 的字段和 Java 的成员变量的类似, 其实差别还是很大的, 因为在 Scala 中定义字段时, 自动生成了 setter/getter 方法; 在 Scala 中, getter 和 setter 分别叫做 XXX和 XXX_=, 比如说字段 color 的 getter/setter 方法就是 color_= 和 color

private String color;

public String color() {
        return this.color;
}

public void color_$eq(String x$1) {
        this.color = x$1;
}

看到这里你可能会疑惑, 因为你发现, 编译器显示的创建的是 color 和 color_$eq 方法, 由于 JVM 不允许在方法名中使用 =, 所以在编译过程中 将 = 替换为 $eq. 结合上面的代码, 我们可以这样理解 Scala 类的字段

字段 = 成员变量 + getter/setter方法

字段的setter/getter方法

Flower 类反编译代码中, 为什么字段 name 只有 getter 方法, 而 字段 color 同时有 getter 和 setter 方法? 在说明这个问题之前, 让我们简化一下之前的类 Flower。

class Flower(val name: String, var color: String) {
  
    var number = 10
    val language = "Missing you"   
}

反编译代码为

public class Flower {
    private final String name;
    private String color;
    private int number;
    private final String language;

    public String name() {
        return this.name;
    }

    public String color() {
        return this.color;
    }

    public void color_$eq(String x$1) {
        this.color = x$1;
    }

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

    public void number_$eq(int x$1) {
        this.number = x$1;
    }

    public String language() {
        return this.language;
    }

    public Flower(String name, String color) {
        this.name = name;
        this.color = color;
        this.number = 10;
        this.language = "Missing you";
    }
}

类中字段定义的具体是规则是:

  1. 如果字段被声明为 var, Scala 会为该字段生成 getter 方法和 setter 方法, 方法的可见性是 public
  2. 如果字段被声明为 val, Scala 会只为该字段生成 getter 方法, 方法的可见性是 public
  3. 如果字段被声明为 private var, Scala 会为该字段生成 getter方法和 setter方法, 方法的可见性是 private
  4. 如果字段被声明为 private val, Scala 会只为该字段生成 getter 方法, 方法的可见性是 private

这也好理解, Scala 中 var定义的是可变引用, val 定义的是一个不变引用, 没法修改, 自然就不可以有 setter方法去修改它. 如果你不需要 getter/setter 方法, 你可以将字段声明为 private var 或者 private val, 这样就没法在类的外部调用了 getter/sette 方法访问字段和修改字段的值了.

转载自:Scala 令人着迷的类设计

===================================

scala的主构造器和辅构造器

主constructor

// Scala中,主constructor是与类名放在一起的,与java不同
// 而且类中,没有定义在任何方法或者是代码块之中的代码,就是主constructor的代码,这点感觉没有java那么清晰
class Student(val name: String, val age: Int) {
  println("your name is " + name + ", your age is " + age)
}

// 主constructor中还可以通过使用默认参数,来给参数默认的值
class Student(val name: String = "leo", val age: Int = 30) {
  println("your name is " + name + ", your age is " + age)
}

// 如果主constrcutor传入的参数什么修饰都没有,比如name: String,那么如果类内部的方法使用到了,则会声明为private[this] name;否则没有该field,就只能被constructor代码使用而已

辅constructor

// Scala中,可以给类定义多个辅助constructor,类似于java中的构造函数重载
// 辅助constructor之间可以互相调用,而且必须第一行调用主constructor
class Student {
  private var name = ""
  private var age = 0
  def this(name: String) {
    this()
    this.name = name
  }
  def this(name: String, age: Int) {
    this(name)
    this.age = age
  }
}

转载自:scala学习笔记-面向对象编程(11)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值