scala OOP

scala 系列

scala 入门基础

scala 数组集合

scala 143个数组函数大全

scala 函数

scala OOP

scala 高级扩展



前言

上一篇博客已经给大家介绍了 scala 函数和方法, 有了 scala 函数和方法的基础再去学习 OOP,会变得无往而不利。

本篇博客将为大家带来 scala OOP的介绍。scala OOP 和 java OOP有很多相似之处,因此以下会经常拿 java OOP 作为对比,学习 scala OOP。


思维导图

在这里插入图片描述

在这里插入图片描述

类和对象

之前说过 scala 是面向函数式编程的语言,除此之外,scala 和 java 一样,是支持面向对象的编程语言,也有类和对象的概念。

创建类和对象

类通过class关键字定义,类通过new关键字创建实例对象

创建一个Person类,并创建它的对象如下:

class OOP01 {
  // 创建类
  class Person{}

  def main(args: Array[String]): Unit = {
    // 创建对象
    val p = new Person()
  }
}

注意:

  1. 如果类是空的,没有任何成员,可以省略 {}
  2. 如果构造器的参数为空,可以省略 ()

上述代码可以改造如下:

object OOP01 {
  // 创建类,省略{}
  class Person

  def main(args: Array[String]): Unit = {
    // 创建对象,省略()
    val p = new Person
  }
}

类拥有成员变量和方法

像 java 一样,类可以有自己的属性和行为,scala中可以通过定义和声明变量来定义类的属性,定义成员方法来定义类的行为。

  • 在类中使用 var/val 来定义成员变量
  • 对象直接使用成员变量名称来访问成员变量
  • 使用def来定义成员方法

例如:

object OOP01 {
  // 创建类
  class Person{
    // 定义成员属性
    var name = ""
    // 使用下划线进行初始化成员属性
    var age:Int = _

    // 定义成员方法
    def show(): Unit ={
      println(s"我叫${name},今年${age}岁")
    }
  }

  def main(args: Array[String]): Unit = {
    // 创建对象
    val p = new Person

    p.name = "hubert"
    p.age = 22

    // 获取变量值
    println(s"${p.name} ${p.age}")

    // 调用成员方法
    p.show()
  }
}

注意:

  1. 定义成员属性时必须初始化属性
  2. 使用 var +下划线 初始化成员变量时,必须显示的指定成员变量的类型,下划线代表成员变量类型默认值
  3. val类型的成员变量,必须要自己手动初始化
  4. 成员属性可以定义在构造器中(下面会说)
  5. 类中无法定义静态成员变量和静态方法,静态变量和静态方法定义在object 单例对象中

访问修饰符

和Java一样,scala也可以通过访问修饰符,来控制成员变量和成员方法是否可以被访问。

java 访问修饰符

ModifierClassPackageSubclassWorld
publicYYYY
protectedYYYN
defaultYYNN
privateYNNN

scala 访问修饰符

ModifierClassCompanionSubclassPackageWorld
defaultYYYYY
protectedYYYNN
privateYYNNN

Java中的访问控制,同样适用于scala,可以在成员前面添加 private/protected 关键字来控制成员的可见性。但在scala中,没有public关键字,任何没有被标为 private 或 protected 的成员都是公共的。

构造器

scala 中的构造器分为主构造器和辅助构造器,当创建对象的时候,会自动调用类的主构造器。而 java 中直接是构造方法的重载操作。

主构造器

类无需明确定义构造方法,通过主构造器的构造参数列表声明为类的一部分。

语法:

class 类名(var/val 参数名:类型 = 默认值, ..., var/val 参数名:类型 = 默认值){
  构造代码块
}

注意:

  1. 主构造器的参数列表是直接定义在类名后面,添加了 val/var 表示直接通过主构造器定义成员变量
  2. 构造器参数列表可以指定默认值
  3. 创建实例,调用构造器可以指定字段进行初始化
  4. 整个class中除了字段定义和方法定义的代码都是构造代码

辅助构造器

在scala中,除了定义主构造器外,还可以根据需要来定义辅助构造器。例如:允许通过多种方式,来创建对象,这时候就可以定义其他更多的构造器。我们把除了主构造器之外的构造器称为辅助构造器。

语法:

def this(参数名:类型, ..., 参数名:类型){
  //第一行必须调用主构造器或者其他构造器
  //构造器代码
}

