2.2 跳出循环的三种方法
1. 使用boolean控制变量
var flag = true
var res = 0
var n = 0
while(flag){
res += n
n += 1
if (n==5) flag = false
}
/****************/
for(i <- 0 until 10 if flag){
res += 1
if (i==4) flag = false
}
2. 在嵌套函数里面使用return
def outer() = {
var res = 0
def inner(){
for (i<-0 until 10){
if (i==5) return
res += 1
}
}
inner()
res
}
3. 使用Breaks对象的break方法
import scala.util.control.Breaks._
var res = 0
breakable{
for(i<-0 until 10){
if (i==5) break;
res += i
}
}
2.3 多维数组、Java数组与Scala数组的隐式转换
多维数组
//构造行与列的二维数组:Array.ofDim方法
val multiDimArr = Array.ofDim[Double](3,4)
multiDimArr(0)(0) = 1.0 // 柯里表达式
// 构造不规则多维数组
val multiDimArr2 = new Array[Array[Int]](3)
multiDimArr2(0) = new Array[Int](1)
multiDimArr2(1) = new Array[Int](2)
multiDimArr2(2) = new Array[Int](3)
multiDimArr2(1)(1) = 1
Java数组与Scala数据的隐式转换
// Scala中的list是ArrayBuffer,无法直接传入Java的API
import scala.collection.JavaConversions.bufferAsJavaList
import scala.collection.mutable.ArrayBuffer
val command = ArrayBuffer("javac", "filePath/test.java")
val processBuilder = new ProcessBuilder(command) // ProcessBuilder是java的类,command作为ArrayBuffer是无法直接传入的,只能通过bufferAsJavaList隐式转换
val process = processBuilder.start()
val res = process.waitFor()
import scala.collection.JavaConversions.asScalaBuffer
import scala.collection.mutable.Buffer
val cmd:Buffer[String] = processBuilder.command() // 逆方向转换
2.4 Tuple拉链操作、Java与Scala的Map隐式转换
Tuple拉链操作
// Tuple拉链操作就是zip操作,是Array类的方法,用于将两个Array合并为一个Array
// 比如Array(v1)和Array(v2),使用zip操作合并后的格式为Array((v1, v2)),合并后的Array元素类型为Tuple
val students = Array("leo","jake")
val scores = Array(80,60)
val studentScores = students.zip(scores)
for ((stu, sco) <- studentScores) println(stu+" "+sco)
val studentScoreMap = studentScores.toMap
studentScoreMap("leo")
Java Map和Scala Map的隐式转换
import scala.collection.JavaConversions.mapAsScalaMap
val javaScoreMap = new java.util.HashMap[String, Int]()
javaScoreMap.put("leo",80)
javaScoreMap.put("jack",70)
val scalaScoreMap: Scala.collection.immutable.Map[String, Int] = javaScoreMap
import scala.collection.JavaConversions.mapAsJavaMap
import java.awt.font.TextAttribute._
val scalaAttrMap = Map(FAMILY -> "Serif", SIZE -> 12)
val font = new java.awt.Font(scalaAttrMap)
2.6 package与import实战详解
package定义
// package第一种定义方式:多层级package定义(不可取)
package com {
package spark {
package scala {
class Test {}
}
}
}
// package第二种定义方式:串联式package定义(也不可取)
package com.spark.scala {
package service {
class Test {}
}
}
//package第三种定义方式:文件顶部package定义
package com.spark.scala.service
class Test{}
package特性
同一个包定义,可以在不同的scala源文件中;一个scala源文件内,可以包含两个包
-
子包中的类,可以访问父包中的类
-
相对包名与绝对包名
-
定义package对象(使用较少)
package内的成员,可以直接访问package对象内的成员
package可见性
package com.spark.scala.
class Person{
private[scala] val name = "leo"
private[spark] val age = 25 // 只在spark包下面的成员可见
}
import特性
- 用import xxx.xxx._的格式可以导入包下所有成员
- scala与java不同之处在于,任何地方都可以使用import,比如类内、方法内,这种方式的好处在于,可以在一定作用域范围内使用导入
- 选择器、重命名、隐藏
import spark.scala.{aaa, bbb}
import spark.scala.{HashMap => scalaHashMap} // 重命名
import scala.util.{HashMap => _, _} // 导入scala.util包下所有的类,但是隐藏掉HashMap类
- 隐式导入
// 每个scala程序默认都会隐式导入以下几个包所有的成员
import java.lang._
import scala._
import Predef._
2.7 重写field的提前定义、Scala继承层级、对象相等性
重写field的提前定义
class Student{
val classNum: Int = 10
val classScores: Array[Int] = new Array[Int](classNum)
}
class PEStudent extends Student{
override val classNum: Int = 3
}
val s = new Student()
val pes = new PEStudent()
s.classScores // Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
pes.classScores // Array[Int] = Array() 为空
- 子类PEStudent的构造函数(无参)调用父类Student的构造函数(无参)
- 父类的构造函数初始化field(结果正确)
- 父类的构造函数使用field执行其他构造代码(classScores),但是此时其他构造代码如果使用了该field,而且field要被子类重写,那么它的getter方法被自动重写,返回初始化的值0(比如Int),因此pes.classScores返回长度为0的数组
- 子类的构造函数再执行,重写field(pes.classNum = 3)
- 但是此时子类从父类继承的其他构造代码,已经出现错误了(pes.classScores)
此时,只能用Scala对象继承的一个高级特性:提前定义,在父类构造函数执行前,先执行子类的构造函数中的某些代码
class PEStudent extends{
override val classNum: Int = 3
} with Student
Scala继承层级
Scala中,最顶端的两个trait是Nothing和Null,Null trait唯一的对象就是null,其次是继承了Nothing trait的Any类,接着AnyVal trait和AnyRef类,都继承自Any类。
Any类是一个比较重要的类,其中定义了inInstanceOf和asInstanceOf等方法,以及equals、hashCode等对象的基本方法。
AnyRef类,增加了一些多线程的方法,比如wait、notify/notifyAll、synchronized等。
对象相等性
如何判断两个引用变量,是否指向同一个对象实例?
AnyRef的eq方法用于检查两个变量是否指向同一个对象实例,AnyRef的equals方法默认调用eq方法实现,也就是说,默认情况下,判断两个变量相等,要求必须指向同一个对象实例。
此外,定义equals方法时,也最好使用同样的fields,重写hashCode方法。
如果只是想简单通过是否指向同一个对象实例,判定变量是否相等,那么直接使用==即可
class Product(val name: String, val price: Double){
final override def equals(other: Any) = {
val that = other.asInstanceOf[Product]
if(that == null) false
else name == that.name && price == that.price
}
final override def hashCode = 13 * name.hashCode + 17 * price.hashCode
}
val p1 = new Product("p1", 1.0)
val p2 = new Product("p2", 2.0)
val p3 = p1 // p3 == p1 true
val p4 = new Product("p1", 1.0)
p4 == p1 // true
2.8 文件操作实战
遍历文件中的每一行
import scala.io.Source
/***1. 使用Source.getLines返回***/
val source = Source.fromFile(filePath,"UTF-8")
val lineIterator = source.getLines // 注意getLines调用后指针在文件尾部,再次调用返回空
for (line <- lineIterator) println(line)
/***2. 将Source.getLines返回的迭代器,转换成数组***/
val source = Source.fromFile(filePath,"UTF-8")
val lines = source.getLines.toArray
for (line <- lines) println(line)
/***3. 调用Source.mkString,返回文本中所有的内容***/
val source = Source.fromFile(filePath,"UTF-8")
val lines = source.mkString
source.close()
遍历一个文件中的每个字符
val source = Source.fromFile(filePath,"UTF-8")
for (c <- source) print(c)
从URL以及字符串中读取字符
val html = Source.fromURL("http://www.baidu.com", "UTF-8")
val str = Source.fromString("Hello World")
结合Java IO流,文件拷贝
import java.io._
val file = new File(filePath)
var len = file.length.toInt
val bytes = new Array[Byte](len)
val fis = new FileInputStream(file)
val fos = new FileOutputStream(new File(outputFilePath))
fis.read(buf)
fos.write(buf, 0, len)
fis.close()
fos.close()
递归遍历子目录
import java.io.File
def getDir(dir: File): Iterator[File] = {
val childDirs = dir.listFiles.filter{_.isDirectory}
childDirs.toIterator ++ childDirs.toIterator.flatMap(getDir _)
}
val iterators = getDir("/home/xxx/test")
for (i <- iterators) println(i)
序列化与反序列化(Java)
// 要实现序列化,一定要有@SerialVersionUID注解,定义一个版本号,并继承Serializable trait
@SerialVersionUID(1L) class Person(val name: String) extends Serializable
val leo = new Person("leo")
import java.io._
val oos = new ObjectOutputStream(new FileOutputStream(filePath))
oos.writeObject(leo)
oos.close()
val ois = new ObjectInputStream(new FileOutputStream(filePath))
val restoredLeo = ois.readObject().asInstanceOf[Person]
restoredLeo.name // restoredLeo与Leo不是一个对象
2.9 偏函数实战详解
偏函数
// 偏函数是没有定义好明确的输入参数的函数,函数体是一连串的case语句
// 偏函数是PartialFunction[A, B]类的一个实例,该类有两个方法,一个是apply()方法,直接调用可以通过函数体内的case进行匹配,返回结果;另一个是isDefinedAt()方法,可以返回一个输入,是否跟任何一个case语句匹配
/***学生成绩查询案例***/
val getStudentGrade: PartialFunction[String, Int] = {
case "Leo" => 90; case "Jack" => 85; case "Marry" => 95
}
getStudentGrade("Leo")
getStudentGrade.isDefinedAt("Tom") // False
2.10 执行外部命令
scala的程序是运行在jvm进程中的,如果scala程序想要执行scala所在进程之外的,比如本地os的命令时,即执行外部命令
import sys.process._
"ls -la .." ! //
2.11 正则表达式支持
// 定义一个正则表达式,使用String类的r方法
val pattern1 = "[a-z]+".r
// 获取一个字符串中,匹配正则表达式的部分,使用findAllIn
for (matchString <- pattern1.findAllIn(str)) println(matchString)
// 同理,使用findFirstIn,可以获取第一个匹配正则表达式的部分
pattern1.findFirstIn(str)
// 使用replaceAllIn,可以将匹配正则的部分替换掉
pattern1.replaceAllIn("hello world", "replacement")
// 使用replaceFirstIn,可以将第一个匹配正则的部分替换掉
pattern1.replaceFirstIn("hello world", "replacement")
2.11 提取器
提取器就是包含了一个unapply方法的对象,跟apply方法正好相反。
apply方法,是接受一堆参数,然后构造出一个对象。
unapply方法,是接受一个字符串,然后解析出对象的属性值。
class Person(val name: String, val age: Int)
Object Person{
def apply(name: String, age: Int) = new Person(name, age)
def unapply(str: String) = {
val splitIndex = str.indexOf(" ") // 按空格切割
if (splitIndex == -1) None // 如果没有空格
else Some((str.substring(0, splitIndex), str.substring(splitIndex + 1)))
}
}
val Person(name, age) = "leo 25"
name
age
样例类的提取器
// case class类似于java中的javaBean,javaBean包含了一堆属性(field),每个field都有一对getter和setter
// scala默认给case class创建getter和setter方法
case class Person(name: String, age: Int)
val p = Person("leo", 25) // 样例类默认提供apply和unapply方法
p match {
// 模式匹配,如果是Person类,调用Person的unapply方法
case Person(name, age) => println(name + ": "+ age)
}
只有一个参数的提取器
如果你的类只有一个字段,即字符串里面只有一个字段,解析出来的字段是没有办法放在tuple中的,因为scala的tuple规定要2个及以上的值。
这个时候,在unapply方法中,只能将一个字段值,封装在Some对象中返回。
class Person(val name: String)
Object Person {
def unapply(input: String): Option[String] = Some(input)
}
val Person(name) = "leo"
2.15 注解
在scala中可以给类、方法、field、�variable、parameter添加注解,并且scala支持给某个对象添加多个注解。
// 特例:如果要给类的主构造函数添加注解,那么需要在构造函数前添加注解,并加上一对圆括号。
class Person @Unchecked() (val name: String, val age: Int)
// 还可以给表达式添加注解,此时需要在表达式后面加上冒号以及注解,如:
val scores = Map("Leo" -> 90, "Jack" -> 85)
(scores.get("Leo"): @unchecked) match { case score => println(score) }
// 开发注解:要自己开发一个注解,就必须扩展Annotation trait
class Test extends annotation.Annotation
@Test
class myTest
// 注解参数
class Test(var timeout: Int) extends annotation.Annotation
@Test(timeout = 100)
class myTest
常用注解介绍
@volatile var name = "leo" // 轻量级的java多线程并发安全控制
jvm中,多线程的每个线程都有自己的工作区,还有一块儿所有线程共享的工作区,每次一个线程拿到一个公共的变量,都需要从共享区中拷贝一个副本到自己的工作区中使用和修改,修改完以后,再在一个合适的时机,将副本的值写回到共享区中。这里就会出现多线程并发访问的安全问题。
volatile关键字修饰的变量,可以保证一个线程在从共享区获取一个变量的副本时,都会强制刷新一下这个变量的值,保证自己获取到的变量的副本值是最新的。但这样也不是百分百保险,仍可能出现错误的风险。
@transient var name = "leo" // 瞬态字段,不会序列化这个字段
@SerialVersionUID(value) // 标记类的序列化版本号(类可能在序列化保存之后改变了,这样反序列化时会报错)
@native // 标注用c实现的本地方法
@throws(classOf[Exception]) def test(){} // 给方法标记要抛出的checked异常
@varargs def test(args: String*){} // 标记方法接受的是变长参数
@BeanProperty // 标记生成JavaBean风格的getter和setter方法
@BooleanBeanProperty // 标记生成is风格的getter方法,用于boolean类型的field
@deprecated(message = "") // 让编译器提示警告
@unchecked // 让编译器提示类型转换的警告
2.17 XML基础操作
scala中定义xml
val books = <books><book>my book</book></books> // scala.xml.Elem,即一个xml元素
val books = <book>my book</book><book>your book</book> // 多个平级的元素,scala.xml.Nodeseq,节点序列
XML节点类型
Node类是所有XML节点类型的父类型,两个重要的子类型是Text和Elem。
Elem表示一个XML元素,也就是一个XML节点。scala.xml.Elem类型的label属性,返回的是标签名,child属性,返回的是子元素。
scala.xml.NodeSeq类型,是一个元素序列,可以用for循环,直接遍历它。可以通过scala.xml.NodeBuffer类型,手动创建一个节点序列。
val booksBuffer = new scala.xml.NodeBuffer
booksBuffer += <book>book1</book>
booksBuffer += <book>book2</book>
val books: scala.xml.NodeSeq = booksBuffer
xml元素的属性
// scala.xml.Elem.attributes属性,可以返回xml元素的属性,是Seq{scala.xml.Node}类型的,继续调用text属性,可以获取属性的值
val book = <book id="1" price="10.0">book1</book>
val bookID = book.attributes("id").text
// 遍历属性
for (attr <- book.attributes) println(attr)
// 获取属性Map
book.attributes.asAttrMap
xml中嵌入scala代码
val books = Array("book1", "book2")
<books><book>{books(0)}</book><book>{books(1)}</book></books>
<books>{ for (book <- books) yield <book>{book}</book>}</books>
// xml属性中嵌入scala代码
<book id="{ books(0) }">{ books(0) }</book>
xml修改元素
// scala中的xml表达式默认是不可改变的,必须拷贝一份再修改
val books = <books><book>book1</book></books>
val booksCopy = books.copy(child = books.child ++ <book>book2</book>)
var book = <book id="1">book1</book>
import scala.xml._
// 修改一个属性
val bookCopy = book % Attribute(null, "id", "2", Null)
// 添加一个属性
val bookCopy = book % Attribute(null, "id", "2", Attribute(null, "price", "10.0", Null))
xml加载和写入外部文档
import scala.xml._
import java.io._
// 使用scala的XML类加载
val books = XML.loadFile(filePath)
// 使用Java的FileInputStream类加载
val books = XML.load(new FileInputStream(filePath))
// 用Java的InputStreamReader类加载
val books = XML.load(new InputStreamReader(new FileInputStream(filePath), "UTF-8"))
// 写入外部xml文档
XML.save(outFilePath, bookss)
2.21 集合元素操作

集合的常用操作
head last tail
length isEmpty
sum max min
count exists filter filterNot
takeWhile dropWhile
takeRight dropRight
sclie
contains startsWith endsWith
indexOf
intersect diff
map
val scoreMap = Map("leo" -> 90, "jack" -> 60)
val names = List("leo", "jack", "tom")
names.map(scoreMap(_)) // names映射为成绩集合
flatMap
val scoreMap = Map("leo" -> List(80, 90), "jack" -> List(60, 70))
names.flatMap(scoreMap(_)) // List(80, 90, 60, 70)
collect
"abc".collect { case 'a' => 1; case 'b' => 2; case 'c' => 3} // Vector(1, 2, 3),"abc"中每个元素与偏函数匹配
foreach
names.foreach(println _)
reduce
List(1, 2, 3, 4).reduceLeft(_ - _) // 1-2-3-4
List(1, 2, 3, 4).reduceRight(_ - _) // 4-3-2-1
fold
List(1, 2, 3, 4).foldLeft(10){ (m, n) => println(m+": "+n); m -n } // 给定起始元素 10-1-2-3-4
List(1, 2, 3, 4).foldRight(10)(_ - _) // 不一样,1-(2-(3-(4-10))))