Scala學習筆記:面向對象 OO: Class & Object

scala的官方文檔寫得太爛了。沒辦法,只能找點資料,或者看scala編譯的class文件,反正最後都是java的語法,然後自己總結一下。

爲便於閱讀,以下展示的編譯後的class文件有做適當調整,且大部分的main函數的object編譯內容沒有展示

OO: Class & Object

Hierarchy

基本的繼承結構如下圖,圖片來自官網:


Object只是AnyRef的一個子類。

class defination

整個class的結構內都是構造函數,構造函數的參數直接跟在申明後面。在new對象的時候,裏面的所有代碼都會執行。

class Person(name: String) {

    println("person info: " + info())    // 這裏可以先引用info

    val age: Int = 18
    var hobby: String = "swimming"

    println("person info: " + info())    // 這裏申明變量後引用

    def info(): String = {
        "name: %s; age: %d; hobby: %s".format(name, age, hobby)
    }
}

object Person {

    def main(args: Array[String]): Unit = {
        new Person("Lilei")
    }
}

// output:
// person info: name: Lilei; age: 0; hobby: null     <--- 變量都沒被初始化
// person info: name: Lilei; age: 18; hobby: swimming

查看編譯後的class文件可以知道大致原因:

public class Person {

    private final String name;
    private final int age;
    private String hobby;

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

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

    public void hobby_$eq(String x$1) {
        this.hobby = x$1;
    }

    public Person(String name) {
        this.name = name;

        Predef$.MODULE$.println(new StringBuilder().append("person info: ").append(info()).toString());

        this.age = 18;
        this.hobby = "";

        Predef..MODULE$.println(new StringBuilder().append("person info: ").append(info()).toString());
    }

    public String info() {
        return new StringOps(Predef$.MODULE$.augmentString("name: %s; age: %d; hobby: %s")).format(Predef$.MODULE$.genericWrapArray(new Object[] { this.name, BoxesRunTime.boxToInteger(age()), hobby() }));
    }

    public static void main(String[] paramArrayOfString) {
        Person$.MODULE$.main(paramArrayOfString);
    }
}

public final class Person$
{
    public static final Person$ MODULE$;

    static {
        new Person$();
    }

    public void main(String[] args) {
        new Person("Lilei");
    }

    private Person$() {
        MODULE$ = this;
    }
}

可見,在類的定義中,除了def,其他語句都是順序執行的
而且object申明的變量和方法都是在一個object_name+$命名的類中以static形式定義出來的

Extends class

語法基本和java一樣,有幾個前提需要注意的:

  1. var類型不能被子類重寫
  2. 從父類繼承的變量通過直接引用的方式使用
  3. 不能使用super去調用父類的變量,var和val類型的都不可以。
  4. 因爲上一點,所以之後對此變量的所有返回值,都是重寫後的值,哪怕是調用的父類方法

scala中定義的繼承關係

class Singer(name: String) {

    val prefix:String = "singer: "
    var gender:String = " male"

    def info(): String = {
        prefix + name
    }
}

class Star(name: String) extends Singer(name) {

    override val prefix: String = "star: "

    override def info(): String = {
        prefix + super.info() + gender
    }
}

object Main {

    def main(args: Array[String]): Unit = {
        val star = new Star("Lilei")
        println(star.info())
    }
}

// output:
// star: star: Lilei male

這樣的輸出其實跟java語言的期望是不一樣的。接着在編譯出的class文件裏找原因:

public class Singer {

    private final String name;

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

    private final String prefix = "singer: ";

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

    public void gender_$eq(String x$1) {
        this.gender = x$1;
    }

    private String gender = " male";

    public String info() {
        return new StringBuilder().append(prefix()).append(this.name).toString();
    }

    public Singer(String name) {
        this.name = name;
    }
}

public class Star extends Singer {

    public Star(String name) {
        super(name);
    }

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

    private final String prefix = "star: ";

    public String info() {
        return new StringBuilder().append(prefix()).append(super.info()).append(gender()).toString();
    }
}

