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 集合元素操作
![](https://ww3.sinaimg.cn/large/006tNbRwgy1fdz4mai1czj30m20dy183.jpg)
集合的常用操作
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))))