Scala入门到精通(尚硅谷学习笔记)章节八——面向对象

发布于:2023-01-22 ⋅ 阅读:(422) ⋅ 点赞:(0)

scala包的作用:
(1)区分相同名字的类
(2)当类很多时,可以很好的管理类
(3)控制访问范围

包的命名

命名规则
只能包含数字、字母、下划线、小圆点.,但不能用数字开头,也不要使用关键字。
命名规范
一般是小写字母+小圆点

com.公司名.项目名.业务模块名

包的管理

Scala有两种包的管理风格。一种方式是每个源文件一个包(包名和源文件所在路径不要求必须一致),包名用“”进行分隔以表示包的层级关系,如com.heria.scala
另一种风格,通过嵌套的风格表示层级关系

import com.heria.scala.Inner
package com{
  object Outer {
    var out:String="out"
    def main(args: Array[String]): Unit = {
      println(Inner)
    }
  }
  package heria{
    package scala{
      //内层包中定义单类对象
      object Inner{
        var in:String = "in"
        def main(args: Array[String]): Unit = {
          println(Outer.out)//out
          Outer.out="Outer"
          println(Outer.out)//Outer
        }
      }
    }
  }
}

该风格具有两个特点:
1)一个源文件中可以声明多个package
(2)子包中的类可以直接访问父包中的内容,而无需导包

导入包

import com.heria.scala 引入com.heria包下scala ( class和object )
import com.heria._ 引入com.heria下的所有成员
import com.heria.scala_ 引入scala(object)下的所有成员
import com.heria.{sca,la} 引入com.heria下的sca和la
import com.heria.{sca=>scala} 引入com.heria下的sca并更名为scala
import com.heria.{sca=>scala,_} 引入com.heria下的所有成员,并将sca更名为scala
import com.heria.{scala=>,} 引入com.heria包下屏蔽scala类
new_root_.java.util.HashMap 引入Java的绝对路径

包对象

在scala中可以为每个包定义一个同名的包对象,包对象的名字需要和该包下的子包一样。在包对象中可以定义变量,方法。在包对象中定义的变量和方法,就可以在对应的包中的任何类中访问、使用。在底层这个包对象会生成两个类 package.classpackage$.class
idea在一个包下可以直接创建package object文件
在这里插入图片描述
案例实操
新建名为chapter08的包,新建package object文件,创建一个常量一个方法用于测试

package object chapter08 {
  val commonValue:String = "heria"
  def commonMethod():Unit = {
    println("This is a test")
  }
}

在同一个包下新建一个名为Test01的object,可以看到Test01可以直接调用属性和方法

object Test01 {
  def main(args: Array[String]): Unit = {
    println(commonValue)//heria
    commonMethod()//This is a test
  }
}

注意:包对象申明时必须要和当前这个包的定义在同一层级下

类和对象

类是对象的抽象,可以看成一类事物的模板,而对象是类的具体实例。类是抽象的,不占用内存,而对象是具体的,占用存储空间。类是用于创建对象的蓝图,它是一个定义包括在特定类型的对象中的方法和变量的软件模板。
基本和语法

[修饰符] class 类名{
	类体
}

(1) Scala语法中,类修饰符可以省略,默认为public
(2)一个scala源文件可以包含多个类
案例实操

object Test01 {
  def main(args: Array[String]): Unit = {
   //创建一个对象
    val student = new Student()
    //student.name //error,不能访问private属性
    println(student.age)//0
    println(student.sex)//null
    student.sex="female"
    println(student.sex)//female
  }
}
//创建类
class Student{
  private var name:String ="alice"
  @BeanProperty
  var age:Int= _
  var sex:String = _
}

封装

封装就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作。
java封装操作如下:(1)将属性进行私有化(2)提供一个公共的set方法,用于对属性赋值(3)提供一个公共的get方法,用于获取属性的值
Scala中的public属性,底层实际为private,并通过get方法(obj.field())和set方法(obj.field_=(value))对其进行操作。因此Scala并不推荐将属性设为private,再为其设置public的get和set方法的做法。但由于很多Java框架都利用反射调用getXXX和setXXX方法,有时候为了和这些框架兼容,也会为Scala的属性设置getXXX和setXXX方法(通过@BeanProperty注解实现)。

