Scala学习第三天

本文深入探讨Scala编程,重点讲解模式匹配的多种应用场景,包括Switch语句、类型检查、数组、列表、元组和Option的匹配。同时,文章详细介绍了类型参数,如泛型类、泛型限定和视图界定。此外,文章还阐述了Scala的隐式转换,解释了如何利用隐式转换丰富API,并展示了如何进行隐式转换参数的使用。最后,简要提到了RPC和AKKA在并发通信中的角色。
摘要由CSDN通过智能技术生成

0. 今日大纲

  • 模式匹配

  • 类型参数

  • 隐式转换

  • 线程通信AKKA

1. 模式匹配

1、模式匹配是Scala中非常强大的一种功能。模式匹配,其实类似于Java中的switch case语法,即对一个值进行条件判断,然后针对不同的输入条件,进行结果处理。
2、Scala的模式匹配的功能比Java的switch case语法的功能要强大地多,Java的switch case语法只能对值进行匹配。但是Scala的模式匹配除了可以对值进行匹配之外,还可以对类型进行匹配、对Array和List的元素情况进行匹配、对case class进行匹配、甚至对有值或没值(Option)进行匹配。

模式匹配常见的语法结构如下:

变量 match {

	case 可能性1 => 操作1

	。。。

	case 可能性N => 操作N

	case _ => 默认/其它操作

}

1.1. 可以用到Switch语句中

Scala强大的模式匹配机制,可以应用在switch语句、类型检查以及“析构”等场合。

//使用模式匹配完成switch的操作
var sign = 0
val ch = '+'
ch match {
    case '-' => sign = -1
    case '*' => sign = -2
    case '+' => sign = 0
    case _ => sign = 1
}
/*
         * switch(ch) {
         *  case '+' :
         *    sign = 0;
         *    break;
         *  case '-' :
         *    sign = -1;
         *    break;
         *  case '*' :
         *    sign = -2;
         *    break;
         *  default:
         *    sign = 1;
         *    break;
         * }
         */
println("sign=" + sign)
//scala中的每一个表达式都有返回值,默认最后一句话就是返回值
sign = ch match {
    case '-' => -1
    case '*' => -2
    case '+' =>  0
    case _ => 1
}
println("sign=" + sign)

1.2. 守卫

def caseOps2: Unit = {
        var sign = 0
        val ch = '-'
        val ret = ch match {
            case '-' => sign = -1
            case '*' => sign = -2
            case _ if Character.isDigit(ch) => 3
            case _ => sign = 1
        }
        println("ret = " + ret)
    }

1.3. 模式中的变量和类型模式

模式匹配中的变量,需要从语法结构说起:

变量 match {

	case 可能性1 => 操作1

	。。。

	case 可能性N => 操作N

	case _ => 默认/其它操作

}

启动如果是匹配的类型,可以将可能性x书写为==> 变量:类型,可能性以就成为=>变量1:类型1,这样做的好处是什么呢?就是直接进行类型转换,可以方便操作其特有的api。

如果类型时确定的,可以将:后面的类型省略掉。
  • 变量

    def caseOps3: Unit = {
        "Hello world~".foreach(c => println(
            c match {
                case ' ' => "space"
                case ch => "Char: " + ch
            }
        ))
    }
    
  • 类型

    val input:Any = StdIn.readLine("请输入内容:")
    //        input.isInstanceOf
    //        input.asInstanceOf
    input match {
        case i:Int => println("int i = " + i)
        case s:String => println("String s = " + s)
        case ch:Char => println("char ch = " + ch)
        case _ => println(input)
    }
    

1.4. 匹配数组、列表和元组

val array = Array(0, -5)
array match {
    //匹配数组有2个元素,分别将两个元素赋值给x和y
    case Array(x, y) => println(s"x=$x, y=$y")
    //匹配数组以0开头
    case Array(0, _*) => println("Array(0, _*)")
    //默认的匹配,如果匹配不到,scala.MatchError
    case _ => println("default")
}

1.5. 样例类

所谓样例类,就是case class,是scala中一种专门用来携带数据的class,类似于java中的javabean,
一般的定义方式需要定义该case class的主构造器,其所有的字段都在主构造器中完成声明,
case class自动会提供所谓getter和setter方法。

