1. 程式人生 > >Scala 程式設計—第七節:類和物件(二)

Scala 程式設計—第七節:類和物件(二)

前言:
        類和物件第二節,主要介紹:單例物件、伴生物件與伴生類、apply方法、抽象類

1.單例物件

Java語言中,如果想直接使用類名點的方式呼叫方法或屬性,直接用static修飾即可。但Scala語言不支援靜態成員,而提供了object物件,這個object物件類似於Java的靜態類,object物件的成員、方法預設都是靜態的。單例物件建立方式如下

object Student {
  private var studentNo: Int = 0
  def uniqueStudentNo() :Int = {
    studentNo = 1
    studentNo
  }

  def main(args: Array[String]): Unit = {
    println(Student.uniqueStudentNo())
  }
}

位元組碼檔案

D:\code\study\scalademo\target\classes\com\harvey\classandobject>javap -p Student$
警告: 二進位制檔案Student$包含com.harvey.classandobject.Student$
Compiled from "Student.scala"
public final class com.harvey.classandobject.Student$ {
  public static final com.harvey.classandobject.Student$ MODULE$;
  private int studentNo;
  public static {};
  private int studentNo();
  private void studentNo_$eq(int);
  public int uniqueStudentNo();
  public void main(java.lang.String[]);
  private com.harvey.classandobject.Student$();
}
D:\code\study\scalademo\target\classes\com\harvey\classandobject>javap -p Student
警告: 二進位制檔案Student包含com.harvey.classandobject.Student
Compiled from "Student.scala"
public final class com.harvey.classandobject.Student {
  public static void main(java.lang.String[]);
  public static int uniqueStudentNo();
}

上述位元組碼檔案中可以看到object Student 最終生成了兩個類,分別是Student和Student$ ,它們都是 final 型別,且Student$ 的構造方法為私有,通過靜態成員域 public static final com.harvey.classandobject.Student$ MODULE$ ; 對Student$ 進行引用,即java中的單例模式,私有化構造方法並提供靜態方法,方法內初始化物件返回。

2.伴生物件與伴生類

在前面單例物件的基礎上,在object student 所在的檔案內定義一個class Student,此時,object student 被稱為 class Studnet 的伴生物件,class student 被稱為 object Student的伴生類,

class Student(var name:String,age:Int)

object Student {
  private var studentNo:Int=0;
  def uniqueStudentNo()={
    studentNo+=1
    studentNo
  }
  def main(args: Array[String]): Unit = {
    println(Student.uniqueStudentNo())
  }
}

位元組碼檔案

D:\code\study\scalademo\target\classes\com\harvey\classandobject>javap -p Student$
警告: 二進位制檔案Student$包含com.harvey.classandobject.Student$
Compiled from "Student.scala"
public final class com.harvey.classandobject.Student$ {
  public static final com.harvey.classandobject.Student$ MODULE$;
  private int studentNo;
  public static {};
  private int studentNo();
  private void studentNo_$eq(int);
  public int uniqueStudentNo();
  public void main(java.lang.String[]);
  private cn.scala.xtwy.Student$();
}
D:\code\study\scalademo\target\classes\com\harvey\classandobject>javap -p Student
警告: 二進位制檔案Student包含com.harvey.classandobject.Student
Compiled from "Student.scala"
public class com.harvey.classandobject.Student {
  private java.lang.String name;
  private int age;
  public static void main(java.lang.String[]);
  public static int uniqueStudentNo();
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  public int age();
  public void age_$eq(int);
  public cn.scala.xtwy.Student(java.lang.String, int);
}

上述位元組碼檔案中,可以看到伴生物件與伴生類本質上是兩個不同的類,只不過伴生類與伴生物件之間可以相互訪問到對方的成員(私有的成員變數和方法),如下

class Student(var name:String,age:Int) {
  private var sex:Int = 0
  // 訪問伴生物件的私有成員 studentNo
  def printCompanionObject()=println(Student.studentNo) 
}

object Student {
  private var studentNo:Int=0;
  def uniqueStudentNo()={
    studentNo+=1
    studentNo
  }
  def main(args: Array[String]): Unit = {
    println(Student.uniqueStudentNo())
  }
}

3.apply 方法

apply 方法可以直接使用類名建立物件,例如:前面第四節集合操作中,可以通過val list = (1, 2, 3)這種方式建立初始化一個列表物件,其實相當於呼叫了List的apply方法,即val list = List.apply(1, 2, 3),只不過val list = (1, 2, 3)這種方式更簡潔一點,但要知道的是apply這種建立方式實際上使用的還是new的方式,只不過在操作的時候可以省去new的操作。下面我們來自己實現apply方法,程式碼如下:

// 伴生類
class Student(var name:String,age:Int) {
  private var sex:Int = 0
  // 訪問伴生物件的私有成員 studentNo
  def printCompanionObject()=println(Student.studentNo)
}

// 伴生物件
object Student {
  private var studentNo:Int=0;
  def uniqueStudentNo()={
    studentNo+=1
    studentNo
  }

  // 定義apply 方法
  def apply(name: String, age: Int) = new Student(name, age)

