scala03--Lazy,并行,类,伴生,特质,抽象,修饰,匹配,样例

本文深入讲解Scala编程语言的关键特性,包括Lazy关键字的延迟加载机制、并行化计算、面向对象编程、伴生类与对象、特质与抽象类的运用、访问权限修饰符的作用、final关键字与类型别名的应用、类型判断及模式匹配的高级技巧,以及样例类的使用场景。

一 Lazy关键字

添加到变量之前, 做到延迟加载, 只有调用的时候才会执行计算
类似于单例中的懒汉模式–> 不调用方法之前对象是不存在的

  1. 构造方法私有化
  2. 创建一个静态私有变量数据类型是当前类的数据类型且不进行初始化
  3. 提供一个公有静态的获取当前对象的方法
  4. 进行判断, 若当前对象没有被创建, 创建对象, 否则返回
object LazyDemo extends App {
  def init():String={
    println("初始化方法")
    "测试"
  }
  //创建一个变量类获取方法
  lazy val method = init()//有无lazy关键字, 结果是不同的
  println("初始化方法调用之后")
  println(method)
}

二 并行化

通过par开启多个线程同时进行计算(开启多线程)
spark代码的使用本地执行 setMaster(“local”) —>

  • local—一个线程
  • local[2]—模拟spark并行化处理(开启两个线程), 最好小于当前电脑空余线程的个数, 不要太多
  • local[*]—有多少线程就用多少线程
并行计算求和
  • sum : 遍历集合, 将集合中的数据进行相加求和, 开启并行计算后, 多个线程并行化处理求和, 多个线程共同计算, 最后在累计求和
  • reduce : 也是一个聚合函数, 作用和sum类似, sum不需要我们传入函数进行操作, 因为sum 里边有一个隐式转换, 可以带有默认操作, 而reduce需要传入一个能计算集合中数据的函数作为参数. 默认reduce会获取集合中元素的数据类型, 作为其泛型的数据类型—>源码中是U, 所以在写函数的时候, 并不需要明确的表示数据类型是什么; 求和可以使用并行化, 但是求差值的时候不能使用并行计算, 因为并行求差值的时候, 所得的结果时不确定的
  • fold : 折叠 可以聚合 初始值(无特定顺序)
    在不开启并行化之前, 计算结果和初始值进行相加
    在开启并行化之后, 得到的结果时不相同的, 这个是因为当前系统中空闲线程类决定的
    第一个_ 和第二个_ 分别代表初始值和集合中的元素
    根据线程数进行计算 每个线程都会和初始值进行一次计算
object ParDemo extends App {
  val arr = Array(1, 2, 3, 4, 5, 6, 7, 7, 4, 3)
  //求和(聚合) sum 遍历集合, 将集合中的数据进行相加求和, 一个线程从头到尾进行计算
  val sumed: Int = arr.sum
  println(sumed) // print 42

  // 开启线程并行化处理求和, 多个线程共同计算, 最后在累计求和
  val sumed2: Int = arr.par.sum
  println(sumed2) // print 42

  /**
    * reduce是聚合函数, 也带有遍历集合的功能, 获取集合中每一个元素的值
    * 第一次会将集合中第一个元素和第二个元素的值赋值给x 和 y
    * 第二次 x 获取的是上一次计算的结果, y会继续获取集合中元组, 然后在求和
    * 以此列推得到最终结果
    */
  //val sumed3 = arr.reduce((x: Int, y: Int) => x + y)
  val sumed3 = arr.reduce(_ + _)
  println(sumed3) // print 42

  //并行化reduce
  val sumed4 = arr.par.reduce(_+_)
  println(sumed4) // print 42

  //使用reduce求差值--单线程
  val sub1 = arr.reduce(_-_)
  println(sub1) // print -40
  //使用reduce求差值--多线程
  //因为在同一段时间内, 有多个线程同时计算, 这个计算结果时不确定的,并且没有办法进行逆推
  val sub2 = arr.par.reduce(_-_)
  println(sub2) // 不一定出什么结果

