《scala 编程(第3版)》学习笔记4

第14章 断言和测试

  1. Predef的assert(condition)assert(condition,explanation),condition如果不满足,则抛出AssertionError。explanation类型为Any,可以传入任何对象,将调用对象的toString打印输出。P258
scala> assert(false,"test error")
java.lang.AssertionError: assertion failed: test error
  at scala.Predef$.assert(Predef.scala:170)
  ... 32 elided
  1. Predef的ensuring(condition),这里的condition为结果类型参数并返回Boolean的前提条件函数(即condition为函数)。使用方法为{代码块}ensuring(condition(x)),ensuring{代码块}的结果x传递给condition(x)函数,如果condition(x)为true,则ensuring返回结果x,否则抛出AssertionError。P259
def func(a:Int):String={
 a.toString
}ensuring(_.length>2,"msg: 位数小于3")

scala> func(2)
java.lang.AssertionError: assertion failed: msg: 位数小于3
  at scala.Predef$Ensuring$.ensuring$extension3(Predef.scala:261)
  at .func(<console>:14)
  ... 32 elided
  1. ensuring一般用于内部测试,断言(assert, ensuring)可通过JVM -ea -da来分别打开或关闭。P260
  2. 外部测试,ScalaTest框架(官方指导),FunSuite风格。
  3. scalatest涉及三方jar包导入,idea的三方jar包导入方式(单个批量),导入后即可正常 org.scalatest.FunSuite(3.2.0版开始已经没有Funsuit了,而是用funsuite.AnyFunSuite(官方指导))。当然,也可以使用构建maven工程+pom.xml方式导入。(todo:有时间单独出一期直接导入和pom导入的教程)
    <dependency>
      <groupId>org.scalatest</groupId>
      <artifactId>scalatest_2.11</artifactId>
      <version>3.2.0</version>
      <scope>test</scope>
    </dependency>
import org.scalatest.funsuite

class MyTest extends funsuite.AnyFunSuite {
  val s = "a b c d"
  //assert 一般断言
  test("长度大于4"){
    assert(s.length > 4)
  }

  test("长度大于8"){
    assert(s.length >8)
  }

  //assertResult 将预期与实际值区分开来;
  val a = 5
  val b = 2
  assertResult(2) {
    a - b
  }

  //assertThrows 以确保一些代码抛出预期的异常。捕获正确异常,则返回succeeded的Assertion,否则,返回TestFailedException
  val s2 = "hi"
  assertThrows[IndexOutOfBoundsException] { // Result type: Assertion
    s2.charAt(-1)
  }

  //intercept和assertThrows 表现一样,但若捕获正常异常的话,则返回IndexOutOfBoundsException。
  val caught =
    intercept[IndexOutOfBoundsException] { // Result type: IndexOutOfBoundsException
      s2.charAt(-1)
    }
  assert(caught.getMessage.indexOf("-1") != -1)

  //带clue版本
  assert(1 + 1 === 3, "this is a clue")
  assertResult(3, "this is a clue") { 1 + 1 }
  withClue("this is a clue") {
    assertThrows[IndexOutOfBoundsException] {
      "hi".charAt(-1)
    }
  }
}

第15章 样例类和模式匹配

  1. 样例类(case classes)和模式匹配(pattern matching)为我们编写规则的、未封装的数据结构提供支持。对于表达树形的递归数据尤为有用。P271
  2. 样例类的定义和普通类一样,只是在class前面增加了case:
// 类定义
case class 类名[(参数列表)]{//注意,区别于函数,没有=
   //类的定义
}

case class  A(name:String)

val a=A("test") //实例化
  1. 样例类case的使用,会添加一些语法上的便利:
    • 会添加一个跟类同名的工厂方法。这样,创建实例的时候,就可以直接用 类名(参数),而不需要 new 类名(参数)P272
      • 实际上是自动创建了伴生对象,并添加了apply,用于不用new创建对象
      • 伴生对象中添加了unapply方法,用于样例类的模式匹配
    • 类参数都隐式获得了一个val前缀,直接变成了字段。也可指明为var型。可直接引用 a.name。P273
    • 编译器会帮我们以“自然"的方式实现toString、hashCode和equals方法(比较包含类及入参的整棵树)。可简单的认为,对于引用类型,==即equals,所以==也被很好的实现了。P273
    • 编译器会添加一个copy方法用于可修改部分内容的拷贝(通过a.cpoy(arg1="新值")实现),如果不修改任何东西,则用老对象中的原值。P273
    • 最大的好处是他们支持模式匹配。
  2. 模式匹配P274
选择器 match { 可选分支 }

