Scala编程进阶

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特性

  1. 同一个包定义,可以在不同的scala源文件中;一个scala源文件内,可以包含两个包

  2. 子包中的类,可以访问父包中的类


  3. 相对包名与绝对包名


  4. 定义package对象(使用较少)

    package内的成员,可以直接访问package对象内的成员


  5. package可见性

package com.spark.scala.
class Person{
  private[scala] val name = "leo"   
  private[spark] val age = 25  // 只在spark包下面的成员可见
}

import特性

  1. 用import xxx.xxx._的格式可以导入包下所有成员
  2. scala与java不同之处在于,任何地方都可以使用import,比如类内、方法内,这种方式的好处在于,可以在一定作用域范围内使用导入
  3. 选择器、重命名、隐藏
import spark.scala.{aaa, bbb} 
import spark.scala.{HashMap => scalaHashMap} // 重命名
import scala.util.{HashMap => _, _} // 导入scala.util包下所有的类,但是隐藏掉HashMap类
  1. 隐式导入
// 每个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() 为空
  1. 子类PEStudent的构造函数(无参)调用父类Student的构造函数(无参)
  2. 父类的构造函数初始化field(结果正确)
  3. 父类的构造函数使用field执行其他构造代码(classScores),但是此时其他构造代码如果使用了该field,而且field要被子类重写,那么它的getter方法被自动重写,返回初始化的值0(比如Int),因此pes.classScores返回长度为0的数组
  4. 子类的构造函数再执行,重写field(pes.classNum = 3)
  5. 但是此时子类从父类继承的其他构造代码,已经出现错误了(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))))
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容