注意:

  1. 定义辅助构造器与定义方法一样,也使用 def 关键字来定义
  2. 这个方法的名字为 this
  3. 辅助构造器的第一行代码,必须要调用主构造器或者其他辅助构造器

之前Person类的构造器使用如下:

object OOP01 {
  // 创建类  主构造器
  class Person(var name:String,var age:Int){
    // 辅助构造器
    def this(arr: Array[Any]){
      this(arr(0).asInstanceOf[String],arr(1).asInstanceOf[Int])
      println("辅助构造器")
    }

    // 定义成员方法
    def show(): Unit ={
      println(s"我叫${name},今年${age}岁")
    }
  }

  def main(args: Array[String]): Unit = {
    // 创建对象 主构造器
    val p = new Person("hubert",22)

    // 创建对象 主构造器
    val p1 = new Person(Array("hubert",22))

    // 调用成员方法
    p.show()
    p1.show()
  }
}

// 结果
辅助构造器
我叫hubert,今年22岁
我叫hubert,今年22

继承

定义

  • scala和Java一样,使用“extends”关键字实现继承。
  • 可以在子类中定义父类中没有的字段和方法,或者重写父类的方法,子类重写父类方法必须使用“override”关键字
  • 类和单例对象(后面会讲)都可以从某个父类继承

例如:

object OOP01 {
  class Person{
    var name:String = _
    var age:Int = _
    def show(): Unit ={
      println(s"我叫${name},今年${age}岁")
    }
  }

	// 类继承
  class Student extends Person

	// 单例对象继承
  object Student extends Person

  def main(args: Array[String]): Unit = {
    val s = new Student
    s.name = "hubert"
    s.age = 22

    s.show()    // 我叫hubert,今年22岁

	// 单例对象优先加载,类似java 静态变量,故而输出null
    println(Student.name)   //  null
  }
}

override 和 super

类似于Java语言,我们在子类中使用override需要来重写父类的成员,可以使用super来引用父类。

注意:

  1. 子类要覆盖父类中的一个方法,必须要使用override关键字
  2. 使用override来重写一个val字段
  3. 使用super关键字来访问父类的成员方法

例如:

object OOP01 {
  class Person{
    val name:String = "jack"
    def getName = name
  }

  class Student extends Person{
    override val name = "hubert"
    override def getName = {
      super.getName + name
    }
  }

  def main(args: Array[String]): Unit = {
    val s = new Student
    println(s.getName)
  }
}

类型判断

isInstanceOf 和 asInstanceOf

在Java中,我们可以使用 instanceof 关键字来判断类型、以及 (类型)object 来进行类型转换,在 scala 中如何实现呢?scala 中对象提供 isInstanceOf 和 asInstanceOf 方法。

语法如下:

// isInstanceOf 判断对象是否为指定类的对象
val trueOrfalse:Boolean = 对象.isInstanceOf[类型]

//  asInstanceOf 将对象转换为指定类型
val 变量 = 对象.asInstanceOf[类型]

例如:

 def main(args: Array[String]): Unit = {
    val a = "hubert"
    val b = 22

    println(a.isInstanceOf[Int])
    println(b.isInstanceOf[String])   //报错   a value of type Int cannot also be a String (the underlying of String)
  }

isInstanceOf 并不是所有的类型都可以使用,anyVal 不能 isInstanceOf【anyRef】中,否则会报错。

getClass 和 classOf

isInstanceOf 只能判断对象是否为指定类以及其子类的对象,而不能精确的判断出,对象就是指定类的对象。如果要求精确地判断出对象就是指定类的对象,那么就只能使用 getClass 和 classOf。

注意:

  • 对象.getClass可以精确获取对象的类型
  • classOf[类名]可以精确获取类型
  • 使用==操作符可以直接比较类型

例如:

object OOP01 {
  class Person

  class Student extends Person

  def main(args: Array[String]): Unit = {
    val s:Person = new Student

    println(s.isInstanceOf[Person])         // ture
    println(s.getClass == classOf[Person])  // false
    println(s.getClass == classOf[Student]) // ture
  }
}

单例对象

Scala 的类中无法定义静态成员,即无 static 关键字。如何像 Java 一样表达类的静态成员变量、成员方法与静态代码块?scala 采用了单例对象——object 来解决这一问题。