//可选分支
case 模式 => 表达式
  1. 模式匹配中的模式:P275-286
    • 常量模式,例如"str1",123,按照==要求跟他们相等的值
    • 变量模式,例如 case e => 表达式中可利用e中的e,变量e可匹配任何值,主要是为了利用它(todo: 在使用时e的类型怎么保证?)
      • 任何字面量都可以当做常量(模式)使用
      • 任何val和单例对象也可当做常量(模式)使用
      • 例如Nil这个单例对象,能且仅能匹配空list
    • 通配模式,即_,匹配任何值,但不利用这些值。
    • 构造方法模式,例如 类名(参数),这样参数也可以是模式,从而减少繁琐的判断。例case ClassA("str1",123, e)=>对e做操作。这样要求选择器是ClassA类型,而且第1个参数是"str1",第二个参数时123。
    • 序列模式,如List或Array
    • 元组模式
    • 带类型的模式-可用于代替冗长的类型测试和类型转换。
      • 类型擦除:类型参数信息不会被scala运行时保留,所以Map[Int,Int]和Map[,]是一样的,匹配不了里面的类型信息P285
      • 类型擦除特例:Array是能保留的,因为scala对Array做了特殊处理P285
    • 通配模式保底是必要的(当然也可用变量模式保底,只不过是给任何值取了个变量名),因为如果有没有匹配上的模式,会报MatchError错误。P278
exp match{
case "str1" | 123 => println("...") //常量模式
case e => println("...") //变量模式
case ClassA("str1",123, e)=> println(e) //构造方法模式
case List(0,_,_) | List(1,_*) => ... //序列模式
case (a,b,c) => ... //元组模式
case s:String | m:Map[_,_] | a:Array[String]=> ... //带类型的模式
case _ =>  //通配模式
}
  1. 常量模式和变量模式的冲突P279
    • 为了防止冲突,scala会把以小写字母开头的名称当做模式变量处理(不取值,做新命名),其他的都是模式常量(可以理解成会先取值,再匹配)
    • 小写字母开头的名称怎么当做模式常量处理呢?
      • 加限定词 如 obj.pithis.pi
      • 用反引号,如`pi`。
  2. 变量绑定:除变量模式外,其他所有模式都可在匹配成功后绑定给一个变量。格式 e @ 模式
  3. scala要求模式都是线性的:同一个模式变量在模式中只能出现一次。P287
  4. 模式守卫:if 变量相关条件表达式,除了模式要匹配,同时还要满足if后面的表达式结果为true,才能匹配成功。P287
  5. 密封类。没有缺省行为,如何保证自己的枚举覆盖了所有场景?使用密封类。P289
    • 密封类除了同一个文件里面定义的子类(用样例类case class实现)之外,不能添加新的子类
    • 密封类的样例类做匹配时,如果遗漏了,编译器会给出警告
    • 如果类打算被用于模式匹配,则推荐使用密封类
    • 可以用缺省行为跳过部分样例类的检查,但是不是推荐方案,推荐方案是用@unchecked注解。编译器对后续模式分支的覆盖完整性检查就会跳过。
sealed abstract class Expr
case class Var(name:String) extends Expr
case class Number(num:String) extends Expr

def func1(e:Expr):String =(e:@unchecked) match{
	case Var(_)=>"a number"
}//不会警告
  1. Option类型。有两种形式 Some(x)和None。用于防止处理无值时的报错情况。scala中的null是Null类型,是引用类AnyRef的子类,不是值类的子类。值类无值的时候(可用Unit的()表示),就不好统一处理。Option的None提供了一种统一处理方案。P292
//Option类的常用提取方法
def getValue(x:Option[String])=x match{
	case Some(s) => s
	case None => "?"
}

  1. 一切皆模式。并不仅限于match表达式。
    • 变量定义
    • case序列
      • {case ...;case ...}这样的语句叫做case序列。可以用在任何允许出现函数字面量的地方,即当做函数使用。
      • case序列本质上就是一个函数字面量。
      • case序列的每个case都是一个入口,每个入口都有自己的参数列表。
      • case序列是一个偏函数(partial function),偏函数是指在某些值没有定义的函数,参数为这些值时会报错。简单理解偏函数和部分应用函数。偏函数定义为PartialFunction,则会自动添加是否有定义的函数isDefinedAt,使用前先判断是否有定义。
    • for表达式
      • 不能匹配给定模式的值会被直接丢弃(相当于省略了普通for语句的if子句)P297
// 变量定义中
val (numb,str)=(123,"abc")
val BinOp(op,left,right)=new BinOp("*",Number(5),Number(1)) //样例类解开

//作为偏函数的case序列
val signal: Int=> Int = {
	//case 0 => 0
	case x if x > 0 => x - 1
	case x if x < 0 => x + 1
}
signal: Int => Int = <function1>
> signal(1)
res0: Int = 0
> signal(0)
scala.MatchError: 0 (of class java.lang.Integer)
  at $anonfun$1.apply$mcII$sp(<console>:13)
  ... 32 elided

