大数据开发成长之路——Scala程序设计(三)

面向对象编程

  • 这部分可以类比Java中的很多概念
  • 在IDEA中新建object
import java.util.Date

class Customer {
  var name:String = _	// 下划线表示占位,传入相应类型的默认值
  var sex:String = _
  val registerDate:Date = new Date

  def sayHi(msg:String):Unit = {	// Unit即void
    println(msg)
  }
}

object Main {
  // main方法必须要放在一个scala的object(单例对象)中才能执行
  def main(args: Array[String]): Unit = {	
    val customer = new Customer
    //给对象的成员变量赋值
    customer.name = "张三"
    customer.sex = "男"

    println(s"姓名: ${customer.name}, 性别:${customer.sex}, 注册时间: ${customer.registerDate}")	// 使用${}格式化输出变量,s表示字符串
    //对象调用方法  
    customer.sayHi("你好!")
  }
}

构造器

  • 主构造器:指在类名的后面跟上一系列参数
  • 辅助构造器:在类中使用this来定义
class Student(val name:String, val age:Int) {	// 类的默认成员属性都是val,可以使用var声明
    val address:String="beijing" 	
  	// 定义一个参数的辅助构造器
  	def this(name:String) {// 子类主构造器(传参即可)
    	// 第一行必须调用主构造器、其他辅助构造器或者super父类的构造器
    	this(name, 20)	// 调用主构造器
  }

  def this(age:Int) {
    this("某某某", age)
  }
}
  • 这里的主构造器类似Java的父类构造器,但是传参即可
  • 辅助构造器类似子类构造器,但必须使用this调用父类的构造方法

注:只是类比学习

对象

  • scala中是没有静态(static)成员
  • 需要实现static变量、static方法的效果,要通过scala中的单例对象(object)
  • scala主要进行函数式编程,这里可以类比JavaScript中的模式,静态属性/方法可以借助对象实现
import java.text.SimpleDateFormat

object DateUtils {

  // 在object中定义的成员变量,相当于Java中定义一个静态变量
  // 定义一个SimpleDateFormat日期时间格式化对象
  val simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm")

  // 构造代码
  println("构造代码")

  // 相当于Java中定义一个静态方法
  def format(date:Date) = simpleDateFormat.format(date)

  // main是一个静态方法,所以必须要写在object中
  def main(args: Array[String]): Unit = {
    println { DateUtils.format(new Date()) };	// 通过 . 调用
  }
}

伴生对象

  • class和object具有同样的名字,那么就称这个object是class的伴生对象,class是object的伴生类
  • 最大特点是:可以相互访问,包括私有变量
class ClassObject {
  val id = 1
  private var name = "itcast"
  def printName(): Unit ={
    //在Dog类中可以访问伴生对象Dog的私有属性
    println(ClassObject.CONSTANT + name )
  }
}

object ClassObject{
  //伴生对象中的私有属性
  private val CONSTANT = "汪汪汪 : "
  def main(args: Array[String]) {
    val p = new ClassObject	// 实例化类
    p.name = "123"	    	//访问私有的字段name
    p.printName()
  }
}

apply方法

  • 我们可以使用如下代码新建数组,不需要关键字new+类名,如何实现?
	// 创建一个Array对象
	val a = Array(1,2,3,4)
  • 通过下面的scala源码可以发现,其实默认调用了Array对象的apply()方法
    在这里插入图片描述
  • 伴生对象的apply方法用来快速地创建一个伴生类的对象(实例)
class Person(var name:String, var age:Int) {
  override def toString = s"Person($name, $age)"
}

object Person {
  // 实现apply方法
  // 返回的是伴生类的对象
  // 有没有点像Java自定义异常?必须实现所有构造方法
  def apply(name:String, age:Int): Person = new Person(name, age)
  // apply方法支持重载
  def apply(name:String):Person = new Person(name, 20)
  def apply(age:Int):Person = new Person("某某某", age)
  def apply():Person = new Person("某某某", 20)
}

object Main2 {
  def main(args: Array[String]): Unit = {
    val p1 = Person("张三", 20)	// Person对象的第一个apply()方法
    val p2 = Person("李四")
    val p3 = Person(100)
    val p4 = Person()

    println(p1)
    println(p2)
    println(p3)
    println(p4)
  }
}

main方法

  • 我们在对象中实现main方法
  • 也可以继承自App Trait(特质),可以理解为Java中的接口
object Main2 extends App {
 	println("hello, scala")
}

继承

  • 使用extends关键字实现继承,基本和Java相同
class Person1 {
  var name = "super"
  def getName = this.name
}

class Student1 extends Person1

object Main1 {
  def main(args: Array[String]): Unit = {
    val p1 = new Person1()
    val p2 = new Student1()
    p2.name = "张三"
    println(p2.getName)
  }
}
// 必须在子类的主构造器中调用父类的构造器
class Person8(var name:String){
    println("name:"+name)
}
// 传参,调用父类的构造器
class Student8(name:String, var clazz:String) extends Person8(name)

object Main8 {
  def main(args: Array[String]): Unit = {
    val s1 = new Student8("张三", "三年二班")
    println(s"${s1.name} - ${s1.clazz}")
  }
}
// output:
// name:张三
// 张三 - 三年二班
  • 对象也可以继承类油!
class Person2 {
  var name = "super"
  def getName = this.name
}

object Student2 extends Person2		// 单例对象继承

object Main2 {
  def main(args: Array[String]): Unit = {
    println(Student2.getName)		// 相当于调用静态方法(object中的方法)
  }
}
  • 与Java不同,子类要覆盖父类中的一个非抽象方法,必须要使用override关键字
  • 相同的是,使用super访问父类成员
class Person3 {
  val name = "super"
  def getName = name
}

class Student3 extends Person3 {
  // 重写val字段
  override val name: String = "child"
  // 重写getName方法
  override def getName: String = "hello, " + super.getName
}

object Main3 {
  def main(args: Array[String]): Unit = {
    println(new Student3().getName)
  }
}

抽象类

  • 就是这么abstract,为了更好的实现多态?
  • 没有方法体的方法称为抽象方法,没有初始化的变量称为抽象字段
abstract class Person(val name:String) {// 还有个构造方法呢
  //抽象方法
  def sayHello:String
  def sayBye:String
  //抽象字段  
  val address:String  
}
class Student(name:String) extends Person(name){
  //重写抽象方法,不需要使用override
  def sayHello: String = "Hello,"+name
  def sayBye: String ="Bye,"+name
  //重写抽象字段,需要使用override
  override val address:String ="beijing "
}
object Main{
  def main(args: Array[String]) {
    val s = new Student("tom")
    println(s.sayHello)
    println(s.sayBye)
    println(s.address)
  }
}

匿名内部类

  • 这个非常常用,配合匿名类使用
  • 匿名内部类是没有名称的子类,直接用来创建实例对象
abstract class Person {
  //抽象方法  
  def sayHello:Unit
}

object Main {
  def main(args: Array[String]): Unit = {
    // 直接用new来实例化一个匿名内部类对象
    val p1 = new Person {	// 通过new实例化,但是实现了抽象方法,还是属于子类
      override def sayHello: Unit = println("我是一个匿名内部类")
    }
    p1.sayHello
  }
}

isInstanceOf

  • 在代码中要经常进行类型的判断和类型的转换,在scala中
  • isInstanceOf判断对象是否为指定类的对象
  • asInstanceOf将对象转换为指定类型
class Person4
class Student4 extends Person4

object Main4 {
  def main(args: Array[String]): Unit = {
    val s1:Person4 = new Student4	// Person4 类型
    // 判断s1是否为Student4类型
    if(s1.isInstanceOf[Student4]) {
	    // 将s1转换为Student3类型
	    val s2 =  s1.asInstanceOf[Student4]
    }
  }
}

getClass

  • isInstanceOf 只能判断出对象是否为指定类以及其子类的对象,而不能精确的判断
  • 要求精确地判断出对象就是指定类的对象,那么就只能使用 getClassclassOf
