国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

06-Scala面向?qū)ο?/h1>

這篇具有很好參考價(jià)值的文章主要介紹了06-Scala面向?qū)ο蟆OM麑Υ蠹矣兴鶐椭?。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

面向?qū)ο缶幊?/h2>

? Scala是一門完全面向?qū)ο蟮恼Z言,摒棄了Java中很多不是面向?qū)ο蟮恼Z法。

? 雖然如此,但其面向?qū)ο笏枷牒?Java的面向?qū)ο笏枷脒€是一致的

Scala包

1)基本語法

Scala中基本的package包語法和 Java 完全一致

例如:

package com.clear.bigdata.scala

2)Scala包的三大作用(和Java一樣)

  • 區(qū)分相同名字的類
  • 當(dāng)類很多時(shí),可以很好的管理類
  • 控制訪問范圍

3)包的命名規(guī)則

? 只能包含數(shù)字、字母、下劃線、小圓點(diǎn),但是不能以數(shù)字開頭,也不要使用關(guān)鍵字

? 命令規(guī)范:一般是小寫字母 + 小圓點(diǎn)

擴(kuò)展語法

Java中package包的語法比較單一,Scala對此進(jìn)行擴(kuò)展

Scala有兩種包的管理風(fēng)格,一種和Java的包管理風(fēng)格相同,每個(gè)源文件一個(gè)包(包名和源文件所在路徑不要求必須一致),包名用”.“進(jìn)行分隔以表示包的層級關(guān)系,如:

com.clear.scala

另一種風(fēng)格,通過嵌套的風(fēng)格表示層級關(guān)系:

  • Scala中的包和類的物理路徑?jīng)]有關(guān)系
  • package關(guān)鍵字可以嵌套聲明使用
  • 一個(gè)源文件中可以聲明多個(gè)package
package com
package clear {
    package bigdata {
        package scala {
            object ScalaPackage {
                def test(): Unit = {
                    println("test...")
                }
            }
        }
    }
}
  • 同一個(gè)源碼文件中子包可以直接訪問父包中的內(nèi)容,而無需import,但是父包訪問子包需要import
// 用嵌套風(fēng)格定義包
package com {
    import com.clear.scala.Inner
    
    // 在外層包中定義單例對象
    object Outer {
        val out: String = "out"
        def main(args: Array[String]): Unit = {     
            println(Inner.in)  //  父包訪問子包需要import
        }
    }
    package clear {
        package scala{
            // 內(nèi)存包中定義單例對象
            object Inner{
                val in: String = "in"
                def main(args: Array[String]): Unit = {
                    Outer.out = "outer"  // 子包中直接訪問父包中的內(nèi)容
                	println(Outer.out)
                }
                
            }
        } 
    }
}
  • Scala中package也可以看作對象(即包對象),并聲明屬性和函數(shù)
package com
package object clear {  // 在 com.clear 包下創(chuàng)建的 clear包對象
    val name : String = "zhangsan"
    def test(): Unit = {
        println( name )
    }
}
package com {
    package clear {
        package scala {  // 在 com.clear.scala 在訪問包對象
            object ScalaPackage {
                def test(): Unit = {
                }
            }
        }
    }
}

包對象

? 在Scala中可以為每個(gè)包定義一個(gè)同名的包對象,定義在包對象中的成員,作為其對應(yīng)包下所有的 class 和 object 的共享變量,可以直接被訪問

說明:

若使用java的包管理風(fēng)格,則包對象一般定義在其對應(yīng)的package.scala文件中,包對象名與保持一致。

package com.clear	


// 默認(rèn)包對象的名稱必須和 包名一致,全局只有一份
// 使用 package修飾
package object oop {			// 我們在 com.clear.oop包下創(chuàng)建了一個(gè)包對象
  // 定義當(dāng)前包共享的屬性和方法
  val commonValue =  "中國"

  def commonMethod(): Unit = {
    println(s"你好, ${commonValue}")
  }
}
package com.clear.oop

object Test {  // 在 com.clear.oop 包下創(chuàng)建了一個(gè)Test單例對象,去訪問包對象定義的屬性和方法
  def main(args: Array[String]): Unit = {
    commonMethod()		 
  }
}

導(dǎo)包說明

  • 通配符導(dǎo)入:Scala中基本的import導(dǎo)入語法和 Java完全一致
import java.util.List
import java.util._  // Scala中使用下劃線代替Java中的星號

Java中 import 導(dǎo)入的語法比較單一,Scala對此進(jìn)行擴(kuò)展

  • 局部導(dǎo)入:Scala中的 import 語法可以在任意位置使用(這一點(diǎn)在python中也可以)
object ScalaImport{
    def main(args: Array[String]): Unit = {
        import java.util.ArrayList
            new  ArrayList()   
    }
}
  • Scala中可以導(dǎo)包,而不是導(dǎo)類
object ScalaImport{
    def main(args: Array[String]): Unit = {
        import java.util
        new util.ArrayList()
    }
}
  • 導(dǎo)入相同包的多個(gè)類:Scala中可以在同一行中導(dǎo)入相同包中的多個(gè)類,簡化代碼(這一點(diǎn)在python中也可以)
import java.util.{List, ArrayList}
  • 屏蔽類:Scala中可以屏蔽某個(gè)包中的類
import java.util._
import java.sql.{ Date=>_, Array=>_, _ }  // 屏蔽Date、Array類
  • 給類起別名:Scala中可以給類起別名,簡化使用(這一點(diǎn)在python中也可以)
import java.util.{ArrayList=>AList}

object ScalaImport{
    def main(args: Array[String]): Unit = {
        new AList()
    }
}
  • 導(dǎo)入包的絕對路徑:Scala中可以使用類的絕對路徑而不是相對路徑
new _root_.java.util.ArrayList
  • 默認(rèn)情況下,Scala中會導(dǎo)入如下包和對象
import java.lang._
import scala._
import scala.Predef._  // println就是在該類

面向?qū)ο缶幊讨蓄惪梢钥闯梢粋€(gè)模板,而對象可以看成是根據(jù)模板所創(chuàng)建的具體事物

定義類

  1. 基本語法
// 聲明類:訪問權(quán)限 class 類名 { 類主體內(nèi)容 } 
[修飾符] class User {  
    // 類的主體內(nèi)容
}
// 對象:new 類名(參數(shù)列表)
new User()