访问权限

(1) scala中属性和方法的默认访问权限为 public,但Scala中无public关键字。
(2) private为私有权限,只在类的内部和伴生对象中可用。
(3) protected 为受保护权限,Scala 中受保护权限比Java中更严格,同类、子类可以访问,同包无法访问。
(4) private[包名]增加包访问权限,包名下的其他类也可以使用
案例实操
在同一包下新建两个object,一个为Test04_access,一个为Test04_classForAccess
Test04_classForAccess

package chapter08
object Test04_classForAccess {
}
//定义一个父类
class Person{
  private var idCard:String = "3525" //私有
  protected var name:String="Alice"  //受保护
  var sex:String="female"
  private[chapter06] var age : Int = 18
  def printInfo(): Unit = {
    println(s"Person: $idCard $name $sex $age")
  }
}

Test04_access

package chapter08

object Test04_access {
  def main(args: Array[String]): Unit ={
  	//创建对象
    val person: Person = new Person()
      //person.idCard    //error,私有属性只在自己类的内部和伴生对象中可用
      //person.name      //error,同类、子类可以访问,同包无法访问
      println(person.age)
    println(person.sex)
  }
}
class Worker extends Person{
  println("worker:")
  //println(idCard)   //私有属性只在自己类的内部和伴生对象中可用
  name="bob"
  age=25
  sex="male"
}

构造器

scala提供主构造器和任意数量的辅助构造器。
基本语法

class 类名(形参列表) { // 主构造器
	// 类体1
	def this(形参列表) { // 辅助构造器
	}
	def this(形参列表) { //辅助构造器可以有多个...
	}
}

注意细节:
1.主构造器
(1)主构造器的声明直接放置于类名之后
(2)主构造器会执行类定义中的所有语句,这里可以体会到 Scala 的函数式编程和面向对象编程融合在一起,即:构造器也是方法(函数),传递参数和使用方法和前面的函数部分内容没有区别
(3)如果想让主构造器变成私有的,可以在()之前加上 private,这样用户只能通过辅助构造器来构造对象了
Scala 构造器作用是完成对新对象的初始化,构造器没有返回值。
2.辅助构造器
(1)辅助构造器,函数的名称this,可以有多个,编译器通过参数的个数及类型来区分。
(2)辅助构造方法不能直接构建对象,必须直接或者间接调用主构造方法。
(3)构造器调用其他另外的构造器,要求被调用构造器必须提前声明。
案例实操

package chapter08

object Test05_Constructor {
  def main(args: Array[String]): Unit = {
    val student1 = new Student()
    student1.Student()
    //主构造方法被调用
    //一般方法被调用

    val student2 = new Student("alice")
    //主构造方法被调用
    //辅助构造器一被调用
    //name:alice age: 0

    val student3 = new Student("alice",25)
    //主构造方法被调用
    //辅助构造器一被调用
    //name:alice age: 0
    //辅助构造方法二被调用
    //name:alice age: 25
  }
}

//定义一个类
class Student{
  //定义属性
  var name:String= _
  var age:Int= _
  println("主构造方法被调用")

  //声明辅助构造方法
  //辅助构造器一
  def this(name:String){
    this()  //直接调用主构造器
    println("辅助构造器一被调用")
    this.name=name
    println(s"name:$name age: $age")
  }
  //辅助构造器二
  def this(name : String,age: Int) {
    this(name)
    println("辅助构造方法二被调用")
    this.age= age
    println(s"name:$name age: $age")
  }

  //一般方法
  def Student():Unit={
    println("一般方法被调用")
  }
}

3.参数
单独定义属性的形式

object Test06_ConstructorParams {
  def main(args: Array[String]): Unit = {
    val student2 = new Student2
    println(s"student2:name=${student2.name},age=${student2.age}")
  }
}

//定义类
//无参构造器
class Student2{
  //单独定义属性
  var name:String = _
  var age:Int = _
}

(1)如果参数使用 var 关键字声明,那么 Scala 会将参数作为类的成员属性使用,并会提供属性对应的 xxx()[类似 getter]/xxx_$eq()[类似 setter]方法,即这时的成员属性是私有的,但是可读写。
传参构造器