  /**
    * reduceLeft 是reduce的变种, 处理函数是相同的
    * 添加和不添加并行处理也是一样的, 单线程累加, 从左向右逐一累加
     */
  val sumed5 = arr.par.reduceLeft(_+_)
  println(sumed5) // print 42
  val sub3 = arr.par.reduceLeft(_-_)
  println(sub3) // print -40

  /**
    * 和reduceLeft相似, reduceRight
    * 反向进行运算
    */
  val sumed6= arr.par.reduceRight(_+_)
  println(sumed6) // print 42
  val sub4 = arr.par.reduceRight(_-_)
  println(sub4) // print -2

  /**
    * 折叠 可以聚合 初始值(无特定顺序)fold
    * 在不开启并行化之前, 计算结果和初始值进行相加
    * 在开启并行化之后, 得到的结果时不相同的, 这个是因为当前系统中空闲线程类决定的
    * 第一个_ 和第二个_ 分别代表初始值和集合中的元素
    * 根据线程数进行计算 每个线程都会和初始值进行一次计算
    */
  //使用单线程---需要传入两个参数, 第一个参数是初始化值, 第二个参数是计算规则
  val sumed7 = arr.fold(0)(_+_)
  println(sumed7) // print 42
  //使用多线程
  // 在初始化都是0的情况下,计算结果相同
  val sumed8: Int = arr.par.fold(0)(_+_)
  println(sumed8) // print 42

  //改变初始值, 将初始值变为10
  val sumed9 = arr.fold(10)(_+_)
  println(sumed9) // print 52
  //使用多线程计算结果不确定
  val sumed10: Int = arr.par.fold(10)(_+_)
  println(sumed10)

  //初始值特定顺序(单线程)
  val sum11 = arr.par.foldLeft(10)(_+_)
  println(sum11)// print 52
  val sum12 = arr.par.foldRight(10)(_+_)
  println(sum12)// print 52

  /**
    * aggregate(0)(_+_,_+_)
    * 聚合函数 初始值 局部聚合 全局聚合
    */
  arr.aggregate(0)(_+_,_+_)
}

三 面向对象

scala语言是一门面向函数编程的语言, 并且兼容面向对象的思想

3.1创建类

在scala中类分为两种

  1. 描述类, class
  2. 执行类, object
    scala中的类不需要使用public修饰, 没有这个修饰符, 一个文件中可以存在多个类, 若没有提供构造方法, 系统会提供默认的无参构造方法
class Student {
  /**
    * 定义属性, 需要给定一个初始值
    * var修饰的属性相当于自带的get/set方法
    * val修饰的属性相当于只有get方法, 没有set方法---只读
    * _ 表示一个占位符, 可以作为初始化使用, 需要注意的是:
    * 若使用下划线作为初始值, 必须声明数据类型
    * _ 相当于给当前变量进行初始值赋值, 数值类型--> 0  引用数据类型 ---> null
    * 引用类型在使用时需要注意空指针
    */
  var name: String = _
  var age: Int = _
  var gender: String = _
  //var add = _  不能这么使用, 无法确定数据类型
  //val add:String = _ 也不能这么使用, val相当于final, 无法进行再次赋值, 在这里禁止使用下划线
  val add = "育新花园"
}

object Test {
  def main(args: Array[String]): Unit = {
    //创建对象 若创建对象没有构造方法, 使用的是默认构造, 所以可以省略小括号
    val stu = new Student
    //调用属性
    println(stu.name)
    println(stu.age)
    println(stu.add)

    //调用属性, 赋值, val修饰的属性不能进行再次赋值
    stu.gender = "男"
    println(stu.gender)
  }
}

