1

Scala 基础 (六):面向对象(下篇)

 1 year ago
source link: https://blog.51cto.com/yizhipan/5570172
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在之前的文章中总结了Scala面向对象模块基础的内容,接下来学习面向对象的高阶内容。

一、抽象类

如何定义?

// 定义抽象类
abstract class 类名{
	// 定义抽象属性 不赋初始值
	val|var 变量名: 类型
	// 定义抽象方法  只声明,不实现
	def 方法名(): 返回值类型
}
  • 如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类
  • 重写非抽象方法需要用 override 修饰,重写抽象方法则可以不加 override
  • 子类中调用父类的方法使用 super 关键字
  • 子类对抽象属性进行实现,父类抽象属性可以用 var 修饰;
  • 子类对非抽象属性重写,父类非抽象属性只支持 val 类型,而不支持 var。因为var修饰为可变量,子类继承后可以直接使用修改,没有必要重写。

举个栗子:

// 定义抽象类
abstract class Person{
  // 定义非抽象属性
  val name: String = "person"
  // 抽象属性
  var age: Int

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

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

class Student extends Person{
  // 实现抽象属性和方法
   var age: Int = 18

   def sleep(): Unit ={
    println("student sleep")
  }

  // 重写非抽象属性和方法
  override val name: String = "Student"

  override def eat(): Unit = {
    super.eat()
    println("Student eat")
  }
}

匿名子类

和 Java 一样,可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类。匿名子类只针对抽象类和接口。

举个栗子:

object Test10_AnnoymousClass {
  def main(args: Array[String]): Unit = {
    // 匿名子类实现
    val person = new Person {
      override var name: String = "alice"

      override def eat(): Unit = println("person eat")
    }

    println(person.name)
    person10.eat()
  }
}

abstract class Person {
  var name: String
  def eat():Unit
}

二、单例对象(伴生对象)

Scala是一门完全面向对象的语言,没有静态操作。为了能够和Java语言进行交互,使用单例对象来取代static关键字的语义,伴生类的静态声明都可放在伴生对象中。

基本语法:

object 伴生对象名{
	val country: String="China"
}
  • 编译后会生成两个类,伴生对象是伴生类(类名为对应类后加$符号)的单例对象。
  • 单例对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致,必须在同一个文件中。
  • 单例对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。
  • Scala 中 obj(arg)的语句实际是在调用该对象的 apply 方法,即 obj.apply(arg)。用以统一面向对象编程和函数式编程的风格。
  • Scala底层对于伴生对象的apply方法进行了优化,调用时可以省略方法名,直接使用单例对象的名称+调用apply方法的参数
  • 当使用 new 关键字构建对象时,调用的其实是类的构造方法,当直接使用类名构建对象时,调用的其实时伴生对象的 apply 方法

举个栗子:

// 伴生类
// 构造器私有化
class Student private(val name: String, val age: Int) {
  def printInfo(): Unit = {
    println(s"Student11: $name $age ${Student11.school}")
  }
}

// 伴生对象
object Student {
  // 看作静态的属性
  val school: String = "清华"
  // 定义类对象实例创建的方法
  def newStudent(name: String,age: Int): Student11 = new Student11(name,age)

  def apply(name: String, age: Int): Student11 = new Student11(name, age)
}


object Test11_Object {
  def main(args: Array[String]): Unit = {
    val student = Student.newStudent("alice", 12)
    student.printInfo()

    val student1 = Student.apply("haha", 12)

    val student2 = Student("lolo", 34)
    student2.printInfo()
  }
}

如何定义特质?

trait 特质名 {
	// 代码块
}
  • Scala 语言中,采用特质 trait(特征)来代替接口的概念
  • 多个类具有相同的特征时,就可以将这个特征提取出来,用继承的方式来复用
  • Scala 中的 trait 中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质。这种感觉类似于 Java 中的抽象类。

基本语法:

有父类 class extends baseClass with trait1 with trait2 ... {}
没有父类 class extends trait1 with trait2 ... {}
  • 如果一个类在同时继承特质和父类时,应当把父类写在 extends 后。
  • 特质中可以定义抽象和非抽象的属性和方法。

特质叠加引发两种冲突

第一种:一个类(Sub)混入的两个 trait(TraitATraitB)中具有相同的具体方法,且两个 trait 之间没有任何关系

Scala 基础 (六):面向对象(下篇)_scala

如果当前父类和特质里面出现了相同的属性和方法,就会起冲突,必须在当前实现的子类(sub)中进行重写。

举个栗子:

class Person {
    val name: String = "Person"
    var age: Int = 18

