Scala程序設計基礎(三)

面向對象編程

  • 這部分可以類比Java中的很多概念
  • 在IDEA中新建object
import java.util.Date

class Customer {
  var name:String = _	// 下劃線表示佔位,傳入相應類型的默認值
  var sex:String = _
  val registerDate:Date = new Date

  def sayHi(msg:String):Unit = {	// Unit即void
    println(msg)
  }
}

object Main {
  // main方法必須要放在一個scala的object(單例對象)中才能執行
  def main(args: Array[String]): Unit = {	
    val customer = new Customer
    //給對象的成員變量賦值
    customer.name = "張三"
    customer.sex = "男"

    println(s"姓名: ${customer.name}, 性別:${customer.sex}, 註冊時間: ${customer.registerDate}")	// 使用${}格式化輸出變量,s表示字符串
    //對象調用方法  
    customer.sayHi("你好!")
  }
}

構造器

  • 主構造器:指在類名的後面跟上一系列參數
  • 輔助構造器:在類中使用this來定義
class Student(val name:String, val age:Int) {	// 類的默認成員屬性都是val,可以使用var聲明
    val address:String="beijing" 	
  	// 定義一個參數的輔助構造器
  	def this(name:String) {// 子類主構造器(傳參即可)
    	// 第一行必須調用主構造器、其他輔助構造器或者super父類的構造器
    	this(name, 20)	// 調用主構造器
  }

  def this(age:Int) {
    this("某某某", age)
  }
}
  • 這裏的主構造器類似Java的父類構造器,但是傳參即可
  • 輔助構造器類似子類構造器,但必須使用this調用父類的構造方法

注:只是類比學習

對象

  • scala中是沒有靜態(static)成員
  • 需要實現static變量、static方法的效果,要通過scala中的單例對象(object)
  • scala主要進行函數式編程,這裏可以類比JavaScript中的模式,靜態屬性/方法可以藉助對象實現
import java.text.SimpleDateFormat

object DateUtils {

  // 在object中定義的成員變量,相當於Java中定義一個靜態變量
  // 定義一個SimpleDateFormat日期時間格式化對象
  val simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm")

  // 構造代碼
  println("構造代碼")

  // 相當於Java中定義一個靜態方法
  def format(date:Date) = simpleDateFormat.format(date)

  // main是一個靜態方法,所以必須要寫在object中
  def main(args: Array[String]): Unit = {
    println { DateUtils.format(new Date()) };	// 通過 . 調用
  }
}

伴生對象

  • class和object具有同樣的名字,那麼就稱這個object是class的伴生對象,class是object的伴生類
  • 最大特點是:可以相互訪問,包括私有變量
class ClassObject {
  val id = 1
  private var name = "itcast"
  def printName(): Unit ={
    //在Dog類中可以訪問伴生對象Dog的私有屬性
    println(ClassObject.CONSTANT + name )
  }
}

object ClassObject{
  //伴生對象中的私有屬性
  private val CONSTANT = "汪汪汪 : "
  def main(args: Array[String]) {
    val p = new ClassObject	// 實例化類
    p.name = "123"	    	//訪問私有的字段name
    p.printName()
  }
}

apply方法

  • 我們可以使用如下代碼新建數組,不需要關鍵字new+類名,如何實現?
	// 創建一個Array對象
	val a = Array(1,2,3,4)
  • 通過下面的scala源碼可以發現,其實默認調用了Array對象的apply()方法
    在這裏插入圖片描述
  • 伴生對象的apply方法用來快速地創建一個伴生類的對象(實例)
class Person(var name:String, var age:Int) {
  override def toString = s"Person($name, $age)"
}

object Person {
  // 實現apply方法
  // 返回的是伴生類的對象
  // 有沒有點像Java自定義異常?必須實現所有構造方法
  def apply(name:String, age:Int): Person = new Person(name, age)
  // apply方法支持重載
  def apply(name:String):Person = new Person(name, 20)
  def apply(age:Int):Person = new Person("某某某", age)
  def apply():Person = new Person("某某某", 20)
}

