2018年第44周-scala入门-面向对象基础语法
基于上面的理論, 并帶著java面向對象的思維, 會更好理解scala的面向對象的語法.
這里得有幾個共識:
類里面的函數, 叫 方法類的字段, 也稱為 成員變量, 它們對應的英文都是field.
類實例化后, 叫 對象(object), 也叫 實例.
類里的字段, 方法, 統稱為 成員.
代碼這個東西, 在你眼睛沒達到能看清細節之前, 看到的代碼, 基本就是個宏觀的圖片. 宏觀的信息很少, 所以需要自己動手, 進入細節, 才獲取更多的細節信息.
定義一個簡單的類
定義類, 包含field以及方法
class HelloWorld{private var name="jc"def sayHello(){print("Hello "+name)}def getName=name }//創建類對象, 并調用其方法 val helloWorld = new HelloWorld helloWorld.sayHello() println(helloWorld.getName) //如果定義方法時沒有帶括號, 則調用方法時不能帶括號主construtor
scala中, 主constructor是與類名放在一起的, 這與java不一樣
而且類中 , 主constructor里的代碼, 是沒有定義在任何方法或者是代碼塊中, 這點與java對比, 就沒那么直觀看出那些是主構造方法的代碼.
主constructor中還可以通過使用默認參數, 來給參數默認的值
class Student(val name:String="jc", val age:Int=30){println("your name is" + name+", your age is "+age ) }如果主constructor傳入的參數沒有修飾符, 比如Student(name:String,age:Int), 那么如果類內部有方法使用到這些傳入的參數, 則會聲明為private[this] name; 否則沒有該field, 就只能被constructor代碼使用而已. 簡單的說就是外部不能訪問name和age
class Student(name:String, age:Int){println("your name is "+name+", your age is "+age) }scala> var s = new Student("jc",29) your name is jc, your age is 29 s: Student = Student@5ed1f76dscala> s.name <console>:14: error: value name is not a member of Students.name^scala> s.age <console>:14: error: value age is not a member of Students.age^class Student(name:String, age:Int){println("your name is "+name+", your age is "+age)def print={println(name)} } scala> var s = new Student("jc",29) your name is jc, your age is 29 s: Student = Student@6db3cc5bscala> s.name <console>:14: error: value name is not a member of Students.name^scala> s.age <console>:14: error: value age is not a member of Students.age輔助constructor
scala中, 可以給類定義多個輔助constructor, 類似與java中的構造函數重載
輔助constructor之間可以互相調用, 而且必須第一行調用主constructor
object
object, 就是class的單個實例, 這里很特別, scala可以直接定義object, 通常在里面放一些靜態的field或者method, 那就跟java的靜態類很像
第一次調用object的方法時, 就會執行object的constructor, 也就是object內部不在method的代碼; 但是object不能定義接受參數的constructor
注意, object的constructor只會在其第一次被調用時執行一次, 以后再次調用就不會再次執行constructor了
object通常用于作為單例模式的實現, 或者放class的靜態成員, 比如工具方法
內部類
scala中, 同樣可以在類中定義內部類, 但是與java不同的是, 每個外部類的對象的內部類, 都是不同的類
scala> :paste // Entering paste mode (ctrl-D to finish)import scala.collection.mutable.ArrayBufferclass Outer {class Inner(val name:String){}val inners = new ArrayBuffer[Inner]def getInner(name:String) ={new Inner(name)} }// Exiting paste mode, now interpreting.import scala.collection.mutable.ArrayBuffer defined class Outerscala> val out1 = new Outer out1: Outer = Outer@5479b0b1scala> val inner1 = out1.getInner("inner1") inner1: out1.Inner = Outer$Inner@7161bdc3scala> out1.inners+=inner1 res3: out1.inners.type = ArrayBuffer(Outer$Inner@7161bdc3)scala> val out2 = new Outer out2: Outer = Outer@54c2ca03scala> val inner2 = out2.getInner("inner2") inner2: out2.Inner = Outer$Inner@747a5354scala> out1.inners+=inner2 <console>:17: error: type mismatch;found : out2.Innerrequired: out1.Innerout1.inners+=inner2java內部類的對比:
Outer.java文件
App.java文件
package com.app;import com.java.Outer;public class App {public static void main(String[] args) {Outer out1 = new Outer();Outer.Inner inner1 = out1.getInner("inner1");out1.inners.add(inner1);Outer out2 = new Outer();Outer.Inner inner2 = out2.getInner("inner1");out1.inners.add(inner2);} }java和scala的內部類, 提供的價值是, 讓你邏輯地組織類, 并且控制它們的訪問.
訪問修飾符
包, 類, 對象的成員都可以被修飾為private或protected來控制其是否可被訪問(調用).
所有修飾符, 無論是java還是scala, 都是用于在編譯器發現錯誤. 所以當不能訪問(調用)都會出現編譯錯誤. 所以這里的訪問(access), 也可以說是調用, 引用等都沒關系, 因為都不會到達運行時才發現錯誤.
private成員
一個被修飾為private的成員, 只能被其class和object里的成員訪問(注意是"和", 后續伴生類概念時會有所體現).
private修飾符的訪問限制, 跟java的很像, 但注意還是有不一樣.
一樣的是: java和scala的private修飾的成員任何其他類不能調用.不一樣的是: java內部類是可以調用其外部類的private成員, 外部類也是可以調用內部類的private成員. 而scala不可以
我們先理解上面一句話的"任何其他類".
在java里的"任何其他類"是除class類內部.
內部類是可以調用其外部類的private成員, 外部類也是可以調用內部類的private成員.
而在scala里的"任何其他類"包括就真的是任何其類, 內部類也屬于任何其他類. 但一點跟java一樣的是, 內部類可以訪問外部類的private成員.
//編譯失敗 class Outer {class Inner{private def f(){println("f")}class InnerMost{f() //ok}}(new Inner).f() //error: f is not accessible}下面例子證明修飾符是用于編譯期發現錯誤, 以下都是可以編譯通過.
package com.java;import java.util.ArrayList; import java.util.List;public class Outer {private int value ;public boolean compare(Out otherOuter){return this.value < otherOuter.value} } class Outer {private var value = 0def compare(otherOuter:Outer) ={this.value < otherOuter.value} }protected成員
在scala, protected修飾的成員和java的就不太一樣. java修飾層次更鮮明點, java的protected是包內的類都能夠訪問.
在scala, protected修飾的成員只能其自身或子類訪問.
在java里, Other類是可以訪問super.f(), 是因為Other和Super都在通過包p下.
public成員
這里又跟java不一樣, 在scala里, 沒有修飾符的, 默認是public.
public成員, 可以隨意調用.
可設置保護范圍
scala中的訪問修飾符可以指定限定符(qualified).
一個private[X]或protected[X]這樣的格式的修飾符, X可以是包, 可以是類, 也可是object.
這樣的設置, 可以讓你達到Java的protected修飾符的效果(只有本包下可以訪問, 哪怕子包也不可以訪問), 如我們修改下上面一小節protected成員的例子, 讓它可以編譯通過:
利用這語法, 可以讓你更靈活的表達訪問權限, 這是java所不具有的:
package bobsrocketspackage navigation {private[bobsrockets] class Navigator {protected[navigation] def useStarChart() {}class LegOfJourney {private[Navigator] val distance = 100}private[this] var speed = 200}}package launch {import navigation._object Vehicle {private[launch] val guide = new Navigator}}Navigator類不修飾為private[bobsrockets], 這樣只有包bobsrockets下面的類或對象才能訪問.
Vehicle對象是不可以訪問Navigator, 因為Vehicle對象是在launch包下, 那怕import navigation包, 也是不可以訪問Navigator類
getter與setter
這一節, 感覺不太嚴謹, 因為都不是java概念中getter和setter, 是硬套進去的概念. 更多是上一節的訪問修飾符的概念.
JVM會考慮以下情況getter與setter.
使用了private來修飾的field, 所以生成的getter和setter是private的.
如果定義field是val, 則只會生成getter方法
class Student{val name="jc" } scala> val jcStu = new Student jcStu: Student = Student@47573529scala> jcStu.name res9: String = jcscala> jcStu.name = "jc2" <console>:12: error: reassignment to valjcStu.name = "jc2"^定義不帶private的var field, 此時scala生成的面向JVM的類時, 會定義為private的name字段, 并提供public的getter和setter方法
class Student{var name="jc" } //調用getter和setter的方法 val jcStu = new Student println(jcStu.name) //其實是調用了getter方法 jcStu.name = "jc2" //其實是調用了setter方法如果不希望生成setter和getter方法, 則將field生命為private[this]
class Student{private[this] var name="jc" } scala> val jcStu = new Student jcStu: Student = Student@4ef55cb9scala> jcStu.name <console>:13: error: value name is not a member of StudentjcStu.name^scala> jcStu.name = "jc2" <console>:12: error: value name is not a member of StudentjcStu.name = "jc2"^ <console>:13: error: value name is not a member of Studentval $ires3 = jcStu.namegetter和setter方法在scala的方法名是name和name_=
class Student{var nameField="jc"def name={ println("call getName"); nameField}def name_= (newName:String){ println("modify name, old name="+nameField);nameField=newName} }scala> val jcStu = new Student jcStu: Student = Student@e90dc68scala> jcStu.name call getName res11: String = jcscala> jcStu.name="jc2" modify name, old name=jc call getName jcStu.name: String = jc2//可以看出, name已經成為方法的名, 而字段name被改為nameField, 感覺getter和setter就是普通的方法, 與nameField關系不大了. 而且與java不一樣, getter方法參數不能與字段同名, 不過下面的構造方法可以使用this自定義getter與setter
class Student{var nameField="jc"def name={ println("call getName"); nameField}def name_= (newName:String){ println("modify name, old name="+nameField);nameField=newName} } //在上面這個例子里, 其實存在兩個getter和setter方法, getter方法分別是nameField和name, setter方法是nameField和和name_ 所以在這里, 我們想控制getter和setter的話, 需要把字段改為private class Student{private var nameField="jc"def name={ println("call getName"); nameField}def name_= (newName:String){ println("modify name, old name="+nameField);nameField=newName} } //自定義setter方法的時候一定要注意scala的語法限制, 簽名, =, 參數不能有空格僅暴露field的getter方法
如果你不希望field有setter方法, 可以將field定義為val, 但此時就再也不能修改field的值了.
所以如果你僅僅是想暴露getter方法, 但還可以繼續修改field的值, 則需要綜合使用private以及自定義getter方法.
當定義field是private時, setter和getter都是private, 對外界沒有暴露, 自己可以實現修改field值的方法, 自己可以覆蓋getter方法
上述這例子, 雖然field名已經不是name了, 但可以對客戶端來說, 我們的field就是name, 所以就可以呈現出name是個字段
看來scala的getter方法和setter方法的理解, 和java不太一樣.
private[this]的使用
如果field使用private來修飾, 那么代表這個field是私有的, 在類的方法中, 可以直接訪問類的其他對象的private field.
這種情況下, 如果不希望field被其他對象訪問到, 那么可以使用private[this], 意味著對象私有的field, 只有本對象內可以訪問到
上面例子可以直接訪問getter方法, 而下面這個就不可以訪問myAge字段, 編譯會失敗
class Student{private[this] var myAge=0def age_=(newValue: Int){if(newValue>0) myAge = newValueelse print("illegal age!")}def age = myAgedef older(s:Student)={myAge > s.myAge} }<pastie>:21: error: value myAge is not a member of StudentmyAge > s.myAge^Java風格的getter和setter方法
scala的getter和setter方法的命名與java的是不同的, scala是field和field_=的方式
如果要讓scala自動生成java風格的getter和setter方法, 只要field添加@BeanProperty注解即可
此時會生成4個方法, name:String, name_=(newValue:String):Unit, getName():String, setName(newValue:String):Unit
伴生對象
如果有一個class, 還有一個與class同名的object, 那么就稱這個object是class的伴生對象, class是object的伴生類
伴生類和伴生對象必須存放在一個scala文件中
伴生類和伴生對象, 最大的特點就在于, 互相可以訪問private field, 不過需通過對象.訪問
讓object繼承抽象類
object的功能其實和class類似, 除了不能定義接受參數的constructor之外, 它也可以繼承抽象類, 并覆蓋抽象類中的方法
abstract class Hello(var message:String){def sayHello(name:String):Unit }object HelloImpl extends Hello("HELLO"){override def sayHello(name:String)={println(message+","+name)} }apply方法
object中非常重要的一個特殊方法, 就是apply方法
通常在伴生對象中實現apply方法, 并在其中實現構造伴生類的對象的功能
而創建伴生類的對象時, 通常不會使用new Class的方式, 而是使用Class()的方式, 隱式地調用伴生對象的apply方法, 這樣會讓對象的創建更加簡潔.
比如, Array類的伴生對象的apply方法就實現了接受可變參數, 并創建一個Array對象的功能
比如, 定義自己的伴生類和伴生對象
class Person(val name:String) object Person{def apply(name: String) = new Person(name) }main方法
就跟java一樣, 如果要運行一個程序, 就必須有個入口, 就是包含main方法的類; 在scala中, 如果要運行一個應用程序, 那么必須有一個main方法, 作為入口
scala中的main方法定義為def main(args:Array[String]), 而且必須定義在object中
除了自己實現main方法之外, 還可以繼承App Trait, 然后將需要在main方法中允許的代碼, 直接作為object的constructor代碼; 二期用args可以接受傳入的參數
object HelloWorld extends App{if(args.length>0) println("hello, "+args(0))else println("Hello World!!") }用object來實現枚舉功能
scala沒有直接提供類似于Java中的Enum這樣的枚舉特性, 如果要實現枚舉, 則需要用object繼承Enumeration類, 并且調用Value方法來初始化枚舉值
object Season extends Enumeration{val SPRING, SUMEMER, AUTUMN, WINTER = Value }scala> for(ele <- Season.values) println(ele) SPRING SUMEMER AUTUMN WINTER還可以通過Value傳入枚舉值的id和name, 通過id和toString可以獲取; 還可以通過id和name來查找枚舉值
object Season extends Enumeration{val SPRING = Value(0,"spring")val SUMMER = Value(1,"summer")val AUTUMN = Value(2,"autumn")val WINTER = Value(3,"winter") }Season(0) Season.withName("spring") scala> for(ele <- Season.values) println(ele) spring summer autumn winter使用枚舉object.values可以遍歷枚舉值
for(ele <- Season.values) println(ele)
extends
scala中, 讓子類繼承父類, 跟java一樣, 也是使用extends關鍵字
繼承就代表, 子類可以從父類繼承父類的field和method, 然后子類可以在自己內部放入父類所有沒有的, 子類特有的field和method, 使用繼承可以有效復用代碼, 玩出更高級的花樣時, 那就是設計模式, 不僅僅復用代碼, 面向擴展也是很方便, 維護也是很方便, 如你知道是裝飾模式, 那么肯定知道有被裝飾者和裝飾者的存在.
子類可以覆蓋父類的field和method, 但是如果父類用final修飾, field和method用final修飾, 則該類是無法被繼承, field和method是無法被覆蓋的.
override和super
scala中, 如果子類要覆蓋一個父類的非抽象方法, 則必須使用override關鍵字
override關鍵字可以幫助我們盡早地發現代碼里的錯誤,在編譯器發現錯誤比運行時發現錯誤成本要低, 比如: override修飾的父類的方法的方法名我們品寫錯了, 比如要覆蓋的父類方法的參數我沒寫錯了,等等.
此外, 在子類覆蓋父類方法之后, 如果我們在子類中就是要調用父類的被覆蓋的方法呢? 那就可以使用super關鍵字, 顯式地指定要調用父類的方法
override field
scala中, 子類可以覆蓋父類的val field, 而且子類的val field還可以覆蓋父類的val field的getter方法, 只要子類使用override關鍵字即可
class Person{val name:String="Person"def age:Int=0 }class Student extends Person{override val name:String="jc"override val age:Int=29 }isInstanceOf和asInstanceOf
如果我們創建了子類的對象, 但是又將其賦值了父類類型的變量, 如果我們有需要將父類類型的變量轉為子類類型的變量時, 該怎么做.
首先, 需要使用isInstanceOf判斷對象是否是指定類的對象, 如果是的話, 則可以使用asInstanceOf將對象轉為指定類型
注意, 如果對象是null, 則isInstanceOf返回false, asInstanceOf返回null
注意, 如果沒有isInstanceOf先判斷對象是否為指定類的實現, 就直接使用asInstanceOf轉換, 可能會出現異常
跟java不一樣, java是使用括號強轉類型
getClass和classOf
isInstanceOf只能判斷對象是否是指定類以及其子類的對象, 而不能精確判斷出, 對象是否是指定類的對象
如果要求精確地判斷對象就是指定類的對象, 那么就只能用getClass和classOf了
對象.getClass可以精確獲取對象的類, classOf[類]可以精確獲取類, 然后使用==操作符即可判斷
使用模式匹配進行類型判斷
這種方式更加簡潔明了, 而且代碼的可維護性和可擴展性也非常的高
使用模式匹配, 功能性上來說, 與isInstanceOf一樣, 也是判斷主要是該類以及該類的子類的對象即可, 不是精準判斷的
調用父類的constructor
scala中, 每個類可以有一個主constructor和任意多個輔助constructor, 而每個輔助constructor的第一行都必須是調用其他輔助constructor或者是主constructor; 因此子類的輔助constructor是一定不可能直接調用父類的constructor的.
只能在子類的主constructor中調用父類的constructor, 以下這種語法, 就是通過子類的主constructor調用父類的constructor
注意, 如果是父類中接受的參數, 比如name個age, 子類中接收時, 就不要用任何val和var來修飾了, 否則會認為是子類要覆蓋父類的field
匿名子類
在scala中, 匿名子類是非常常見, 而且非常強大的.
對應著Java的匿名內部類
匿名子類, 可以定一個類的沒有名字的子類, 并直接創建其對象, 然后將對象的引用賦予一個變量.之后甚至可以將該匿名子類的對象傳遞給其他函數.
抽象類
如果在弗雷中, 有某些方法無法立刻實現, 而需要依賴不同的子類來覆蓋, 重寫實現自己不同的方法實現. 此時可以將父類中的這些方法不給出具體的實現, 只有方法前面, 這種方法就是抽象方法
而一個類中如果有一個抽象方法, 那么類就必須用abstract來聲明為抽象類, 此時抽象類是不可以實例化的
在子類中覆蓋抽象類的抽象方法時, 不需要使用override關鍵字
抽象field
如果在父類中 ,定義field, 但沒有給出初始值, 則此field為抽象field
抽象field意味著, scala會根據自己的規則, 為var或val類型的field生成對應的getter和setter方法, 但是父類中是沒有該field的
子類必須覆蓋field, 以定義自己的具體field, 并且覆蓋抽象field, 不需要使用override關鍵字
將trait作為接口使用
scala中Trait是一種特殊的概念
首先我們可以將Trait作為接口來使用, 此時的Trait就與java中的接口非常類似了
在Trait中可以定義抽象方法, 就與抽象類中的抽象方法一樣, 只要不給出方法的具體實現即可
類可以使用extends關鍵字繼承Trait, 注意, 這里不是implement, 而是extends, 在scala中沒有implement的概念, 無論繼承類還是Trait, 統一都是extends
類繼承Trait后, 必須實現其中的抽象方法, 實現時不需要使用override關鍵字
scala不支持類進行多繼承, 但是支持多重繼承Trait, 使用with關鍵字即可
在Trait中定義具體方法
scala中的Trait可以不是只定義抽象方法, 還可以定義具體方法, 此時Trait更像是包含了通用的工具方法的東西
有一個專有的名詞來形容這種情況, 就是說Trait的功能混入了類
Java8 也支持這一功能, 叫默認方法
舉例來說, Trait中可以包含一些很多類都通用的功能方法, 比如日志打印等, spark中就用了Trait來定義通用的日志打印方法
在Trait中定義具體字段
scala中的Trait可以定義具體field, 此時繼承Trait的類就自動獲得了Trait中定義的field
但是這種獲取field的方式與繼承class是不同的: 如果是繼承clas獲取field, 實際是定義在父類中, 而繼承Trait獲取的field, 就直接被添加到子類中
在Trait中定義抽象字段
scala中的Trait可以定義抽象的field, 而Trait中的具體方法則可以基于抽象field來編寫
但是繼承Trait的類, 則必須覆蓋抽象field, 提供具體值
為實例混入Trait
有時候我們可以在創建類的對象時, 指定該對象混入某個Trait, 這樣, 就只有這個對象混入該Trait的方法, 而類的其他對象則沒有
trait Logger{def log(msg:String) {} }trait MyLogger extends Logger{override def log(msg:String) {println("log: "+ msg)} }class Person(val name:String) extends Logger{def sayHello{println("Hi, I'm "+ name); log("sayHello is invoked!")} } val p1 = new Person("jc") p1.sayHello val p2 = new Person("jay") with MyLogger p2.sayHelloTrait調用鏈
scala中支持讓類繼承多個Trait后, 依次調用多個Trait中的同一個方法, 只要讓多個Trait的同一個方法中, 在最后都執行super.方法 即可
類中調用多個Trait中都有的這個方法是, 首相會從最右邊的Trait的方法開始執行, 然后依次往左執行, 形成一個調用鏈條
這種特性非常強大, 其實就相當于設計模式中的責任鏈模式的一種具體實現依賴
在Trait中覆蓋抽象方法
在Trait中, 是可以覆蓋父Trait的抽象方法的
但是覆蓋時, 如果使用了super.方法的代碼, 則無法通過編譯. 因為super.方法就會去掉用父Trait的抽相反, 此時子Trait的該方法還是會被認為是抽象的
此時如果要通過編譯, 就得給子Trait的方法加上abstract override修飾
混合使用Trait的具體方法和抽象方法
在Trait中, 可以混合使用具體方法和抽象方法
可以讓具體方法依賴于抽象方法, 而抽象方法則放到繼承Trait的類中去實現
這種Trait其實就是設計模式中的模板設計模式的體現
Trait的構造機制
在scala中, trait也是有構造代碼的, 也就是在trait中的, 不包含在任何方法中的代碼
而繼承了trait的類的構造機制如下:
Trait字段的初始化
在scala中, Trait是沒有接收參數的構造函數的, 這是Trait和class的唯一區別, 但是如果需求就是要trait能夠對field進行初始化, 該怎么辦? 只能使用scala中非常特殊的一種高級特性:提前定義
trait SayHello{val msg:Stringprintln(msg.toString) }class Personval p = new {val msg:String="init"} with Person with SayHelloclass Person extends { val msg:String = "init"} with SayHello{}//另外一種方式就是使用lazy value trait SayHello{lazy val msg:String = nullprintln(msg.toString) }class Person extends SayHello{override lazy val msg:String = "init" }Trait繼承class
在scala中, Trait也可以繼承自class, 此時這個class就會成為所有繼承該Trait的類的父類
class MyUtil{def printMessage(msg:String) = println(msg) }trait Logger extends MyUtil{def log(msg:String) = printMessage("log: "+ msg) }class Person(val name:String) extends Logger{def sayHello{log("Hi, I'm " +name)printMessage("Hi, I'm "+name)} }總結
以上是生活随笔為你收集整理的2018年第44周-scala入门-面向对象基础语法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【游戏渲染】【译】Unity3D Sha
- 下一篇: 虚拟化:gva、gpa、hva、hpa转