可以看到:

  1. 父類的屬性是通過調用super()進行初始話的,這點和java一樣
  2. 父類的屬性都是private修飾符,對外暴露public的getter。如果子類重寫父類的屬性,那麼對應的getter就是子類重寫的方法,且只會返回子類的屬性值,所以父類的就丟掉了,即子類繼承的屬性與父類脫離關係

Trait

trait類似於java中的接口,但支持部分的實現。一個trait其實是被編譯成了對應的接口和抽象類兩個文件。

trait Singer {

    val song:String = "sunshine"

    def singe(): Unit = {
        println("singing " + song)
    }

    def name(): String
}

class Star extends Singer {

    override def name(): String = {
        return "Lilei"
    }
}

object Star {

    def main(args: Array[String]): Unit = {
        val s = new Star()
        s.singe()
        println(s.name())
    }
}

// output:
// singing sunshine
// Lilei

編譯出的class文件:

public abstract interface Singer {

    public abstract void com$study$classdef$witht$Singer$_setter_$song_$eq(String paramString);

    public abstract String song();

    public abstract void singe();

    public abstract String name();
}

public abstract class Singer$class {

    public static void $init$(Singer $this) {
        $this.com$study$classdef$witht$Singer$_setter_$song_$eq("sunshine");
    }

    public static void singe(Singer $this) {
        Predef..MODULE$.println(new StringBuilder().append("singing").append($this.song()).toString());
    }
}

public class Star implements Singer {
    private final String song;

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

    public void com$study$classdef$witht$Singer$_setter_$song_$eq(String x$1) {
        this.song = x$1;
    }

    public void singe() {
        Singer$class.singe(this);
    }

    public Star() {
        Singer$class.$init$(this);
    }

    public String name() {
        return "Lilei";
    }
}

可以看到:

  • 在Star的構造函數中會調用trait的抽象類的static $init$()方法來初始化從trait繼承來的song屬性。繼承來的song屬性被申明到了Star中,與scala中的trait已經脫離了關係。在java中,interface裏申明的都是static變量,所以scala這樣實現也是很合理的。

Mixin Class Composition

scala中除了標準的繼承,還能通過with關鍵字組合trait類型。

class Person(name: String, age: Int) {

    println("person info: " + info())

    def info(): String = {
        "name: %s; age: %d".format(name, age)
    }
}

trait Child {

    println("child info: " + info())

    def info(): String = {
        " I am a child"
    }

    def play(): Unit
}

trait Chinese {

    println("chinese info: " + info())

    def info(): String = {
        "I am a Chinese, %s, %s".format(province(), gender)
    }

    def province(): String = {
        "Hunan"
    }

    val gender: String  = "male"
}

class Student(name: String, age: Int, grade: Int) extends Person(name, age) with Child with Chinese {

    println("student info: " + info())

    override def info(): String = {
        super.info() + " grade: %d".format(grade)
    }

    override def play(): Unit = {}
}

object Student {

    def main(args: Array[String]): Unit = {
        new Student("Lilei", 18, 1)
    }
}

// output:
// person info: I am a Chinese, Hunan, null grade: 1
// child info: I am a Chinese, Hunan, null grade: 1
// chinese info: I am a Chinese, Hunan, null grade: 1
// student info: I am a Chinese, Hunan, male grade: 1

編譯的class文件是這樣的:

public abstract interface Child {

    public abstract String info();

    public abstract void play();
}

public abstract class Child$class {

    public static void $init$(Child $this) {
        Predef$.MODULE$.println(new StringBuilder().append("child info: ").append($this.info()).toString());
    }

    public static String info(Child $this) {
        return " I am a child";
    }
}

public abstract interface Chinese {

    public abstract void com$study$classdef$Chinese$_setter_$gender_$eq(String paramString);

    public abstract String info();

    public abstract String province();

    public abstract String gender();
}

public abstract class Chinese$class
{
    public static String info(Chinese $this) {
        return new StringOps(Predef..MODULE$.augmentString("I am a Chinese, %s, %s")).format(Predef..MODULE$.genericWrapArray(new Object[] { $this.province(), $this.gender() }));
    }

