scala基礎學習--面向物件
一.面向物件之類
1.構造器
主構造器:Scala中的每個類都有一個主構造器,主構造器的引數直接放置類名後面,與類交織在一起.
注意:主構造器會執行類中定義的所有語句
package com.legendlee.oop.constructor class Student(var n: String, var a: Int) { var name = n var age = a a = a + 1 var sex: String = _ //表示佔位符 println("主構造器預設會執行類中定義的所有語句") //輔助構造器 def this(n: String, a: Int, sex: String) { //第一行必須呼叫主構造器的程式碼 this(n, a) println("執行輔助構造器") this.sex = sex } //定義一個方法 def study(s:String): Unit ={ println(this.age+"歲的"+this.name+"在學習"+s) } } object Demo01 { def main(args: Array[String]): Unit = { //建立類的一個物件 //呼叫主構造器 var stu=new Student("ss",23) stu.study("linux") //呼叫輔助構造器 var stu2=new Student("legendlee",25,"男") stu2.study("scala") } }
執行結果:
注意:佔位符_只能將變數宣告為var時才能使用
2.訪問許可權
構造器的訪問許可權:
package com.legendlee.oop.privilege //構造器的訪問許可權 class Student private (var n: String, var a: Int) {//私有主構造器 var name = n var age = a var sex:String=_//_表示佔位符 //私有化輔助構造器 private def this( name:String,age:Int,sex:String){ this(name,age) this.sex=sex } } object Demo01 { def main(args: Array[String]): Unit = { var stu =new Student("ss",23) println(stu) } }
表明在Demo01中無法訪問到Student類中的構造器
說明:在主構造器或輔構造器修飾為private後,訪問許可權僅限:本類和伴生物件中使用
類中變數的訪問許可權:
當把類中的成員變數設為私有時,其他類裡無法訪問
解決方案:
1.在Student類中新增setter getter方法
2.使用伴生物件
package com.legendlee.oop.privilege //類中變數的訪問許可權 class Student{ //將類中的成員變數私有化 private var name:String =_ private var age:Int=_ //定義一個方法 def study(c:String): Unit ={ println(this.age+"歲的"+this.name+"在學習"+c) } //提供set方法 def setName(name:String): Unit ={ this.name=name } //get def getName(): String ={ name } //提供set方法 def setAge(age:Int): Unit ={ this.age=age } //get def getAge(): Int ={ age } } object Demo02 { def main(args: Array[String]): Unit = { var stu =new Student() stu.setName("legendlee") stu.setAge(25) println(stu.getName()) stu.study("scala") } }
執行結果:
二.面向物件程式設計之物件
1.scala中的Object
object相當於class的單個例項(單例模式),通常在裡面放一些靜態的field或者method
在Scala中沒有靜態方法和靜態欄位,但是可以使用object這個語法結構來達到同樣的目的.
單例模式的特點就是物件只有一個。放在Object中相當於靜態的,不需要去例項化,可以直接訪問
package com.legendlee.oop.object1
class Session{}//伴生類
//伴生物件 伴生物件名要和伴生類名保持一致
object Session{//只要是書寫在Object裡面的就是靜態的
val session=new Session()
//獲得session物件
def getSession(): Session ={
session
}
}
object Demo01 {
def main(args: Array[String]): Unit = {
//相當於單例模式
var s=Session.getSession()//通過類名直接呼叫方法
var s2=Session.session//通過類名直接呼叫物件
//列印s s2
println(s)
println(s2)
}
}
執行結果:
2.scala中的伴生物件
首先,需要明確一個問題,什麼是伴生物件?
伴生物件的解釋:
如果有一個class,還有一個與class同名的object,那麼就稱這個object是class的伴生物件,class是object的伴生類
要求: 伴生類和伴生物件必須存放在一個.scala檔案中
特點: 伴生類和伴生物件的最大特點是,可以相互訪問(可以訪問私有成員)
可以理解為:
隨著類的載入而自動出現的一個物件(單例)
關鍵字:Object
必須和類名保持一致
好處:可以在伴生物件中直接訪問伴生類中的私有成員
package com.legendlee.oop.object1
class Person private(n: String) {
//全部都定義為私有的
private var name: String = n
private var age: Int = _
private def this(name: String, age: Int) {
this(name)
this.age = age
}
}
//伴生物件
object Person {
val person=new Person("ss")
person.name="珊珊"
person.age=23
def getName(): String ={
this.person.name
}
}
object Demo02 {
def main(args: Array[String]): Unit = {
println(Person.getName())
}
}
執行結果:
總結:伴生物件裡面可以訪問到伴生類中的私有的成員資訊
3.scala中的apply方法
1.apply方法的引入
var arr = Array(10,20,30)
var list = ListBuffer(10,20,30)
在使用Array(10,20,30)和ListBuffer(10,20,30)時,其實使用的並不是陣列或集合類本身,而是使用Array類和ListBuffer類的object伴生物件
package com.legendlee.oop.object1
class Person private(n: String) {
//全部都定義為私有的
private var name: String = n
private var age: Int = _
private def this(name: String, age: Int) {
this(name)
this.age = age
}
}
//伴生物件
object Person {
//建立apply方法
def apply(name:String): Person ={
new Person(name)
}
}
object Demo03 {
def main(args: Array[String]): Unit = {
val p=Person("ss")
}
}
當我滑鼠點選Person的時候會出現apply方法:
說明不需要再重新new一個物件,其直接呼叫了伴生物件中的apply方法.
apply方法也可以過載
package com.legendlee.oop.object1
class Person private(n: String) {
//全部都定義為私有的
var name: String = n
var age: Int = _
private def this(name: String, age: Int) {
this(name)
this.age = age
}
}
//伴生物件
object Person {
//建立apply方法
def apply(name:String): Person ={
new Person(name)
}
def apply(name:String,age:Int): Person ={
new Person(name,age)
}
}
object Demo03 {
def main(args: Array[String]): Unit = {
val p=Person("ss",23)
println(p.name,p.age)
}
}
根據傳參的個數和型別系統自動呼叫對應的apply方法。
三.面向物件之繼承
1.繼承的概念
繼承是面向物件的概念,用於程式碼的可重用性。被擴充套件的類稱為超類或父類, 擴充套件的類稱為派生類或子類。
Scala 中,讓子類繼承父類,與 Java 一樣,也是使用 extends 關鍵字;
繼承就代表,子類可繼承父類的field和method,然後子類還可以在自己的內部實現父類沒有的,子類特有的 field 和method,使用繼承可以有效複用程式碼
在Scala中的繼承:
1、private修飾的field和method不可以被子類繼承,只能在類的內部使用
2、使用final修飾符時,修飾類:類不能被繼承、修飾field和method:不能被覆寫
package com.legendlee.extendsDemo
//父類
class Person {
final def say() = {
println("打招呼")
}
}
//子類
class Student extends Person {
def show() = {
say()
}
}
object Demo01 {
def main(args: Array[String]): Unit = {
//例項化子類物件
val stu = new Student()
stu.show()
}
}
執行結果:
子類可以直接使用父類中的方法。
2.scala中的override和super關鍵字
override的使用場景:
1、Scala中,如果子類要覆蓋父類中的一個非抽象方法,必須要使用 override 關鍵字
2、子類可以覆蓋父類的val修飾的field,只要在子類中使用 override 關鍵字即可
(注意:針對父類中的var修飾的field,子類不能覆寫)
package com.legendlee.extendsDemo
class Phone{
val core:String="android0011"
def call():Unit={
println("打電話.....")
}
}
class NewPhone extends Phone{
//核心升級 針對父類中的val變數,需要使用override覆寫
override val core:String ="android1100"
//打電話功能升級 針對父類中的非抽象方法,子類在重寫時必須新增:override關鍵字
override def call()={
println("視訊...")
super.call()
}
}
object ExtendsDemo2 {
def main(args: Array[String]): Unit = {
val p= new NewPhone()
println(p.core)
p.call
}
}
super關鍵字的使用場景:
super.父類中的方法
在子類覆蓋父類方法後,如果在子類中要呼叫父類中被覆蓋的方法,則必須要使用super關鍵字(顯示的指出要呼叫的父類方法)
3.scala中的protected
在Scala中同樣提供了protected關鍵字來修飾field和method,方便子類去繼承父類中的field和method
private修飾的成員變數:本類可訪問 ,伴生物件可以訪問
private[this]修飾的成員變數:僅限本類訪問
private修飾的成員變數:本類可訪問 ,伴生物件可以訪問
private[this]修飾的成員變數:僅限本類訪問
protected修飾的成員變數:本類、本類的伴生物件、子類、子類的伴生物件
protected[this]修飾的成員變數:本類、子類
完整程式碼如下:
package com.legendlee.extendsDemo
class Person{
private val id1:String ="9527"
private[this] val id2:String ="8888"
//private修飾的成員變數:本類可訪問 ,伴生物件可以訪問
//private[this]修飾的成員變數:僅限本類訪問
protected[this] var name:String ="張三"
//protected修飾的成員變數:本類、本類的伴生物件、子類、子類的伴生物件
//protected[this]修飾的成員變數:本類、子類
protected var age:Int=_
protected def sayHello(): Unit ={
println(name+"打招呼...")
}
}
object Person{
var p=new Person
p.id1
}
class Student extends Person{
def study()={
println(""+this.name)
}
}
object Student extends App{ //學生類的伴生物件
val stu = new Student()
stu.age=22
stu.study()
}
4.呼叫父類中的constructor
package com.legendlee.extendsDemo
class Person {
println("Person類主構造器....")
var name: String = _
var age: Int = _
//過載的輔助構造器
def this(name: String) = {
this() //呼叫主構造器
this.name = name
println(s"Person($name)類輔助構造器....")
}
def this(name: String, age: Int) = {
this() //呼叫主構造器
this.name = name
this.age = age
println("Person(String,Int)類輔助構造器....")
}
}
//建立子類繼承父類
class Student extends Person{
println("Student類的主構造器....")
//子類的輔助構造器
def this(name:String)={
this()
this.name = name
println("Student(String)類的輔助構造器....")
}
//子類的輔助構造器
def this(name:String,age:Int)={
this()
this.name = name
println("Student(String,Int)類的輔助構造器....")
}
}
object ExtendsDemo extends App{
val stu = new Student("張三",18)
}
分析下程式碼的執行過程:
1.先呼叫Person類的主構造器
2.再呼叫Student類的主構造器
3.再呼叫Student類的輔助構造器
執行結果如下:
5.scala中的抽象類
1.抽象類的概念
如果在父類中,有某些方法無法立即實現,而需要依賴不同的子類來覆蓋,重寫實現不同的方法。此時,可以將父類中的這些方法編寫成只含有方法簽名,不含方法體的形式,這種形式就叫做抽象方法。
一個類中,如果含有一個抽象方法或抽象field,就必須使用abstract將類宣告為抽象類,該類是不可以被例項化的。
在子類中覆蓋抽象類的抽象方法時,可以不加override關鍵字。
package com.legendlee.extendsDemo
abstract class Person {
//抽象內容
var name: String //抽象欄位
def hello(): String //抽象方法
//非抽象內容
var age: Int = _
def sayHello(msg: String) = {
println("你好," + msg)
}
}
//子類繼承抽象類,必須重寫所有的抽象內容
class Student extends Person {
override var name = "老唐"
override def hello(): String = {
name
}
}
object AbstractDemo1 {
def main(args: Array[String]): Unit = {
//例項化學生類
val stu1 = new Student()
stu1.name = "老李"
stu1.sayHello(stu1.hello())
}
}
執行結果:
6.Scala中的isInstanceOf和asInstanceOf
在多型中,父型別的引用指向子型別的物件。
//判斷person物件是不是屬於Student類
if(person.isInstanceOf[Student]){
//把person物件轉為Student例項
var stu:Student=person.asInstanceOf[Student]
}
四.面向物件程式設計之Trait
1.trait是什麼?
trait相當於java中的介面,
與java中介面的不同為:它除了定義抽象方法外,還可以定義屬性和方法的實現。
scala中沒有implement的概念,無論是繼承還是trait,都是用extends關鍵字。然後必須實現所有的抽象方法,實現時可以不用override關鍵字。
案例一:HelloTrait
//定義一個trait
trait HelloTrait {
def sayHello(msg:String)
}
//子類繼承Trait,並重寫抽象方法
class TraitChild extends HelloTrait{ //trait的單一繼承
def sayHello(msg: String): Unit = {
println("你好,"+msg)
}
}
object TraitChild{
def main(args: Array[String]): Unit = {
var tc = new TraitChild()
tc.sayHello("ss")
}
}
輸出結果:
Scala不支援對類進行多繼承,但是支援多重繼承 trait,使用 with 關鍵字即可
案例二:Trait多重繼承
//定義第一個trait
trait TraitOne{
def getDate():String
}
//定義第二個trait
trait TraitTwo{
def printDate(data:String)
}
//當繼承多個Trait時,使用關鍵字:with
class TraitChildClass extends TraitOne with TraitTwo{
override def getDate(): String = {
"測試資料"
}
override def printDate(data: String): Unit = {
println(data)
}
}
object TraitTest1 extends App{
val tc = new TraitChildClass()
tc.printDate( tc.getDate() )
}
執行結果:
2.在trait中書寫的定義
在trait中除了可以寫抽象方法外,還可以寫抽象欄位(變數沒有初始化值),具體方法,具體欄位。
案例:
package com.legendlee.traitDemo
trait TraitPerson{
//抽象方法
def study(): Unit
//具體方法
def eat(food:String): Unit ={
println(food)
}
//抽象欄位
var name:String
//具體欄位
var age:Int=_
}
//建立一個子類繼承Trait
class Person extends TraitPerson{
override def study(): Unit = {
println(this.age+"歲的"+this.name+"在學習")
}
override var name: String = _
}
object Demo01 {
def main(args: Array[String]): Unit = {
var person =new Person()
person.name="ss"
person.age=23
person.study()
}
}
執行結果:
3.Trait的高階應用
3.1在例項物件指定混入某個trait
Trait高階知識: 建立物件例項時,trait加入到物件例項中
可在建立類的物件時,為該物件指定混入某個trait,且只有混入了trait的物件才具有trait中的方法,而其他該類的物件則沒有
格式: val 引用變數 = new 類名() with Trait
在建立物件時,使用with關鍵字指定混入某個trait
//定義第一個trait
trait TraitOne{
def getDate():String
}
//定義第二個trait
trait TraitTwo{
def printDate(data:String)
}
//當繼承多個Trait時,使用關鍵字:with
class TraitChildClass extends TraitOne with TraitTwo{
override def getDate(): String = {
"測試資料"
}
override def printDate(data: String): Unit = {
println(data)
}
}
object TraitTest1 extends App{
val tc = new TraitChildClass()
tc.printDate( tc.getDate() )
}
執行結果為:
3.2、Trait呼叫鏈
問題:當類繼承多個Trait時,而這些Trait中都共有同一個方法,那麼子類呼叫時到底執行哪個Trait中的方法?
//驗證資料
trait ValidTrait{
def handler(data:String): Unit ={
println("最終的資料驗證:"+data)
}
}
//資料簽名驗證
trait SignatureValidTrait extends ValidTrait{
override def handler(data:String):Unit={
println("資料簽名驗證:"+data)
//呼叫鏈: 多個Trait具有相同的方法
super.handler(data) //會執行ValidTrait中的handler方法
}
}
//資料的合法性驗證
trait DataValidTrait extends ValidTrait{
override def handler(data:String):Unit={
println("資料合法性驗證:"+data)
super.handler(data)
}
}
class Request() extends DataValidTrait with SignatureValidTrait{
def getParamter(data:String): Unit ={
handler(data)
}
}
object ValidTest extends App{
val r = new Request()
r.getParamter("測試資料")
}
執行的結果為:
Trait的呼叫鏈機制:
當多個Trait中共同都具有一個相同的方法時,子類在繼承了這些Trait後,當子類物件呼叫這個共同的方法時:
先執行extends後面最右邊的Trait中的方法,依次向左邊Trait中的方法順序執行下去(使用了:Super.共同方法() )
3.3、混合使用trait的具體方法和抽象方法
在trait中,可以混合使用具體方法和抽象方法
案例:
trait ValidTrait {
//抽象方法
def getData: String
//非抽象方法
def valid(data: String): Boolean = {
data.equals(getData) //在非抽象方法中使用抽象方法
}
}
class ValidClass extends ValidTrait {
override def getData = {
"資料"
}
}
object ValidTest extends App {
val vc = new ValidClass()
if (vc.valid("資料")){
println("合法資料")
}else{
println("非法資料")
}
}
執行結果:
3.4、Trait的構造機制
3.4.1、構造機制
在Scala中,trait也是有構造的,即在trait中,不包含在任何方法中的程式碼
說明:Trait中只有無參構造(主構造器)
//父類
class Father{
println("Father類的主構造器.....")
}
//父Trait
trait TraitParent{
println("TraitParent構造器執行了一次......")
}
trait TraitOne extends TraitParent{
println("TraitOne構造器.....")
}
trait TraitTwo extends TraitParent{
println("TraitTwo構造器.....")
}
//子類繼承了父類,同時又繼承了兩個Trait (Trait也繼承父Trait)
class TraitChildClass extends Father with TraitTwo with TraitOne {
println("TraitChildClass主構造器.......")
}
object ConstructorDemo extends App{
val tc = new TraitChildClass()
}
執行結果:
子類繼承了父類的同時,也繼承了多個Trait(Trait也有繼承)時,構造機制:
1、父類的構造器
2、多個Trait從左向右依次執行(構造Trait時,先構造父Trait)
注意:如果多個Trait繼承同一個父Trait,則父Trait只會構造一次
3、子類的構造器
說明:trait的構造器的呼叫順序和trait呼叫鏈的呼叫順序相反
3.4.2、Trait中的field初始化
在Scala中,Trait是沒有帶引數構造器的(也是Trait和class的區別)
如果想要對Trait中的field進行初始化,怎麼解決呢?
答案: Trait的高階特性:提前定義
首先,看一段程式碼
trait MyTrait{
//抽象欄位
val msg:String
//主構造器程式碼
println(s"訊息的長度是:${msg.length}")
}
class MyTraitChild extends MyTrait{
override val msg = "測試資料"
}
object TraitFieldDemo1 extends App{
val my = new MyTraitChild()
}
其中在trait中定義了一個抽象的欄位,在其子trait例項化了這個抽象欄位。
執行結果:
為什麼會出現這個問題?
分析錯誤原因: 結合前面學習的Trait構造機制
先去執行MyTrait中的構造程式碼,而在MyTrait的構造程式碼中需要使用“抽象filed”,但該field還未初始值。“null.length”造成空指標異常。
解決方案:
1.修改例項化的程式碼,使用混入方案
trait MyTrait{
//抽象欄位
val msg:String
//主構造器程式碼
println(s"訊息的長度是:${msg.length}")
}
class MyTraitChild extends MyTrait{
override val msg = "測試資料"
}
object TraitFieldDemo1 extends App{
//修改例項化的程式碼,使用混入方案
val my = new { override val msg = "測試資料"} with MyTraitChild()
}
執行結果:
2.不會破壞例項化程式碼,修改extends後滿的程式碼。
trait MyTrait{
//抽象欄位
val msg:String
//主構造器程式碼
println(s"訊息的長度是:${msg.length}")
}
//相當於提前初始化
class MyTraitChild extends { override val msg = "測試資料"} with MyTrait{
}
object TraitFieldDemo1 extends App{
val my = new MyTraitChild()
}
執行結果:
3.5、Trait繼承class
當Trait繼承了某個class後,Trait就繼承了該class中的方法,當子類在繼承Trait後,也同時繼承了class中的方法。
class Person {
var name: String = _
var age: Int = _
def sayHello(): Unit = {
println("打招呼....")
}
}
//trait繼承Person後,就可以直接使用Person中的成員
trait TraitPerson extends Person {
def eat(food: String): Unit = {
println(this.name + "吃" + food) //可以直接使用父類中的成員
}
}
class Student extends TraitPerson {
}
object Test extends App {
val stu = new Student()
stu.name = "張三"
stu.age = 22
stu.sayHello()
stu.eat("食物")
}
執行結果: