1.Scala隐式彻底详解
隐式转换 implicit ,可通过手动指定将某种类型的对象或者某种类转换成其他类型的类或者对象
形式:implicit def function
隐式函数、隐式参数、隐式对象、隐式类
隐式函数:指有implicit前置修饰的函数
Scala会根据上下文,利用隐式转换函数的签名主要是输入类型,在程序运行时利用隐式转换函数,将接受的隐式函数参数类型定义的对象自动升级转换成隐式转换后的对象。转换成另外一种对象返还,任务完成后重新转换为原来类型。如果调用对象没有要调用的方法,scala会在出错前的最后一步尝试,进行隐式转换,找其隐式转换后的对象有无该方法,有则自动升级为隐式转换后的类后调用升级后的方法。
语法格式:
Implicitdef 函数名(参数) (函数名最好定义为原类to隐式转换后的类)
这里我们定义了两个类,在调用创建了一个Person对象后调用其code方法,系统报错,因为类无该方法。
class Person(val name: String)
class Engineer(val name: String, val salary: Double){
def code{ println("Coding...")}
}
new Person("Spark").code//报错
def toCode(p: Person){p.code}//也会报错
implicit def personToEngineer(p: Person): Engineer = {
new Engineer(p.name,1000000)
}
def toCode(p: Person){
p.code
}
这里我们将Engineer类中加入了toCode方法,然后定义了一个隐式函数,同时定义了一个toCode方法调用person对象的toCode方法,按照java的语法汇报错,因为类Person无此方法。但是Scala会在报错前,去找隐式函数,例如我们定义的隐式函数,根据函数签名,主要是输入参数,找到了person2Engineer方法,并利用此方法将person对象转换为了Engineer对象,然后发现Engineer对象有toCode方法,直接调用其toCode方法,打印输出。运行后,Person对象和Engineer类型无任何关系。
这里需要注意一点,虽然在定义implicit函数时scala并未要求要写明返回类型,但我们应写明返回类型,这样有助于代码阅读。
在大型项目中,接口在语法上是不可变的,因为要保持接口的稳定,但是在后期,接口要加入一些扩展的东西,这一点存在矛盾。但是scala的隐式转换,可以扩展接口。并且,它是隐式的,在代码重构是随时删除和增加。
package com.test
import scala.io.Source
import java.io.File
class RicherFile(val file:File){
def read = Source.fromFile(file.getPath()).mkString
}
class File_Implicits( path: String) extends File(path)
object File_Implicits{
implicit def file2RicherFile(file:File)= new RicherFile(file) //File -> RicherFile
}
object Implicits_Internals {
def main(args: Array[String]) {
val file = new File_Implicits("content.txt")//当前工程目录下
println(file.read)
}
}
这里我们定义了一个File_Implicits类,注意它其中没有read方法,在它的伴生对象中我们定义了一是转换函数file2RicherFile,然后我们又定义了一个RicherFile类,这个类中包含了read方法。在主函数中我们首先创建了一个File_Implicits对象file,然后打印它的read方法的返回值。首先,scala会在类File_Implicits中找方法read,没有找到,此时不报错,而是在类的半生对象中找隐式转换,发现隐式转换函数,并且输入类型一样,然后将输入对象自动隐式转换为RicherFile,然后发下RicherFile类中有read方法,scala就直接调用已经隐式转换后的file的read方法,打印结果。
隐式参数:
由运行时上下文实际赋值注入的参数。不需要手动赋值,它会自动发生效果。
实际机制:如果定义了一个隐式参数,在实际运行时我们不需要手动提供这个参数,运行时环境会根据上下文的隐式值(可以静态的写入也可以是动态产生也可以是配置文件等等),会根据隐式值的类型和隐式值本身注入程序中,让我们的程序正常运行。(会在两个范围内查找隐式值,当前作用域的implicit val,implicit var,另外是会到隐式参数类型的伴生对象中去找隐式值,一般都是第二个范围)
这里,我们定义了一个Level类,表示等级,有定义了一个科里化函数toWork,他有两个参数,第一个是String的name,第二个是隐式参数Level类型的level,然后函数内部打印name和level的等级。
然后我们定义了一个隐式参数level,然后我们调用toWork方法,但是我们只传入了一个参数,scala打印出了结果,
这里,scala从上下文中找到了隐式值level = new Level(8),自动注入到了函数toWork中,作为第二个参数。
object Context_Implicits{
implicit val default:String = "Flink"//隐式
}
object Param{
def print(content:String)(implicit language:String){
println(language+":"+content)
}
}
object Implicit_Parameters {
def main(args: Array[String]) {
Param.print("Spark")("Scala")
import Context_Implicits._//导入Context_Implicits下所有内容
Param.print("Hadoop")
}
}
这里,我们定义了一个隐式对象,在main方法中我们导入了它中的所有内容(主要是隐式参数),那么我们在导入后就可以使用在他内部的隐式参数了。
输出结果:
Scala:Spark
Flink :Hadoop
隐式对象
m是传入的隐式对象
abstract class Template[T]{
def add(x:T,y:T):T
}
abstract class SubTemplate[T] extends Template[T]{
def unit:T
}
object Implicits_Object{
implicit object StringAdd extends SubTemplate[String]{
override def add(x:String,y:String) = x concat y
override def unit:String = ""
}
implicit object IntAdd extends SubTemplate[Int]{
override def add(x:Int,y:Int) = x+y
override def unit:Int = 0
}
def sum[T](xs:List[T])(implicit m:SubTemplate[T]):T=
if(xs.isEmpty) ys.unit
else ys.add(xs.head,sum(xs.tail))//tail是第一个元素外剩下元素
println(sum(List(1,2,3,4,5)))
println(sum(List("Scala","Spark","Kafka")))
}
}
首先我们定义了几个含有泛型的抽象类,Template是父类,而SubTempalte是子类,而这个子类有有两个子对象StringAdd和IntAdd,下面来看下主函数中定义的sum函数,它是一个含有泛型的函数,它的第一个参数是含有泛型的List类型的xs,第二个参数是含有泛型的SubTemplate类型的隐式参数m,函数返回值是一个泛型,首先,函数里先判断传入的第一个参数是否为空,若为空则调用隐式参数m,有由于scala可以自动进行类型推到,所以运行时,泛型T是一个确定类型,要么为Int要么为String,但是为空时,我们调用隐式参数m的unit是不同的,Int为0,而String为“”,所以我们定义了两个隐式对象对其进行处理,IntAdd隐式类复写了unit,使之为0,StringAdd隐式类复写了unit,使之为””,这样程序就可以正常执行了。同理,隐式对象方法对add方法进行复写,完成了sum操作。
输出结果:
15
ScalaSparkKafka
隐式类
import scala.io.Source
import java.io.File
object Context_Helper{
implicit class FileEnhancer(file: File){
def read = Source.fromFile(file.getPath).mkString
}
implicit class OP(x: Int){
def addSAP(second: Int) = x + second
}
}
object Implicit_Class{
def main(args: Array[String]){
import Context_Helper._
println(1.addSAP(2))
println(new File("context.txt").read)
}
}
这里我们在Context_Helper对象中定义了两个隐式类FileEnhancer和Op,在主方法中,我们到如了这对象中的所有内容,然后println打印时,调用了1.addSAP(2),首先Int类无这个方法,所以scala会自动隐式转换为RichInt,发现仍然找不到这个方法,然后再上下文中进行查找,由于导入Context_Helper,所以scala找到了隐式类Op由于其主构造器的传入类型正好是Int,所以他在Op内部进行查找,发现了addSAP方法且参数一致,所以scala会直接调用这个addSAP进行处理,得到结果3,第二个打印函数也类似。
输出结果:
3
hi Scala
查找隐式转换的四个方法或路径:
最先从当前类的伴生对象中去找,
第二,把隐式对象和隐式值放在一个Object中,然后导入进来,会在这个里面进行查找
第三,会在当前作用域中的隐式中去找
第四,在用的时候,才导入或进行隐式的书写。
2.Scala并发编程详解
一般,所有的高手在进行程序开发时,一定都是并发编程的消息通信模式加上缓存的绝妙的使用。
并发编程是高效利用cpu的核心。Scala中利用Actor实现的。(java的多线程是基于共享全局变量的加锁机制,他一定不可避免的出现死锁和状态失控,而在做分布式时有一个黄金准则就是一定不要使用共享的全局变量,更不要说加锁机制了。)而scala中的Actor去掉了共享的全局变量,变量是私有的;Actor之间的通信是通过发消息是实现的。避免了传统的并发编程中出现的死锁等一列问题,scala中提供一个基于Actor的框架,Akka。
java Thread缺点: 基于共享全局变量的加锁机制带来死锁,状态失控
优点:利用了多核并发,利用了物理硬件
Actor去掉了共享全局变量的加锁机制
import scala.actors.Actor
//Actor样例
class HiActor extends Actor{
def act(){
while(true){
receive{
case name: String => println(name)
}
}
}
}
val actor = new HiActor()
actor.start()
actor ! “Spark”//Actor发消息,!右边是内容
输出:Spark
这里的Actor类似java中Thread,Actor的act方法类似Thread的run方法。Actor中有一个特殊的语法receive来接收消息(它们是通过消息机制实现多线程通信的),然后我们进行模式匹配,若是String类型的值,就将值传给name,然后打印出来。
发送消息的语法格式:Actor名 ! 消息内容。
利用case class来实现消息通信
case class Basic(name: String, age: Int)
case class Worker(name: String, age: Int)
class BasicActor extends Actor{
def act(){
while(true){
receive{
case Basic(name, age) => println("Basic Information:" + name +":" + age)
case Worker(name, age) => println("Worker Information:" + name +":" + age)
}
}
}
}
输入:
val b = new BasicActor()
b.start()
b ! Basic(“Scala”, 13)
输出 Basic Information:Scala:13
输入:b ! Worker(“Spark”, 16)
输出:Worker Information:Spark:16
同步消息 语法格式: Actor名 !? 消息内容
接收异步消息 语法格式 val 变量名 = actror名 !! 消息内容 (在未来某个时刻变量或得到actor线程的发送的值)