說明:

? 在Scala中,類class 默認(rèn)就是public全局公有可見的,但是類并不聲明為public

? Scala中一個(gè)源文件中可以聲明多個(gè)公共類(這一點(diǎn)是Java所不具有的)

package com.clear.oop

import scala.beans.BeanProperty

object Test2 {
  def main(args: Array[String]): Unit = {
    val student = new Student()
    // student.name // 編譯錯誤,不能直接訪問 private屬性
    student.sex = "男"
    println(student.sex)
  }
}

// 定義一個(gè)類
//public class Stu {  // 報(bào)錯
//
//}

class Student {  // 對于Scala的底層,它其實(shí)是會將屬性包裝成private,然后提供了get、set,從而避免了想Java一樣有很多像get、set這樣的冗余代碼
  // 定義屬性
  //public var name: String = "張三"    // 編譯報(bào)錯,默認(rèn)就是public,無須修飾

  private var name: String = "張三"
  @BeanProperty  // 顯式地實(shí)現(xiàn)像Java一樣的提供屬性的get、set方法
  var age: Int = 18
  var sex: String = _  // _ 表示初始值為空(注意:這種情況必須使用 var)
}

如下就是Student類,反編譯的源碼:

package com.clear.oop;

import scala.reflect.ScalaSignature;

@ScalaSignature(bytes="\006\005I3A!\....0\t")

public class Student
{
  private String name = "張三";

  private int age = 18;
  private String sex;

  private String name(){ 
    return this.name; 
  } 
  private void name_$eq(String x$1) {
  	this.name = x$1; 
  } 
  public int age() {
    return this.age;
  } 
  public void age_$eq(int x$1) {
      this.age = x$1; 
  } 
  public String sex() {
      return this.sex; 
  } 
  public void sex_$eq(String x$1) {
      this.sex = x$1; 
  }

  public int getAge()
  {
    return age(); 
  } 
  public void setAge(int x$1) { 
      age_$eq(x$1);
  }
}

屬性

  1. 基本語法
class User {
    var name : String = _  // 類屬性其實(shí)就是類變量
    var age : Int = _  // 下劃線表示類的屬性默認(rèn)初始化
}
  1. 擴(kuò)展語法

Scala中的屬性其實(shí)在編譯后也會生成方法

class User {
    var name : String = _
    val age : Int = 30
    private var email : String = _  // 一般情況下,不建議聲明private,Scala的屬性默認(rèn)底層就是private
    @BeanProperty var address : String = _  // @BeanProperty 會顯式的生成get、set方法
}

User類反編譯后,如下

import scala.reflect.ScalaSignature;

@ScalaSignature(bytes="\006\0...\001")
public class User
{
  private String name;
  private final int age = 30;
  private String email;
  private String address;

  public String name()
   {
    return this.name; } 
  public void name_$eq(String x$1) { this.name = x$1; } 
  public int age() { return this.age; } 
  private String email() { return this.email; } 
  private void email_$eq(String x$1) { this.email = x$1; } 
  public String address() { return this.address; } 
  public void address_$eq(String x$1) { this.address = x$1; } 
  public String getAddress() { return address(); } 
  public void setAddress(String x$1) { address_$eq(x$1); }
}

面向?qū)ο笕筇匦?/h3>

封裝

? 封裝就是把抽象出的數(shù)據(jù)和對數(shù)據(jù)的操作封裝在一起,數(shù)據(jù)被保護(hù)在內(nèi)部,程序的其它部分只有通過被授權(quán)的操作(成員方法),才能對數(shù)據(jù)進(jìn)行訪問。Java封裝操作如下:

    1. 將屬性進(jìn)行私有化
    1. 提供一個(gè)公共的 set 方法,用于對屬性賦值
    1. 提供一個(gè)公共的 get 方法,用于獲取屬性的值

Scala中的 public不聲明默認(rèn)就是)屬性,底層實(shí)際就是 private,并通過 get 方法(obj.field())和 set方法(obj.field_=(value) )對其進(jìn)行操作。所以 Scala并不推薦我們將屬性設(shè)置為 private,再為其設(shè)置 public的get 和 set 方法。但是由于很多Java框架都利用反射調(diào)用 getXXX 和 setXXX 方法,有時(shí)候?yàn)榱思嫒葸@些框架,也會為Scala的屬性設(shè)置 @BeanProperty 注解(以顯式實(shí)現(xiàn) getXXX 和 setXXX 方法)

訪問權(quán)限

? 在Java中,訪問權(quán)限分為:public、private、protected 和 默認(rèn)。在Scala中,你可以通過類似的修飾符到達(dá)同樣的效果。但是使用上有所區(qū)別:

  • Scala 中的屬性和方法的默認(rèn)訪問權(quán)限為public公有訪問權(quán)限,但Scala中無 public 關(guān)鍵字,所以不能顯式的聲明。

  • private 為私有權(quán)限,只在類的內(nèi)部和伴生對象中可用

  • protected 為受保護(hù)權(quán)限,Scala中受保護(hù)權(quán)限比Java中還嚴(yán)格,同類、子類可以訪問,同包不能訪問

  • private[包名] 增加了 包訪問權(quán)限,包名下的其他類也可用使用

package com.clear.oop

object PackagingTest {
}

// 定義一個(gè)父類
class Person {
  private var idCard: String = "2445"  // 只有本類和伴生對象可以使用
    
  protected var name: String = "張三"	 // 只有本類和子類可以訪問
    
  var sex: String = "男"	 // 無修飾符,在Scala中默認(rèn)為public公有
    
  private[oop] var age: Int = 18  // 包訪問權(quán)限,同包下都可以訪問

  def printInfo(): Unit = {
    println(s"Person: $idCard $name $sex $age")
  }
}
package com.clear.oop

// 定義一個(gè)子類
class Worker extends Person {
  override def printInfo(): Unit = {
    // println(idCard)  // 報(bào)錯,私有屬性即使是子類也訪問不到
    name = "李四"  //  protected修飾,只能在當(dāng)前類 或 子類訪問
    age = 20	// 包訪問權(quán)限,可訪問
    sex = "女"  // 無修飾符,可訪問
    println(s"Worker: $name $sex $age")
  }
}

object PackagingAccess {
  def main(args: Array[String]): Unit = {
    // 創(chuàng)建對象
    val person: Person = new Person
    // person.idCard  // 父類私有屬性,無法訪問
    // person.name  // 報(bào)錯, protected修飾,其他類不能訪問
      
    println(person.age)   // 包訪問權(quán)限,在同一個(gè)包下可以訪問
    println(person.sex)
    person.printInfo()  // Person: 2445 張三 男 18

    val worker: Worker = new Worker()
  }
}
方法

基本語法

def 方法名(參數(shù)列表)[: 返回值類型] = {
	// 方法體
}

說明:

? 在Scala中,如果方法的返回值是 Unit,這樣的方法稱之為 過程(procedure)

? 過程 的等號= 可以省略不寫,例如:

def sayHello() = println("hello world")
// 等價(jià)于

def sayHello() { println("hello world") }  // {} 不可以省略
創(chuàng)建對象

Scala中的對象和 Java 是類似的

val | var 對象名 [:類型]  = new 類型()
var user : User = new User()
構(gòu)造器

和 Java 一樣,Scala中構(gòu)造對象也需要調(diào)用類的構(gòu)造方法來創(chuàng)建。并且一個(gè)類中可以有任意多個(gè)不相同的構(gòu)造方法(即重載)。

這些構(gòu)造方法可以分為2大類:主構(gòu)造函數(shù)輔助構(gòu)造函數(shù)。

基本語法

class 類名(形參列表) {  // 主構(gòu)造器
	// 類體
	
	def this(形參列表) {  // 輔助構(gòu)造函數(shù),使用this關(guān)鍵字聲明
						// 輔助構(gòu)造函數(shù)應(yīng)該直接或間接調(diào)用主構(gòu)造函數(shù)
	}
	def this(形參列表) {  // 輔助構(gòu)造器可以有多個(gè)(可重載)
						 // 構(gòu)造器調(diào)用其他另外的構(gòu)造器,要求被調(diào)用構(gòu)造器必須提前聲明
	}
} 

說明:

  • 輔助構(gòu)造器,函數(shù)的名稱必須為 this,可以有多個(gè),編譯器通過參數(shù)的個(gè)數(shù)及類型來區(qū)分。
  • 輔助構(gòu)造方法不能直接構(gòu)造對象,必須直接或簡介調(diào)用主構(gòu)造方法
  • 構(gòu)造器調(diào)用其他另外的構(gòu)造器,要求被調(diào)用構(gòu)造器必須提前聲明

例如:

package com.clear.oop

object Constructor {
  def main(args: Array[String]): Unit = {
    // val s1 = new Student()  // ()可省略
    val s1 = new Student
    s1.student()

    val s2 = new Student("張三")
    val s3 = new Student("張三",18)
  }
}

// 定義一個(gè)類
class Student() { // 如果主構(gòu)造器沒有參數(shù)列表,()可省略
  // 定義屬性
  var name: String = _
  var age: Int = _

  println("1 主構(gòu)造方法被調(diào)用")

  // 聲明輔助構(gòu)造方法
  def this(name: String) {
    // 輔助構(gòu)造方法應(yīng)該直接或間接調(diào)用主構(gòu)造方法
    this() // 直接調(diào)用主構(gòu)造器
    println("2 輔助構(gòu)造方法一被調(diào)用")
    this.name = name
    println(s"name:  $name age:  $age")
  }

  def this(name: String, age: Int) {
    this(name) // 間接調(diào)用主構(gòu)造器
    println("3 輔助構(gòu)造方法二被起調(diào)用")
    this.age = age
    println(s"name:  $name age:  $age")
  }

  def student(): Unit = { // 定義一個(gè)與類名一樣的方法,但是它只是一個(gè)普通方法
    println("普通方法被調(diào)用")
  }
}
構(gòu)造器參數(shù)

Scala類的主構(gòu)造器函數(shù)的形參包括三種類型:未用任何修飾、var修飾、val修飾

  • 未用任何修飾符修飾,這個(gè)參數(shù)就是一個(gè)局部變量
  • var修飾參數(shù):作為類的成員屬性使用,可以修改
  • val修飾參數(shù):作為類只讀屬性使用,不能修改

例如:

object ConstructorParams {
  def main(args: Array[String]): Unit = {
    val s1 = new Student2
    println(s"s1 name: ${s1.name} ${s1.age}")
    val s2 = new Student3("張三", 18)
    println(s"s1 name: ${s2.name} ${s2.age}")
  }
}

// 定義一個(gè)類
class Student2 { // 無參構(gòu)造器
  // 單獨(dú)定義屬性
  var name: String = _
  var age: Int = _
}

// 上面定義等價(jià)于這種方式
class Student3(var name: String, var age: Int) // var修飾參數(shù):作為類的成員屬性使用,可以修改


class Student4(_name: String, _age: Int) { // 主構(gòu)造器參數(shù)無修飾,這些參數(shù)就是一個(gè)局部變量
  var name = name   // 這種屬于雞肋語法,受Java毒害太深了,Scala不推薦這種方式
  var age = age

}
class Student5(val name: String, val age: Int) // var修飾參數(shù):作為類的成員屬性使用,可以修改

繼承與多態(tài)

繼承

和 Java一樣,Scala中的繼承也是單繼承,且使用extends關(guān)鍵字。

子類繼承父類的屬性和方法

繼承的調(diào)用順序:父類構(gòu)造器 > 子類構(gòu)造器

基本語法

class 子類名 extends 父類名 { 類體 }

例如·:

package com.clear.oop

object Inherit {
  def main(args: Array[String]): Unit = {
    val son1 = new Son("張三",18)
    println("--------------------------")
    val son2 = new Son("張三",18,"666")
  }
}

// 定義一個(gè)父類
class Father() {
  var name: String = _
  var age: Int = _

  println("1 父類主構(gòu)造器被調(diào)用")

  def this(name: String, age: Int) {
    this() // 直接調(diào)用主構(gòu)造器
    println("2 父類輔助構(gòu)造器被調(diào)用")
    this.name = name
    this.age = age
  }

  def printInfo(): Unit = {
    println(s"Father: $name $age")
  }
}

class Son(name: String, age: Int) extends Father { // 這里會調(diào)用父類主構(gòu)造器
  var idCard: String = _
  println("3 子類主構(gòu)造器被調(diào)用")

  def this(name: String, age: Int, idCard: String) {
    this(name, age)
    println("4 子類輔助構(gòu)造器被調(diào)用")
    this.idCard = idCard
  }

  override def printInfo(): Unit ={
    println(s"Son: $name $age $idCard")
  }
}

結(jié)果:

1 父類主構(gòu)造器被調(diào)用
3 子類主構(gòu)造器被調(diào)用     	// 調(diào)用子類構(gòu)造器前先調(diào)用父類構(gòu)造器
--------------------------
1 父類主構(gòu)造器被調(diào)用
3 子類主構(gòu)造器被調(diào)用
4 子類輔助構(gòu)造器被調(diào)用
多態(tài)

? 在 Scala 中,多態(tài)(Polymorphism)可以通過兩種方式實(shí)現(xiàn):子類型多態(tài)(Subtype Polymorphism)和參數(shù)多態(tài)(Parametric Polymorphism)。

子類型多態(tài)(Subtype Polymorphism): 子類型多態(tài)是指在父類的引用變量中可以存儲子類的對象(即父類引用指向子類對象),并且可以通過父類的引用變量調(diào)用子類的方法。在 Scala 中,子類型多態(tài)可以通過繼承和方法重寫來實(shí)現(xiàn)。

class Animal {
  def sound(): Unit = {
    println("Animal makes a sound")
  }
}

class Dog extends Animal {
  override def sound(): Unit = {
    println("Dog barks")
  }
}

class Cat extends Animal {
  override def sound(): Unit = {
    println("Cat meows")
  }
}
object PolymorphismTest{
  def main(args: Array[String]): Unit = {
    val animal: Animal = new Dog ()  // 父類引用指向子類對象
    animal.sound ()  // 編譯看左邊,執(zhí)行看右邊,實(shí)際上都是調(diào)用子類的實(shí)現(xiàn),所以輸出: Dog barks

    val anotherAnimal: Animal = new Cat ()
    anotherAnimal.sound ()   // Cat meows
  }
}

參數(shù)多態(tài)(Parametric Polymorphism): 參數(shù)多態(tài)是指在函數(shù)或類中使用泛型類型參數(shù),使其可以適用于多種類型。在 Scala 中,參數(shù)多態(tài)可以通過類型參數(shù)和高階函數(shù)來實(shí)現(xiàn)。

object PolymorphismTest{
  def main(args: Array[String]): Unit = {
    def printList[A](list: List[A]): Unit = {  // printList 函數(shù)使用了類型參數(shù) A,使其可以接受不同類型的 List
      list.foreach(println)                   // 函數(shù)定義中使用類型參數(shù),實(shí)現(xiàn)了參數(shù)多態(tài)
    }

    val intList: List[Int] = List(1, 2, 3, 4, 5)
    val stringList: List[String] = List("apple", "banana", "orange")

    printList(intList)  // 輸出:1 2 3 4 5
    printList(stringList)  // 輸出:apple banana orange
  }
}

抽象類

抽象屬性與抽象方法

1)基本語法

  • 定義抽象類

Scala將一個(gè)不完整的類稱之為抽象類。

abstract class Person {  // 通過abstract關(guān)鍵字標(biāo)記抽象類
}
  • 定義抽象屬性

Scala中如果一個(gè)屬性只有聲明沒有初始化,那么是抽象屬性,因?yàn)樗煌暾?/p>

abstract class Person {
    var|val name:String  // 該屬性沒有初始化,就是抽象屬性
    // 只有在抽象類中才可以
}
  • 定義抽象方法

Scala中如果一個(gè)方法只有聲明而沒有實(shí)現(xiàn),那么是抽象方法,因?yàn)樗煌暾?/p>

abstract class Person {
    def test():Unit  // 只聲明而沒有實(shí)現(xiàn)的方法,就是抽象方法
}

注意:

? 只要出現(xiàn)了抽象屬性或抽象方法,那么該類一定是抽象類

? 抽象類在也可以有普通屬性、普通方法

2)繼承與重寫

  • 通過父類為抽象類,那么子類需要將抽象的屬性和方法實(shí)現(xiàn),否則子類也需要聲明為抽象類
abstract class Person {
    var name:String
}
class User extends Person {
    var name : String = "zhangsan"  // 實(shí)現(xiàn)父類的抽象屬性、方法,否則子類也需要聲明為抽象類
}
  • 重寫非抽象方法需要用 override 修飾,重寫抽象方法則可以不加override
  • 子類中調(diào)用父類的方法,需要使用 super 關(guān)鍵字
  • 子類對抽象屬性進(jìn)行實(shí)現(xiàn),父類抽象屬性可以用 var 修飾
  • 子類對非抽象屬性重寫,父類非抽象屬性只支持 val 類型,不支持 var
    • 因?yàn)?var 修飾的為可變變量,子類繼承之后就可以直接使用,沒有必要重寫
package com.clear.oop

object AbstractCass {
  def main(args: Array[String]): Unit = {

  }
}

// 定義抽象類
abstract class Person2 {
  // 非抽象屬性
  val name: String = "person"
  var idCard = "666"  // var修飾的
  // 抽象屬性(未初始化值)
  var age: Int

  // 非抽象方法
  def eat(): Unit = {
    println("person eat")
  }

  // 抽象方法(沒有書寫方法體)
  def sleep(): Unit
}

// 定義子類
class User2 extends Person2 {
  // todo 子類實(shí)現(xiàn)抽象屬性、抽象方法
  var age = 18

  override def sleep(): Unit = { // 重寫抽象方法 override 可以省略
    println("user sleep")
  }

  // todo 子類重寫非抽象屬性、方法
  override val name: String = "user"
  override def eat(): Unit = {
    println("user eat")
  }
  // todo 重寫 var 修飾的非抽象屬性
  // override var idCard = "777"  // 報(bào)錯,idea中沒有提示,但是編譯報(bào)錯
}

匿名子類

和 Java一樣,可以通過包含帶有定義或重寫的代碼塊的方式創(chuàng)建一個(gè)匿名的子類

