欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Scala学习笔记:Scala编程进阶

程序员文章站 2022-05-01 10:12:37
...

目录

Scala编程进阶

1.Scala函数式编程

(1)Scala的函数

(2)Scala的匿名函数

(3)Scala的闭包和柯里化

(4)Scala高阶函数

2.Scala中的集合

(1)Map

(2)List

(3)序列

(4)Set

(5)Scala中的模式匹配

(6)样本类(CaseClass)

3.Scala高级特性

(1)泛型类

(2)泛型函数

(3)泛型的上界和下界

(4)视图界定(View bounds)

(5)协变和逆变

(6)隐式转换函数

(7)隐式参数

(8)隐式类


Scala编程进阶

1.Scala函数式编程

(1)Scala的函数

函数是Scala的头等公民
值函数:可以在变量中存放函数

Scala学习笔记:Scala编程进阶

(2)Scala的匿名函数

匿名函数就是没有名字的函数
举例:定义一个匿名函数,作用是对某个数本身乘以3
Scala学习笔记:Scala编程进阶
定义一个数组Array(1,2,3) ,然后使用Array的函数map,作用是通过对这个数组的所有元素应用一个函数来构建一个新集合Array(3,6,9)
Scala学习笔记:Scala编程进阶

(3)Scala的闭包和柯里化

闭包:就是函数的嵌套,即在定义一个函数的时候,包含了另外一个函数的定义,在内函数中,可以访问外函数的变量
       到目前为止,所有函数的例子仅参考了传入的参数,例如(x:Int) => x>0里,函数体x>0用到的唯一变量x被定义为函数参数。然而,当函数定义为(x:Int)=> x+more时,函数把“more”加入参考,但什么是more呢?从这个函数来看,more是个*变量,因为函数字面量没有给出其含义。相对的,变量x是一个绑定变量,因为它在函数的上下文中有明确意义,被定义为函数的唯一参数。如果尝试独立使用这个函数字面量,而范围内并没有任何more的定义,则编译器会报错。
       另一方面,只要有一个叫做more的某种东西,同样的函数字面量将工作正常。依靠这个函数字面量在运行时创建的函数值(对象)被称为闭包。名称源自于通过“捕获”*变量的绑定,从而对函数字面量执行的“关闭”行动。
Scala学习笔记:Scala编程进阶
上面的例子,创建了一个函数makeIncreaser,x是这个函数的绑定变量,而more是个*变量,more的值取决于外界传入的值,从而成为一个闭包。当定义inc1函数时,每次调用makeIncreaser都会创建一个新闭包,每个闭包都会访问闭包创建时活跃的more变量,在这个例子里,more=1,当more被定下来之后,inc1的值也可以被定下来了。
Scala学习笔记:Scala编程进阶
下划线表示List里的任一数字,因此sum成为了一个闭包,因为累加的值并不固定

柯里化(Currying)
使函数柯里化的意思是,把具有多个参数的函数转换成一条函数链,在每个节点上都是单一的函数参数
首先我们定义一个函数:def add(x:Int,y:Int) = x+y
等价于这种柯里化写法:def add(x:Int)(y:Int) = x+y
那么是怎么实现的呢
add(x)(y)实际上是依次调用两个普通函数,第一次调用使用一个参数x,返回一个函数类型的值,第二次使用参数y调用这个函数类型的值,实质上最先演变成这样一个方法:def add(x:Int) = (y:Int) => x+y,闭包right?y是add函数的绑定变量,x是*变量,当定义一个val add1=add(1)的时候,调用add(1),捕获值1作为x的绑定的闭包被创建并返回,因此add1(2)=3

(4)Scala高阶函数

高阶函数即以函数作为参数的函数
Scala学习笔记:Scala编程进阶

第一个是普通函数,而第二个是高阶函数,someAction函数的参数是函数f,f属于匿名函数,也就是说,f(x)函数的参数是Double,返回值是Double,而这里的x=10,someAction所要返回的值,就是f(10)
举一个具体的例子
Scala学习笔记:Scala编程进阶

可以看到定义了一个普通函数mytest,传入两个Int,返回值也是Int,作用是将两个数相乘再加上100
myfunc1是属于模块化编程,调用mytest来计算传入的参数a和b,返回值同样设为Int
myfunc属于高阶函数,定义了一个匿名函数f(Int,Int),f的返回值是Int,而a和b是myfunc的参数,myfunc的返回值类型是Int,返回值是f(a,b)。用一个比较直白的例子就是sqrt(sqr(x)),对x求了乘方然后再开方,对于函数sqrt而言,他的参数就是函数sqr(x)。高阶函数可以使我们开发出来的程序非常简洁。

Scala常用高阶函数

定义一个集合:val numbers = List(1,2,3,4,5,6,7,8,9,10)
(1)map:对集合中的每个元素执行一个函数运算,返回一个包含相同数目的新的集合
 举例:集合中所有乘以2
Scala学习笔记:Scala编程进阶              
(2)foreach: 类似map,对集合中的每个元素执行一个函数运算,跟map的区别是没有返回值
Scala学习笔记:Scala编程进阶
(3)filter: 过滤,相当于SQL中where
举例:选择能够被2整除的元素
Scala学习笔记:Scala编程进阶             
(4)zip: 把两个列表的元素合并到一个由元素对组成的新的列表中
Scala学习笔记:Scala编程进阶
(5)partition:分区,根据条件(断言)的返回值对列表进行分区
Scala学习笔记:Scala编程进阶
(6)find: 查找第一个满足条件的元素
Scala学习笔记:Scala编程进阶
(7) flatten:把一个嵌套的结构展开
Scala学习笔记:Scala编程进阶
(8) flatMap: 相当于flatten + map
Scala学习笔记:Scala编程进阶

2.Scala中的集合

集合分为可变集合和不可变集合
不可变集合必须要用val进行定义,否则里面的元素还是可以被修改的,当使用var进行对不可变集合的定义时,实际上是返回了一个新的集合,而老的集合并没有发生改变。
定义不可变集合需要导入scala.collection.immutable._,定义可变集合需要导入scala.collection.mutable._
对于immutable对象,它是线程安全的,在多线程下安全,没有竞态条件,而且由于不需要支持可变性, 可以尽量节省空间和时间的开销。所有的不可变集合实现都比可变集合更加有效的利用内存。

下面的图表显示scala.collection.immutable中的所有集合类。

Scala学习笔记:Scala编程进阶

下面的图表显示scala.collection.mutable中的所有集合类。 

Scala学习笔记:Scala编程进阶

根据图以及源码可以很清晰地看出scala中的集合类可以分为三大类: 
1.Seq,是一组有序的元素。 
2.Set,是一组没有重复元素的集合。 
3.Map,是一组k-v对。

(1)Map

Map在前面有提到过,就是<key,value>的键值对

package practice

object mydemo1 {
  def main(args: Array[String]): Unit = {
    
    //定义不可变集合(Map)
    val math = scala.collection.immutable.Map("Tom"->80,"Mary"->90)
    
    //可变集合
    val chinese = scala.collection.mutable.Map("Tom"->80,"Mary"->90)
    
    //集合的操作
    //1.获取集合中的元素
    println(chinese("Mary"))
    
    //2.获取一个不存在中的元素 
    //println(chinese("Jone"))
    //会抛出异常Exception in thread "main" java.util.NoSuchElementException: key not found: Jone
    //所以在获取元素前应该要做一个条件判断
    if (chinese.contains("Jone")){
      println(chinese("Jone"))
    }else{
      println(-1)
    }
    
    //简写
    chinese.getOrElse("Jone", -1)
    
    //3.更新集合中的元素 该集合必须是一个可变集合或者用var定义的集合
    chinese("Mary")=95
    println(chinese("Mary"))
    
    //4.添加新的元素
    chinese +="Jone"->88
    chinese.foreach(println)
    
    //5.移除元素
    chinese-="Mary"
    chinese.foreach(println)
  }
}

(2)List

List同样分immutable和mutable,具体的方法可以查阅API,这里只写了部分
List属于seq,所以是有序的,可重复的
immutable List的定义和一些基本操作

package practice

object mydemo02 {
  
  //不可变列表
  //定义一个字符串列表
  val nameList = List("Tom","Mary","Mike")
  
  //整数列表
  val intList = List(1,2,3,4,5)
  
  //定义空列表
  //那么List()呢?一个空的List,他是什么类型呢?肯定不是List[Int],因为里面并没有Int,也不是List[Any],因为里面啥都没有,没有理由推导出任何一个其他类型,于是这个List()只能是List[Nothing]。
  val nullList = List()
  //另一个写法就是泛型了
  val nullList1:List[Nothing] = List()
  
  //二维列表
  val dim:List[List[Int]] = List(List(1,2,3),List(4,5,6))
  
  //或者使用List.tabulate()方法来创建,,第一个参数是行和列的长度,第二个参数是创建List所使用的函数 
  val mul = List.tabulate(4, 5)(_*_)
  /**
   * 这里使用的_*_是一个语法糖,等价于下面的写法
   * val mul = List.tabulate(4, 5)((row: Int, col: Int) => row * col) 
   *  计算过程row->0-3 col ->0-4
   * 0x0 0x1 0x2 0x3 0x4
   * 1x0 1x1 1x2 1x3 1x4
  ...
  */
  
  def main(args: Array[String]): Unit = {
    
    //常用操作
    
    //输出第一个元素
    println(nameList.head)
    
    //输出除去第一个元素外的剩下的List
    println(nameList.tail)
    
    //判断List是否为空
    println(nameList.isEmpty)  
  }
}

mutable的定义和一些基本操作

package practice

object mydemo3 {
  
  def main(args: Array[String]): Unit = {
    //可变列表,注意在2.11.8版本下LinkedList已经被废弃了
  val myList = scala.collection.mutable.LinkedList(1,2,3,4,5)
  var list1 = List(1,2,3,4)
  var list2 = List(1,2,3,4)
  val listA: List[String] = List("A", "B", "C", "D");
  val listB= List("A", "B", "C", "D");
  
  //实现:将每个元素乘以2
  //定义一个指针
  var cur = myList
  while(cur !=Nil){
    cur.elem*= 2
    //指针移到下一个元素
    cur=cur.next
  }
  println(myList)
  
  //把一个新元素组合到list的最前面
  list1 =5 :: list1
  println(list1)
  
  //如果将两个list用中缀操作符连接,输出结果如下,因为是将list1这个整体看成一个元素,然后组合到list2的最前面了
  var list3=list1 :: list2
  println(list3)
  //List(List(5, 1, 2, 3, 4), 1, 2, 3, 4)
  
  //所以如果要连接两个list的元素,要用这个:::操作符,或者++
  var list4=list1 ::: list2
  println(list4)
  //List(5, 1, 2, 3, 4, 1, 2, 3, 4)
  }
}

(3)序列

序列分两种:Vector和Range

Vector:支持快速的查找和更新
Range:表示是一个整数序列,一段整数的范围
两者均属于seq,即有序也可重复

package practice

object demox {
  //定义Vector
  val v = Vector(1,2,3,4,5,6)
  
  //定义Range,表示[0,5),左闭右开区间
  var r1 = Range(0,5)
  
  //使用until来定义Range,左闭右开区间
  var r2 = (0 until 5)
  
  //使用to来定义Range,闭区间
  var r3 = (0 to 4)
  
  //指定步长的方式定义Range,Range里的是1-10里的单数
  var r4 = Range(1,10,2)
  
  def main(args: Array[String]): Unit = {
    
    //找出符合要求的第一个元素
    println(v.find(_>3)) //输出Some(4)
    
    //将第三个元素变成100,注意,这里用了val,而Vector又是不可变集合,所以直接输出v结果是不会变化的
    v.updated(2,100)
    println(v.updated(2,100))
    
    //输出r1,r2
    println(r1)
    println(r2)
    println(r3)
    println(r4)
    
    //Range相加:两个Range按顺序首尾相接
    println((0 to 9) ++ ('A' to 'Z'))
  }
  
}

(4)Set

Set是不重复元素的集合,本身是无序的,常用的有HashSet和TreeSet。

HashSet有以下特点:
使用元素的hashCode值来决定元素的存储位置
不能保证元素的排列顺序,顺序有可能发生变化 
不是Synchronized的,因此线程不安全
集合元素可以是null,但只能放入一个null

TreeSet有以下特点:
TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。 
TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。 

LinkedHashSet有以下特点:
LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。 
这样使得元素看起 来像是以插入顺 序保存的,也就是说,当遍历该集合时候, 
LinkedHashSet将会以元素的添加顺序访问集合的元素。 
LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。
 

package practice

import scala.collection.mutable.SortedSet
import scala.collection.mutable.TreeSet
import scala.collection.mutable.Set

object DemoSet {
  var s1 = Set(2,0,1)
  //指定为Int类型的空Set
  var sx:Set[Int] =Set()
  //用empty来定义一个空集合
  var st = Set.empty()
  
  
  def main(args: Array[String]): Unit = {
    println(s1)
    //添加一个重复的元素,Set不会有任何改版
    s1 + 1
    
    //添加一个不重复的元素
    s1 + 100
    
    //空集合赋值需要这么写
    sx = sx + 1

    //创建可以排序的set,SortedSet其实是一个接口,Treeset是其唯一的实现类
    var s2 = SortedSet(1,2,3,10,4,5)
    var s3 = TreeSet(2,1,3,10,4,5)
    
    var set1=Set(1,2,3,4,5,6)
    var set2=Set(5,6,7,8,9,10)
    
    //并集,当然集合操作得出的结果也是无序的
    println(set1 union set2)
    //交集
    println(set1 intersect set2)
    //差集
    println(set1 diff set2)
    //判断一个set是否为另一个set的子集
    println(set1.subsetOf(set2))
    
  }
}

(5)Scala中的模式匹配

给定一个子串,要求在某个字符串中找出与该子串相同的所有子串,这就是模式匹配。
Scala有一个强大的模式匹配机制,可以应用在switch语句,类型检查等
Scala还提供了样本类(case class),对模式匹配进行了优化
 

package practice

object demomp {
  
  def main(args: Array[String]): Unit = {
    //普通模式匹配,类似switch
    var sign = 0 
    var ch1 = '-'
    ch1 match{
      case '+' => sign = 1
      case '-' => sign = -1
      case _ => sign = 0
    }
    println(sign)
    
    //Scala的守卫:匹配某种类型的所有值
    var ch2='1'
    var digit:Int = -1
    ch2 match{
      case '+' => println("这是一个加号")
      case '-' => println("这是一个减号")
      //使用if守卫,如果ch2是数字字符,就给digit返回ch2数字字符的数值
      case _ if Character.isDigit(ch2) => digit = Character.digit(ch2, 10)
      case '1' | '2' =>println("1 or 2") 
      case _ => println("其他")
      
    }
    println("digit:"+digit);
    
    //模式匹配中使用变量
    var str3="Hello World"
    var ch='o'
    str3(7) match{   
      case '+' => println("这是一个加号")      
      case '-' => println("这是一个减号")
      case ch  => println("这个字符是:"+ch)
    }
    
    //类型匹配,其中变量x,s的值就是v4的值
    var v4:Any = 100
    v4 match{
      case x:Int => println("Int")
      case s:String => println("String")
      case _ => println("others")
    }
    
    //数组匹配
    var myArray = Array(1,2,3)
    myArray match{
      case Array() => println("这是一个空数组")
      case Array(0) => println("这个数组是Array(0)")
      case Array(x,y) => println("这个数组包含两个元素,和是"+(x+y))
      case Array(x,y,z) => println("这个数组包含三个元素,和是"+(x+y+z))
      case Array(x,_*) => println("这是一个数组")
    }
    
    //List匹配
    var myList = List(1,2,3)
    myList match{
      case List(0) => println("这个List是List(0)")
      case List(x,y) => println("这个List包含两个元素,和是"+(x+y))
      case List(x,y,z) => println("这个List包含三个元素,和是"+(x+y+z))
      case List(x,_*) => println("这是一个List")
    }
  }
}

(6)样本类(CaseClass)

简单的来说,Scala的样本类激素在普通的类定义前加case这个关键字,然后你可以对这些类来模式匹配
CaseClass带来的最大的好处是它们支持模式识别
 

package practice

object demoCaseClass {
  
  class Fruit
  
  class Apple(name:String) extends Fruit 
  class Banana(name:String) extends Fruit
  
  class Vehicle
  
  case class Car(name:String) extends Vehicle
  case class Bike(name:String) extends Vehicle
  
  def main(args: Array[String]): Unit = {
    var aApple:Fruit = new Apple("apple")
    var bBanana:Fruit = new Banana("banana")
    
    //使用isInstanceOf来判断某个对象是否是某一个类的对象
    println(aApple.isInstanceOf[Fruit])
    println(aApple.isInstanceOf[Apple])
    println(bBanana.isInstanceOf[Apple])
    
    var aCar:Vehicle = new Car("这是一辆汽车")
    aCar match{
      case Car(name) => println("这是一个汽车")
      case Bike(name) => println("这是一个自行车")
      case _ => println("不太确定")
    }
  } 
}