3.2构造方法
  • 在类名后面添加()完成
  1. 一个类只能有一个主构造方法, 可以有多个辅助构造方法
  2. 若类中提供了构造方法, 可以省略书写属性, 在构造方法中提供了这些形参名,其实就是类的属性, 我们只需提供修饰即可对外访问;不使用修饰之前, 这些形参只能够在类中使用; 在使用val或var修饰之后, 这些形参就可以在外部使用
  3. 在构造的时候, 可以给属性一个默认值
class Person(val name: String, val age: Int, val faceValue: Int = 50) {

  var gender: String = _

  //没有var或者val修饰的时候, 可以在类的内部进行调用
  def show() = {
    println(name)
  }

  //提供辅助构造器
  def this(name: String, age: Int, faceValue: Int, gender: String) {
    //第一行必须调用主构造方法
    this(name, age, faceValue)
    this.gender = gender
  }
}

object PersonTest {
  def main(args: Array[String]): Unit = {
    //创建对象
    val p1 = new Person("laoliang", 37, 80)
    val p2 = new Person("laochen", 40)
    println(p2.faceValue)
    val p3 = new Person("laocao", 45, 2, "男")

  }
}
3.3单例对象

scala中没有静态的概念, 所以scala中需要创建一个单例对象作为程序的入口, 所以就创建了object类

3.3.1单例设计模式
  1. 懒汉式
  2. 饿汉式
3.3.2Java写单例流程
  1. 构造方法私有化
  2. 懒汉创建一个静态的属性当做类的类型(不做初始化), 饿汉创建一个静态的属性当做类的类型(需要进行初始化)
  3. 提供一个静态方法返回值类型是当前类的类型, 懒汉因为懒汉没有提供初始化, 所以需要进行判断当前对象是否创建, 若创建就直接返回对象, 否则就创建对象并返回, 饿汉因为已经创建完成, 返回即可
  4. 需要注意懒汉线程不安全需要加锁

简单单例
5. 私有构造方法
6. 创建一个静态不可变的对象并初始化
(1)public static final 类型 对象名 + new 类型()
(2)枚举单例—可以实现方法 enum{对象 方法}

scala中简单单例

object SingleDemo {
  //构造对象
  private val instance = SingleDemo

  //对外提供访问方法
  def getInstance = instance

}

四 伴生类和伴生对象

scala中的一个独有的概念, scala中有一个类object类型—>执行类,单例
在一个文件中有两个类, 一个是class类, 另一个类是object, 并且两个类是同名的, object类就是class类的伴生类

  • 类和伴生类之间可以互相访问私有成员
//普通类
class Animal extends App {
  var age = 1
  private val name = "shenshou"

  def showName: String = {
    var name1 = "yangtuo"
    name1
  }
}

//伴生类-->伴生类创建出出来的对象就是伴生对象
object Animal {
  def showAnimal(): Unit ={
    //创建类的对象
    val an = new Animal
    an.age = 100
    //伴生类中可以访问类中的私有属性
    println(an.name)
    an.showName()
  }
}

object AnimalTest{
  def showAnimal(): Unit ={
    val an = new Animal
    //在这里只能访问到age和showName两个属性
  }
}
apply和unapply

apply方法是注入方法, 在类的伴生对象做一些初始操作
apply方法的参数列表不需要和构造器的参数类表统一
unapply方法是提取方法, 使用unapply方法可以提取固定数量的对象或值

class Dog(val name: String, val age: Int, val gender: String = "不详") {

}

object Dog {
  /**
    * apply方法是被隐式调用的,
    */
  //注入方法
  def apply(name: String, age: Int): Dog = {
    //创建
    new Dog(name, age)
  }

  /**
    * 返回值必须是Option, 因为Option有两个值
    * 1. None
    * 2. Some
    * 若取值不成功, 可以返回一个空值
    * 若取值成功, 返回具体的数值
    * 
    */
  def unapply(dog: Dog): Option[(String, Int, String)] = {
    if (dog == null) {
      None
    } else {
      Some(dog.name, dog.age, dog.gender)
    }
  }
}