在Scala中,可以使用匿名子類來創(chuàng)建一個(gè)沒有命名的子類。匿名子類可以用于實(shí)現(xiàn)接口、擴(kuò)展類或重寫方法。

基本格式

val 變量名: 父類或接口的類型 = new 父類或接口的類型 {
  // 子類的實(shí)現(xiàn)代碼
}

例1:

package com.clear.oop

object AnonymousClass {
  def main(args: Array[String]): Unit = {
    val dog : Animal2 = new Animal2 {  // 抽象類,不能直接實(shí)例化,我們可以創(chuàng)建一個(gè)他的一個(gè)匿名子類
      override var name: String = "dog"

      override def play(): Unit = {
        println("汪汪汪")
      }
    }
    // 直接調(diào)用
    println(dog.name)
    println(dog.play())
  }
}

// 定義抽象類
abstract class Animal2 {
  var name: String  // 抽象屬性

  def play(): Unit  // 抽象方法
}

例2:

object AnonymousClass2 {
  def main(args: Array[String]): Unit = {
    // 創(chuàng)建匿名子類,實(shí)現(xiàn)了特質(zhì),重寫了抽象方法
    val animal: Animal3 = new Animal3 {
      override def sound(): String = "Animal makes sound"
    }

    println(animal.sound()) //  Animal makes sound
  }
}

// 定義一個(gè)特質(zhì)(類似于Java的接口)
trait Animal3 {
  // 抽象方法
  def sound(): String
}

單例對象(伴生對象)

  • 所謂的單例對象,就是在程序運(yùn)行過程中,指定類的對象只能創(chuàng)建一個(gè),而不能創(chuàng)建多個(gè)。這樣的對象可以由特殊的設(shè)計(jì)方式獲得,也可以由語言本身設(shè)計(jì)得到,比如 object 伴生對象
  • Scala 語言是完全面向?qū)ο蟮恼Z言,所以并沒有靜態(tài)的操作(即在Scala中沒有靜態(tài)的概念)。但是為了能夠 和Java 語言交互(因?yàn)镴ava中有靜態(tài)概念),就產(chǎn)生了一種特殊的對象來模擬類對象,該對象為單例對象。若單例對象名與類名一致,則稱該單例對象這個(gè)類的伴生對象,這個(gè)類的所有“靜態(tài)”內(nèi)容都可以放置在它的伴生對象中聲明,然后通過伴生對象名稱直接調(diào)用
  • 如果類名和 伴生對象名稱保持一致,那么這個(gè)類稱之為伴生類Scala編譯器可以通過伴生對象的 apply 方法創(chuàng)建伴生類對象。apply方法可以重載,并傳遞參數(shù),且可由Scala編譯器自動識別。所以在使用時(shí),其實(shí)是可以省略的。
  • 在Scala中,可以使用單例對象(Singleton Object)來創(chuàng)建一個(gè)只有一個(gè)實(shí)例的類。單例對象在Scala中非常常見,用于實(shí)現(xiàn)全局共享的狀態(tài)、提供工具方法、作為應(yīng)用程序的入口點(diǎn)等
  • 要使用單例對象,只需通過對象名直接調(diào)用其中的方法或訪問其中的字段,就像調(diào)用普通類的靜態(tài)方法一樣(與java類似)
  • 單例對象的特點(diǎn)是在程序運(yùn)行期間只有一個(gè)實(shí)例存在,因此可以在不同的地方共享狀態(tài)和數(shù)據(jù)。此外,單例對象還可以實(shí)現(xiàn)接口、擴(kuò)展類、混入特質(zhì)等,具有與普通類相同的靈活性
  • 單例對象不能被實(shí)例化,因?yàn)樗鼈円呀?jīng)是單例。因此,不能使用new關(guān)鍵字來創(chuàng)建單例對象的實(shí)例
object TestMain {
  def main(args: Array[String]): Unit = {
    // 實(shí)例化Student類
    //val student = new Student("張三",18)  // 編譯錯誤,private類不能被外部訪問
    // 通過 伴生對象名.屬性 方式實(shí)例化類
    val student = Student.apply("張三",18) // 通過伴生對象的apply方法構(gòu)造伴生類實(shí)例
    student.printInfo()
    // todo scala編譯器省略apply方法,自動完成調(diào)用
    val student2 = Student("李四",19) 
    student2.printInfo()
  }
}

// 一個(gè)類:也是伴生類
class Student private(val name: String, val age: Int) {
  def printInfo(): Unit = {
    println(s"student [name=$name, age=$age, school= ${Student.school}]")
    // 其中 Student.school 是直接通過 伴生對象名.屬性 方式訪問的
  }
}

// 一個(gè)單例對象,名稱與類一致,也稱伴生對象
object Student {
  val school: String = "xs"

  // todo 在伴生對象中,可以直接訪問private
  // 定義一個(gè)方法,實(shí)例化對象
  def apply(name: String, age: Int): Student = new Student(name, age)  // 構(gòu)造伴生類實(shí)例
}

說明

  • 單例對象采用 object 關(guān)鍵字聲明
  • 單例對象對應(yīng)的類稱之為伴生類,伴生對象的名稱應(yīng)該與伴生類一致。
  • 單例對象的屬性和方法都可以通過伴生對象名(類名)直接調(diào)用

單例對象與伴生對象的區(qū)別?

  • 在Scala中,單例對象(Singleton Object)和伴生對象(Companion Object)是兩個(gè)不同的概念,但它們經(jīng)常一起使用。
  • 單例對象是一個(gè)獨(dú)立的對象,它在程序運(yùn)行期間只有一個(gè)實(shí)例存在。它可以包含方法、字段和其他成員,可以用于實(shí)現(xiàn)全局共享的狀態(tài)、提供工具方法等。單例對象的定義使用object關(guān)鍵字。
  • 伴生對象是與某個(gè)類關(guān)聯(lián)的對象,它與類名相同,并且在同一個(gè)源文件中定義。伴生對象和類之間可以相互訪問對方的私有成員。伴生對象通常用于定義與類相關(guān)的靜態(tài)成員、工廠方法等。伴生對象的定義使用object關(guān)鍵字。

特質(zhì)

Scala將多個(gè)類的相同特征從類中剝離出來,形成一個(gè)獨(dú)立的語法結(jié)構(gòu),稱之為“特質(zhì)”(特征)。