Scala 单例对象的特点:

  • 使用“object”关键字声明,可包含静态变量、静态方法与静态代码块定义
  • 单例对象中的成员变量、成员方法通过单例对象名直接调用
  • 单例对象第一次被访问时初始化,并执行全部代码块,java 静态是本地的,虚拟机直接装载 class 信息的
  • 单例对象不能new,且无构造参数
  • 程序入口main()方法必须定义在单例对象中
  • 单例对象与同名类定义在同一文件中时形成绑定关系,即伴生对象(下面会讲)

例如:

object Person {
  // 静态变量
  val address:String = "江苏省南京市"

  // 静态方法
  def show(): Unit ={
    println(s"我的地址是${address}")
  }

  def main(args: Array[String]): Unit = {
    println(Person.address)
    Person.show()
  }
}

在 object 中定义的成员变量类似于 Java 的静态变量,在 object 中定义的成员方法类似于 Java 的静态方法,单例对象表示全局仅有一个对象,类似于 java 的 static 概念,事实上 scala 把 java 的 class 拆成了 class 和 object 两个而已。

scala和Java一样,如果要运行一个程序,必须有一个main方法。而在Java中main方法是静态的,而在scala中没有静态方法。在scala中,这个main方法必须放在一个单例对象中。此外也可以通过 实现App Trait 来定义入口,具体如下:

// 实现App Trait 来定义 main 入口
object Person extends App {
  // 静态变量
  val address:String = "江苏省南京市"

  // 静态方法
  def show(): Unit ={
    println(s"我的地址是${address}")
  }
  
  println(Person.address)
  Person.show()
}

伴生对象

在 Java 中,经常会有一些类,同时有实例成员又有静态成员; scala 中要实现类似的效果,可以使用伴生对象来实现。

定义伴生对象

单例对象与同名类定义在同一文件中时形成绑定关系,就是伴生对象。伴生对象有以下特点:

  • 同名类称为单例对象的伴生类(class)
  • 单例对象称为同名类伴生对象(object)
  • 伴生类与伴生对象可相互访问各自private成员
  • 伴生对象可为伴生类增加静态成员

private [this] 访问权限

当然某个成员的权限设置为private[this],表示只能在当前类中访问,伴生对象也不可以访问。

下述代码会编译报错:

class Person(private[this]var name:String)
object Person{
  def show(person:Person): Unit ={
    println(person.name)
  }

  def main(args: Array[String]): Unit = {
    val p = new Person("hubert")
    Person.show(p)
  }
}

apply

当scala中类或者对象有一个主要用途的时候,apply方法就是一个很好地语法糖。单例对象用于持有一个类的唯一实例。通常用于工厂模式。
使用伴生对象的apply()方法省掉new关键字。例如:Person.apply()等价于Person()

在这里插入图片描述

语法定义:

object A{
 def apply(field:fieldType, ... ,field:fieldType): A = new A(field, ... , field)
}

// 调用
A(field, ... , field)

unapply

伴生对象中,还有一个unapply方法。与apply相反,unapply 是将该类的对象,拆解为一个个的元素

在这里插入图片描述

例如:

class Ba(name:String,age:Int) {
  var username = name
  var userage = age
}
object Ba{
  // 构造	属性 =》 对象
  def apply(name: String, age: Int): Ba = new Ba(name, age)
  
  // 解构	对象=》 Option
  def unapply(arg: Ba): Option[(String, Int)] = {
    if (null ==arg){
      None
    }else{
      Some(arg.username,arg.userage)
    }
  }

  def main(args: Array[String]): Unit = {
    var b = Ba("ZS",20)
    
    // 样例类的模式匹配
    b match {
      case Ba(name,age) => println(name,age)
      case _=> println("未匹配到")
    }
  }
}

scala 的 object 和 class 有什么差异?

  1. 在 scala 中没有静态方法和静态字段,所以在 scala 中可以用 object 来实现这些功能,直接用对象名调用的方法都是采用这种实现方式,例如Array.toString。对象的构造器在第一次使用的时候会被调用,如果一个对象从未被使用,那么他的构造器也不会被执行;对象本质上拥有类(scala中)的所有特性,除此之外,object 还可以一扩展类以及一个或者多个特质
  2. object 不能提供构造器参数
  3. 所有的 main 方法都必须在 object 中被调用,来提供程序的主入口
  4. 类和伴生对象可以相互访问他们的私有属性,但是他们必须在同一个源文件内。