object Test06_ConstructorParams {
  def main(args: Array[String]): Unit = {
    val student3 = new Student3("bob",23)
    println(s"student3:name=${student3.name},age=${student3.age}")
  }
}

//定义类
//传参构造器
class Student3(var name:String,var age:Int)

(2)Scala 类的主构造器的形参未用任何修饰符修饰,那么这个参数是局部变量,无法在主函数中直接打印,只能在构造器中
提供printInfo方法

object Test06_ConstructorParams {
  def main(args: Array[String]): Unit = {
    val student4 = new Student4("bob",23)
    student4.printInfo()
  }
}

//定义类
//传参构造器
class Student4(name:String,age:Int){
  def printInfo(){
    println(s"student4:name=${name},age=$age")
  }
}

(3)构造器参数用val关键字声明,那么 Scala 会将参数作为类的私有的只读属性使用,无法再做更改

继承和多态

继承

基本语法:class 子类名 extends 父类名 (类体)
(1)子类继承父类的属性和方法
(2) scala是单继承

object Test07_inheritance {
  def main(args: Array[String]): Unit = {
    val student1 = new Student7("alice",18)
    //1.父类的主构造器调用
    //3.子类的主构造器调用
    println("--------------")
    val student2 = new Student7("alice",18,"std001")
    //1.父类的主构造器调用
    //3.子类的主构造器调用
    //4.子类的辅助构造器调用
  }
}
class Person7(){
  var name:String= _
  var age:Int = _
  println("1.父类的主构造器调用")
  def this(name:String,age:Int){
    this()
    println("2.父类的辅助构造器调用")
    this.name = name
    this.age = age
  }
  def printInfo():Unit = {
    println(s"Person:$name $age")
  }
}
class Student7(name:String,age:Int) extends Person7{
  var stdNo:String = _
  println("3.子类的主构造器调用")
  def this(name:String,age:Int,stdNo:String){
    this(name,age)
    println("4.子类的辅助构造器调用")
    this.stdNo=stdNo
  }

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

val student1 = new Student7("alice",18)调用了子类的主构造器,但前提是先调用父类的主构造器,因此顺序是1,3
val student2 = new Student7("alice",18,"std001")调用了子类的辅助构造器,由于this(name,age),需要先调用子类的主构造器,才能打印4.子类的辅助构造器调用这句话,而调用子类的主构造器前要先调用父类的主构造器,因此顺序是1,3,4

多态

多态也叫"动态绑定",指在执行期间判断所引用对象的实际类型,根据其实际类型调用其相应的方法。(编译期间不绑定方法,而是在运行时判断是对象是哪个类的实例,在调用该类方法)
跟java的多态的区别是:java静态绑定属性,动态绑定方法;scala的属性和方法全部实现动态绑定
案例实操

object Test08_DynamicBind {
  def main(args: Array[String]): Unit = {
    val student:Person8 = new Student8
    println(student.name)//student
    student.hello()//hello student
  }

}
class Person8{
  val  name:String = "person"
  def hello():Unit = {
    println("hello person")
  }
}
class Student8 extends Person8 {
  override val  name:String = "person"
  override def hello():Unit = {
    println("hello person")
  }
}

抽象类

在Scala中一个被abstract 修饰的类,类中包含没有实现的成员,即对象没有初始值,定义的方法没有方法体,则该类就是抽象类。
基本语法

// 定义抽象类
abstract class 抽象类名 {
	// 定义抽象字段
	val 抽象字段名:类型
	// 定义抽象方法
	def 方法名(参数:参数类型,参数:参数类型…):返回类型
}

继承和重写
(1)如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明
为抽象类
(2)重写非抽象方法需要用override修饰。重写抽象方法则可以不加override。(3)子类中调用父类的方法使用super关键字
(4)子类对抽象属性进行实现,父类抽象属性可以用var修饰;子类对非抽象属性重写,父类非抽象属性只支持val类型,而不支持var。因为var修饰的为可变变量,子类继承之后就可以直接使用,没有必要重写

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

  }

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

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

//定义具体实现的子类
class Student9 extends Person9{
  var age:Int = 18
  def sleep():Unit={
    println("student sleep")
  }

  //重写非抽象属性和方法
  override def eat(): Unit = {
    println("student eat")
  }
}

单例对象