class Person
class Student extends Person

object Student{
  def main(args: Array[String]) {
    val p:Person=new Student
    //判断p是否为Person5类的实例
    println(p.isInstanceOf[Person])//true

    //判断p的类型是否为Person5类
    println(p.getClass == classOf[Person])//false

    //判断p的类型是否为Student5类
    println(p.getClass == classOf[Student])//true
  }
}

访问修饰符

  • 可以在成员前面添加private/protected关键字来控制成员的可见性
  • 但在scala中,任何没有被标为private或protected的成员都是公共的,没有public关键字

private[this]

  • 被修饰的成员只能在当前类中被访问
class Person6 {
  // 只有在当前对象中能够访问
  private[this] var name = "super"
  def getName = this.name	// 正确!
  def sayHelloTo(p:Person6) = {
    println("hello" + p.name)     // 报错!无法访问
  }
}

object Person6 {
  def showName(p:Person6) = println(p.name)  // 报错!外部无法访问
}

protected[this]

  • 可以通过this.访问或者子类通过this.访问,和java很像
class Person7 {
  // 只有在当前对象以及继承该类的当前对象中能够访问
  protected[this] var name = "super"
  
  def getName = {
    // 正确!
    this.name
  }
  def sayHelloTo1(p:Person7) = {
    // 编译错误!无法访问
    println(p.name)	// 只能this.
  }
}

class Student7 extends Person7 {
  def showName = {
    // 正确!
    println(name)	// 默认使用this.
  }
  def sayHelloTo2(p:Person7) = {
    // 编译错误!无法访问
    println(p.name)
  }
}

特质

  • 特质trait是scala中代码复用的基础单元
  • 它可以将方法和字段定义封装起来,然后添加到类中,我们叫混入
  • 一个类可以添加任意数量的特质,像Java接口吧!!!
  • 使用extends来继承trait,如果要继承多个trait,则使用with关键字
trait Logger {
  // 抽象方法
  def log(msg:String)
}

trait MessageSender {
  def send(msg:String)
}

class ConsoleLogger extends Logger with MessageSender {
  override def log(msg: String): Unit = println(msg)
  override def send(msg: String): Unit = println(s"发送消息:${msg}")
}

object LoggerTrait {
  def main(args: Array[String]): Unit = {
    val logger = new ConsoleLogger2
    logger.log("控制台日志: 这是一条Log")
    logger.send("你好!")
  }
}
  • trait中也可以定义具体的方法
  • 实例对象混入trait
trait LoggerMix {
  def log(msg:String) = println(msg)
}

class UserService

object FixedInClass {
  def main(args: Array[String]): Unit = {
    // 使用with关键字直接将特质混入到对象中
    val userService = new UserService with LoggerMix// 只有这个实例的对象有
    userService.log("你好")
  }
}
  • 可以了解一下trait调用链
    在这里插入图片描述
  • 参考如下代码,理解一下trait的优势
// 支付数据处理
trait HandlerTrait {
  def handle(data: String) = {
    println("处理数据...")
  }
}

// 数据校验处理
trait DataValidHandlerTrait extends HandlerTrait {
  override def handle(data: String) = {
    println("验证数据...")
    super.handle(data)
  }
}

// 签名校验处理
trait SignatureValidHandlerTrait extends HandlerTrait {
  override def handle(data: String) = {
    println("检查签名...")
    super.handle(data)
  }
}

// 支付服务
class PaymentService extends DataValidHandlerTrait with SignatureValidHandlerTrait {
  def pay(data:String) = {
    println("准备支付...")
    this.handle(data)
  }
}

object PaymentService {
  def main(args: Array[String]) {
    val payService = new PaymentService()
    payService.pay("signature:10233123||md5:123889a3d5s1f6123||data:{order:001,money:200}")
  }
}
// 程序运行输出如下:
// 准备支付...
// 检查签名...
// 验证数据...
// 处理数据...