3.Scala高级特性

(1)泛型类

和Java相似,类和特质可以带类型参数,在Scala中,使用方括号来定义类型参数
这里定义了一个普通的类,但是content只能是Int类型,如果希望保存任意的类型,则需要使用泛型类
Scala学习笔记:Scala编程进阶
这里的T就是泛型类,可以代表任意类型。下划线表示content可以是任意值
Scala学习笔记:Scala编程进阶
测试泛型类,可以看到这里能传入Int跟String类型
Scala学习笔记:Scala编程进阶

(2)泛型函数

函数和方法也可以带类型参数。和泛型类一样,我们需要把类型参数放在方法名之后
用例子说明
创建一个函数,用以创建一个Int类型的数组
Scala学习笔记:Scala编程进阶
创建一个函数,用以创建一个String类型的数组
Scala学习笔记:Scala编程进阶
创建一个泛型函数,使其能创建任意类型的数组,在创建泛型函数的时候,我们需要导入import.reflect.ClassTag,ClassTag会帮我们存储T的信息,Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。泛型擦除是为了兼容jdk1.5之前的jvm,在这之前是不支持泛型的。
Scala学习笔记:Scala编程进阶
进行测试
Scala学习笔记:Scala编程进阶

(3)泛型的上界和下界

类型的上界(Upper Bounds)与下界(Lower Bounds),是用来定义类型变量的范围。
类型上界:S <:T,也就是S必须是类型T本身或它的子类
类型下界:S >:T,也就是S必须是类型T本身或它的父类
用一些例子说明
基本类型:Int x:100<=x<=200,这里x的下界就是100,上界就是200
类型变量:A ->B->C->D,依次是后面的父类,定义变量D<: y<: B,这个时候变量y的类型只能是B、C、D

一个复杂一点的例子(上界)
Scala学习笔记:Scala编程进阶

(4)视图界定(View bounds)

视界比上界使用的范围更广,除了所有的子类型,还允许隐式转换的类型,用<%表示。
尽量使用视界来取代泛型的上界,因为使用的范围更加广泛。

示例
定义一个函数,将两个字符串连接起来。
可以发现传入Int参数的时候,就会报错,因为类型不对,这里设定的上界是String
Scala学习笔记:Scala编程进阶
然后我们使用视界,在这个例子里,可以接收String和String的子类,以及可以转换为String类型的类型
但是实际运行的时候还是会报错
Scala学习笔记:Scala编程进阶
根据错误信息可以知道,没有定义隐式转换的规则
所以我们定义一下隐式转换规则,这里用的是Int类型的toString方法
定义好规则后,Scala会自动调用
Scala学习笔记:Scala编程进阶
再次运行,成功
Scala学习笔记:Scala编程进阶

(5)协变和逆变

Scala的类或特征的泛型定义中,如果在类型参数前面加入+符号,就可以使类或特征变成协变了
协变是指泛型变量的值可以是其本身的类或者其子类的类型
Scala学习笔记:Scala编程进阶

逆变就是协变反过来了,是指泛型变量的值可以是其本身或者其父类
在类或特征的定义中,在类型参数之前加上一个-符号,就可以定义逆变泛型类和特征了

(6)隐式转换函数

隐式转换函数指的是以implicit关键字声明的带有单个参数的函数
前面讲视界的时候,定义转换规则用的就是隐式转换函数

再举一个例子
将Fruit对象转换成Monkey对象
Scala学习笔记:Scala编程进阶

(7)隐式参数

使用implicit声明的函数参数叫作隐式参数,我们也可以使用隐式参数实现隐式的转换
Scala学习笔记:Scala编程进阶

(8)隐式类

隐式类就是对类增加implicit修饰符,其作用主要是对类的功能加强
Scala学习笔记:Scala编程进阶

隐式类执行的过程:
Step1:当执行1.add(2)时,scala的编译器不会马上报错,它会在当前域中查找有没有implicit修饰的,同时是将Int作为参数的构造器,并且具有add方法的类,通过查找,找到了Calc类
Step2:利用隐式类Calc来执行add方法

相关标签: Scala