Scala的面向对象与高级语法

Scala的面向对象与高级语法

一、面向对象

1、类和对象详解

(1)类组成结构

构造函数、成员变量、成员方法(函数)、局部变量、代码块、内部类

(2)构造器

  • scala有两类构造器:主构造器和辅助构造器
  • 主构造器跟在类名后面,如 class Student2(val name: String, var age: Int) 。类名后面不写括号或者括号里没参数,则是个空参主构造器
  • 辅助构造器是一个特殊的方法,定义在类中 def this(name:String,age:Int,gender:String)
  • 在辅助构造器中第一行必须调用其他构造器
  • 辅助构造器的参数不能和主构造器的参数完全一致(参数个数,参数类型,参数顺序)
  • 可以定义空参的辅助构造器,但是必须对主构造器的参数进行初始化赋值
  • 创建类的实例的时候可以调用任意一个构造器
  • 作用域:辅助构造器的作用域,只在方法中,主构造器的作用域是类中除了成员属性和 成员方法之外的所有范围(可以通过反编译查看源码)
  • 主构造器的参数没有使用var/val修饰 ,那么他的参数就是局部变量,只能在本类中使用
  • val修饰的构造函数的参数是类的成员属性 ,只读
  • var修饰的构造函数的参数是类的成员属性 ,但是是可读可写的

注意:主构造器会执行类定义中的所有语句。相当于整个类都在主构造器中

class User { // 类默认有一个无参的主构造函数}
val user = new User
class User3 {
   var name: String = _
   var age: Int = _
  // 辅助构造函数
  def this(name: String, age: Int) {
    // 构造函数中的首行必须调用主构造函数或者其他构造函数
    this()
    this.name = name
    this.age = age
  }
  // 辅助构造函数
  def this(msg: String) = {
    // 首行调用一个构造
    this("ww", 12)
    println(msg)
  }
}
val u1 = new User3()
val u2 = new User3("")
val u3 = new User3("lisi", 23)
println(u3.name)

(3)成员变量

  • 主构造函数中使用val 和 var修饰的变量为成员变量
  • 定义在类中的变量也是成员变量
  • val 修饰的变量默认只有getter方法 一要初始化
  • var 修饰的变量默认有 get和set方法 直接点属性操作 使用 _ 占位可以稍后赋值
  • 在成员变量上使用 @BeanProperty 会生成getMsg setMsg方法
// 1 主构造函数中使用val 和 var修饰的变量为成员变量
class Person(val address:String) {
  // 2 val 修饰的变量默认只有getter方法   一要初始化
  val name:String = "val修饰这里必须赋值"
  // 3 var 修饰的变量默认有 get和set方法  直接点属性操作  使用 _ 占位可以稍后赋值
  var age:Int= _  // 占位可以稍后赋值
  // 4 @BeanProperty会生成getMsg setMsg方法
  @BeanProperty  
  var msg:String =  _
}

(4)成员方法/函数

在类的成员位置定义的函数或者方法是类的成员的一部分

(5)局部变量

定义在函数/方法的参数中 , 定义在代码块中 , 定义在流程控制的条件表达式中的变量成为局部变量,只能在局部代码块中使用

(6)代码块

在类中的代码块,每次实例化(创建对象)时都会执行一次。
在伴生对象中的代码块,是静态代码块,只在类加载的时候执行一次,之后不再执行。

(7)伴生类和伴生对象

条件 1:在同一个源文件中, 条件 2:对象名和类名相同

class Demo{
  val id = 1
  private var name = "xiaoqing"
  def printName(): Unit ={
    //在 Dog 类中可以访问伴生对象 Dog 的私有属性
    println(Demo.CONSTANT + name )
  }
}
/**
  * 伴生对象
  */
object Demo{
  //伴生对象中的私有属性
  private val CONSTANT = "汪汪汪 : "
  def main(args: Array[String]) {
    val p = new Demo
    //访问私有的字段 name
    p.name = "123"
    p.printName()
  }
}

伴生对象和伴生类之间可以互相访问彼此的私有属性和私有方法

(8)apply方法

apply方法是一个特殊的方法 , 在调用的时候只需要传入参数即可调用,不用写方法名

class Demo1 {
//在Demo1的半生类中定义apply方法
  def apply() {
    println("这是类")
  }
}

object Demo1 {
//在Demo1的半生对象中定义apply方法
  def apply() {
    println("这是对象")
  }
}

object Demo2 {
  def main(args: Array[String]): Unit = {
    val demo = new Demo1()
    //调用半生类中的apply方法
    demo() //输出:这是类
    //调用半生对象中的apply方法
    Demo1() //输出:这是对象
  }
}

注意:

  • apply方法的主要目的是不使用new来获取实例对象, 并且默认是单例的!!!
  • apply方法建议编写在伴生对象中
  • apply方法如果没有参数也不要省略()

(9)classOf、isInstanceOf、asInstanceOf

  • classOf[T]看成Java里的T.class
  • obj.isInstanceOf[T]判断是否是T的实例对象
  • obj.asInstanceOf[T]强制类型转换

(10)、class、object、case class、case object区别

class 类似Java中的class;
object Scala不能定义静态成员,用定义单例对象代之;
case class被称为样例类,是一种特殊的类,常被用于模式匹配。

一、class 和 object 关系:

  • 1.单例对象不能带参数,类可以
  • 2.对象可以和类名一样时,object被称为伴生对象,class被称为伴生类;
  • 3.类和伴生对象可以相互访问其私有属性,但是它们必须在一个源文件当中;
  • 4.类只会被编译,不会被执行。要执行,必须在Object中。

二、case class 与 class 区别:

  • 1.case class初始化的时候可以不用new,也可以加上,但是普通类必须加new;
  • 2.case class默认实现了equals、hashCode方法;
  • 3.case class默认是可以序列化的,实现了Serializable;
  • 4.case class自动从scala.Product中继承一些函数;
  • 5.case class 构造函数参数是public的,我们可以直接访问;
  • 6.case class默认情况下不能修改属性值;
  • 7.case class最重要的功能,支持模式匹配,这也是定义case class的重要原因。

三、case class 和 case object 区别:

  • 1.类中有参和无参,当类有参数的时候,用case class ,当类没有参数的时候那么用case object。

四、当一个类被声名为case class的时候,scala会帮助我们做下面几件事情:

  • 1.构造器中的参数如果不被声明为var的话,它默认的话是val类型的,但一般不推荐将构造器中的参数声明为var
  • 2.自动创建伴生对象,同时在里面给我们实现子apply方法,使得我们在使用的时候可以不直接显示地new对象
  • 3.伴生对象中同样会帮我们实现unapply方法,从而可以将case class应用于模式匹配,关于unapply方法我们在后面的“提取器”那一节会重点讲解
  • 4.实现自己的toString、hashCode、copy、equals方法
  • 除此之此,case class与其它普通的scala类没有区别

2、权限修饰符

scala中的权限修饰符有public、protected、private,默认情况下使用的是public,任何范围都可以访问

protected 修饰的属性、方法在类及其伴生对象中有效,在子类及其子类的伴生对象中有效,其他地方无效。

private 修饰的属性或者方法的访问范围是本类和伴生对象中

scala中有更加严格的访问权限的控制

  • private [this] 作用域为当前类中,伴生对象无法访问
  • private [packageName] 指定包及其子包有效。包名的写法,直接写报名,不需要层级路径。注意:不能写和当前包同级别的包名,只能写当前包或当前包的父包、祖父包等

对于类、主构造器、辅助构造器、主构造器中的属性、类中的属性、类中的方法都通用

3、特质

(1)特质使用

Trait(特质)相当于 java 的接口。比接口功能更强大。

特质中可以定义属性和抽象方法和普通方法(有具体实现的方法)。 实现类需要实现所有抽象方法,会继承特质中的属性与普通方法,可直接调用。

Scala 的类只能够继承单一父类,但是可以实现(继承,混入)多个特质(Trait)

在scala中没有implements,使用 extends和 with 实现多个特质
with后面只能跟特质

注意 特质不能有主构造函数

//特质1
trait T1 {
  val name:String = "我是特质"
  def add(x:Int)
  def show(desc:String)={
    println(desc)
  }
}
// 特质2
trait  T2{
  def show()
}
// 实现多个特质  
class Demo extends  T1 with T2{
  // 实现接口中的抽象方法
  override def add(x:Int): Unit = {
  }
  // 实现抽象方法  override 关键字可以省略
  def show(): Unit = {}
}

(2)特质的动态混入

动态混入:在创建实例对象的时候再继承特质实现对应的方法。
使用 with 特质 来在创建对象时混入特质
可以混入多个特质 with 特质1 with 特质2 重写所有特质的抽象方法

class A {
}
class B{
  def  haha={
    // 动态混入 在使用的时候再实现具体的接口重写对应的抽象方法
    val a = new A with T1 with T2 {
      override def add(x: Int): Unit = ???
      override def show(): Unit = ???
    }
  }
}

4、抽象类

在 Scala 中, 使用 abstract 修饰的类称为抽象类。
在抽象类中可以定义属性、未实现的方法(抽象方法)和具体实现的方法。
抽象类里可以没有抽象方法,有抽象方法的类一定是抽象类
类只能继承一个抽象类,with后面只能跟特质

//abstract 修饰的类是一个抽象类
abstract class Animal {
  println("Animal's constructor ....")
  // 定义一个 name 属性
  val name: String = "animal"
  // 没有任何实现的方法
  def sleep()
  // 带有具体的实现的方法
  def eat(f: String): Unit = {
    println(s"$f")
  }}

5、样例类

使用case修饰的类就是样例类

  • 构造器中的参数默认是val修饰的
  • 样例类会自动创建伴生对象, 同时在里面实现apply和unapply方法 ,创建对象的时候不用new
  • 很好的支持匹配模式
  • 默认实现了自己的toString , hashCode , copy , equals方法
// 定义一个样例类
case class Person(name:String , var age:Int) {
}

case class 和 class 的一些区别:

  • case class 在初始化的时候,不用 new,而普通类初始化时必须要 new。
  • case class 重写了 toString 方法。 默认实现了 equals 和 hashCode
  • case class 实现了序列化接口 with Serializable
  • case class 支持模式匹配(最重要的特征),所有 case class 必须要有参数列表
  • 有参数用 case class,无参用 case object
  • case class,和 case object 可以当作消息进行传递

二、高级语法

1、高阶函数

  • 函数的参数是函数的函数或者方法
  • 函数的返回值是函数的函数或者方法
  • 函数的参数和返回值都是函数的函数或者方法

函数或者方法的参数是函数
map、filtter等方法就是把函数作为参数的方法

//将函数当作参数传进来
val c=(a : Int,b: Int,f:(Int,Int)=>Int) => {
  f(a,b)
}

//这样可以让用户定义处理逻辑
println(c(3, 4, (x: Int, y: Int) => x  + y ))
println(c(3, 4, (x: Int, y: Int) => x  * y ))

函数的返回值是函数

def urlBuilder(ssl: Boolean, domainName: String): (String, String) => String = {
  val schema = if (ssl) "https://" else "http://"
  (endpoint: String, query: String) => s"$schema$domainName/$endpoint?$query"
}

val domainName = "www.example.com"
//获得返回函数
def getURL = urlBuilder(ssl=true, domainName)
val endpoint = "users"
val query = "id=1"
val url = getURL(endpoint, query) // "https://www.example.com/users?id=1": String

2、偏函数

偏函数是一个特质 ,是专门用来处理一种类型数据用的

//  方式一   过滤器形式实现偏函数
val list = List(1, 2, 3, 4, "hello")
val res: List[Int] = list.filter(x => x.isInstanceOf[Int]).map(x => x.asInstanceOf[Int] + 1)
res.foreach(println)

// 方式二   匹配模式
val res2: List[Any] = list.map(x => x match {
  //int类型加1
  case x: Int => x + 1
  //其他不处理
  case _ =>
})
res2.filter(x => x.isInstanceOf[Int]).foreach(println)

// 方式三  使用偏函数  参数1 输入的数据类型 参数2 返回的数据类型
val pp = new PartialFunction[Any,Int] {
  // 返回true
  override def isDefinedAt(x: Any) = {
    x.isInstanceOf[Int]
  }
  // 执行下一个方法
  override def apply(v1: Any) = {
    v1.asInstanceOf[Int]+1
  }
}

 //  list.map(pp).foreach(println)
 list.collect(pp).foreach(println)
  • PartialFunction 是个特质(看源码)
  • 构建偏函数时,参数形式 [Any, Int]是泛型,第一个表示输入参数的类型,第二个表示返回数据的数据类型
  • 当使用偏函数时,会遍历集合的所有元素,编译器执行流程时先执行isDefinedAt()如果为true ,就会执行 apply, 构建一个新的Int 对象返回
  • 执行isDefinedAt() 为false 就过滤掉这个元素,即不构建新的Int对象.
  • map函数不支持偏函数,因为map底层的机制就是所有循环遍历,无法过滤处理原来集合的元素
  • collect函数支持偏函数

偏函数简写

//定义函数的形式  返回类型为 PartialFunction
def myPartialFunction: PartialFunction[Any, Int] = {
  case x: Int => x * x
}

//直接在collect()方法中用匹配模式
list.collect({case x:Int=>x*x}).foreach(println)

3、匹配模式

模式匹配,其实类似于Java中的swich case语法,即对一个值进行条件判断,然后针对不同的条件,进行不同的处理。

但是Scala的模式匹配的功能比Java的swich case语法的功能要强大地多,Java的swich case语法只能对值进行匹配。但是Scala的模式匹配除了可以对值进行匹配之外,还可以对类型进行匹配、对Array和List的元素情况进行匹配、对case class进行匹配、甚至对有值或没值(Option)进行匹配。

对于Spark来说,Scala的模式匹配功能也是极其重要的,在spark源码中大量地使用了模式匹配功能。

基本语法:

_  match {
	case _ 2 => TODO
	case _1 => TODO
	case _ => 
}

(1)值匹配

val str:String = "111"

str match {
  case "111" => println("111")
  case "222" => println("222")
}

(2)数据类型匹配

val  arr = Array(1,"a",1.40)
// 生成一个随机的整数数据  随机取出数组中的一个元素
val index = Random.nextInt(arr.length)
arr(index) match {
  case x:Int => println(s"这是一个Int类型的值$x")
  case x:String => println(s"这是一个String类型的值$x")
  case x:Double => println(s"这是一个Double类型的值$x")

  // 可以匹配集合类型和数据类型
  case x:Array[Int]=>println("这是一个数组")
  case x:List[String]=>println("List集合")
  case x:Map[String,Int]=>println("map集合")
  case x:MyUser=>println("MyUser")
  case _ =>println("不知道什么类型")
}

(3)内部数据结构解析

元组

val tap: ((String), (Int), (String)) = Tuple3("3",1,"2")
tap match {
  //匹配元组元素个数
  case Tuple3(_,_,_) => println(s"这是一个元组")
  //匹配元组元素个数并获取第一个值
  case (x,_,_) => println(s"这是一个元组+$x")
  //匹配第一个值为3且元素个数为3个的元组
  case ("3",x,_) => println(s"这是一个元组+$x")
  //匹配元组元素个数及元素类型
  case (x:String,y:Int,z:String) => println(s"这是一个元组+$x")
}

Array

val arr1: Array[Any] = Array[Any]("abc",12,3,4,5,6)
arr1 match {
  //匹配数组元素个数,并获取第一个值
  case Array(x,_,_,_,_,_) => print(s"$x")
  //匹配数组元素个数及元素类型
  case Array(x:String,_,_,_,_,_) => print(s"$x")
  //匹配以‘abc’为第一个元素的数组
  case Array("abc",_*) =>print("这个数组是以字符串abc开头 : "+arr.length)
  //以上都不符合时走这个
  case _ => println("hah")
}

List

x match {
  //匹配只有一个元素的List
  case head::Nil => println(s"只有一个元素的Lis")
  case List(_) => println(s"只有一个元素的Lis")
  case List(x) => println(s"只有一个元素的Lis")
  
  //匹配List元素个数
  case List(_,_,x,_,_,_) =>println(s"有5个元素的list  第三个位置是 $x")
  case head::second::thr::Nil =>println(s"3个元素的list集合  第一个元素是 $head")

  //匹配List元素类型
  case List(x:Int,_*) => println(s"以$x 开头的List")
  
  //匹配List指定元素
  case List(1,_*) => println(s"以1开头的List")
  
  //这种方式可以匹配到所有List  head为第一个元素,tail为第二个元素,a为剩下所有元素
  case head::tail::a => println(s"$a")
}

样例类

val user =User(1,"zss",13)

user match {
  case User(_,_,_) =>
  case User(1,_,_) =>
  case User(x:Int,y:String,z:Int) =>
}

Option

val map = Map("yangzi"->27,"reba"->30 ,"yangmi"->41)
val res: Int = map.get("fengjie") match {
  case Some(v) => v
  case None => -1
}

Map集合和自定义的类不支持模式匹配结构解析,是因为结构解析本质上是调原先类的 unapply 方法实现的。样例类默认实现 unapply 方法,支持结构解析。

4、闭包

闭包是一个函数,返回值依赖于声明在函数外部的一个或多个变量。

闭包通常来讲可以简单的认为是可以访问一个函数里面局部变量的另外一个函数。

var factor = 3  
val multiplier = (i:Int) => i * factor  

在 multiplier 中有两个变量:i 和 factor。其中的一个 i 是函数的形式参数,在multiplier 函数被调用时,i 被赋予一个新的值。然而,factor不是形式参数,而是*变量,这个变量定义在函数外面。

这样定义的函数变量 multiplier 成为一个"闭包",因为它引用到函数外面定义的变量,定义这个函数的过程是将这个*变量捕获而构成一个封闭的函数。

5、柯里化详解

有多个参数列表的函数就是柯里化函数,所谓的参数列表就是使用小括号括起来的函数参数列表

curry化最大的意义在于把多个参数的function等价转化成多个单参数function的级联,这样所有的函数就都统一了,方便做lambda演算。 在scala里,curry化对类型推演也有帮助,scala的类型推演是局部的,在同一个参数列表中后面的参数不能借助前面的参数类型进行推演,curry化以后,放在两个参数列表里,后面一个参数列表里的参数可以借助前面一个参数列表里的参数类型进行推演。这就是为什么 foldLeft这种函数的定义都是curry的形式

柯里化函数优点:

  • 方便类型推导和数据演变
//传入一个列表,找这个列表中是否有符合条件的值
def findElement[T](ls: List[T], f: T => Boolean): Option[T] = {
  ls match {
    case Nil => None
    case head :: tail => if (f(head)) Some(head) else findElement(tail, f)
  }
}
//柯里化的形式
def findElement2[T](ls: List[T])(f: T => Boolean): Option[T] = {
  ls match {
    case Nil => None
    case head :: tail => if (f(head)) Some(head) else findElement2(tail)(f)
  }
}
//普通方法,无法根据前面的List[Int]的内容推算出自己的类型是Int  e后面必须指定类型
val opt: Option[Int] = findElement(List[Int](1, 2, 3, 4, 5, 6), (e: Int) => e > 2)
//柯里化方法,会根据前面的List[Int]的内容推算出自己的类型是Int  e无需指定类型
val opt2: Option[Int] = findElement2(List[Int](1, 2, 3, 4, 5, 6))((e) => e > 2)

opt2 match {
  case None => println("没有值")
  case Some(value) => println(s"找到的元素是: $value")
}
  • 减少方法的参数传递
def  mkSal(deptno:Int , sal:Int)(comm:Int):Int={
  deptno+sal+comm
}

//前面括号参数不变时,可以不再传前面参数
val f: Int => Int = mkSal(1, 2)
println(f(3))
println(f(4))
  • 将数据和处理逻辑分离
def add(x:Int,y:Int)(f:(Int,Int)=>Int):Int={
  f(x,y)
}

val f: ((Int, Int) => Int) => Int = add(1, 2)
val i1: Int = f(_ + _)
val i2: Int = f(_ - _)

5、隐式详解

使用implicit 修饰的内容是隐式内容, 隐式的特点就是遇到适应的类型会自动的应用

意义:

  • 使得静态类型动态化
  • 为现有类库添加功能
  • 隐式的代理增强一个类或者一个方法

所有的隐式值和隐式方法都必须放到object中。

(1)隐式变量

如果作用域中找到两个匹配的隐式变量,会报错,所以作用域中不能有相同类型的隐式变量

一个方法如果有多个参数,要实现部分参数的隐式转换,必须使用柯里化这种方式,隐式关键字出现在后面,只能出现一次

//隐式变量  使用 implicit修饰的变量为隐式变量
implicit val str1 = "java基础老师"
implicit val age= 32

def show(name:String)(implicit msg:String,age:Int): Unit ={
  println(s"$name 是$msg,今年$age 岁了")
}
//在方法调用的时候  隐式参数可以不传递, 自动传:前提是从上下文中 可以找到一个对应的隐式变量
show("zss")

(2)隐式转换

当Scala编译器进行类型匹配时,如果找不到合适的候选,那么隐式转化提供了另外一种途径来告诉编译器如何将当前的类型转换成预期类型。

其中最核心的就是自己定义隐式转换方法(implicit conversion function),Scala会根据隐式转换方法的签名,在程序中使用到隐式转换方法接收的参数类型定义的对象时,会自动将其传入隐式转换方法,转换为另外一种类型的对象并返回。

什么时候要进行隐式转换?

  • 当对象调用类中不存在的方法或成员时,编译器会自动将对象进行隐式转换
  • 当方法中的参数类型与目标类型不一致时

隐式转换的限制:

  • implicit关键字只能用来修饰方法、变量(参数)
  • 隐式转换的方法在当前范围内才有效,如果隐式方法不在当前范围内定义(比如定义在了另一个类中或者包含在某个对象中),就必须通过import语句导入

(3)隐式函数

隐式函数只与函数的参数类型和返回类型有关,与函数名称无关,所以作用域内不能有相同的参数类型和返回类型的不同名称隐式函数。

// 隐式函数  当使用 File的时候 发现上下文中有一个 File => BufferedSource 方法  隐式转换
// 对类进行功能项的增强和扩展
implicit  def richFile(file:File): BufferedSource ={
   val bs: BufferedSource = Source.fromFile(file)
   bs
 }
 
val file = new File("d://user.txt")
//隐式转换  file可以直接调用 BufferedSource 的方法 getLines
val lines: Iterator[String] = file.getLines()

方法的参数列表中有个隐式函数

调用方法时,可以不传函数,编译器自动调用已经定义好的隐式函数,也可以传入自定义函数

//隐式函数
implicit def add(x:String,y:Int): String ={
  x+y
}

//正常函数的参数用隐式参数或隐式函数
def m1 ( x:Int, y:String)(implicit f:(String,Int)=>String):String={
    f(y,x)
}
println(m1(10, "nihao"))//输出:nihao10

(4)隐式类

类似于装饰者模式,包装一个类,使其功能增强

隐式类约束

  • 隐式类必须有一个带一个参数的主构造函数
  • 必须定义在另一个class/object/trait里面(不能独立定义)
  • 隐式类构造器只能带一个不是implicit修饰的参数
  • 作用域中不能有与隐式类类型相同的成员变量,函数以及object名称
object ImplicitContext{
//隐式类只能对一种类型的类进行增强  B类或者子类增强
  implicit class RichB(b:B){
    def multiply(x: Int, y: Int) = {
      x * y
    }
  }
}

class B {
  val add = (x: Int, y: Int) => {
    x + y
  }
}

def main(args: Array[String]): Unit = {
  val b = new B   // B类  以及他的子类
  //调用自己的方法
  println(b.add(12, 12))
  //引入隐式类
  import ImplicitContext._
  //调用隐式类的方法
  println(b.multiply(12, 12))
}

(5)使用隐式转换实现自定义类的排序

定义隐式的比较器 Ordering

implicit  val  ording: Ordering[User] = new Ordering[User] {
  override def compare(x: User, y: User): Int = {
    y.age - x.age
  }
}

val users  = List(new User(1, "zss", 13), new User(2, "lss", 23))
//调用sorted方法时,比较器会被当作参数隐式传入
println(users.sorted)

定义隐式方法将User转为 Ordered
注:这种方法无法在compare方法中取到属性值

implicit def UserSored[User](user:User): Ordered[User] = {
  new Ordered[User] {
    override def compare(that: User): Int = {
      -(that.hashCode() - user.hashCode())
    }
  }
}

val users  = List(new User(1, "zss", 13), new User(2, "lss", 23))
//调用sorted方法时,User会被隐式转换为 Ordered 
println(users.sorted)

使用泛型定义隐式函数

implicit  def t2Com[T](t:T):Ordered[T]={
      new Ordered[T] {
        override def compare(that: T): Int = that.hashCode() - t.hashCode()
      }
    }

//在方法中使用隐式函数
def max[T](x:T,y:T)(implicit order:T=>Ordered[T]): T ={
      if(x > y) x else y
    }
//也可不写隐式参数,会自动隐式转换
def max[T](x:T,y:T) ={
      if(x > y) x else y
    }

6、泛型详解

Scala的泛型类似于java中的泛型 ,泛型可以定义在类上 ,方法上和属性上 ,用于约束类型 !
如果我们要求函数的参数可以接受任意类型。也可以使用泛型,这个类型可以代表任意的数据类型。

(1)泛型类

//将泛型定义在类上  
class GenericClass[K,V](k:K,v:V) {
  def show(): Unit ={
    println(k+"----->"+v)
  }
}

object  Test1{
  def main(args: Array[String]): Unit = {
    // 不需要指定类型 自动推断 
    val gc = new GenericClass("Jack",999.99)
    // 指定类型
    val gc1 = new GenericClass[String , String]("jim", "cat")
    // 指定类型 , 如果传入的数据怒符合类型会报错
    val gc2 = new GenericClass[String , Int]("jim", "cat")
    gc.show()
  }
}

(2)泛型方法

方法中若要用到泛型,必须先在方法名后面定义泛型

// 求两个数字的最大值 Int  Double  Float
// 定义泛型方法  接收任意的数据类型
def max[T](x:T,y:T): T ={
  if(x > y) x else y
}
// 使用隐式函数将 T 转换成一个可以比较的  name T类型就可以用 > 比较
implicit  def t2Com[T](t:T):Ordered[T]={
 new Ordered[T] {
   override def compare(that: T): Int = t.hashCode() - that.hashCode()
 }
 }
// 接收一个任意类型集合 List  取所有偶数索引位置的数据
def getElements[E](ls:List[E]): List[E] ={
  val es = for (elem <- 0 to ls.length - 1 if elem % 2 == 0) yield ls(elem)
  es.toList
}

val res1: List[Int] = getElements[Int](List[Int](1, 3, 5, 7, 9, 2, 4, 6, 8))
val res2: List[String] = getElements[String](List[String]("tom","jim","cat","rose"))

(3)泛型上下界

java中的上下界:

  • <T extends A> A类型为上界
  • <T super A> A类型为下界

Scala中的上下界

  • 上界 [T <: A] A类型为上界,传入的类型必须是A类型及其子类
  • 下界 [T >: A] A类型为下界,传入的类型必须是A类型及其父类

定义泛型的上界为 Ordered 类,则可以对泛型对象进行比较操作

// B为上界上界 参数必须是指定类型及其子类型
def test1[T <:B](t:T): Unit ={

}
// B为下界 参数是指定类型及其父类型
def test2[T >:B](t:T){
}

class A extends B
class B {
}
class C  {
}

test1(new A)
test1(new B)
test1(new C)  //报错  C不是B的子类型

//指定下界后,以下这种写法并不会报错,因为[]不写默认是Any,Any是所有类的父类 
test2(new A)
//写成这样会报错
test2[A](new A)

(4)上下文界定

在隐式参数详解的时候 ,需要一个隐式参数来将泛型中的类型隐式转换成对应的类型

def myMax(x1:T,x2:T)(implicit o:T=>Ordered[T])={
   if(x1 > x2) x1 else x2
 }

7、IO

(一)输入

获取输入文件源:Source.fromFile("")

(1)输入行

object LineIO {
  def main(args: Array[String]): Unit = {
   // 读取文件
    val bs: BufferedSource = Source.fromFile("d://data.txt")
    // 获取所有的行
    val lines: Iterator[String] = bs.getLines()
    // 遍历所有的行
    for (line <- lines) {
      println(line)
    }
    // 行列表
    val list: List[String] = lines.toList
    //行数组
    val array: Array[String] = lines.toArray
    // 整个字符串
    val content: String = lines.mkString
    // 释放资源
    bs.close()
  }
}

(2)输入字节

object ByteIo {
  def main(args: Array[String]): Unit = {
    val bs: BufferedSource = Source.fromFile("d://data.txt")
    // 获取输入流对象
    val reader: InputStreamReader = bs.reader()
    //跳过指定长度  到指定位置
    reader.skip(1)
    // 读取一个字节 
    val byte: Int = reader.read()
    println(byte) // 99
    reader.close()
    bs.close()
  }
}
def main(args:Array[String]):Unit={
  val file = new File("F:\\info.bin")
  val in = new FileInputStream(file)
  val bytes = new Array[Byte](file.length.toInt)
  in.read(bytes)
  in.close
}

(3)读取其他数据源

//从URL读取
val source= Source.fromURL("http://www.baidu.com","UTF-8")
val lineIterator =source.getLines
for(l<-lineIterator){
  println(l.toString())
}
//从给定的字符串读取--调试有用
val source2= Source.fromString("Hello DOIT")
println(source2.mkString)//Hello DOIT
//从标准输入读取
val in: BufferedReader = Console.in
println(in.readLine())

(二)输出

//为True时是追加
val out = new FileWriter("D:\\index.txt",true)
for (i <- 0 to 15){
out.write(i.toString)
out.append"111")
out.close()
val writer = new PrintWriter("D:\\index.txt")
for(i <- 1 to 10)
writer.println(i)// 这里是自动换行 不用再加\n 切记切记

writer.write("111")
writer.append("111")
writer.close()
上一篇:scala语言


下一篇:scala学习2-集合 计算高级函数