抽象类

和 java 一样,scala 也可以定义抽象类,scala 和 java 抽象几乎一致。抽象类的特点如下:

  • 抽象类可包含未实现的方法,即抽象方法;可以包含未初始化的变量,即抽象属性
  • 抽象类无法实例化
  • 抽象类使用“abstract”关键字修饰
  • 子类重写父类抽象方法时,“override”关键字可选,子类重写父类非抽象方法,“override”关键字必写

例如:

abstract class Person{
  // 抽象属性
  val address:String

  def show(): Unit ={
    println(s"我的地址是${address}")
  }

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

class Student extends Person{
  // 重写抽象属性
  override val address: String = "江苏省南京市"

  // 重写抽象方法,override 可选
  def learn(): Unit = {
    println("好好学习")
  }

  override def show(): Unit = {
    println(s"我的地址是${address}")
  }
}

匿名内部类

一个类可以作为另一个类的成员,称为内部类。Java 内部类是外部类的成员,而 Scala 内部类是绑定到外部类的对象实例。

而内部类最常用的就是匿名内部类。 匿名内部类是没有名称的子类,直接用来创建实例对象。Spark 的源代码中有大量使用到匿名内部类。 scala中的匿名内部类使用与Java一致。

例如:

abstract class Person{
  def show():Unit
}
object PersonTest{
  def main(args: Array[String]): Unit = {
    // 匿名内部类的创建
    val p = new Person {
      override def show(): Unit = println("我是人")
    }
    p.show()
  }
}

样例类

样例类是一种特殊的类,它可以用来快速定义一个用于保存数据的类(类似于Java POJO类),在后续的学习并发编程和spark,flink这些框架也都会经常使用它。样例类常用于描述不可变的值对象(Value Object)

样例类定义

语法:case class 样例类名([val/var] 成员变量名1:类型1 , ... , [val/var] 成员变量名n:类型n )

例如:

//定义样例类
case class Teacher(name:String,age:Int)
object TeacherText{
  def main(args: Array[String]): Unit = {
    //创建样例类的实例,无需new关键字
    val tea = Teacher("Jason",19)
    //访问对象属性
    println(tea.name)
  }
}

样例类特点:

  • 样例类构造参数默认声明为“val”,自动实现类构造参数的getter
  • 样例类构造参数声明为“var”时,自动实现类构造参数的setter和getter
  • 样例类自动创建伴生对象
  • 样例类自动实现的其他方法:toString()、equals()、copy()、hashCode()和伴生对象中的apply()、unapply()

样例类方法

样例类自动实现的其他方法包括 toString()、equals()、copy()、hashCode()和伴生对象中的apply()、unapply()

apply 和 unapply 方法

apply 方法可以让我们快速地使用类名来创建对象。unapply 方法是将该类的对象,拆解为一个个的元素。之前已经说过,这里不再细说

toString 方法

toString返回样例类名称(成员变量1,成员变量2,成员变量3…),我们可以更方便查看样例类的成员。

case class Teacher(name:String,age:Int)
object TeacherText{
  def main(args: Array[String]): Unit = {
    val tea = Teacher("Jason",19)
    println(tea.toString)
    // 输出:Teacher(Jason,19)
  }
}

equals 方法

样例类自动实现了equals方法,可以直接使用==比较两个样例类是否相等,即所有的成员变量是否相等。

case class Teacher(name:String,age:Int)
object TeacherText{
  def main(args: Array[String]): Unit = {
    val tea1 = Teacher("Jason",19)
    val tea2 = Teacher("Jason",19)
    println(tea1.equals(tea2))
    // 输出:true
  }
}

hashCode 方法

样例类自动实现了hashCode方法,如果所有的成员变量的值相同,则 hash 值相同,只要有一个不一样,则hash值不一样。

case class Teacher(name:String,age:Int)
object TeacherText{
  def main(args: Array[String]): Unit = {
    val tea1 = Teacher("Jason",19)
    val tea2 = Teacher("Jason",19)
    println(tea1.hashCode())    // 输出:1835026438
    println(tea2.hashCode())    // 输出:1835026438
  }
}

copy方法

样例类实现了copy方法,可以快速创建一个相同的实例对象,可以使用带名参数指定给成员进行重新赋值。

case class Teacher(name:String,age:Int)
object TeacherText{
  def main(args: Array[String]): Unit = {
    val tea1 = Teacher("Jason",19)
    val tea2 = tea1.copy("hubert")
    println(tea2.toString)		// 输出: Teacher(hubert,19)
  }
}

样例类对象

使用case object 可以创建样例对象。样例对象是单例的,而且它没有构造器。

它主要用在两个地方:定义枚举和作为没有任何参数的消息传递。

object Weekday  extends Enumeration {
  //枚举值从0开始计数
  val Mon,Tue,Wed,Thu,Fri,Sat,Sun=Value
}

//枚举的使用
Weekday.Sun
Weekday.Sun.id //获取枚举值的计数值
Weekday.values.foreach(println)

样例类与枚举区别

