【Scala】Scala高级使用技巧之二

1、重写field的提前定义

默认情况下,如果父类中的构造函数代码,用到了会被子类重写的field,那么会出现以下情形:
1.  子类的构造函数(无参)调用父类的构造函数(无参);
2.  父类的构造函数初始化field(结果正确);
3.  父类的构造函数使用field执行其他构造代码,但是此时其他构造代码如果使用了该field,而且field要被子类重写,那么它的getter方法被重写,返回0(比如Int);
4.  子类的构造函数再执行,重写field(结果也正确);
5.  但是此时子类从父类继承的其他构造代码,已经出现了错误了。

class Student {
	val classNumber: Int = 10
	val classScores: Array[Int] = new Array[Int](classNumber)
}

class PEStudent {
	override val classNumber: Int = 3
}

本来我们期望的是,PEStudent可以从Student继承来一个长度为3的classScores数组,但结果PEStudent对象只有一个长度为0的classScores数组。此时只能使用Scala对象继承的一个高级特性:提前定义,在父类构造函数执行之前,先执行子类的构造函数中的某些代码。

class PEStudent extends student {
	override val classNumber: Int = 3
} with Student

2、Scala的继承层级

这里我们大概知道一下Scala的继承层级,我们写的所有的Scala trait和class,都是默认继承自一些Scala根类的,有一些基础的方法。
Scala中,最顶端的两个trait是Nothing和Null,Null trait唯一的对象就是null,其次是继承了Nothing trait的Any类,接着是Anyval trait和AnyRef类,都继承自Any类。
Any类是个比较重要的类,其中定义了isInstanceOf和asInstanceOf等方法,以及equals、hashCode等对象的基本方法,Any类有点像Java中的Object基类。
AnyRef类增加了一些多线程的方法,比如wait、notify/notifyAll、synchronized等,也是属于Java Object类的一部分。

3、对象相等性

在scala中,如何判断两个引用变量是否指向同一个对象实例?
AnyRef的eq方法用于检查两个变量是否指向同一个对象实例,AnyRef的equals方法默认调用eq方法实现,也就是说,默认情况下,判断两个变量相等,要求必须指向同一个对象实例。
通常情况下,自己可以重写equals方法,根据类的fields来判定是否相等,此外,定义equals方法时,也最好使用同样的fields,重写hashCode方法。
如果只是想要简单地通过是否指向同一个对象实例,判定变量是否相当,那么直接使用==操作符即可,默认判断null,然后调用equals方法。

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
}

4、文件操作

(1)遍历一个文件中的每一行,必须导入scala.io.Source类:import scala.io.Source。
1.  方法一:使用Source.getLines返回的迭代器。

val source = Source.fromFile("C://Users//Administrator//Desktop//test.txt", "UTF-8")
val lineIterator = source.getLines
for (line <- lineIterator) println(line)

2.  方法二:将Source.getLines返回的迭代器,转换成数组。
这里说明一点:一个BufferedSource对象的getLines方法,只能调用一次,一次调用完之后,遍历了迭代器里所有的内容,就已经把文件里的内容读取完了。如果反复调用source.getLines,是获取不到内容的,此时,必须重新创建一个BufferedSource对象。

val source = Source.fromFile("C://Users//Administrator//Desktop//test.txt", "UTF-8")
val lines = source.getLines.toArray
for(line <- lines) println(line)

3.  方法三:调用Source.mkString,返回文本中所有的内容。

val source = Source.fromFile("C://Users//Administrator//Desktop//test.txt", "UTF-8")
val lines = source.mkString

使用完BufferedSource对象之后,调用BufferedSource.close方法,关闭IO流资源
(2)遍历一个文件中的每一个字符。
BufferedSource也实现了一个Iterator[Char]的这么一个trait。

val source = Source.fromFile("C://Users//Administrator//Desktop//test.txt", "UTF-8")
for(c <- source) print(c)

(3)从URL以及字符串中读取字符。

val source = Source.fromURL("http://www.baidu.com", "UTF-8")
val source = Source.fromString("Hello World")

(4)结合Java IO流,读取任意文件。
1.  结合Java IO流,进行文件拷贝。

import java.io._

val fis = new FileInputStream(new File("C://Users//Administrator//Desktop//test.txt"))
val fos = new FileOutputStream(new File("C://Users//Administrator//Desktop//test3.txt"))

val buf = new Array[Byte](1024)
fis.read(buf)
fos.write(buf, 0, 1024)

fis.close()
fos.close()