case class的主构造函数接收的参数通常不需要使用var或val修饰,Scala自动就会使用val修饰(但是如果你自己使用var修饰,那么还是会按照var来)
Scala自动为case class定义了伴生对象,也就是object,并且定义了apply()方法,该方法接收主构造函数中相同的参数,并返回case class对象
def caseOps6: Unit = {
    abstract class Expr //抽象类 表达式
    case class Var(name:String) extends Expr
    case class UnOp(expr:Expr, operator:String) extends Expr
    case class Number(num:Double) extends Expr
    case class BinOp(left:Expr, operator:String, right:Expr) extends Expr

    def test(expr:Expr) = {
        expr match {
            case Var(name) => println("Var: " + name)
            case Number(num) => println("Number: " + num)
            case UnOp(Var(name), "+") => println(name + "+")
            case BinOp(Number(num1), "+", Number(num2)) => println(num1 + num2)
            case BinOp(Number(num1), "-", Number(num2)) => println(num1 - num2)
            case BinOp(Number(num1), "*", Number(num2)) => println(num1 * num2)
            case _ => println(expr)
        }
    }
	
    test(BinOp(Number(3.0), "-", Number(5.2)))
}

1.6. 模拟枚举

  • 枚举的一般定义方式

    object _02EnumrationOps {
        def main(args: Array[String]): Unit = {
           def accrossRoad(light: TrafficLight.Value): Unit = {
               light match {
                   case TrafficLight.GREEN => println("畅行无阻,及时通过")
                   case TrafficLight.YELLOW => println("hurry up")
                   case TrafficLight.RED => println("行车不规范,亲人泪两行")
                   case _ => println("翻车了")
               }
           
    
            accrossRoad(TrafficLight.GREEN)
        }
    }
    //普通的枚举的定义
    object TrafficLight extends Enumeration {
        val GREEN, YELLOW, RED = Value
    }
    
  • 枚举的模式匹配方式
    scala中的枚举操作
    scala中没有想java中的enum这个结构来定义枚举,但是有一个工具类Enumeration来辅助枚举
    枚举的结尾必须要用Value来进行赋值
    所以scala中枚举的类型就是Xxx.Value

    sealed class密封类,一个类的所有的子类型都是已知的,都必须要在定义期间给出
    当然一般这个sealed class密封类通常会被声明为抽象,使用子类来完成具体构建
    密封类的所有子类都必须在与该密封类相同的文件中定义。
    如果某个类是密封的,那么在编译期所有的子类是可知的,因而可以检查模式语句的完整性。
    让所有同一组的样例类都扩展某个密封的类或特质是个好的做法。
    
    sealed abstract class TrafficLight(color:String)
    case class RED(red:String) extends TrafficLight(red)
    case class GREEN(green:String) extends TrafficLight(green)
    case class YELLOW(yellow:String) extends TrafficLight(yellow)
    
    val light:TrafficLight = RED("酒后来驾车,亲人泪两行")
    light match {
        case RED(red) => println(red)
        case GREEN(green) => println(green)
        case YELLOW(yellow) => println(yellow)
    }
    

1.7. Option

Option是scala中有值(Some)或者没有值(None)的类型,scala也可以对应Option进行模式匹配进行处理。
val map = Map[String, String](
	"China" -> "BJ",
    "India" -> "XDL",
    "Japan" -> "Tokyo"
)

map.get("India") match {
    case Some(capital) => println("India's capital is " + capital)
    case None => println("所查国家不存在")
}

2. 类型参数

所谓类型参数,其实就是java中的泛型,java中的泛型定义可以使任意的标识符,
可以是一个字符,两个等等,但是通常都用一个大写的字母来表示,泛型就是用来表示某一种类型,
所谓广泛的类型,同时泛型在定义的时候,需要使用<>来包裹,泛型可以定义在类上面,方法上面。
额外说明一点,成员变量上不可以定义泛型,只能沿用类上声明的泛型。

scala中的泛型和java中的泛型高度相似,也可以定义在类,方法上,成员变量上的泛型也需要沿用类的声明,
定义的是一般也是用一个大写字母,但是泛型定义时需要使用[]包裹。

最后需要说明一点的是,scala中的泛型要比java中的泛型更加的灵活和强大。

2.1. 泛型类

所谓泛型类,就是在类上定义一个泛型。
object _01GenericOps {
    def main(args: Array[String]): Unit = {
        val stu1 = new Student1("first", "second")
        stu1.smaller()

        val stu2 = new Student2(3, 4)
        stu2.smaller()

        val stu3 = new Student3('a', 'A')
        stu3.smaller()
        
        val stu = new Student[String, String]("aaa", "bbb")
        val stu4 = new Student("aaa", 5)//因为有类型的推断,泛型可以省略掉
    }
    class Student1(first:String, second:String) {
        def smaller(): Unit = {
            if(first.compareTo(second) < 0) {
                println("samller is " + first)
            } else {
                println("samller is " + second)
            }
        }
    }
    class Student2(first:Int, second:Int) {
        def smaller(): Unit = {
            if(first.compareTo(second) < 0) {
                println("samller is " + first)
            } else {
                println("samller is " + second)
            }
        }
    }
    class Student3(first:Char, second:Char) {
        def smaller(): Unit = {
            if(first.compareTo(second) < 0) {
                println("samller is " + first)
            } else {
                println("samller is " + second)
            }
        }
    }
    class Student[T, S](first: T, second: S) {
        def smaller(): Unit = {
            println("类上的泛型")
        }
    }
}

2.2. 方法上的泛型、泛型限定、视图界定

  • java版

    public class GenericMethodOps {
         
        public static void main(String[] args) {
         
            int[] arr = {
         3, -1, 0, 5, -5, 7, 6};
            Float[] floatArr = {
         3f, -1f, 0f, 5f, -5f, 7f, 6f};
            String[] strArr = {
         "abc", "acC", "ccc", "a", "shit"};
            System.out.println("排序前的数组:" + Arrays.toString(strArr));
    //        insertSort(arr);
            insertSort(strArr);
            System.out.println("排序后的数组:" + Arrays.toString(strArr));
        }
    
        /**
         * 所谓的插入排序,指的是,将一个元素试图插入到一个有序的集合中。
         * 首先,只有一个元素的集合,是否有序?有序。
         * 从第二个元素开始进行处理
         * 第一次:-1
         *  3, -1, 0, 5, -5, 7, 6
         *      --》 -1, 3, 0, 5, -5, 7, 6
         * 第二次:0
         *  -1, 3, 0, 5, -5, 7, 6
         *      ---> -1, 0, 3, 5, -5, 7, 6
         * ....
         *
         *  -5, -1, 0, 3, 5, 6, 7
         * 通过上述的操作,发现需要两层循环,外循环用于控制每次插入的元素,内循环用于控制每次的比较
         *
         * 泛型的限定
         * T extends Comparable<T>
         */
        private static <T extends Comparable<T>> void insertSort(T[] arr) {
         
            for(int x = 1; x < arr.length; x++) {
         
                for(int y = x; y > 0 ; y--) {
         
                    if(arr[y].compareTo(arr[y-1]) < 0) {
         
                        swap(arr, y, y-1);
                    }else {
         
                        break;
                    }
                }
            }
        }
    
        private static void swap(int arr[], int y, int x) {
         
      //^是异或运算符,异或规则是转换成二进制比较相同为0 不同为1 
            arr[y] = arr[y] ^ arr[x];
            arr[x] = arr[y] ^ arr[x];
            arr[y] = arr[y] ^ arr[x];
        }
        private static <T> void swap(T arr[], int y, int x) {
         
            T temp = arr[y];
            arr[y] = arr[x];
            arr[x] = temp;
        }
    }
    

    两个知识点:一个是方法上声明泛型,还有一个是泛型的上限限定。

  • scala版

    //scala版本的方法泛型
    object _02GenericOps {
        def main(args: Array[String]): Unit = {
            val arr = Array[Integer](3, -1, 0, 5, -5, 7, 6)
            println("排序前的数组:" + arr.mkString("[", ", ", "]"))
            insertSort(arr)
            println("排序后的数组:" + arr.mkString("[", ", ", "]"))
        }
    
        /*
            scala中的泛型的限定语法格式
                T <: 类型
            java中的泛型限定语法格式
                T extends 类型
         */
        def insertSort[T <: Comparable[T]](array: Array[T]): Unit = {
            for(x <- 1 until array.length) {
                for(y <- 1 to x reverse) {
                    if(array(y).compareTo(array(y - 1)) < 0) {
                        swap(array, y, y - 1)
                    }
                }
            }
        }
    
        private def swap[T](arr: Array[T], y: Int, x: Int): Unit = {
            val temp = arr(y)
            arr(y) = arr(x)
            arr(x) = temp
        }
    }
    
    

    我们将java的版本修正为scala的版本,传入Integer类型的数组,完美解决问题,但是如果传递的是一个Int类型的数组,编译就会报错:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XZgxVJXr-1575337956275)(assets/1572925381551.png)]

    这个错误说的是Int类型,不具备比较性,观察Int的源码:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VyQM7L0n-1575337956277)(assets/1572925413164.png)]

    确实没有体现出Comparable的特性,所以是不能直接进行比较,但是Int类型的数据,在scala中确确实实可以进行加减乘除等等运算,比较也是可以的,所以我们想要让上述代码也能执行成功,就需要将泛型定义的修改为:

    [T <: Comparable[T]] ==> [T <%: Comparable[T]]

    其中T <% Comparable[T]的操作,其实是对T做了类型的内部转换或者功能的增强,把这种操作我们称之为scala类型参数(泛型)的视图界定,其本质是通过下面要学习的隐式转换函数来实现的。

2.3. 泛型的协变和逆变

首先需要明确的是,在java中等号左右两侧的泛型必须是要一直的,不可以出现泛型之间继承关系,纵然类型可以出现继承。
class Person{
   }
class Student extends Person{
   }
List<Person> list = new ArrayList<Person>(); //ok
不可以
List<Person> list = new ArrayList<Student>();
or
List<Student> list = new ArrayList<Person>();
scala是一个非常灵活的语言,java办不到的往往scala可以轻而易举的搞定。

当然默认情况下,scala也只能支持=左右两侧泛型是一直的,但是为了程序更加的灵活和通用,scala也支持泛型出现继承关系,所谓协变和逆变。
//泛型的协变和逆变
object _03GenericOps {
    def main(args: Array[String]): Unit = {
        val myList1:MyList[Person] = new MyList[Person]()
        val myList2:MyList[Person] = new MyList[Student]()//泛型的协变
        
        val myList3:MyList1[Student] = new MyList1[Person]()//泛型的逆变
    }
}

class MyList[+T] {//泛型协变的定义
}
class MyList1[-T] {//泛型逆变的定义
}

class Person {}
class Student extends Person{}

3. 隐式转换

3.0 隐式转换

  • 说明

    scala提供的能够将一种类型根据需要,自动转化成其他类型的操作方式,进而可以让原有的类型具备其没有的功能,丰富现有api,而不会对原有的代码造成过多的污染。

    这一点是scala程序非常优秀的一点设计,是java等其它语言所不具备的能力。其实在前面的视图界定案例中,视图界定就采用的是隐式转换将Int类型转化成为了RichInt,而RichInt扩展了Comparable接口,自然可以进行比较了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vMPRPeLp-1575337956277)(assets/1572934320555.png)]

