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一樣,有幾個前提需要注意的:
-
var
類型不能被子類重寫 - 從父類繼承的變量通過直接引用的方式使用
- 不能使用
super
去調用父類的變量,var和val類型的都不可以。 - 因為上一點,所以之后對此變量的所有返回值,都是重寫后的值,哪怕是調用的父類方法
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();
}
}
可以看到:
- 父類的屬性是通過調用
super()
進行初始話的,這點和java一樣 - 父類的屬性都是
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()
和with
trait的$init$()
方法。結合前面trait
的實現,可以知道一定是后一個父類/trait的變量的值會作為新值,賦值給子類的同名變量。
并且從info()
的實現可以看到super
引用被翻譯成了Chinese
,也就是后一個父類/trait定義的方法也會覆蓋前一個。
由此可以總結出繼承的特點:
- 繼承
class A extends B with C with D
其實可以看作是class A extends << class B with C with D >>
,切對于這個父類class B with C with D
:- 多重繼承的關鍵字
with
只使用于trait
- 當出現多重繼承,后面
trait
申明的屬性和方法會覆蓋前面的類/trait
最終組成的新class,作為class A
的父類
- 多重繼承的關鍵字
- 從父類繼承的屬性會作為子類獨立的屬性被引用,且子類無法通過
super
引用父類的屬性。 - 對于方法的繼承,一切同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