在这里插入图片描述

模板模式

  • 使用具体方法依赖于抽象方法,而抽象方法可以放到继承trait的子类中实现
trait Logger {
  // 抽象方法
  def log(msg:String)
  // 具体方法,依赖于抽象方法log
  def info(msg:String) = log("INFO:" + msg)
  def warn(msg:String) = log("WARN:" + msg)
  def error(msg:String) = log("ERROR:" + msg)
}

class ConsoleLogger extends Logger {
  // 实现抽象方法
  override def log(msg: String): Unit = println(msg)
}

object LoggerTrait {
  def main(args: Array[String]): Unit = {
    val logger = new ConsoleLogger
    logger3.info("这是一条普通信息")
    logger3.warn("这是一条警告信息")
    logger3.error("这是一条错误信息")
  }
}

trait构造机制

  • trait也有构造代码,但和类不一样,特质不能有构造器参数
  • 每个特质只有一个无参数的构造器
  • 一个类继承另一个类、以及多个trait,当创建该类的实例时,它的构造顺序如下:
  1. 执行父类的构造器
  2. 从左到右依次执行trait的构造器
  3. 如果trait有父trait,先构造父trait,如果多个trait有同样的父trait,则只初始化一次
  4. 执行子类构造器
class Person_One {
  println("执行Person构造器!")
}
trait Logger_One {
  println("执行Logger构造器!")
}
trait MyLogger_One extends Logger_One {
  println("执行MyLogger构造器!")
}
trait TimeLogger_One extends Logger_One {
  println("执行TimeLogger构造器!")
}
class Student_One extends Person_One with MyLogger_One with TimeLogger_One {
  println("执行Student构造器!")
  }
object exe_one {
  def main(args: Array[String]): Unit = {
    val student = new Student_One
  }
}
// 程序运行输出如下:
// 执行Person构造器!
// 执行Logger构造器!
// 执行MyLogger构造器!
// 执行TimeLogger构造器!
// 执行Student构造器!

模式匹配

  • scala有一个十分强大的模式匹配机制,可以应用到很多场合
  • 并且scala还提供了样例类,对模式匹配进行了优化,可以快速进行匹配
  • 类似于Java中的switch这里使用match
//todo:匹配字符串
object CaseDemo01 extends App{
  //定义一个数组
  val arr=Array("hadoop","zookeeper","spark","storm")

  //随机取数组中的一位,使用Random.nextInt
  val name = arr(Random.nextInt(arr.length))
  println(name)

  name match {
    case "hadoop"     => println("大数据分布式存储和计算框架...")
    case "zookeeper"  => println("大数据分布式协调服务框架...")
    case "spark"      => println("大数据分布式内存计算框架...")
      //表示以上情况都不满足才会走最后一个
    case _            => println("我不认识你")
  }
}

//todo:匹配类型
object CaseDemo02 extends App{
  //定义一个数组
  val arr=Array("hello",1,-2.0,CaseDemo02)

  //随机获取数组中的元素
  val value=arr(Random.nextInt(arr.length))
  println(value)

    
  value match {
    case x:Int                => println("Int=>"+x)
    case y:Double if(y>=0)    => println("Double=>"+y)
    case z:String             => println("String=>"+z)
    case _                    => throw new Exception("not match exception")
  }
}

//匹配数组
object CaseDemo03 extends App{

  //匹配数组
  val  arr=Array(1,3,5)
  arr match{
    case Array(1,x,y) =>println(x+"---"+y) // 会分别将数组中的元素复制给x,y
    case Array(1,_*)  =>println("1...")
    case Array(0)     =>println("only 0")
    case _            =>println("something else")
      
  }
}

//匹配集合
object CaseDemo04 extends App{

  val list=List(0,3,6)
  list match {
    case 0::Nil        => println("only 0")	// Nil表示空
    case 0::tail       => println("0....")	// :: 表示追加到列表,tail表示列表尾部
    case x::y::z::Nil  => println(s"x:$x y:$y z:$z")
    case _             => println("something else")	// _ 表示啥也没匹配到,即default
  }
} 