这背后是通过一个叫做隐式转换函数的结构来时实现的。
  • 隐式转换函数

    现在有这样一个需求:

    val x:Int = 3.5

    要求这个赋值的变量,编译通过,显然正常情况下是不可以的,但是在scala中一切皆有可能,就需要用到隐式转换,本质是隐式转换函数。

    隐式转换函数就是在普通的函数前添加一个关键字implicit,即可完成。一般的结构为

    implicit def source2Target(source:Source):Target = {
        source ---> target 操作
    }
    

    这样,变量source就会在自己的作用域范围内自动的搜索是否有这样的隐式转换,如果有,则自动完成类型转换或者加强。

    implicit def double2Int(d:Double):Int = d.toInt
    val a:Int = 3.5
    println("a=" + a)
    
    

3.1. 利用隐士转换丰富现有类库的功能

利用隐士转换丰富现有类库的功能,举例说明,通过java.io.File来读取一个文件中的内容,但是File没有read的功能,如何能做到让File也具备流的读写功能?

在scala中的答案就是,隐式转换!
def main(args: Array[String]): Unit = {
    val file = new File("E:/data/hello.txt")
    file.read().foreach(println)
}

implicit def file2RichFile(file:File):RichFile = new RichFile(file)
//将file最后要转化为RichFile,才能具备read的功能
class RichFile(file:File) {
    def read() = Source.fromFile(file).getLines()
}

