面向?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)建的具體事物
定義類
- 基本語法
// 聲明類:訪問權(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);
}
}
屬性
- 基本語法
class User {
var name : String = _ // 類屬性其實(shí)就是類變量
var age : Int = _ // 下劃線表示類的屬性默認(rèn)初始化
}
- 擴(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封裝操作如下:
-
- 將屬性進(jìn)行私有化
-
- 提供一個(gè)公共的 set 方法,用于對屬性賦值
-
- 提供一個(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)混入,為對象提供額外的功能。
基本語法
- 基本語法
trait 特質(zhì)名稱{
// 定義方法、字段、抽象成員
}
class 類名 extends 父類(特質(zhì)1) with 特質(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í)行,因此通常用于編寫簡單的腳本或測試代碼。文章來源:http://www.zghlxwxcb.cn/news/detail-724795.html
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)!