// 定义为PartialFunction,则会自动添加是否有定义的函数isDefinedAt
val signal: PartialFunction[Int, Int] = {
    //case 0 => 0
    case x if x > 0 => x - 1
    case x if x < 0 => x + 1
}
signal: PartialFunction[Int,Int] = <function1>

scala> signal(0)
scala.MatchError: 0 (of class java.lang.Integer)
  at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:253)
  at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:251)
  at $anonfun$1.applyOrElse(<console>:11)
  at $anonfun$1.applyOrElse(<console>:11)
  at scala.runtime.AbstractPartialFunction$mcII$sp.apply$mcII$sp(AbstractPartialFunction.scala:36)
  ... 32 elided

scala> signal.isDefinedAt(0) //使用前先判断
res3: Boolean = false

// for 表达式
 val a=List(Some("a"),None,Some("b"))
 > for( Some(x)<- a) println(x) //None被自动跳过
a
b

第26章 提取器

本章相当于15章 的进阶版本,所以放在这里先讲。

  1. 提取器是拥有名为unapply成员方法的对象,即提取器是对象Object。P591
    • 通常还会定义一个apply方法对应unapply的逆过程,非必选。
    • unapply方法返回Option类型或Boolean类型(博主目前看到这两种,或许还有其他类型)。
    • 该对象可以是伴生对象,那样的话,如果编写了apply方法,则不需要用new来创建对象了。
    • 通过样例类(case classs)的学习,可以知道样例类实际上就是已经实现了apply和unaply伴生类
      在这里插入图片描述
      在这里插入图片描述
//实例1
class Student10 {
  var name:String = _   // 姓名
  var age:Int = _       // 年龄
  
  // 实现一个辅助构造器
  def this(name:String, age:Int) = {
    this()
    
    this.name = name
    this.age = age
  }
}

object Student10 {
  def apply(name:String, age:Int): Student10 = new Student10(name, age)

  // 实现一个解构器
  def unapply(arg: Student10): Option[(String, Int)] = Some((arg.name, arg.age))
}

object App2 extends App {
  val zhangsan = Student10("张三", 20) //因为apply方法的存在,不需要用new

  zhangsan match {
    case Student10(name, age) => println(s"姓名:$name 年龄:$age")//自动调用unapply方法解构后做模式匹配
    case _ => println("未匹配")
  }
}

//实例2,通过样例类的学习,可以知道样例类实际上就是已经实现了apply和unaply伴生类

case class Student10( name:String,age:Int) 

object Student10 { //再用伴生对象就会和样例类自动生成的伴生对象冲突
  def apply(name:String, age:Int): Student10 = Student10(name, age)

  // 实现一个解构器
  def unapply(arg: Student10): Option[(String, Int)] = Some((arg.name, arg.age))
}

object App2 extends App {
  val zhangsan = Student10("张三", 20) 

  zhangsan match {
    case Student10(name, age) => println(s"姓名:$name 年龄:$age")
    case _ => println("未匹配")
  }
}
报错:
ambiguous reference to overloaded definition,
both method apply in object Student10 of type (name: String, age: Int)org.example.Student10
and  method apply in object Student10 of type (name: String, age: Int)org.example.Student10
报错:
method unapply is defined twice
  conflicting symbols both originated in file 'D:\git\scala_test\pon_test\src\main\scala\org\example\Student10.scala'
case class Student10( name:String,age:Int)

// 实例3,样例类等价形式:
case class Student10( name:String,age:Int) 

object App2 extends App {
  val zhangsan = Student10("张三", 20) 

  zhangsan match {
    case Student10(name, age) => println(s"姓名:$name 年龄:$age")
    case _ => println("未匹配")
  }
}
  1. Some(a,b)跟Some((a,b))是同一个意思P592。
  2. 一个更直观更单纯的提取器。P592
    • 待匹配值和unapply的参数类型一致并不是必须的 P593
    • 模式匹配逻辑首先检查给定值是否符合unapply的参数类型的要求,符合要求则转换成unapply的参数类型(博主推测是通过多态实现),不符合的话模式匹配就会失败。
object EMail{
	def unapply(str:String):Option[(String,String)]={
		val parts= str split "@"
		
		if (parts.length == 2) {
			Some(parts(0),parts(1))
		}else None
	}
}
//应用
val x: Any ="xlt@1243.com" //可转换为String
x match{
   case EMail(user,domain)=> println(user,domain)
}
(xlt,1243.com)

scala>  val x:Any=123//类型无法转换,匹配失败
scala> x match{
     |    case EMail(user,domain)=> println(user,domain)
     | }