    public static String province(Chinese $this) {
        return "Hunan";
    }

    public static void $init$(Chinese $this) {
        Predef$.MODULE$.println(new StringBuilder().append("chinese info: ").append($this.info()).toString());

        $this.com$study$classdef$Chinese$_setter_$gender_$eq("male");
    }
}

public class Person {

    private final String name;

    public Person(String name, int age) {
        Predef$.MODULE$.println(new StringBuilder().append("person info: ").append(info()).toString());
    }

    public String info() {
        return new StringOps(Predef$.MODULE$.augmentString("name: %s; age: %d")).format(Predef..MODULE$.genericWrapArray(new Object[] { this.name, BoxesRunTime.boxToInteger(this.age) }));
    }
}

public class Student extends Person implements Child, Chinese {

    private final String gender;

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

    public void com$study$classdef$Chinese$_setter_$gender_$eq(String x$1) {
        this.gender = x$1;
    }

    public String province() {
        return Chinese.class.province(this);
    }

    public Student(String name, int age, int grade) {
        super(name, age);
        Child$class.$init$(this);
        Chinese$class.$init$(this);

        Predef$.MODULE$.println(new StringBuilder().append("student info: ").append(info()).toString());
    }

    public String info() {
        return new StringBuilder().append(Chinese.class.info(this)).append(new StringOps(Predef..MODULE$.augmentString(" grade: %d")).format(Predef..MODULE$.genericWrapArray(new Object[] { BoxesRunTime.boxToInteger(this.grade) }))).toString();
    }

    public static void main(String[] paramArrayOfString) {
        Student..MODULE$.main(paramArrayOfString);
    }

    public void play() {}
}

可以看出,這只是前面extends和trait兩種情況的複合版。在子類student的構造函數中,會順序執行super()withtrait的$init$()方法。結合前面trait的實現,可以知道一定是後一個父類/trait的變量的值會作爲新值,賦值給子類的同名變量。
並且從info()的實現可以看到super引用被翻譯成了Chinese,也就是後一個父類/trait定義的方法也會覆蓋前一個。

由此可以總結出繼承的特點:

  1. 繼承class A extends B with C with D其實可以看作是class A extends << class B with C with D >>,切對於這個父類class B with C with D:
    1. 多重繼承的關鍵字with只使用於trait
    2. 當出現多重繼承,後面trait申明的屬性和方法會覆蓋前面的類/trait
      最終組成的新class,作爲class A父類
  2. 父類繼承的屬性會作爲子類獨立的屬性被引用,且子類無法通過super引用父類的屬性。
  3. 對於方法的繼承,一切同java一致

Duck Typing

只要會鴨子叫,就視爲鴨子。

class Person {

    def singe(): Unit = {
        println("singing")
    }
}

object Main {

    def singe(singer: {def singe():Unit}): Unit = {  // 只要實現的singe方法就可以作爲參數
        singer.singe()
    }

    def main(args: Array[String]): Unit = {
        val person = new Person()
        singe(person)
    }
}

// output:
// singing

這裏需要注意的是:因爲得是鴨子叫,所以方法名也必須一致。

Currying

柯里化。不廢話了,看例子。

object Main {

    def currying(left: Int)(middle: Int)(right: Int): Int = left + middle + right

    def main(args: Array[String]): Unit = {
        val two = currying(2)(_)
        println(two(3)(4))

        val towPlusThree = two(3)
        println(towPlusThree(4))
    }
}

// output:
// 9
// 9

函數可以依次傳入多個參數,切每傳入一個參數就能生成一個可多次使用的新函數。

Generic

比較簡單的例子

class Valuable(value:Int) {

    def getValue(): Int = value
}

object Comparable {

    def add[E <: { def getValue():Int }](l: E, r: E):Int = l.getValue() + r.getValue()

    def main(args: Array[String]): Unit = {
        println(add(new Valuable(1), new Valuable(2)))
    }
}

// output:
// 3
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章