1. 程式人生 > >scala基礎學習--面向物件

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("食物")
}

執行結果:
在這裡插入圖片描述

四.模式匹配