  • 枚举更简单,代码更少
  • 样例类的字段比枚举的值更强大
  • 样例类可扩展

样例类与普通类的区别

  • 样例类通常用于描述不可变的数据,数据完全依赖构造参数
  • 样例类默认不可变,通过模式匹配可分解
  • 两个样例类“==”操作时,通过按值比较而不是按引用
  • 样例类操作更简单
  • 如果一个对象在内部执行有状态计算,或者表现出其他类型的复杂行为,那么它应该是一个普通类

特质

scala 中没有Java 中的接口(interface),替代的概念是——特质。特质可以作为接口使用,可以定义具体的属性和方法;常用于模板模式、动态混入 trait和调用链模式。

作为接口使用

trait 作为接口使用,与 java 的接口使用方法一样。

abstract class A {
  def eat():Unit
  println("A的构造器")
}

class B extends A{
  println("B的构造器")
  override def eat(): Unit = {
    println("B吃饭")
  }
}

trait C extends A{
  println("C的构造器")
  def sleep() = {
    println("C睡觉")
  }
  override def eat(): Unit = {
    println("C吃饭")
  }
}

trait E extends F{
  println("E的构造器")
}

trait F{
  println("F的构造器")
}

class D extends B with C with E{
  println("D的构造器")
}

object K {
  def main(args: Array[String]): Unit = {
    val d = new D()
    d.sleep()
    d.eat()

	 /**
      * 输出:
      * A的构造器
      * B的构造器
      * C的构造器
      * F的构造器
      * E的构造器
      * D的构造器
      * C睡觉
      * C吃饭
      */
  }
}

从上面可以看出,如果一个类实现了多个 trait ,那这些 trait 是如何构造的呢?

一个类继承另一个类、以及多个trait,当创建该类的实例时,它的构造顺序如下:

  1. 执行父类的构造器
  2. 从左到右依次执行trait的构造器
  3. 如果 trait 有父 trait ,先构造父 trait ,如果多个 trait 有同样的父 trait,则只初始化一次
  4. 执行子类构造器

定义具体的属性和方法

和类一样,trait 还可以定义具体的方法和属性,继承 trait 的子类自动拥有 trait 中定义的属性和方法。

例如:

trait Logger{
  // 具体属性
  val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
  // 抽象属性
  val types:String
  // 抽象方法
  def log(msg:String)
}

class ConsoleLogger extends Logger{
  override val types: String = "控制台打印"

  override def log(msg: String): Unit = println(s"${types}:${sdf.format(new Date())}:${msg}")
}

object LoggerTest{
  def main(args: Array[String]): Unit = {
    val logger = new ConsoleLogger
    logger.log("我爱scala")
  }
}

模板模式

在一个特质中,具体方法依赖于抽象方法,而抽象方法可以放到继承tarit的子类中实现,这种设计方法也称为模板模式。

例如:

trait Logger{
  def log(msg:String)