object Main2 {
  def main(args: Array[String]): Unit = {
    val p1 = Person("張三", 20)	// Person對象的第一個apply()方法
    val p2 = Person("李四")
    val p3 = Person(100)
    val p4 = Person()

    println(p1)
    println(p2)
    println(p3)
    println(p4)
  }
}

main方法

  • 我們在對象中實現main方法
  • 也可以繼承自App Trait(特質),可以理解爲Java中的接口
object Main2 extends App {
 	println("hello, scala")
}

繼承

  • 使用extends關鍵字實現繼承,基本和Java相同
class Person1 {
  var name = "super"
  def getName = this.name
}

class Student1 extends Person1

object Main1 {
  def main(args: Array[String]): Unit = {
    val p1 = new Person1()
    val p2 = new Student1()
    p2.name = "張三"
    println(p2.getName)
  }
}
// 必須在子類的主構造器中調用父類的構造器
class Person8(var name:String){
    println("name:"+name)
}
// 傳參,調用父類的構造器
class Student8(name:String, var clazz:String) extends Person8(name)

object Main8 {
  def main(args: Array[String]): Unit = {
    val s1 = new Student8("張三", "三年二班")
    println(s"${s1.name} - ${s1.clazz}")
  }
}
// output:
// name:張三
// 張三 - 三年二班
  • 對象也可以繼承類油!
class Person2 {
  var name = "super"
  def getName = this.name
}

object Student2 extends Person2		// 單例對象繼承

object Main2 {
  def main(args: Array[String]): Unit = {
    println(Student2.getName)		// 相當於調用靜態方法(object中的方法)
  }
}
  • 與Java不同,子類要覆蓋父類中的一個非抽象方法,必須要使用override關鍵字
  • 相同的是,使用super訪問父類成員
class Person3 {
  val name = "super"
  def getName = name
}

class Student3 extends Person3 {
  // 重寫val字段
  override val name: String = "child"
  // 重寫getName方法
  override def getName: String = "hello, " + super.getName
}

object Main3 {
  def main(args: Array[String]): Unit = {
    println(new Student3().getName)
  }
}

抽象類

  • 就是這麼abstract,爲了更好的實現多態?
  • 沒有方法體的方法稱爲抽象方法,沒有初始化的變量稱爲抽象字段
abstract class Person(val name:String) {// 還有個構造方法呢
  //抽象方法
  def sayHello:String
  def sayBye:String
  //抽象字段  
  val address:String  
}
class Student(name:String) extends Person(name){
  //重寫抽象方法,不需要使用override
  def sayHello: String = "Hello,"+name
  def sayBye: String ="Bye,"+name
  //重寫抽象字段,需要使用override
  override val address:String ="beijing "
}
object Main{
  def main(args: Array[String]) {
    val s = new Student("tom")
    println(s.sayHello)
    println(s.sayBye)
    println(s.address)
  }
}

匿名內部類

  • 這個非常常用,配合匿名類使用
  • 匿名內部類是沒有名稱的子類,直接用來創建實例對象
abstract class Person {
  //抽象方法  
  def sayHello:Unit
}

object Main {
  def main(args: Array[String]): Unit = {
    // 直接用new來實例化一個匿名內部類對象
    val p1 = new Person {	// 通過new實例化,但是實現了抽象方法,還是屬於子類
      override def sayHello: Unit = println("我是一個匿名內部類")
    }
    p1.sayHello
  }
}

isInstanceOf

  • 在代碼中要經常進行類型的判斷和類型的轉換,在scala中
  • isInstanceOf判斷對象是否爲指定類的對象
  • asInstanceOf將對象轉換爲指定類型
class Person4
class Student4 extends Person4

object Main4 {
  def main(args: Array[String]): Unit = {
    val s1:Person4 = new Student4	// Person4 類型
    // 判斷s1是否爲Student4類型
    if(s1.isInstanceOf[Student4]) {
	    // 將s1轉換爲Student3類型
	    val s2 =  s1.asInstanceOf[Student4]
    }
  }
}

