Scala学习笔记:面向对象

scala的官方文档写得太烂了。没办法,只能找点资料,或者看scala编译的class文件,反正最后都是java的语法,然后自己总结一下。

为便于阅读,以下展示的编译后的class文件有做适当调整,且大部分的main函数的object编译内容没有展示

OO: Class & Object

Hierarchy

基本的继承结构如下图,图片来自官网:


hierarchy.png

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容