    def sayHi(): Unit = {
        println(s"hello from : $name")
    }
}

trait Young {
    // abstract and non-abstract attribute
    var age: Int
    val name: String = "young"

    // method
    def play(): Unit = {
        println(s"young people $name is playing")
    }
    def dating(): Unit
}

trait Knowledge {
    var amount: Int = 0
    def increase(): Unit = {
        amount += 1
    }
}

trait Talent {
    def increase(): Unit = {
        println("increase talent")
    }
}

class Student extends Person with Young with Knowledge with Talent{
    override val name: String = "alice"

    def dating(): Unit = {
        println(s"Sutdent $name $age is dating")
    }

    def study(): Unit = println(s"Student $name is studying")

    override def sayHi(): Unit = {
        super.sayHi()
        println(s"hello from : student $name")
    }

    override def increase(): Unit = {
        super.increase() 
        println(s"studnet $name knowledge increase: $amount")
    }
}

object Trait {
    def main(args: Array[String]): Unit = {
        val s = new Student()
        s.sayHi()
        s.increase()

        s.study()
        s.increase()

        s.play()
        s.increase()

        s.dating()
        s.increase()
    }
}

第二种:一个类(Sub)混入的两个 trait(TraitATraitB)中具有相同的具体方法,且两个 trait 继承自相同的 trait(TraitC

Scala 基础 (六):面向对象(下篇)_大数据_02

Scala采用了特质叠加的策略来解决这类冲突。

举个栗子:

trait Ball {
  def describe(): String = "ball"
}

trait ColorBall extends Ball {
  val colorBall: String = "red"

  override def describe(): String = {
     colorBall+"-----"+super.describe()
  }
}

trait CategoryBall extends Ball{
  val categoryBall: String = "foot"

  override def describe(): String = categoryBall+"-----"+super.describe()
}

// 叠加顺序 MyFootBall -> ColorBall -> CategoryBall -> Ball
class MyFootBall extends CategoryBall with ColorBall {
  override def describe(): String = "my ball is a" + super.describe()
}
  • 当一个类混入多个特质的时候,scala 会对所有的特质及其父特质按照一定的顺序进行排序,而此案例中的super.describe()调用的实际上是排好序后的下一个特质中的 describe()方法。
  • 要调用某个指定的混入特质中的方法,可以增加约束:super[]
	super[CategoryBall].describe()

自身类型

  • 自身类型可实现依赖注入的功能。
  • 一个类或者特征指定了自身类型的话,它的对象和子类对象就会拥有这个自身类型中的所有属性和方法。
  • 是将一个类或者特征插入到另一个类或者特征中,属性和方法都就像直接复制插入过来一样,能直接使用。但不是继承,不能用多态。

举个栗子:

object Test16_TraitSelfType {
  def main(args: Array[String]): Unit = {
    val user = new RegisterUser("alice", "1212121")
    user.insert()
  }
}

// 用户类
class User(val name: String,val password: String){

}

trait UserDao{
  // 定义自身类型
  _: User =>
  // 注册用户
  def insert():Unit={
    println (s" insert into db: = ${this.name} , ${this.password}")
  }
}

 // 定义注册用户
class RegisterUser(name: String,password: String) extends User(name,password) with UserDao
  • _: SelfType =>,其中_的位置是别名定义,也可以是其他,_指代this。插入后就可以用this.xxx来访问自身类型中的属性和方法了。

抽象类和特质的区别?

优先使用特质。一个类可以扩展多个特质,但是只能扩展一个抽象类。
需要构造函数参数,使用抽象类,抽象类可以定义带参的构造器,特质只是无参的构造器。

四、扩展内容

类型检查和转换

  • obj.isInstanceOf[T]:判断 obj 是不是 T 类型。
  • obj.asInstanceOf[T]:将 obj 强转成 T 类型
  • classOf 获取对象的类名。结果是class package.xxx.className
  • 获取对象的类:obj.getClass

枚举类

  • 需要继承 Enumeration
  • Value类型定义枚举值。
// 定义枚举类
object WorkDay extends Enumeration{
  val MONDAY = Value(1,"星期一")
  val TUESDAY = Value(2,"星期二")
}

	println(WorkDay.MONDAY)

应用类

  • 继承App,包装了main方法,就不需要显式定义main方法了,可以直接执行。
object TestApp extends App{
  println("app start"
}

定义新类型

  • 使用 type 关键字可以定义新的数据数据类型名称,本质上就是类型的一个别名
  • type SelfDefineType = TargetType
 type MyString= String
  val a: MyString = "abc"
  println(a)

本次分享的内容到这里就结束了,希望对大家学习Scala语言有所帮助!!!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK