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$();
}