object ApplyAndUnapplyDemo {
  def main(args: Array[String]): Unit = {
    val dog = Dog("小白", 11) //这里执行了apply的方法
    //分支的时候没有switch-case这个语句
    //提供了一个匹配模式, 这种模式类似于Java中的switch-case
    dog match {
      case Dog(name, age, gender) => println("对象存在")
      case _ => println("什么都没有")
        
    }
    val arr = Array()
  }
}

五 特质和抽象类

5.1特质

scala中Trait(特质) 相当于Java中的接口

  • scala中的特质比Java中接口更加强大, 与接口的不同在于特质中可以有属性和方法, 方法可以实现
  • 一般情况下, scala的类智能继承一个父类, 但是可以继承多个接口
  • 但是实际上, 他的强大只是针对Java7之前的, 在Java8中也可以实现方法, 所以两者是完全一样的
/**
  * scala中的特质, 除了关键字不一样外, 在当前代码中定义的和class类中定义的是完全一样的
  * 但是不能提供构造方法
  */
trait Fly {
  //声明有值的的属性
  val height = 2000
  //声明没有值的属性
  val speed:String
  //声明实现方法
  def fly():String = {
    "I can fly"
  }
  //声明一个没有实现的方法
  def play():String
}
5.2抽象类
  • 和Java一样, 使用abstract关键字
/**
  * 企鹅类,抽象类, abstract修饰
  */
abstract  class QQ {
  //声明一个有值的字段
  val age = 5
  //声明一个无值的字段
  val name:String
  //声明实现的方法
  def show():String={
    "QQQQ"
  }
  //声明没有实现的方法
  def run():String

  var gender:String = "男"
}

5.3继承特质和抽象类
/**
  * 若在没有继承类的前提下, 直接使用extends 就可以继承特质
  * 若只是继承抽象类或其他类 也是使用extends
  * 若继承一个类实现一个特质, extends 类名 with 特质名
  * extends 类名 with 特质名 with 特知名 with 特知名......
  */
class Human extends QQ with Fly {
  override val name: String = "fly_name"

  override def run(): String = "跑"

  override val speed: String = "1000KM/H"

  override def play(): String = "斗地主"

  //重写
  override val age: Int = 20

  //被var修饰的属性不能被重写, 重写不会报错, 但是运行的时候会报错
  //override var gender:String="不详"
}

object Human{
  def main(args: Array[String]): Unit = {
    val h = new Human
    println(h.gender)
  }
}

六 访问权限修饰符

scala中访问权限修饰符能够显示的写出来的只有private–私有的, 其他的都是public—公有的
private 修饰构造方法(主) 外部就不能访问了

class Student private (name:String){}

private 可以修饰成员变量, 如果修饰了成员变量, 只能在当前类中和伴生类中使用
private 可以修饰类, 保证当前类在什么位置可以访问

private[包名] class 类名

在这里插入图片描述

private [this] class 类名---> 只能在当前包下访问

七 final关键字和Type关键字

  • final修饰的类不能被继承, 修饰的方法不能被重写, 修饰的属性不能被重写
  • Type可以给数据类型添加别名
val name:String="小白"
type str = String
val name:str = "小黑"

八 类型判断和匹配模式

  • scala中一切的父类是Any
  • AnyRef是Any的子类, 是所有引用类型的父类
  • AnyVal是Any的子类, 是所有数值类型的父类
  • AnyVal子类有9个(基本数据类型), Byte, Short, Int, Long, Double, Float, Char, boolean, Unit
8.1对类型的基本操作
  1. 判断当前对象是否是当前类型
    • Java中: obj instanceof 类
    • scala中: obj isinstanceOf[类]
  2. 强制转换
    • Java中: (类型)obj
    • scala中: obj asInstanceOf[类]
  3. 获取类型的Class对象
    • Java中: class
    • scala中: classOf[类]
8.2模式匹配