//匹配元组
object CaseDemo05 extends App{
  
  val tuple=(1,3,5)
  tuple match{
    case (1,x,y)    => println(s"1,$x,$y")
    case (2,x,y)    => println(s"$x,$y")
    case _          => println("others...")
  }
}

样例类

  • 样例类是一种特殊类,它可以用来快速定义一个用于保存数据的类
  • 使用case class关键字
// 定义一个样例类
// 样例类有两个成员name、age
case class CasePerson(name:String, age:Int)

// 使用var指定可变成员变量
case class CaseStudent(var name:String, var age:Int)

object CaseClassDemo {
  def main(args: Array[String]): Unit = {
    // 1. 使用new创建实例
    val zhangsan = new CasePerson("张三", 20)// 一般不这样做(意思是也可以)
    println(zhangsan)

    // 2. 使用类名直接创建实例
    val lisi = CasePerson("李四", 21)
    println(lisi)

    val xiaohong = CaseStudent("小红", 23)
    xiaohong.age = 24
    println(xiaohong)
  }
}
  • 样例对象时可以序列化的,先了解即可
case class SubmitTask(id: String, name: String)
case class HeartBeat(time: Long)
case object CheckTimeOutTask

object CaseDemo06 extends App{	// 继承App特质
  val arr = Array(CheckTimeOutTask,
                  HeartBeat(10000), 
                  SubmitTask("0001", "task-0001"))

  arr(Random.nextInt(arr.length)) match {
      
       case SubmitTask(id, name) => println(s"id=$id, name=$name")
       case HeartBeat(time) => println(s"time=$time")
       case CheckTimeOutTask => println("检查超时")
	   case _	=> println("what???")
  }
}

Option类型

  • Option[T] 是一个类型为 T 的可选值的容器
  • 如果值存在, Option[T] 就是一个 Some[T] ,如果不存在, Option[T] 就是对象 None
  • 包含两个子类:
  1. Some包装了某个值
    在这里插入图片描述
  2. None表示没有值
    在这里插入图片描述
val myMap: Map[String, String] = Map("key1" -> "value")
val value1: Option[String] = myMap.get("key1")	// 我希望是String类型的
val value2: Option[String] = myMap.get("key2") 
println(value1) // Some("value1")
println(value2) // None

object TestOption {
  def main(args: Array[String]) {
    val map = Map("a" -> 1, "b" -> 2)
    val value: Option[Int] = map.get("b")	// 我希望是Int类型的
    val v1 =value match {
      case Some(i) => i
      case None => 0
    }
    println(v1)	// 2

    //更好的方式
    val v2 = map.getOrElse("c", 0)
    println(v2)	// 0
  }
}

偏函数

  • 定义一个函数,只接受和处理其参数定义域范围内的子集,对于参数范围外的参数则抛出异常
  • 它是PartialFunction[A, B]的一个实例
  • A代表输入参数类型
  • B代表返回结果类型
  • 实例得到的偏函数是一个特质trait
object TestPartialFunction {
  // func1是一个输入参数为Int类型,返回值为String类型的偏函数
  val func1: PartialFunction[Int, String] = {// 使用定义方法的格式
    case 1 => "一"
    case 2 => "二"
    case 3 => "三"
    case _ => "其他"
  }
  func1(1)	// String: 一

  def main(args: Array[String]): Unit = {
    val list=List(1,2,3,4,5,6)
    // filter继承PartialFunction的特质
    val result=list.filter{
      case x if x >3 => true
      case _ => false
    }
    println(result)	// List(4,5,6)
  }
}

小结

这里主要介绍了scala面向对象编程中经常用到的方法,每种特性都有适用的场合。还有很多可以扩展的地方,可以根据场景再次拓宽。下一节从异常处理开始,总结其基本概念和用法。

发布了9 篇原创文章 · 获赞 24 · 访问量 3736

猜你喜欢

转载自blog.csdn.net/weixin_39757637/article/details/104933508