getClass

  • isInstanceOf 只能判斷出對象是否爲指定類以及其子類的對象,而不能精確的判斷
  • 要求精確地判斷出對象就是指定類的對象,那麼就只能使用 getClassclassOf
class Person
class Student extends Person

object Student{
  def main(args: Array[String]) {
    val p:Person=new Student
    //判斷p是否爲Person5類的實例
    println(p.isInstanceOf[Person])//true

    //判斷p的類型是否爲Person5類
    println(p.getClass == classOf[Person])//false

    //判斷p的類型是否爲Student5類
    println(p.getClass == classOf[Student])//true
  }
}

訪問修飾符

  • 可以在成員前面添加private/protected關鍵字來控制成員的可見性
  • 但在scala中,任何沒有被標爲private或protected的成員都是公共的,沒有public關鍵字

private[this]

  • 被修飾的成員只能在當前類中被訪問
class Person6 {
  // 只有在當前對象中能夠訪問
  private[this] var name = "super"
  def getName = this.name	// 正確!
  def sayHelloTo(p:Person6) = {
    println("hello" + p.name)     // 報錯!無法訪問
  }
}

object Person6 {
  def showName(p:Person6) = println(p.name)  // 報錯!外部無法訪問
}

protected[this]

  • 可以通過this.訪問或者子類通過this.訪問,和java很像
class Person7 {
  // 只有在當前對象以及繼承該類的當前對象中能夠訪問
  protected[this] var name = "super"
  
  def getName = {
    // 正確!
    this.name
  }
  def sayHelloTo1(p:Person7) = {
    // 編譯錯誤!無法訪問
    println(p.name)	// 只能this.
  }
}

class Student7 extends Person7 {
  def showName = {
    // 正確!
    println(name)	// 默認使用this.
  }
  def sayHelloTo2(p:Person7) = {
    // 編譯錯誤!無法訪問
    println(p.name)
  }
}

特質

  • 特質trait是scala中代碼複用的基礎單元
  • 它可以將方法和字段定義封裝起來,然後添加到類中,我們叫混入
  • 一個類可以添加任意數量的特質,像Java接口吧!!!
  • 使用extends來繼承trait,如果要繼承多個trait,則使用with關鍵字
trait Logger {
  // 抽象方法
  def log(msg:String)
}

trait MessageSender {
  def send(msg:String)
}

class ConsoleLogger extends Logger with MessageSender {
  override def log(msg: String): Unit = println(msg)
  override def send(msg: String): Unit = println(s"發送消息:${msg}")
}

object LoggerTrait {
  def main(args: Array[String]): Unit = {
    val logger = new ConsoleLogger2
    logger.log("控制檯日誌: 這是一條Log")
    logger.send("你好!")
  }
}
  • trait中也可以定義具體的方法
  • 實例對象混入trait
trait LoggerMix {
  def log(msg:String) = println(msg)
}

class UserService

object FixedInClass {
  def main(args: Array[String]): Unit = {
    // 使用with關鍵字直接將特質混入到對象中
    val userService = new UserService with LoggerMix// 只有這個實例的對象有
    userService.log("你好")
  }
}
  • 可以瞭解一下trait調用鏈
    在這裏插入圖片描述
  • 參考如下代碼,理解一下trait的優勢
// 支付數據處理
trait HandlerTrait {
  def handle(data: String) = {
    println("處理數據...")
  }
}

// 數據校驗處理
trait DataValidHandlerTrait extends HandlerTrait {
  override def handle(data: String) = {
    println("驗證數據...")
    super.handle(data)
  }
}

// 簽名校驗處理
trait SignatureValidHandlerTrait extends HandlerTrait {
  override def handle(data: String) = {
    println("檢查簽名...")
    super.handle(data)
  }
}

// 支付服務
class PaymentService extends DataValidHandlerTrait with SignatureValidHandlerTrait {
  def pay(data:String) = {
    println("準備支付...")
    this.handle(data)
  }
}

object PaymentService {
  def main(args: Array[String]) {
    val payService = new PaymentService()
    payService.pay("signature:10233123||md5:123889a3d5s1f6123||data:{order:001,money:200}")
  }
}
// 程序運行輸出如下:
// 準備支付...
// 檢查簽名...
// 驗證數據...
// 處理數據...