  //具体方法定义,调用抽象方法
  def info(msg:String) = log("提示:"+msg)
  def warn(msg:String) = log("警告:"+msg)
  def error(msg:String) = log("错误:"+msg)
}

class ConsoleLogger extends Logger{
  override def log(msg: String): Unit = println(msg)
}

object LoggerTest{
  def main(args: Array[String]): Unit = {
    val logger = new ConsoleLogger

    //创建类对象,调用不同级别的日志进行输出
    logger.info("我爱scala")
    logger.warn("val i =2.3")
    logger.error("val a:String = 1")
  }
}

java 中的log4j 就是这样实现的。

动态混入 trait

当某个特质被用于组合类时,被称为混入,一个类只能有一个父类但是可以有多个混入(分别使用关键字extends和with)。

scala 中可以将 trait 混入到对象中,就是将 trait 中定义的方法、字段添加到一个对象中。

例如:

class MyTrait {
  self:Eat=>
  def show() ={
    myeat()
  }
}

trait Eat{
  def myeat():Unit
}

trait Person extends Eat{
  override def myeat(): Unit = {
    println("人吃饭")
  }
}

trait Pig extends Eat{
  override def myeat(): Unit = {
    println("猪吃饭")
  }
}

object MyTrait{
  def main(args: Array[String]): Unit = {
    (new MyTrait() with Person).myeat()
  }
}

self:Type=> 自身类型,表示该类实例化时必须混入相应特质或子特质,self是this的别名。

此外,需要注意的是动态混入,不能和伴生共用。

trait 实现调用链模式

实现一个模拟支付过程的调用链如下:
在这里插入图片描述

// 定义 HandlerTrait
trait HandlerTrait{
  def handler(data:String)={
    println("处理支付数据...")
  }
}
// 定义数据校验
trait DataValidateTrait extends HandlerTrait{
  override def handler(data: String): Unit = {
    println("数据校验...")
    super.handler(data)
  }
}
// 定义签名校验
trait SignatureValidateTrait extends HandlerTrait{
  override def handler(data: String): Unit = {
    println("签名校验")
    super.handler(data)
  }
}
// 定义支付服务
class PayService extends DataValidateTrait with SignatureValidateTrait{
  override def handler(data: String): Unit = {
    println("准备支付")
    super.handler(data)
  }
}
//创建支付对象,调用支付方法
object PayText{
  def main(args: Array[String]): Unit = {
   val service = new PayService
    service.handler("支付数据")
  }
}

结果:

准备支付
签名校验
数据校验...
处理支付数据...

当类继承了多个 trait 后,可以依次调用多个 trait 中的同一个方法,只要让多个trait中的同一个方法在最后都依次执行super关键字即可。类中调用多个tait中都有这个方法时,首先会从最右边的trait方法开始执行,然后依次往左执行,形成一个调用链条。

scala 的Trait 和 class 的区别

特质是 scala 中代码复用的基础单元,它可以将方法和字段定义封装起来,然后添加到类中,与类继承不一样的是,类继承要求每个类都只能继承一个超类,而一个类可以添加任意数量的特质。

使用 extends 来继承 trait(scala 不论是类还是特质,都是使用 extends 关键字);如果要继承多个 trait,则使用 with 关键字。

scala 的Trait 和 java 的 interface 的区别

  1. 接口不会有构造器,特质可以有构造器,并且在实现类继承特质的时候,先要调用特质的构造器。
  2. Scala 的特质能够提供具体方法的实现,而 java 的接口只有方法的定义,这一点很像 java 的抽象类,1.8之后有 default 方法和静态方法
  3. 接口中不能有未初始化的属性,且属性的修饰都是public static final, 特质中可以有未初始化的属性变量,即抽象字段;未初始化的属性可以有提前定义的特性
  4. Scala 的特质能在对象生成时临时加入,java 则没有这个特质,scala 中为实例提供 混入 trait (只有某个对象有 trait 的性质,其他对象没有),Java 中没有
  5. 类实现 implements 多个接口,且用逗号“,”隔开,类继承 extends 多个 trait 且用 with 隔开。
  6. Java 中接口不能继承普通类,但是 scala 中特质可以extends class,并且该类成为所有继承 trait 的类的父类

特质与抽象类的选择

  • 优先使用特质:抽象类只能继承一次,特质可混入多个
  • 需要使用带参构造方法时,使用抽象类
  • 与Java互操作性:抽象类与Java完全可互操作,特质只有在不包含任何实现代码时才可互操作。

泛型类

scala 和Java 一样,类和特质、方法和参数类型都可以支持泛型。泛型类指可以接受类型参数的类,泛型类在集合类中被广泛使用,与Java不同,定义泛型类使用“[]”。

泛型种类

泛型参数

当不确定存放元素的类型,或者说确定存放元素的具体类型时,均可采用泛型去约束集合存放元素的类型,泛型参数常用于指定集合存放的元素类型泛型,例如:

scala> val list:List[String] = List("hubert","rose","jack")
list: List[String] = List(hubert, rose, jack)

泛型方法

当不确定一个方法的具体的传入形参类型,但确定出入形参类型在一个范围内(父类)时,常常通过泛型方法指定的不同类型来控制形参具体限制的类型

语法如下:

def 方法名[泛型名称]():{
	...
}

例如:

object Test {
  def getMiddleElement[T](array: Array[T])={
    array(array.length/2)
  }