scala.MatchError: 123 (of class java.lang.Integer)
  ... 34 elided
  1. 提取0个参数的模式。
    • unapply返回False表示匹配失败
    • 不带参数的模式需要加上(),否则变成比较对象的相等性了 P595
  2. 多个提取器嵌套时,从外层匹配到内层。如case Email(Twice(x @UpperCase()),domain)P595
  3. 变长参数匹配。定义unapplySeq方法,返回类型必须是Option[Seq[T]]类型。
object Domain{
	def unapplySeq(whole:String):Option[Seq[String]]={
		Some(whole.split("\\.").reverse)
	}
}
//使用
"www.baidu.com" match {
	case Domain("com", x @ _*)=>true //取到的x是WrappedArray[T] 类型
	case _=> false
}
  1. 提取器 vs 样例类P600
    • 样例类的缺点:把数据的具体类型暴露给使用方。如case C()中的C。如果样例类被别人使用并包含了模式匹配,重命名或者改名样例类的继承关系都会影响使用方。提取器则没有这个问题。
    • 如果要暴露给别人使用,则考虑从提取器开始、
    • 如果看不出哪个好,从样例类开始,因为样例类更简单。
  2. 正则表达式。P603
    • raw字符串表示:"""any string"""
    • 正则表达式的类型:scala.util.matching.Regex。两种创建方法:P602
      • val r=new Regex("""any string""")
      • """val r=any string""".r
    • 正则表达式定义了一些常用方法。findFristIn,finddAllIn findPrefixOf等,返回Option或者Iterator类型。
    • 每个正则表达式都定义了一个提取器。正则表达式的group对应提取器unapplySeq返回的Seq的每个元素。
val R="""(-)?(\d+)(\.\d*)?""".r
val R(s,i,d)="-1.23" //使用提取器的模式匹配

其他

  1. 下划线用途总结

1、存在性类型:Existential types
def foo(l: List[Option[_]]) = ...
 
2、高阶类型参数:Higher kinded type parameters
case class A[K[_],T](a: K[T])
 
3、临时变量:Ignored variables
val _ = 5
 
4、临时参数:Ignored parameters
List(1, 2, 3) foreach { _ => println("Hi") }
 
5、通配模式:Wildcard patterns
Some(5) match { case Some(_) => println("Yes") }
match {
     case List(1,_,_) => " a list with three element and the first element is 1"
     case List(_*)  => " a list with zero or more elements "
     case Map[_,_] => " matches a map with any key type and any value type "
     case _ =>
 }
val (a, _) = (1, 2)
for (_ <- 1 to 10)
 
6、通配导入:Wildcard imports
import java.util._
 
7、隐藏导入:Hiding imports
// Imports all the members of the object Fun but renames Foo to Bar
import com.test.Fun.{ Foo => Bar , _ }
 
// Imports all the members except Foo. To exclude a member rename it to _
import com.test.Fun.{ Foo => _ , _ }
 
8、连接字母和标点符号:Joining letters to punctuation
def bang_!(x: Int) = 5
 
9、占位符语法:Placeholder syntax
List(1, 2, 3) map (_ + 2)
_ + _   
( (_: Int) + (_: Int) )(2,3)
 
val nums = List(1,2,3,4,5,6,7,8,9,10)
 
nums map (_ + 2)
nums sortWith(_>_)
nums filter (_ % 2 == 0)
nums reduceLeft(_+_)
nums reduce (_ + _)
nums reduceLeft(_ max _)
nums.exists(_ > 5)
nums.takeWhile(_ < 8)
 
10、偏应用函数:Partially applied functions
def fun = {
    // Some code
}
val funLike = fun _
 
List(1, 2, 3) foreach println _
 
1 to 5 map (10 * _)
 
//List("foo", "bar", "baz").map(_.toUpperCase())
List("foo", "bar", "baz").map(n => n.toUpperCase())
 
11、初始化默认值:default value
var i: Int = _
 
12、作为参数名:
//访问map
var m3 = Map((1,100), (2,200))
for(e<-m3) println(e._1 + ": " + e._2)
m3 filter (e=>e._1>1)
m3 filterKeys (_>1)
m3.map(e=>(e._1*10, e._2))
m3 map (e=>e._2)
 
//访问元组:tuple getters
(1,2)._2
 
13、参数序列:parameters Sequence 
_*作为一个整体,告诉编译器你希望将某个参数当作参数序列处理。例如val s = sum(1 to 5:_*)就是将1 to 5当作参数序列处理。
//Range转换为List
List(1 to 5:_*)
 
//Range转换为Vector
Vector(1 to 5: _*)
 
//可变参数中
def capitalizeAll(args: String*) = {
  args.map { arg =>
    arg.capitalize
  }
}
 
val arr = Array("what's", "up", "doc?")
capitalizeAll(arr: _*)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值