在這裏插入圖片描述

模板模式

  • 使用具體方法依賴於抽象方法,而抽象方法可以放到繼承trait的子類中實現
trait Logger {
  // 抽象方法
  def log(msg:String)
  // 具體方法,依賴於抽象方法log
  def info(msg:String) = log("INFO:" + msg)
  def warn(msg:String) = log("WARN:" + msg)
  def error(msg:String) = log("ERROR:" + msg)
}

class ConsoleLogger extends Logger {
  // 實現抽象方法
  override def log(msg: String): Unit = println(msg)
}

object LoggerTrait {
  def main(args: Array[String]): Unit = {
    val logger = new ConsoleLogger
    logger3.info("這是一條普通信息")
    logger3.warn("這是一條警告信息")
    logger3.error("這是一條錯誤信息")
  }
}

trait構造機制

  • trait也有構造代碼,但和類不一樣,特質不能有構造器參數
  • 每個特質只有一個無參數的構造器
  • 一個類繼承另一個類、以及多個trait,當創建該類的實例時,它的構造順序如下:
  1. 執行父類的構造器
  2. 從左到右依次執行trait的構造器
  3. 如果trait有父trait,先構造父trait,如果多個trait有同樣的父trait,則只初始化一次
  4. 執行子類構造器
class Person_One {
  println("執行Person構造器!")
}
trait Logger_One {
  println("執行Logger構造器!")
}
trait MyLogger_One extends Logger_One {
  println("執行MyLogger構造器!")
}
trait TimeLogger_One extends Logger_One {
  println("執行TimeLogger構造器!")
}
class Student_One extends Person_One with MyLogger_One with TimeLogger_One {
  println("執行Student構造器!")
  }
object exe_one {
  def main(args: Array[String]): Unit = {
    val student = new Student_One
  }
}
// 程序運行輸出如下:
// 執行Person構造器!
// 執行Logger構造器!
// 執行MyLogger構造器!
// 執行TimeLogger構造器!
// 執行Student構造器!

模式匹配

  • scala有一個十分強大的模式匹配機制,可以應用到很多場合
  • 並且scala還提供了樣例類,對模式匹配進行了優化,可以快速進行匹配
  • 類似於Java中的switch這裏使用match
//todo:匹配字符串
object CaseDemo01 extends App{
  //定義一個數組
  val arr=Array("hadoop","zookeeper","spark","storm")

  //隨機取數組中的一位,使用Random.nextInt
  val name = arr(Random.nextInt(arr.length))
  println(name)

  name match {
    case "hadoop"     => println("大數據分佈式存儲和計算框架...")
    case "zookeeper"  => println("大數據分佈式協調服務框架...")
    case "spark"      => println("大數據分佈式內存計算框架...")
      //表示以上情況都不滿足纔會走最後一個
    case _            => println("我不認識你")
  }
}

//todo:匹配類型
object CaseDemo02 extends App{
  //定義一個數組
  val arr=Array("hello",1,-2.0,CaseDemo02)

  //隨機獲取數組中的元素
  val value=arr(Random.nextInt(arr.length))
  println(value)

    
  value match {
    case x:Int                => println("Int=>"+x)
    case y:Double if(y>=0)    => println("Double=>"+y)
    case z:String             => println("String=>"+z)
    case _                    => throw new Exception("not match exception")
  }
}

//匹配數組
object CaseDemo03 extends App{

  //匹配數組
  val  arr=Array(1,3,5)
  arr match{
    case Array(1,x,y) =>println(x+"---"+y) // 會分別將數組中的元素複製給x,y
    case Array(1,_*)  =>println("1...")
    case Array(0)     =>println("only 0")
    case _            =>println("something else")
      
  }
}

//匹配集合
object CaseDemo04 extends App{

  val list=List(0,3,6)
  list match {
    case 0::Nil        => println("only 0")	// Nil表示空
    case 0::tail       => println("0....")	// :: 表示追加到列表,tail表示列表尾部
    case x::y::z::Nil  => println(s"x:$x y:$y z:$z")
    case _             => println("something else")	// _ 表示啥也沒匹配到,即default
  }
} 

//匹配元組
object CaseDemo05 extends App{
  
  val tuple=(1,3,5)
  tuple match{
    case (1,x,y)    => println(s"1,$x,$y")
    case (2,x,y)    => println(s"$x,$y")
    case _          => println("others...")
  }
}

樣例類

  • 樣例類是一種特殊類,它可以用來快速定義一個用於保存數據的類
  • 使用case class關鍵字
// 定義一個樣例類
// 樣例類有兩個成員name、age
case class CasePerson(name:String, age:Int)

// 使用var指定可變成員變量
case class CaseStudent(var name:String, var age:Int)

object CaseClassDemo {
  def main(args: Array[String]): Unit = {
    // 1. 使用new創建實例
    val zhangsan = new CasePerson("張三", 20)// 一般不這樣做(意思是也可以)
    println(zhangsan)

    // 2. 使用類名直接創建實例
    val lisi = CasePerson("李四", 21)
    println(lisi)

    val xiaohong = CaseStudent("小紅", 23)
    xiaohong.age = 24
    println(xiaohong)
  }
}
  • 樣例對象時可以序列化的,先了解即可
case class SubmitTask(id: String, name: String)
case class HeartBeat(time: Long)
case object CheckTimeOutTask

object CaseDemo06 extends App{	// 繼承App特質
  val arr = Array(CheckTimeOutTask,
                  HeartBeat(10000), 
                  SubmitTask("0001", "task-0001"))

  arr(Random.nextInt(arr.length)) match {
      
       case SubmitTask(id, name) => println(s"id=$id, name=$name")
       case HeartBeat(time) => println(s"time=$time")
       case CheckTimeOutTask => println("檢查超時")
	   case _	=> println("what???")
  }
}

Option類型

  • Option[T] 是一個類型爲 T 的可選值的容器
  • 如果值存在, Option[T] 就是一個 Some[T] ,如果不存在, Option[T] 就是對象 None
  • 包含兩個子類:
  1. Some包裝了某個值
    在這裏插入圖片描述
  2. None表示沒有值
    在這裏插入圖片描述
val myMap: Map[String, String] = Map("key1" -> "value")
val value1: Option[String] = myMap.get("key1")	// 我希望是String類型的
val value2: Option[String] = myMap.get("key2") 
println(value1) // Some("value1")
println(value2) // None

object TestOption {
  def main(args: Array[String]) {
    val map = Map("a" -> 1, "b" -> 2)
    val value: Option[Int] = map.get("b")	// 我希望是Int類型的
    val v1 =value match {
      case Some(i) => i
      case None => 0
    }
    println(v1)	// 2

    //更好的方式
    val v2 = map.getOrElse("c", 0)
    println(v2)	// 0
  }
}

偏函數

  • 定義一個函數,只接受和處理其參數定義域範圍內的子集,對於參數範圍外的參數則拋出異常
  • 它是PartialFunction[A, B]的一個實例
  • A代表輸入參數類型
  • B代表返回結果類型
  • 實例得到的偏函數是一個特質trait
object TestPartialFunction {
  // func1是一個輸入參數爲Int類型,返回值爲String類型的偏函數
  val func1: PartialFunction[Int, String] = {// 使用定義方法的格式
    case 1 => "一"
    case 2 => "二"
    case 3 => "三"
    case _ => "其他"
  }
  func1(1)	// String: 一

  def main(args: Array[String]): Unit = {
    val list=List(1,2,3,4,5,6)
    // filter繼承PartialFunction的特質
    val result=list.filter{
      case x if x >3 => true
      case _ => false
    }
    println(result)	// List(4,5,6)
  }
}

小結

這裏主要介紹了scala面向對象編程中經常用到的方法,每種特性都有適用的場合。還有很多可以擴展的地方,可以根據場景再次拓寬。下一節從異常處理開始,總結其基本概念和用法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章