3.2. 引入隐士转换(在不同包下的话可以在主方法中导入包)

当开发出一个隐式转换函数时,该如何使用呢?
最简单的其实就是,只要在要转换的变量的作用域内能够访问的到,即可进行自动转换,不需要人工干预。
在同一个文件中定义的隐式转换,不需要做任何的干预,但是如果要导入其它类中定义的隐式转换,要想在本类中使用,就必须要向导包一样导入。只要在其作用域内导入,即可。同时这个导入的隐式转换因为和导包一样,也不存在对原有类的结构发生入侵。
我们在导入的时候,我们倾向于导入到最靠近变量的部分。越靠近变量,其作用域范围越小,影响越小。
object _03ImplicitOps {
    def main(args: Array[String]): Unit = {
        import _02ImplicitOps._
        val file = new File("E:/data/hello.txt")
        file.read().foreach(println)
    }
}

3.3. 隐式转换参数

一个函数的参数被implicit修饰,称该参数为隐式转换参数,这个隐式转换参数在传递的时候,可以传,有时候也可以不用传。
def main(args: Array[String]): Unit = {
    val list = List(3, -3, 0, -8, 45, 4)

    list.foreach(println)
    println("---------------")
    implicit val ord = new Ordering[Int](){
        override def compare(x: Int, y: Int) = {
            y.compareTo(x)
        }
    }
    //显示的传递隐式转换参数
    list.sorted(ord).foreach(println)
    println("-------隐式传递---------")
    //隐式传递隐式转换参数
    list.sorted.foreach(println)
}

说明:隐式转换参数,可以显示的指定,也可以隐式的指定,如果是隐式指定,就需要让该变量在其作用域内访问到被implicit关键字修饰的相关变量,自动进行赋值。

4. RPC和AKKA

不管是RPC还是AKKA,都是大数据体系中不同框架中所采用的通信框架。比如RPC就是大名鼎鼎的Hadoop中Hdfs里面NameNode,dataNode等等通信所采用的框架,AKKA在早起的Spark版本中(1.6一下)所采用的通信框架,1.6以上也开始采用基于Netty的RPC框架(其实自己基于RPC,模仿AKKA手写一个通信的框架)。
这些通信框架都是干嘛?通信!主要指的是进程间的消息的传递与处理。
RPC和AKKA便是这些通信框架中的两个典型代表。除此以外,还有非常多的进程间的通信框架。

eg:
WebService、Thirft、RMI等等。
WebService:在传统的j2ee中使用还是比较广泛。数据传输基于xml,客户端读取到请求的响应之后,就需要解析xml。比如进行天气情况的显示、航班信息的显示等等。
Thirft: 是apache的一个顶级项目,是一种接口描述语言和二进制通讯协议,它被用来定义和创建跨语言的服务。
RMI: remote method invocation.远程方法调用,是jdk中自带的一个进程间通信的框架,在java.rmi包下面。

4.1. RPC

RPC是Remote process call(远程过程调用),是IPC(Inter-Process Communication进程间调用)中的一种,IPC分为了两种,一种便是RPC,还有一种LPC(Local process call),LPC相当于RPC要简单的多,在大多数分布式系统中,多采用RPC来完成消息的传递。
RPC的工作原理是这样的。首先RPC底层通过TCP/IP进行消息的传递,采用客户端和服务端通信的方式。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lLgjavZJ-1575337956278)(assets/1572940241822.png)]
rpc的使用需要用到hadoop相关的依赖,导入maven依赖

<dependency>
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值