scala中, 模式匹配有两种形式

  1. 标准形式
数据 match{
	case 条件=>输出
}
  1. 偏函数(先以show代替)
def show()={
	case 条件=>输出
}
  • scala中match-case 和Java中switch-case 有类似, 但是前者更加强大, Java中的switch-case只能匹配char, 枚举, 数值(只能是int), 在1.7之后可以匹配字符串, 但是scala中的match-case什么都可以匹配
    • 模式匹配中, scala提供了一个类似于Java枚举的声明–>样例类
    • Java中的switch-case需要使用break, 不然就会贯穿(从开始执行到结束)
    • scala中的match-case没有break, 只要case满足就执行, 执行完就自动退出, 不会执行下一个case
    • scala中使用_ 代替Java中的default, 最后的默认情况, 一定要放到最后
import scala.util.Random

object MatchCaseDemo {
  def main(args: Array[String]): Unit = {
    //创建一个数组
    val arr1 = Array("laoliang", "laocao", "laochen", "dazhao")
    //通过随机获取值进行匹配输出
    arr1(Random.nextInt(arr1.length)) match {
      case "laoliang" => println("laoliangjianggushi")
      case "laocao" => println("laocaolaocao")
      case "laochen" => println("laochenlaochen")
      case "dazhao" => println("heiheihei")
      case _ => println("默认")
    }

    println("-" * 50)
    val arr2 = Array("hello123", 1, 2, 3L, MatchCaseDemo)
    arr2(Random.nextInt(arr2.length)) match {
      //可以向写方法形参一样
      case x: Int => println("Int Type")
      case y: Double if (y < 0) => println("double Type")
      case MatchCaseDemo => {
        println("MatchCaseDemo Type")
      }
      case _ => println("default")

    }

    val arr3: Array[Int] = Array(2, 3, 45, 5, 6, 6)
    arr3 match {
      case Array(2, 3, x, y) => println("x+y=" + (x + y))
      case Array(2, 3, _*) => println("_* 代表后边所有值")
      case _ => println("default")
    }

    val tup1 = (1, 2, 5)
    tup1 match {
      case (1, 2, x) => println("x=" + x)
      case (x, y, z) => println("x+y+z: " + (x + y + z))
      case (_, x, 5) => println("下划线尽量不要使用, 可以使用变量接收")
      case _ => println("default")
    }
  }
}

九 样例类

主要是为了提供给模式匹配类, 可以什么都不写, 只是封装, 也可以写入一些数据

case class 类名(构造方法) //封装数据使用
case object 类名 //可以匹配方法

样例类的使用

import scala.util.Random

/**
  * 定义样例类, 在类的外部, 可以在其他文件中
  * 需要使用case关键字, class或者object
  * class可以写构造方法, 多用于封装数据
  * object可以写类名, 用于方法匹配
  * 建议: 可以参考枚举来理解
  */
case object CheckTimeOutTask {

}

//样例类的封装
case class SubmitTask(id: String, taskName: String)

case class HeatBeat(time: Long)

//case class 另外的样式
case class Cat(name: String, king: String) {
  //进行一些数据操作
}

object CaseClassDemo {
      def main(args: Array[String]): Unit = {
        val arr: Array[Product with Serializable] = Array(CheckTimeOutTask, SubmitTask("0001", "task_0001"), HeatBeat(15000))
        arr(Random.nextInt(arr.length)) match {
          case CheckTimeOutTask => println("CheckTimeOutTask")
          //使用case class 可以携带数据, 匹配成功够就可以进行操作
          case SubmitTask(id, taskName) => println(id + " " + taskName)
          case HeatBeat(time) => println(time)
        }

    /**
      * 上面的代码等价于Java代码:
      * class Person{}
      * class Animal{}
      * class Dog{}
      * Person p = new Person()
      * Animal a = new Animal()
      * Dog d = new Dog()
      *
      * Object arr= new Object[](p,a,d)
      *
      */
  }
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值