這種方式在Java中稱之為接口,但是 Scala中沒有接口的概念。所以 scala 中沒有 interface關(guān)鍵字,而是采用特殊的關(guān)鍵字 trait 來聲明特質(zhì)。

Scala中的 tarit中可以有抽象的屬性、方法,也可以有普通的屬性、方法。

如果一個(gè)類符合某一個(gè)特征(特質(zhì)),那么就可以將這個(gè)特征(特質(zhì))“混入”到類中。這種混入的操作可以在聲明類時(shí)使用,也可以在創(chuàng)建類對象時(shí)動態(tài)使用。

Scala引入 trait 特質(zhì),第一可以替代Java的接口,第二個(gè)也是對單繼承機(jī)制的一種補(bǔ)充。

特質(zhì)可以被類繼承,使用extends關(guān)鍵字,也可以被其他特質(zhì)混入,使用with關(guān)鍵字。

特質(zhì)的特點(diǎn):

  • 多重繼承:一個(gè)類可以繼承多個(gè)特質(zhì),從而獲得多個(gè)特質(zhì)的功能。
  • 抽象成員:特質(zhì)可以定義抽象方法和字段,需要在繼承或混入的類中實(shí)現(xiàn)。
  • 默認(rèn)實(shí)現(xiàn):特質(zhì)可以提供方法的默認(rèn)實(shí)現(xiàn),繼承或混入的類可以選擇重寫或繼承默認(rèn)實(shí)現(xiàn)。
  • 動態(tài)混入:特質(zhì)可以在對象創(chuàng)建時(shí)動態(tài)混入,為對象提供額外的功能。

基本語法

  1. 基本語法
trait 特質(zhì)名稱{
    // 定義方法、字段、抽象成員
}
		
class 類名 extends 父類(特質(zhì)1with 特質(zhì)2 with 特質(zhì)3 trait Operator {
				// extends 繼承特質(zhì) 
    			// with 混入特質(zhì)
}

例如:

// 定義一個(gè)特質(zhì)
trait Young{
  // 聲明抽象屬性
  val name: String = "young"
  // 聲明抽象方法、非抽象方法
  def dating(): Unit
  def play(): Unit = {
    println("young people is playing")
  }
}

// 定義User類,繼承Person,混入Young特質(zhì)
class User extends Person with Young {
  // todo 重寫沖突的屬性(因?yàn)镻erson、Young中都有)
   val string = "李四"

  // 實(shí)現(xiàn)抽象方法
  override def dating(): Unit = {
    println("user...")
  }
}

特質(zhì)混入

  • 特質(zhì)混入(Trait Mixin)是Scala中一種靈活的代碼組合機(jī)制,通過將特質(zhì)混入到類中,可以為類提供額外的功能。

  • 特質(zhì)混入使用with關(guān)鍵字,將特質(zhì)添加到類的定義中。一個(gè)類可以混入多個(gè)特質(zhì),特質(zhì)的順序決定了方法的調(diào)用順序

  • 特質(zhì)混入的順序很重要,如果多個(gè)特質(zhì)中有相同的方法名,那么最后一個(gè)混入的特質(zhì)的方法會覆蓋之前的方法

    • 例如,如果MyClass類同時(shí)混入了兩個(gè)特質(zhì),且兩個(gè)特質(zhì)都有一個(gè)名為print()的方法,那么最后一個(gè)混入的特質(zhì)的print()方法會被調(diào)用。
  • 特質(zhì)混入是Scala中實(shí)現(xiàn)代碼復(fù)用和組合的重要機(jī)制,可以根據(jù)需要選擇混入不同的特質(zhì),靈活地組合和擴(kuò)展類的功能。

例:

trait Printable {
  def print(): Unit
}

trait Loggable {
  def log(message: String): Unit
}

// 混入兩個(gè)特質(zhì)
class MyClass extends Printable with Loggable {
  def print(): Unit = {
    println("Printing...")
  }
  
  def log(message: String): Unit = {
    println(s"Logging: $message")
  }
}

val obj = new MyClass()
obj.print() // 輸出:Printing...
obj.log("Hello") // 輸出:Logging: Hello
動態(tài)混入
  • 動態(tài)混入(Dynamic Mixing)是指在運(yùn)行時(shí)為對象添加特質(zhì)的能力。在Scala中,可以使用with關(guān)鍵字將特質(zhì)動態(tài)混入到對象中

例如:

// SomeClass是一個(gè)類,SomeTrait是一個(gè)特質(zhì)
// 通過使用with關(guān)鍵字,我們可以在創(chuàng)建SomeClass的實(shí)例時(shí),動態(tài)地將SomeTrait混入到該實(shí)例中
val obj = new SomeClass with SomeTrait
  • 通過動態(tài)混入特質(zhì),我們可以為對象添加特定的行為,而不需要修改原始類的定義。這種方式可以在不改變類層次結(jié)構(gòu)的情況下,為對象提供額外的功能。
  • 需要注意的是,動態(tài)混入特質(zhì)只對當(dāng)前對象有效,不會影響其他對象或類。每個(gè)對象可以根據(jù)自己的需要選擇要混入的特質(zhì),從而實(shí)現(xiàn)個(gè)性化的行為

特質(zhì)疊加

由于一個(gè)類可以混入(mixin)多個(gè)trait,且 trait 中可以有多個(gè)具體的屬性和方法,若混入的特質(zhì)中有相同的方法(方法名、形參列表、返回值均相同),那必然會導(dǎo)致繼承沖突問題。沖突分為兩種:

1)一個(gè)類(sub)混入的兩個(gè)trait 中兩個(gè)相同的具體方法,且兩個(gè)trait之間沒有任何的關(guān)系,解決這類沖突,直接在類中重寫沖突方法即可