  def main(args: Array[String]): Unit = {
    println(getMiddleElement(Array(1,2,3,4,5)))
    println(getMiddleElement(Array("1","2","3","4","5")))
  }
}

泛型类

scala的类也可以定义泛型。

语法:class 类名[泛型名称] (...){...}

例如:

class AAA[T]{
  println("kkk")
}

类型边界

在Scala中,我们在定义方法/类的泛型时,限定必须从哪个类继承、或者必须是哪个类的父类。就是说类型参数可以有一个类型边界约束,又称上下界。具体如下:

  • 类型上界:将类型限制为另一种类型的子类
    • T<:A 表示类型变量T应该是类型A的子类
    • A是具体类型,T是泛型
  • 类型下界:将类型声明为另一种类型的超类
    • T>:A 表示类型变量T应该是类型A的超类
    • A是具体类型,T是泛型
  • 如果类既有上界、又有下界。下界写在前面,上界写在后面

例如:

class MyTt {
  def abc[T<:Father](t:T)={
    val list:ListBuffer[String] = ListBuffer.empty[String]
    list.append()
  }

  def aaa[T>:Father](t:T)={
    val list:ListBuffer[String] = ListBuffer.empty[String]
    list.append()
  }
}
object A{
  def main(args: Array[String]): Unit = {
    new MyTt().abc(new Father())
    new MyTt().abc(new Son())

    new MyTt().aaa(new String())
  }
}

class Father  {
  println("father")
}

class Son extends Father{
  println("son")
}

结果:

father
father
son

型变

spark的源代码中大量使用到了协变、逆变、非变,学习该知识点对我们将来阅读spark源代码很有帮助。

协变

class Foo[+T] // 协变类对于两种类型 A 和 B,如果 A 是 B 的子类型,那么 Foo[A] 就是 Foo[B] 的子类型

逆变

class Bar[-T] // 逆变类对于两种类型 A 和 B,如果 A 是 B 的子类型,那么 Bar[B] 就是 Bar[A] 的子类型

不变

class Baz[T] // 不变类默认情况下,Scala中的泛型类是不变的

用一张图表示如下:
在这里插入图片描述
代码如下:

class Super
class Sub extends Super

class Temp1[T]
class Temp2[+T]
class Temp3[-T]

object Test {
  val a:Temp1[Sub] = new Temp1[Sub]
  
  // 编译报错 
  // 非变 val b:Temp1[Super] = new Temp1[Sub]
  
  // 协变
  val c:Temp2[Sub] = new Temp2[Sub]
  val d:Temp2[Super] = c
  
  // 逆变
  val e:Temp3[Super] = new Temp3[Super]
  val f:Temp3[Sub] = e
}

协变,相当于向上转型,逆变,相当于向下转型

包与包对象

Scala包:package 包名

  • 只能包含数字、字母、下划线、圆点
  • 不能用数字开头, 不能使用关键字
  • 可以在同一个.scala文件中,声明多个并列的package

例如:

package com.kgc{
	package scala1 {... }
	package scala2 {... }
}
package scala3 {... }

Scala包对象

包可以包含类、对象和特质,但不能包含变量或方法的定义,应使用包对象解决这个问题

例如:

package com.kgc{
package object scala {	//对应包com.kgc.scala,每个包都可以有一个包对象
    val name="Wow"
}...
package scala{...}//与包对象同名的包可直接使用包对象中定义的变量和方法
}

包引用

  • import让包和包对象的成员可以直接通过名称访问

    常用:

    //易于访问Fruit
    import cn.kgc.Fruit
    //易于访问cn.kgc的所有成员
    import cn.kgc._
    //易于访问cn.kgc.Fruits的所有成员
    import cn.kgc.Fruits._
    //只引用Apple与Orange,并且Apple重命名为McIntosh
    import cn.kgc.Fruits.{Apple=>McIntosh,Orange}
    
  • import灵活引用

  1. 可以出现在任何地方
  2. 可以是对象和包
  3. 可以重命名或隐藏一些被引用的成员
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值