Scala语言是完全面向对象的语言,所以并没有静态的操作(即在scala中没有静态的概念,没有static关键字)。但是为了能够和Java语言交互,产生了一种特殊的对象来模拟这类对象,该对象为单例对象。若单例对象名与类名一致,则称该单例对象这个类的伴生对象,这个类的所有“静态”内容都可以放置在它的伴生对象中声明。
语法

object 伴生对象名称(必须跟伴生类名称一致){
	//定义静态属性和方法
}

说明
(1))单例对象采用object关键字声明
(2)单例对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。
(3)单例对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。
案例实操1
还是Student类的例子,输出学生的姓名,年龄和学校。考虑到学校相同,是个静态属性学校不需要定义在类的属性里了,可以定义在伴生对象中。

object Test11_Object {
  def main(args: Array[String]): Unit = {
    val student = new Student11("alice",18)
    student.printerInfo()
  }
}
//定义类
class Student11(val name:String,age:Int){
  def printerInfo(): Unit ={
    println(s"student:name = $name,age = $age,school = ${Student11.school}")
  }
}
object Student11{
  val school:String = "school11"
}

案例实操2
在实际项目中,我们会采用工厂方法模式(FACTORY METHOD)编程。我们不希望直接创建类实例,可以采用将构造器私有化,要想调用方法,在伴生对象中新建工厂方法创建出来。

object Test11_Object {
  def main(args: Array[String]): Unit = {
    //val student = new Student11("alice",18)
    val student = Student11.newStudent("alice",18)
    student.printerInfo()
  }
}
//定义类
class Student11 private(val name:String,age:Int){
  def printerInfo(): Unit ={
    println(s"student:name = $name,age = $age,school = ${Student11.school}")
  }
}
object Student11{
  val school:String = "school11"
  //创建类的对象实例的创建方法
  def newStudent(name:String,age:Int):Student11 = new Student11(name,age)
}

伴生对象中,也可以定义apply方法,在调用伴生对象的apply方法时可以省略apply

object Test11_Object {
  def main(args: Array[String]): Unit = {
    //val student = new Student11("alice",18)
    val student = Student11("alice",18)
    student.printerInfo()
  }
}
//定义类
class Student11 private(val name:String,age:Int){
  def printerInfo(): Unit ={
    println(s"student:name = $name,age = $age,school = ${Student11.school}")
  }
}
object Student11{
  val school:String = "school11"
  //创建类的对象实例的创建方法
  def apply(name:String,age:Int):Student11 = new Student11(name,age)
}

单例设计模式
一个类只能有一个实例,并提供对该实例的全局访问点。通俗地说,就是一个类只能创建一个对象,并且在程序的任何地方都能够访问到该对象。
如下代码new出来的Student11对象实例全局只有一份,可以创建student1和student2,通过println( student1)println( student2)代码打印出的地址是相同的

object Test11_Object {
  def main(args: Array[String]): Unit = {
    //val student = new Student11("alice",18)
    val student1 = Student11.getInstance
    student1.printerInfo()
    val student2 = Student11.getInstance
    student2.printerInfo()
    println( student1)
    println( student2)
  }
}
//定义类
class Student11 private(val name:String,age:Int){
  def printerInfo(): Unit ={
    println(s"student:name = $name,age = $age,school = ${Student11.school}")
  }
}

//饿汉式
//object Student11{
//  val school:String = "heria"
//  private val student:Student11 = new Student11("alice",18)
//
//  def getInstance:Student11 = student
//}

object Student11{
  private var student:Student11 = _
  def getInstance():Student11 = {
    if(student==null){
      student = new Student11("alice",18)
    }
    student
  }
}

特质

Scala语言中,采用特质trait(特征)来代替接口的概念,也就是说,多个类具有相同
的特质(特征)时,就可以将这个特质(特征)独立出来,采用关健字trait声明。
Scala中的trait中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可
以混入(mixin)多个特质。这种感觉类似于Java中的抽象类。
Scala 引入trait特征,第一可以替代Java的接口,第二个也是对单继承机制的一种补充。
基本语法

trait 特质{
	trait 主体
}