2)一個(gè)類(sub)混入的兩個(gè)trait 中兩個(gè)相同的具體方法,且兩個(gè)trait都繼承自同一個(gè)trait(即鉆石問題),解決這類沖突,Scala采用了特質(zhì)疊加的策略。

  • 在Scala中,特質(zhì)疊加(Trait Stacking)是一種特質(zhì)組合的方式,通過將多個(gè)特質(zhì)疊加在一起,形成一個(gè)新的特質(zhì),從而將多個(gè)特質(zhì)的功能合并到一個(gè)特質(zhì)中。(特質(zhì)疊加類似于Java中的接口繼承)
  • 特質(zhì)疊加使用with關(guān)鍵字,將多個(gè)特質(zhì)按順序疊加在一起。特質(zhì)的疊加順序是從右往左的。也就是說,當(dāng)一個(gè)類混入多個(gè)特質(zhì)時(shí),特質(zhì)的代碼會按照從右到左的順序被疊加到類中。
  • 通過合理的特質(zhì)疊加,可以實(shí)現(xiàn)代碼的復(fù)用和組合,提高代碼的靈活性和可維護(hù)性
trait A {
    def foo(): Unit = {
        println("A foo")
    }
}

trait B {
    def foo(): Unit = {
        println("B foo")
    }
}

// 特質(zhì)C疊加特質(zhì)A,特質(zhì)B
trait C extends A with B {
    override def foo(): Unit = {
        super[A].foo() // 調(diào)用特質(zhì)A的foo方法
        super[B].foo() // 調(diào)用特質(zhì)B的foo方法
        println("C foo")
    }
}

class MyClass extends C {
    def bar(): Unit = {
        foo() // 調(diào)用特質(zhì)C的foo方法
    }
}

val obj = new MyClass()
obj.bar() // 當(dāng)調(diào)用MyClass的bar()方法,會按照從右到左的順序調(diào)用特質(zhì)的方法,即先調(diào)用特質(zhì)B中的foo()方法,然后調(diào)用特質(zhì)A中的foo()方法

// 總結(jié)來說,特質(zhì)的疊加順序是從右往左的,可以使用super關(guān)鍵字來指定調(diào)用特定特質(zhì)的方法。

特質(zhì)混入與特質(zhì)疊加

特質(zhì)混入(Trait Mixin)和特質(zhì)疊加(Trait Stacking)是Scala中兩種不同的特質(zhì)組合方式。

特質(zhì)混入是將一個(gè)或多個(gè)特質(zhì)添加到類的定義中,通過使用with關(guān)鍵字將特質(zhì)混入到類中。一個(gè)類可以混入多個(gè)特質(zhì),特質(zhì)的順序決定了方法的調(diào)用順序。特質(zhì)混入可以為類提供額外的功能,類可以使用混入的特質(zhì)中定義的方法和字段。

特質(zhì)疊加是將多個(gè)特質(zhì)按順序疊加在一起,形成一個(gè)新的特質(zhì)。疊加的順序決定了方法的調(diào)用順序,從左到右依次調(diào)用。特質(zhì)疊加可以將多個(gè)特質(zhì)的功能組合在一起,形成一個(gè)新的特質(zhì),提供更豐富的功能。

特質(zhì)混入和特質(zhì)疊加都可以實(shí)現(xiàn)代碼的復(fù)用和組合,提高代碼的靈活性和可維護(hù)性。它們在使用方式和效果上有一些區(qū)別:

  • 特質(zhì)混入是將特質(zhì)添加到類的定義中,類可以使用混入的特質(zhì)中定義的方法和字段。特質(zhì)混入是一種靜態(tài)組合方式,特質(zhì)的功能在編譯時(shí)就確定了。
  • 特質(zhì)疊加是將多個(gè)特質(zhì)按順序疊加在一起,形成一個(gè)新的特質(zhì)。特質(zhì)疊加是一種動態(tài)組合方式,特質(zhì)的功能在運(yùn)行時(shí)才確定。

特質(zhì)混入和特質(zhì)疊加可以根據(jù)具體的需求選擇使用。特質(zhì)混入適用于在類定義時(shí)就確定需要使用的特質(zhì),而特質(zhì)疊加適用于在運(yùn)行時(shí)根據(jù)需要動態(tài)組合特質(zhì)的功能。

特質(zhì)自身類型

在Scala中,特質(zhì)(Trait)可以具有自身類型(Self Type),用于指定特質(zhì)可以被哪些類型的類混入

自身類型可以實(shí)現(xiàn)依賴注入的功能

自身類型可以通過以下方式定義

trait MyTrait { _: SomeType =>	// 表示特質(zhì)只能混入到SomeType類中
    // Trait body
}

自身類型的作用是限制特質(zhì)的使用范圍,確保特質(zhì)只能被指定的類型的類混入。這樣可以在特質(zhì)中使用類型SomeType的成員,而不需要在特質(zhì)中重新定義這些成員。

自身類型的使用:

trait Logger {
    self: Person =>  // 指定自身類型為Person,即只有Person類能夠混入該特質(zhì)

    def log(message: String): Unit = {
        println(s"${self.name}: $message")	// self.name 在特質(zhì)中訪問Person類name屬性
        									// this.name 也可以
    }
}

class Person(val name: String) extends Logger {  // 混入Logger特質(zhì)
    def sayHello(): Unit = {
        log("Hello!")
        println(s"My name is $name.")
    }
}

object Main extends App {
    val person = new Person("Alice")
    person.sayHello()
}

結(jié)果:

Alice: Hello!
My name is Alice.

特質(zhì)與抽象類的區(qū)別

在Scala中,特質(zhì)(Trait)和抽象類(Abstract Class)都可以用于定義可復(fù)用的代碼片段和行為。它們有一些相似之處,但也有一些重要的區(qū)別。

主要區(qū)別:

  • 繼承關(guān)系:一個(gè)類可以繼承多個(gè)特質(zhì),但只能繼承一個(gè)抽象類。這是因?yàn)镾cala支持多重繼承特質(zhì),但不支持多重繼承抽象類。
  • 構(gòu)造函數(shù):特質(zhì)不能有構(gòu)造函數(shù)參數(shù),而抽象類可以有構(gòu)造函數(shù)參數(shù)。這是因?yàn)樘刭|(zhì)在被混入到類中時(shí),是作為一個(gè)獨(dú)立的模塊存在的,而抽象類是作為一個(gè)類的一部分存在的。
  • 實(shí)例化:特質(zhì)不能被實(shí)例化,而抽象類可以被實(shí)例化。特質(zhì)只能被混入到類中使用,而抽象類可以作為獨(dú)立的類被實(shí)例化。
  • 默認(rèn)實(shí)現(xiàn):特質(zhì)可以提供方法的默認(rèn)實(shí)現(xiàn),而抽象類可以提供方法的具體實(shí)現(xiàn)。特質(zhì)中的方法可以是抽象的,也可以是具體的,而抽象類中的方法可以有具體的實(shí)現(xiàn)。
  • 繼承順序:特質(zhì)的混入順序是從左到右,而抽象類的繼承順序是從父類到子類。當(dāng)一個(gè)類混入多個(gè)特質(zhì)時(shí),特質(zhì)的方法會按照混入的順序被調(diào)用。
  • 使用場景:特質(zhì)通常用于定義可復(fù)用的行為,而抽象類通常用于定義具有共同特征的類的基類。特質(zhì)更適合用于組合多個(gè)行為,而抽象類更適合用于定義類的繼承關(guān)系。