2.  结合Java IO流,写文件。

val pw = new PrintWriter("C://Users//Administrator//Desktop//test4.txt")
pw.println("Hello World")
pw.close()

(5)递归遍历子目录。

def getSubdirIterator(dir: File): Iterator[File] = {
  val childDirs = dir.listFiles.filter(_.isDirectory)
  childDirs.toIterator ++ childDirs.toIterator.flatMap(getSubdirIterator _)
}

val iterator = getSubdirIterator(new File("C://Users//Administrator//Desktop"))
for(d <- iterator) println(d)

(6)序列化以及反序列化(与Java序列化和反序列化机制相同)。
如果要序列化,那么就必须让类有一个@SerialVersionUID,定义一个版本号,要让类继承一个Serializable trait。

@SerialVersionUID(42L) class Person(val name: String) extends Serializable
val leo = new Person("leo")

import java.io._

val oos = new ObjectOutputStream(new FileOutputStream("C://Users//Administrator//Desktop//test.obj"))
oos.writeObject(leo)
oos.close()

val ois = new ObjectInputStream(new FileInputStream("C://Users//Administrator//Desktop//test.obj"))
val restoredLeo = ois.readObject().asInstanceOf[Person]
restoredLeo.name

5、偏函数

简单来说,偏函数是什么,其实就是没有定义好明确的输入参数的函数,函数体就是一连串的case语句。
一般的函数:

def getStudentGrade(name: String) = {
	...
}

偏函数是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")

6、Scala执行外部命令

Scala程序是运行在java虚拟机中的,也就是咱们平时常说的JVM,所以我们之前能够看到,Scala可以直接调用JDK。Scala的程序,是运行在JVM虚拟机进程中的。
我们的Scala程序可以去执行Scala所在进程之外的本地操作系统的其他命令,甚至是启动其他的进程。
案例:使用Scala编译和执行外部的Java程序。

import sys.process._

"javac HelloWorld.java" !
"java HelloWorld" !

7、Scala的正则表达式支持

正则表达式是用一个表达式,来匹配一系列的字符串。
例:[a-z]+:一个或多个a~z范围的26个小写英文字母,比如hello,world。
定义一个正则表达式,使用String类的r方法,此时返回的类型是scala.util.matching.Regex类的对象。

val pattern1 = "[a-z]+".r

从一个长的字符串中,提取出匹配正则表达式的各个部分:

val str = "hello 123 world 456"

获取一个字符串中,匹配正则表达式的部分,使用findAllIn,会获取到一个Iterator迭代器。然后就可以去遍历各个匹配正则的部分进行处理。

for (matchString <- pattern1.findAllIn(str)) println(matchString)

使用findFirstIn,可以获取第一个匹配正则表达式的部分。

pattern1.findFirstIn(str)

使用replaceFirstIn,可以将匹配正则的部分,替换掉。

pattern1.replaceFirstIn("hello world", "replacement")

使用replaceAllIn,可以将第一个匹配正则的部分,替换掉。

pattern1.replaceAllIn("hello world", "replacement")

8、提取器

(1)apply方法
伴生对象里面,可以定义一个apply方法,直接调用类(参数),就相当于在调用apply方法,此时在apply方法中,通常来说,会创建一个伴生类的对象返回回去。
用这种方式创建对象不用每次都是new 类(参数),而是类(参数)。
(2)unapply方法
提取器就是一个包含了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

9、样例类的提取器

Scala中的样例类类似于Java中的JavaBean,包含了一堆属性field,每个field都有一对getter和setter方法。
JavaBean:

public class Person {
	
	private String name;
	private int age;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
}

Scala中的样例类,默认就是提供apply方法和unapply方法的:

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

val p = Person("leo", 25)

p match {
	case Person(name, age) => println(name + ": " + age)
}

10、只有一个参数的提取器

普通的提取器接收一个字符串,作为参数,然后从字符串里面解析出来多个字段值,然后将多个字段值封装在一个tuple中,作为Some类型的对象返回。
而如果你的类只有一个字段,字符串里面只有一个字段,解析出来的一个字段,是没有办法放在tuple中的,因为scala中的tuple规定了,必须要两个以及两个以上的值。这个时候,在提取器的unapply方法中,只能将一个字段值封装在Some对象中,直接返回。

class Person(val name: String)

object Person {
  def unapply(input: String): Option[String] = Some(input)
}

val Person(name) = "leo"
  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

打赏
文章很值,打赏犒劳作者一下
相关推荐
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页

打赏

魏晓蕾

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者