一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,
所以在使用时,也采用了extends关键字,如果有多个特质或存在父类,那么需要采用with
关键字连接。
说明
(1)类和特质的关系:使用继承的关系。
(2)当一个类去继承特质时,第一个连接词是extends,后面是with。
(3)如果一个类在同时继承特质和父类时,应当把父类写在extends后。

//没有父类
class 类名 extends 特质1 with 特质2 with特质3  ...
//有父类
class 类名 extends 父类 with 特质1 with 特质2 with特质3  ...

案例实操

object Test13_Trait {
  def main(args: Array[String]): Unit = {
    val student = new Student13
    student.sayHello()
//    hello from:heria
//    hello from :student heria
    student.study()//student heria is studying
    student.play()//young people can play
  }
}
class Person13{
  val name:String = "person"
  var age:Int = 18
  def sayHello():Unit={
    println("hello from:" +name)
  }
}
//定义特质
trait Young{
  //声明抽象和非抽象属性
  var age:Int
  val name:String = "young"

  def play():Unit = {
    println("young people can play")
  }
  def dating():Unit
}
class Student13 extends Person13 with Young{
  //重写冲突的属性
  override val name: String ="heria"

  def dating():Unit = println(s"student $name is dating")
  def study():Unit = println(s"student $name is studying")
  //重写父类方法
  override def sayHello(): Unit = {
    super.sayHello()
    println(s"hello from :student $name")
  }
}

特质混入

还是以上一个学生的例子为例,我们不希望在定义某一个类时就把特质混入进去,而是在创建实例时将特质加入。在创建有Young特质的学生的时候,再用val student = new Student13 with Young{}将特质混入

object Test13_Trait {
  def main(args: Array[String]): Unit = {
    val student = new Student13 with Young{
      //重写冲突的属性
      override val name: String ="heria"

      def dating():Unit = println(s"student $name is dating")
      def study():Unit = println(s"student $name is studying")
    }

    student.sayHello()
//    hello from:heria
//    hello from :student heria
    student.study()//student heria is studying
    student.play()//young people can play
  }
}
class Person13{
  val name:String = "person"
  var age:Int = 18
  def sayHello():Unit={
    println("hello from:" +name)
  }
}
//定义特质
trait Young{
  //声明抽象和非抽象属性
  var age:Int
  val name:String = "young"

  def play():Unit = {
    println("young people can play")
  }
  def dating():Unit
}
class Student13 extends Person13{

  //重写父类方法
  override def sayHello(): Unit = {
    super.sayHello()
    println(s"hello from :student $name")
  }
}

特质叠加

由于一个类可以混入(mixin)多个trait,且trait 中可以有具体的属性和方法,若混入
的特质中具有相同的方法(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。
冲突分为以下两种:
第一种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法。方法的重写需要super语句,子类在继承父类或者特质时,后一个将前一个同名的方法覆盖,最终super返回调用的是最后一个特质对应的方法。
在这里插入图片描述
第二种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait继承自相同的trait (TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala采用了特质叠加的策略。
在这里插入图片描述
案例实操

object Test15_TraitOverLying {
  def main(args: Array[String]): Unit = {
    var ball = new MyFootBall
    println(ball.describe())
  }
}
//定义球类特征
trait Ball{
  def describe():String = "ball"
}
//定义颜色特征
trait ColorBall extends Ball{
  var color:String = "red"
  override def describe():String  = color + "-" +super.describe()
}
//定义种类特征
trait CategoryBall extends Ball{
  var category:String = "foot"
  override def describe():String  = category + "-" +super.describe()
}
class MyFootBall extends CategoryBall with ColorBall{
  override  def describe():String  ="my ball is a "+super.describe()
}

最终输出的是my ball is a red-foot-ball。当一个类混入多个特质的时候,scala 会对所有的特质及其父特质按照一定的顺序进行排序,而此案例中的super.describe()调用的实际上是排好序后的下一个特质中的 describe()方法。class MyFootBall extends CategoryBall with ColorBall排序规则如下:
1)列出混入的第一个特质(Category),作为临时叠加顺序
2)列出混入的第二个特质(Color)的继承关系,并将该顺序叠加到临时顺序前面,已经出现的特质不再重复
3)将子类(MyBall)放在临时叠加顺序的第一个,得到最终的叠加顺序MyClass-->Color-->Category-->Ball


网站公告

今日签到

点亮在社区的每一天
去签到