建議:

? 優(yōu)先使用特質(zhì),一個(gè)類擴(kuò)展多個(gè)特質(zhì)是很方便,但是只有擴(kuò)展一個(gè)抽象類

? 如果需要使用構(gòu)造器參數(shù),使用抽象類。因?yàn)槌橄箢惪梢杂袔?shù)的構(gòu)造器,而特質(zhì)不行。

擴(kuò)展

類型檢查和轉(zhuǎn)換

(1)obj.isInstanceOf[T]:判斷 obj 是不是 T 類型

(2)obj.asInstanceOf[T]:將 obj 強(qiáng)轉(zhuǎn)為 T 類型

(3)classOf 獲取對象的類名

Scala提供了兩種類型轉(zhuǎn)換的方式:隱式類型轉(zhuǎn)換和顯式類型轉(zhuǎn)換。

  • 隱式類型轉(zhuǎn)換(Implicit Conversion):隱式類型轉(zhuǎn)換是指在編譯器自動進(jìn)行的類型轉(zhuǎn)換,無需顯式地調(diào)用轉(zhuǎn)換方法。隱式類型轉(zhuǎn)換通常用于解決類型不匹配的問題,使得代碼更加簡潔和易讀。例如,將一個(gè)整數(shù)轉(zhuǎn)換為浮點(diǎn)數(shù):
val x: Int = 10
val y: Double = x // 隱式類型轉(zhuǎn)換,將Int類型轉(zhuǎn)換為Double類型
  • 顯式類型轉(zhuǎn)換(Explicit Conversion):顯式類型轉(zhuǎn)換是指通過調(diào)用轉(zhuǎn)換方法來顯式地將一個(gè)對象從一種類型轉(zhuǎn)換為另一種類型。顯式類型轉(zhuǎn)換使用asInstanceOf方法進(jìn)行轉(zhuǎn)換。例如,將一個(gè)Any類型的對象轉(zhuǎn)換為String類型:
class Person{
}
object Person {
    def main(args: Array[String]): Unit = {
        val person = new Person

        //(1)判斷對象是否為某個(gè)類型的實(shí)例
        val bool: Boolean = person.isInstanceOf[Person]

        if ( bool ) {
            //(2)將對象轉(zhuǎn)換為某個(gè)類型的實(shí)例(顯式類型轉(zhuǎn)換)
            val p1: Person = person.asInstanceOf[Person]
            println(p1)
        }

        //(3)獲取類的信息
        val pClass: Class[Person] = classOf[Person]
        println(pClass)
    }
}

注意:顯式類型轉(zhuǎn)換可能會導(dǎo)致類型轉(zhuǎn)換異常(ClassCastException),因此在進(jìn)行顯式類型轉(zhuǎn)換時(shí)需要確保對象的實(shí)際類型與目標(biāo)類型是兼容的。

另外,Scala還提供了一種特殊的類型轉(zhuǎn)換方式,即模式匹配(Pattern Matching)。模式匹配可以根據(jù)對象的類型進(jìn)行匹配,并執(zhí)行相應(yīng)的操作。模式匹配可以用于類型轉(zhuǎn)換、條件判斷等場景。例如,將一個(gè)Any類型的對象轉(zhuǎn)換為String類型并進(jìn)行處理:

val x: Any = "Hello"
x match {
  case s: String => println(s) // 如果x是String類型,則打印字符串
  case _ => println("Not a string") // 如果x不是String類型,則打印"Not a string"
}

總結(jié)來說,Scala中的類型轉(zhuǎn)換可以通過隱式類型轉(zhuǎn)換和顯式類型轉(zhuǎn)換來實(shí)現(xiàn),還可以使用模式匹配進(jìn)行類型判斷和轉(zhuǎn)換。在進(jìn)行類型轉(zhuǎn)換時(shí)需要注意類型的兼容性,并避免類型轉(zhuǎn)換異常的發(fā)生。

枚舉類和應(yīng)用類

枚舉類(Enumeration):可以使用Enumeration關(guān)鍵字定義枚舉類。

枚舉類的每個(gè)取值都是該類的實(shí)例對象

// 定義枚舉類對象
object Weekday extends Enumeration {
  type Weekday = Value
  val Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday = Value
}
// 可以通過 Weekday.Monday 等方式訪問枚舉值

應(yīng)用類(Application):應(yīng)用類是一種特殊的類,它可以直接運(yùn)行,而無需顯式地定義main方法

在Scala中,可以通過繼承App特質(zhì)來定義應(yīng)用類。應(yīng)用類的代碼會在程序啟動時(shí)自動執(zhí)行

object MyApp extends App {
  println("Hello, World!")
}

注意:

? 應(yīng)用類只能有一個(gè),且必須是頂級對象(即不能是內(nèi)部類)。應(yīng)用類的代碼會在程序啟動時(shí)執(zhí)行,因此通常用于編寫簡單的腳本或測試代碼。

Type定義新類型

使用type關(guān)鍵字可以定義新的數(shù)據(jù)數(shù)據(jù)類型名稱,本質(zhì)上就是類型的一個(gè)別名文章來源地址http://www.zghlxwxcb.cn/news/detail-724795.html

type Age = Int
val age: Age = 25

type Name = String
val name: Name = "John"

type Person = (Name, Age)
val person: Person = ("John", 25)

到了這里,關(guān)于06-Scala面向?qū)ο蟮奈恼戮徒榻B完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包