  def main(args: Array[String]): Unit = {
    println(Student.uniqueStudentNo())

    // 使用new建立物件
    val stu1 = new Student("tom", 18)
    println(stu1.sex)

    // 直接利用類名建立物件,實際上是呼叫上面的apply方法實現,好處是省去了new的操作
    val stu2 = Student("john", 20)
    println(stu2.name)
    println(stu2.sex)
  }
}

上述我們允許程式都需要指定main方法入口,Scala 中提供了一個App類,整合該類後,不用main方法,直接執行即可,如下

object AppDemo extends App{
  println("App")
}

App 原始碼

package scala
trait App extends scala.AnyRef with scala.DelayedInit {
  @scala.deprecatedOverriding("executionStart should not be overridden")
  val executionStart : scala.Long = { /* compiled code */ }
  @scala.deprecatedOverriding("args should not be overridden")
  protected def args : scala.Array[scala.Predef.String] = { /* compiled code */ }
  @scala.deprecated("The delayedInit mechanism will disappear.")
  override def delayedInit(body : => scala.Unit) : scala.Unit = { /* compiled code */ }
  @scala.deprecatedOverriding("main should not be overridden")
  def main(args : scala.Array[scala.Predef.String]) : scala.Unit = { /* compiled code */ }
}

上述程式碼可以看到App是一種trait,幫我們定義了main

4.抽象類

抽象類是一種不能被例項化的類,抽象類中包括了若干沒有方法體的方法,這些方法由子類繼承父類重寫。定義抽象類Scala中同Java一樣使用abstract關鍵字,如下定義抽象類Animal

abstract class Animal {
  def eat: Unit
}

位元組碼檔案:

D:\code\study\scalademo\target\classes\com\harvey\classandobject>javap -p Animal
警告: 二進位制檔案Animal包含com.harvey.classandobject.Animal
Compiled from "Animal.scala"
public abstract class com.harvey.classandobject.Animal {
  public abstract void eat();
  public com.harvey.classandobject.Animal();
}

抽象類中也可以有抽象欄位

// 抽象類
abstract class Animal {
  // 抽象欄位,抽象類中的欄位可以不用初始化
  var age: Int

  def eat: Unit
}

// Dog 整合 Animal,重寫eat方法
class Dog(var age: Int) extends Animal {
  override def eat() = {
    println("dog eat food!")
  }
}

// 通過擴充套件App建立程式入口
object Dog extends App{
  new Dog(5).eat()
}

上述程式碼,會生成四個位元組碼檔案,Animal.class、Dog$.class、Dog$delayedInit$body.class、Dog.class

// Animal 對應的位元組碼檔案,欄位對應的get/set方法均使用abstract修飾,即抽象
D:\code\study\scalademo\target\classes\com\harvey\classandobject>javap -p Animal
警告: 二進位制檔案Animal包含com.harvey.classandobject.Animal
Compiled from "Animal.scala"
public abstract class com.harvey.classandobject.Animal {
  public abstract int age();
  public abstract void age_$eq(int);
  public abstract void eat();
  public com.harvey.classandobject.Animal();
}
// Dog 伴生類對應位元組碼檔案
D:\code\study\scalademo\target\classes\com\harvey\classandobject>javap -p Dog
警告: 二進位制檔案Dog包含com.harvey.classandobject.Dog
Compiled from "Animal.scala"
public class com.harvey.classandobject.Dog extends com.harvey.classandobject.Animal {
  private int age;
  public static void main(java.lang.String[]);
  public static void delayedInit(scala.Function0<scala.runtime.BoxedUnit>);
  public static java.lang.String[] args();
  public static void scala$App$_setter_$executionStart_$eq(long);
  public static long executionStart();
  public static void delayedEndpoint$com$harvey$classandobject$Dog$1();
  public int age();
  public void age_$eq(int);
  public void eat();
  public com.harvey.classandobject.Dog(int);
}
// Dog 伴生物件對應位元組碼檔案
D:\code\study\scalademo\target\classes\com\harvey\classandobject>javap -p Dog$
警告: 二進位制檔案Dog$包含com.harvey.classandobject.Dog$
Compiled from "Animal.scala"
public final class com.harvey.classandobject.Dog$ implements scala.App {
  public static final com.harvey.classandobject.Dog$ MODULE$;
  private final long executionStart;
  private java.lang.String[] scala$App$$_args;
  private final scala.collection.mutable.ListBuffer<scala.Function0<scala.runtime.BoxedUnit>> scala$App$$initCode;
  public static {};
  public long executionStart();
  public java.lang.String[] scala$App$$_args();
  public void scala$App$$_args_$eq(java.lang.String[]);
  public scala.collection.mutable.ListBuffer<scala.Function0<scala.runtime.BoxedUnit>> scala$App$$initCode();
  public void scala$App$_setter_$executionStart_$eq(long);
  public void scala$App$_setter_$scala$App$$initCode_$eq(scala.collection.mutable.ListBuffer);
  public java.lang.String[] args();
  public void delayedInit(scala.Function0<scala.runtime.BoxedUnit>);
  public void main(java.lang.String[]);
  public final void delayedEndpoint$com$harvey$classandobject$Dog$1();
  private com.harvey.classandobject.Dog$();
}