Scala:函数式语言

文章目录

1.Scala入门介绍

1.1 概述

Scala由马丁·奥德斯基(Martin Odersky)设计,是一门多范式的编程语言,一种类似java的编程语言 ,设计初衷是实现可伸缩的语言 、并集成面向对象编程和函数式编程的各种特性

1.2 特点

Scala是一门以Java虚拟机(JVM)为运行环境并将面向对象和函数式编程的最佳特性结合在一起的静态类型编程语言。
1)Scala是一门多范式的编程语言,Scala支持面向对象和函数式编程。
2)Scala源代码(.scala)会被编译成Java字节码(.class),然后运行于JVM之上,并可以调用现有的Java类库,实现两种语言的无缝对接。
3)Scala单作为一门语言来看,非常的简洁高效。
4)Scala在设计时,马丁·奥德斯基是参考了Java的设计思想,可以说Scala是源于Java,同时马丁·奥德斯基也加入了自己的思想,将函数式编程语言的特点融合到JAVA中。

1.3 Scala和Java关系

  • Scala可以使用java的类库和自己的类库
  • Scala编译完成后也是字节码,直接运行在JVM上
    在这里插入图片描述

1.4 环境搭建

1.4.1 window环境搭建

1.准备java环境
2.解压 scala.zip包
3.配置环境变量

  • SCALA_HOME
  • PATH

1.4.2 idea环境搭建

1.安装支持scala开发的插件(装完重启)
2.创建maven project
3.添加 scala 的支持
在这里插入图片描述
4.创建一个 object
在这里插入图片描述

2.变量和数据类型

2.1 注释

含义:用于注解说明解释程序的文字,注释提高了代码的阅读性

1.基本语法

(1)单行注释://
(2)多行注释:/* */
(3)文档注释:
			/**
			*
			*/

2.代码规范

(1)使用一次tab操作,实现缩进,默认整体向右边移动,用shift+tab整体向左移
(2)或者使用ctrl + alt + L来进行格式化
(3)运算符两边习惯性各加一个空格。比如:2 + 4 * 5。
(4)一行最长不超过80个字符,超过的请使用换行展示,尽量保持格式优雅

2.2 标识符的命名规范

含义:Scala对各种变量、方法、函数等命名时使用的字符序列称为标识符。即:凡是自己可以起名字的地方都叫标识符

1.命名规则

(1)以字母或者下划线开头,后接字母、数字、下划线
(2)以操作符开头,且只包含操作符(+ - * / # !等)
(3)第一种和第二种拼接,第一种在前,二者以下划线分隔
(4)用反引号``可包括任意字符串,即使是关键字(39个)也可以

2.案例参考

hello    // ok
hello12 // ok
1hello  // error
h-b      // error
x h      // error
h_4      // ok
_ab      // ok
Int      // ok , 因为在Scala Int是预定义的字符,不推荐
Float    // ok 
_        // error ,单独一个下划线不可以作为标识符
Abc      // ok
+*-      // ok
+a       // error,a需在前
$a		 // ok , 但不要让scala的标识符出现$,因为scala编译器会使用$
` `			//ok, 反引号,可包括任意字符串,定义标识符

3.Scala关键字(39个)

  • package, import, class, object, trait, extends, with, type, for
  • private, protected, abstract, sealed, final, implicit, lazy, override
  • try, catch, finally, throw
  • if, else, match, case, do, while, for, return, yield
  • def, val, var
  • this, super
  • new
  • true, false, null

2.3 变量

1.基本语法

var | val 变量名 [: 变量类型] = 变量值
说明:在Scala中声明一个变量时,可以不指定类型,编译器根据值确定

2.案例说明

(1)声明变量时,类型可以省略(编译器自动推导,即类型推导)
(2)类型确定后,就不能修改,说明Scala是强数据类型语言
(3)变量声明时,需要初始值

package com.jaffe.chapter03
object TestVar {

    def main(args: Array[String]): Unit = {

        //(1)声明变量时,类型可以省略(编译器自动推导,即类型推导)
        var age = 18
        age = 30

        //(2)类型确定后,就不能修改,说明Scala是强数据类型语言。
		//  age = "tom" // 错误

        //(3)变量声明时,需要初始值
		//  var name //错误
    }
}

(4)在声明/定义一个变量时,可以使用var或者val来修饰,var修饰的变量可改变,val修饰的变量不可改

object TestVar {
    def main(args: Array[String]): Unit = {

        var num1 = 10   // 可变
        val num2 = 20   // 不可变

        num1 = 30  // 正确
        //num2 = 100  //错误,因为num2是val修饰的
    }
}

(5)val修饰的变量在编译后,等同于加上final通过反编译看下底层代码

object TestVar {
    var num1 = 10   // 可变
    val num2 = 20   // 不可变

    def main(args: Array[String]): Unit = {
        num1 = 30  // 正确
        //num2 = 100  //错误,因为num2是val修饰的
    }
}

通过反编译软件,得到对应的底层的.class是

public final class TestVar$
{
    public static final MODULE$;
    private int num1;
    private final int num2;

(6)var修饰的对象引用可以改变,val修饰的则不可改变,但对象的状态(值)却是可以改变的。(比如:自定义对象、数组、集合等等)

object TestVar {
    def main(args: Array[String]): Unit = {

        // p1是var修饰的,p1的属性可以变,而且p1本身也可以变
        var p1 = new Person()
        p1.name = "zzh"
        p1 = null

        // p2是val修饰的,则p2的属性可以变,但是p2本身不可变(即p2的内存地址不能变)
        val p2 = new Person()
        p2.name="xiaolian"
		//p2 = null // 错误的,因为p2是val修饰的
    }
}

class Person{
    var name : String = "jaffe"
}

2.4 字符串输出

1.基本语法

(1)字符串,通过+号连接
(2)printf用法:字符串,通过%传值
(3)字符串,通过$引用

2.案例说明

package com.jaffe.chapter02
object TestCharType {
    def main(args: Array[String]): Unit = {
        var name: String = "jinlian"
        var age: Int = 18

        //(1)字符串,通过+号连接
        println(name + " " + age)

        //(2)printf用法字符串,通过%传值。
        printf("name=%s age=%d\n", name, age)

        //(3)字符串,通过$引用
        println(s"name=$name age=$age")

        println(
            s"""
             name=${name}
             age=${age}
             """
        )
    }
}

2.4.1 字符串插值

1.s插值(重要)

val a: Int = 10
val b: Int = 20
//s插值
val res: String = s"a=$a, b=$b"
     println(res)
//输出 a=10, b=20

2.raw插值

 var w = raw"\r \n \t"
    println(w)
 //输出 \r \n \t

2.4.2 字符串模板(多行字符串)

var c =
      """
        |111
        |222
        |333""".stripMargin

    println(c)
//输出
111
222
333

2.5 键盘输入

在编程中,需要接收用户输入的数据,就可以使用键盘输入语句来获取

1.基本语法

StdIn.readLine()、StdIn.readShort()、StdIn.readDouble()

2.案例说明

需求:可以从控制台接收用户信息,【姓名,年龄,薪水】

import scala.io.StdIn
object TestInput {
    def main(args: Array[String]): Unit = {

        // 1 输入姓名
        println("input name:")
        var name = StdIn.readLine()

        // 2 输入年龄
        println("input age:")
        var age = StdIn.readShort()

        // 3 输入薪水
        println("input sal:")
        var sal = StdIn.readDouble()

        // 4 打印
        println("name=" + name)
        println("age=" + age)
        println("sal=" + sal)
    }
}

Java输入语法

// 从键盘读数据
// 1. java: 使用System.in直接读, 做一些封装
val reader = new BufferedReader(new InputStreamReader(System.in))
val line: String = reader.readLine()
println(line)
// 2. java: jdk 1.5之后, Scanner
val scanner = new Scanner(System.in)
val line2: String = scanner.nextLine()
println(line2)

2.6 数据类型关系

在这里插入图片描述
Scala数据类型关系总结

  • Scala中一切数据都是对象,都是Any的子类
  • Scala中数据类型分为两大类:数值类型(AnyVal)、引用类型(AnyRef),不管是值类型还是引用类型都是对象
  • Scala数据类型仍然遵守低精度的值类型向高精度值类型自动转换(隐式转换)
  • Scala特殊的类型之Null,它只有一个实例就是Null,它是所有引用类型(AnyRef)的子类
  • Scala特殊类型之Nothing,是所有数据类型的子类,主要在一个函数没有正常返回值使用,因为这样我们可以把抛出的返回值,返回给任何的变量或者函数

2.7 整数类型(Byte、Short、Int、Long)

Scala的整数类型就是用于存放整数值的,比如12,30,3456等等

1.整型分类

数据类型描述
Byte [1]8位有符号补码整数。数值区间为 -128 到 127
Short [2]16位有符号补码整数。数值区间为 -32768 到 32767
Int [4]32位有符号补码整数。数值区间为 -2147483648 到 2147483647
Long [8]64位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807 = 2的(64-1)次方-1

2.案例说明

(1)Scala各整数类型有固定的表数范围和字段长度,不受具体操作的影响,以保证Scala程序的可移植性。

object TestDataType {
    def main(args: Array[String]): Unit = {

        // 正确
        var n1:Byte = 127
        var n2:Byte = -128

        // 错误
        // var n3:Byte = 128
        // var n4:Byte = -129
    }
}

(2)Scala的整型,默认为Int型,声明Long型,须后加‘l’或‘L’

object TestDataType {
    def main(args: Array[String]): Unit = {

        var n5 = 10
        println(n5)

        var n6 = 9223372036854775807L
        println(n6)
    }
}

(3)Scala程序中变量常声明为Int型,除非不足以表示大数,才使用Long

2.8 浮点类型(Float、Double)

Scala的浮点类型可以表示一个小数,比如123.4f,7.8,0.12等等

1.浮点型分类

数据类型描述
Float [4]32 位, IEEE 754标准的单精度浮点数
Double [8]64位 IEEE 754标准的双精度浮点数

2.案例实操

(1)Scala的浮点型常量默认为Double型,声明Float型常量,须后加‘f’或‘F’。

object TestDataType {
    def main(args: Array[String]): Unit = {

        // 建议,在开发中需要高精度小数时,请选择Double
        var n7 = 2.2345678912f
        var n8 = 2.2345678912

        println("n7=" + n7)
        println("n8=" + n8)
    }
}
//运行的结果
n7=2.2345679
n8=2.2345678912

2.9 字符类型(Char)

1.基本说明

字符类型可以表示单个字符,字符类型是Char,16位无符号Unicode字符(2个字节),区间值为U+0000到U+FFFF

2.案例说明

(1)字符常量是用单引号 ’ ’ 括起来的单个字符
(2)可以直接给Char赋一个整数,然后输出时,会按照对应的unicode字符输出

object TestCharType {
    def main(args: Array[String]): Unit = {

        //(1)字符常量是用单引号 ' ' 括起来的单个字符。
        var c1: Char = 'a'
        println("c1=" + c1)

        //(2)可以直接给Char赋一个整数,然后输出时,会按照对应的unicode字符输出
        println("c1码值=" + c1.toInt)
    }
}

(3)Char类型是可以进行运算的,相当于一个整数,因为它都对应有Unicode码。

object TestCharType {
    def main(args: Array[String]): Unit = {

        var c2: Char = 98 // 正确,因为直接将一个数值给char,编译器只判断是否越界
        var c3: Char = 'a' + 1 // 错误,Int高->char低,编译器判断类型

        var c4: Char = ('a' + 1).toChar
    }
}

(4)\t :一个制表位,实现对齐的功能
(5)\n :换行符
(6)\ :表示
(7)" :表示"

object TestCharType {
    def main(args: Array[String]): Unit = {
    
        //(4)\t :一个制表位,实现对齐的功能
        println("姓名\t年龄")
        
        //(5)\n :换行符
        println("西门庆\n潘金莲")
        
        //(6)\\ :表示\
        println("c:\\岛国\\avi")
        
        //(7)\" :表示"
        println("同学们都说:\"我最帅\"")
    }
}

2.10 布尔类型:Boolean

1.基本说明

(1)布尔类型也叫Boolean类型,Booolean类型数据只允许取值true和false
(2)boolean类型占1个字节

2.案例说明

object TestBooleanType {
    def main(args: Array[String]): Unit = {
        
        var isResult : Boolean = false
        var isResult2 : Boolean = true
    }
}

2.11 Unit类型、Null类型和Nothing类型

1.基本说明

数据类型描述
Unit表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()
Nullnull , Null 类型只有一个实例值null
NothingNothing类型在Scala的类层级的最底端;它是任何其他类型的子类型。当一个函数,我们确定没有正常的返回值,可以用Nothing来指定返回类型,这样有一个好处,就是我们可以把返回的值(异常)赋给其它的函数或者变量(兼容性)

2.案例说明

(1)Null类只有一个实例对象,Null类似于Java中的null引用。Null可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal)

object TestDataType {
    def main(args: Array[String]): Unit = {

        //null可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal)
        var n1: Int = null // 错误
        println("n1:" + n1)

        var cat = new Cat();
        cat = null	// 正确
    }
}
class Cat {

}

(2)Unit类型用来标识过程,也就是没有明确返回值的函数,类似于Java里的void。Unit只有一个实例——( ),这个实例也没有实质意义

object TestSpecialType {
    def main(args: Array[String]): Unit = {

        def sayOk : Unit = {// unit表示没有返回值,即void
            println("say ok")
        }
        sayOk
    }
}

(3)Nothing,可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方法不会正常返回,而且由于Nothing是其他任意类型的子类,他还能跟要求返回值的方法兼容。

object TestSpecialType {
    def main(args: Array[String]): Unit = {

        def test() : Nothing={
            throw new Exception()
        }
        test
    }
}

2.12 数值类型间转换

2.12.1 数值类型自动转换

当Scala程序在进行赋值或者运算时,精度小的类型自动转换为精度大的数值类型,这个就是自动类型转换(隐式转换)。数据类型按精度(容量)大小排序为:

在这里插入图片描述
1.基本说明

(1)自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度大的那种数据类型,然后再进行计算。
(2)当我们把精度大的数值类型赋值给精度小的数值类型时,就会报错,反之就会进行自动类型转换。
(3)(byte,short)和char之间不会相互自动转换。
(4)byte,short,char他们三者可以计算,在计算时首先转换为int类型。

2.案例实操

object TestValueTransfer {
    def main(args: Array[String]): Unit = {

        //(1)自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度大的那种数值类型,然后再进行计算。
        var n = 1 + 2.0
        println(n)  // n 就是Double

        //(2)当我们把精度大的数值类型赋值给精度小的数值类型时,就会报错,反之就会进行自动类型转换。
        var n2 : Long = 1L
        //var n3 : Int = n2 //错误,原因不能把高精度的数据直接赋值和低精度。

        //(3)(byte,short)和char之间不会相互自动转换。
        var n4 : Byte = 1
        //var c1 : Char = n4  //错误

        //(4)byte,short,char他们三者可以计算,在计算时首先转换为int类型。
        var n5 : Byte = 1
        var c2 : Char = 1
        // var n : Short = n5 + c2 //当n5 + c2 结果类型就是int
        // var n6 : Short = 10 + 90 //错误
        var n7 : Short = 100 //正确
    }
}

注意:Scala还提供了非常强大的隐式转换机制(隐式函数,隐式类等)

2.12.2 强制类型转换

1.基本说明

自动类型转换的逆过程,将精度大的数值类型转换为精度小的数值类型。使用时要加上强制转函数,但可能造成精度降低或溢出,格外要注意。
java : int num = (int)2.5
scala : var num : Int = 2.7.toInt

2.案例说明

(1)当进行数据的从大——>小,就需要使用到强制转换
(2)强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级

object TestForceTransfer {
    def main(args: Array[String]): Unit = {

        //(1)当进行数据的从大——>小,就需要使用到强制转换
        var n1: Int = 2.5.toInt // 这个存在精度损失
        
        //(2)强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级
        var r1: Int = 10 * 3.5.toInt + 6 * 1.5.toInt  // 10 *3 + 6*1 = 36
        var r2: Int = (10 * 3.5 + 6 * 1.5).toInt  // 44.0.toInt = 44
        
        println("r1=" + r1 + " r2=" + r2)
    }
}

(3)Char类型可以保存Int的常量值,但不能保存Int的变量值,需要强转
(4)Byte和Short类型在进行运算时,当做Int类型处理

object TestForceTransfer {
    def main(args: Array[String]): Unit = {

        //(3)Char类型可以保存Int的常量值,但不能保存Int的变量值,需要强转
        var c2: Char = 98 // 正确,因为直接将一个数值给char,编译器只判断是否越界
        var c3: Char = 'a' + 1 // 错误,Int高->char低,编译器判断类型
        var c4: Char = ('a' + 1).toChar


        //(4)Byte和Short类型在进行运算时,当做Int类型处理。
        var a : Short = 5
        // a = a-2 // 错误, Int->Short

        var b : Byte = 3
        // b = b + 4 // 错误,Int->Byte
    }
}

2.13 数值类型和String类型间转换

1.基本说明

在程序开发中,我们经常需要将基本数值类型转成String类型。或者将String类型转成基本数值类型。

2.案例说明

(1)基本类型转String类型(语法:将基本类型的值+"" 即可)
(2)String类型转基本数值类型(语法:s1.toInt、s1.toFloat、s1.toDouble、s1.toByte、s1.toLong、s1.toShort)

object TestStringTransfer {
    def main(args: Array[String]): Unit = {

        //(1)基本类型转String类型(语法:将基本类型的值+"" 即可)
        var str1 : String = true + ""
        var str2 : String = 4.5 + ""
        var str3 : String = 100 +""

        //(2)String类型转基本数值类型(语法:调用相关API)
        var s1 : String = "12"
        
        var n1 : Byte = s1.toByte
        var n2 : Short = s1.toShort
        var n3 : Int = s1.toInt
        var n4 : Long = s1.toLong
    }
}

3.注意事项

在将String类型转成基本数值类型时,要确保String类型能够转成有效的数据,比如我们可以把"123",转成一个整数,但是不能把"hello"转成一个整数。

3.运算符

运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等

在scala中, 没有真正的运算符, 所有的运算符其实都是对象的方法.
当调用方法的时候, 省略了 . 和 圆括号的时候, 方法就成了运算符了.

  1. 方法没有优先级
  2. 运算符有优先级
  3. 运算符还有结合性(左结合和右结合)

3.1 算术运算符

1.基本语法

运算符运算范例结果
+正号+33
-负号b=4; -b-4
+5+510
-6-42
*3*412
/5/51
%取模(取余)7%52
+字符串相加“He”+”llo”“Hello”

(1)对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。
(2)对一个数取模a%b,和Java的取模规则一样。

2.案例实操

object TestArithmetic {
    def main(args: Array[String]): Unit = {

        //(1)对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。
        var r1: Int = 10 / 3 // 3
        println("r1=" + r1)

        var r2: Double = 10 / 3 // 3.0
        println("r2=" + r2)

        var r3: Double = 10.0 / 3 // 3.3333
        println("r3=" + r3)
        println("r3=" + r3.formatted("%.2f")) // 含义:保留小数点2位,使用四舍五入

        //(2)对一个数取模a%b,和Java的取模规则一样。
        var r4 = 10 % 3 // 1
        println("r4=" + r4)
    }
}

3.2 关系运算符(比较运算符)

1.基本语法

运算符运算范例结果
==相等于4==3false
!=不等于4!=3true
<小于4<3false
>大于4>3true
<=小于等于4<=3false
>=大于等于4>=3true

2.案例实操

object TestRelation {
    def main(args: Array[String]): Unit = {

        // 测试:>、>=、<=、<、==、!=
        var a: Int = 2
        var b: Int = 1

        println(a > b)      // true
        println(a >= b)     // true
        println(a <= b)     // false
        println(a < b)      // false
        println("a==b" + (a == b))    // false
        println(a != b)     // true
    }
}

3.3 逻辑运算符

1.基本语法

用于连接多个条件(一般来讲就是关系表达式),最终的结果也是一个Boolean值。
假定:变量A为true,B为false

运算符描述实例
&&逻辑与(A && B) 运算结果为 false
||逻辑或(A || B)运算结果为 true
!逻辑非!(A && B) 运算结果为 true

2.案例实操

object TestLogic {
    def main(args: Array[String]): Unit = {

        // 测试:&&、||、!
        var a = true
        var b = false

        println("a&&b=" + (a && b))     // a&&b=false
        println("a||b=" + (a || b))     // a||b=true
        println("!(a&&b)=" + (!(a && b))) // !(a&&b)=true
    }
}

3.4 赋值运算符

1.基本语法

赋值运算符就是将某个运算后的值,赋给指定的变量

运算符描述实例
=简单的赋值运算符,将一个表达式的值赋给一个左值C = A + B 将 A + B 表达式结果赋值给 C
+=相加后再赋值C += A 等于 C = C + A
-=相减后再赋值C -= A 等于 C = C - A
*=相乘后再赋值C *= A 等于 C = C * A
/=相除后再赋值C /= A 等于 C = C / A
%=求余后再赋值C %= A 等于 C = C % A
<<=左移后赋值C <<= 2 等于 C = C << 2
>>=右移后赋值C >>= 2 等于 C = C >> 2
&=按位与后赋值C &= 2 等于 C = C & 2
^=按位异或后赋值C ^= 2 等于 C = C ^ 2
|=按位或后赋值C |= 2 等于 C = C | 2

注意:Scala中没有++、–操作符,需要通过+=、-=来实现同样的效果

2.案例实操

object TestAssignment {   
   def main(args: Array[String]): Unit = {
        
        var r1 = 10
        
        r1 += 1 // 没有++
        r1 -= 2 // 没有--
    }
}

3.5 位运算符

1.基本语法

运算符描述实例
&按位与运算符(a & b) 输出结果 12 ,二进制解释: 0000 1100
|按位或运算符(a | b) 输出结果 61 ,二进制解释: 0011 1101
^按位异或运算符(a ^ b) 输出结果 49 ,二进制解释: 0011 0001
~按位取反运算符(~a ) 输出结果 -61 ,二进制解释: 1100 0011, 在一个有符号二进制数的补码形式
<<左移动运算符a << 2 输出结果 240 ,二进制解释: 1111 0000
>>右移动运算符a >> 2 输出结果 15 ,二进制解释: 0000 1111
>>>无符号右移A >>>2 输出结果 15, 二进制解释: 0000 1111

2.案例实操

object TestPosition {
    def main(args: Array[String]): Unit = {

        // 测试:1000 << 1 =>10000
        var n1 :Int =8

        n1 = n1 << 1
        println(n1)
    }
}

4.流程控制

4.1 分支控制if-else

让程序有选择的的执行,分支控制有三种:单分支、双分支、多分支
在Scala中任何的语法结构都有值:

  1. 针对 if语句来说, if语句值, 是执行的分支中的最后一行代码的值
  2. 赋值语句( = , +=, …)作为一个语法结构, 他也有值. 但是他的值是 Unit类型. ()

4.1.1 单分支

1.基本语法

if (条件表达式) {
执行代码块
}
说明:当条件表达式为ture时,就会执行{ }的代码

2.案例实操

需求:输入人的年龄,如果该同志的年龄大于18岁,则输出“age > 18”

object TestIfElse {
    def main(args: Array[String]): Unit = {
        
        println("input age:")
        var age = StdIn.readShort()

        if (age > 18){
            println("age>18")
        }
    }
}

4.1.2 双分支

1.基本语法

if (条件表达式) {
执行代码块1
} else {
执行代码块2
}

2.案例实操
需求:输入年龄,如果年龄大于18岁,则输出“age >18”。否则,输出“age <= 18”。

object TestIfElse {
    def main(args: Array[String]): Unit = {

        println("input age:")
        var age = StdIn.readShort()

        if (age > 18){
            println("age>18")
        }else{
            println("age<=18")
        }
    }
}

4.1.3 多分支

1.基本语法
if (条件表达式1) {
执行代码块1
}
else if (条件表达式2) {
执行代码块2
}
……
else {
执行代码块n
}

2.案例实操

(1)需求:岳小鹏参加Scala考试,他和父亲岳不群达成承诺:如果,成绩为100分时,奖励一辆BM;成绩为(80,99]时,奖励一台iphone;其它时,什么奖励也没有。

object TestIfElse {    
    def main(args: Array[String]): Unit = {

        println("请输入成绩")
        val grade = StdIn.readInt()

        if (grade == 100){
            println("成绩为100分,奖励一辆BM")
        }else if (grade > 80 && grade <= 90){
            println("奖励一台iphone")
        }else{
            println("什么奖励也没有")
        }
    }
}

(2)需求:Scala中if else表达式其实是有返回值的,具体返回值取决于满足条件的代码体的最后一行内容。

object TestIfElse {
    def main(args: Array[String]): Unit = {

        println("input your age")
        var age = StdIn.readInt()

        var res = if(age > 18){
            "您以成人"
        }else{
            "小孩子一个"
        }
        println(res)
    }
}

(3)注意:如果大括号{}内的逻辑代码只有一行,大括号可以省略。
Scala中是没有三元运算符,因为可以这样简写

object TestIfElse {
    def main(args: Array[String]): Unit = {

        // Java
		// int result = flag?1:0

        // Scala
        var flag:Boolean = true
        var result = if(flag) 1 else 0
        println(result)
    }
}

4.2 嵌套分支

在一个分支结构中又完整的嵌套了另一个完整的分支结构,里面的分支的结构称为内层,分支外面的分支结构称为外层分支。嵌套分支不要超过3层。

1.基本语法

if(){
        if(){
       }else{
    }	
}

2.案例实操

参加百米运动会,根据性别提示进入男子组或女子组。如果是女子组,用时8秒以内进入决赛,否则提示淘汰。

object TestIfElse {
    def main(args: Array[String]): Unit = {

        println("输入性别:")
        var gender = StdIn.readChar()

        if (gender == '男'){
            println("男子组")
        }else{
            println("女子组")
            
            println("输入成绩:")
            var grade = StdIn.readDouble()

            if (grade > 8.0){
                println("你被淘汰了")
            }else{
                println("成功晋级")
            }
        }
    }
}

4.3 Switch分支结构

在 java中:

- `if else`

- `switch`

  1. 限制太多. 

     ```java
     switch(值){
         case 常量: 
             break;
         case 常量: 
             
     }
     ```

     `值`的类型是有限制: `byte, char, int, short, enum, String(1.7新增)`

     `case`后面必须是常量

  2. `case`穿透问题

     忘记添加`break`, 则会导致`case`穿透

在Scala中没有Switch,而是使用模式匹配来处理。
待续。。。

4.4 For循环控制

Scala也为for循环这一常见的控制结构提供了非常多的特性,这些for循环的特性被称为for推导式或for表达式

4.4.1 范围数据循环方式1

1.基本语法

for(i <- 1 to 3){
  print(i + " ")
}
println()

(1)i 表示循环的变量,<- 规定to
(2)i 将会从 1-3 循环,前后闭合

2.案例实操

需求:输出10句 “宋宋,喜欢海狗人参丸”

object TestFor {
    def main(args: Array[String]): Unit = {

        for(i <- 1 to 10){
            println("宋宋,喜欢海狗人参丸"+i)
        }
    }
}

4.4.2 范围数据循环方式2

1.基本语法

for(i <- 1 until 3) {
  print(i + " ")
}
println()

(1)这种方式和前面的区别在于i是从1到3-1
(2)即使前闭合后开的范围

2.案例实操

需求:输出10句 “宋宋,喜欢海狗人参丸”

object TestFor 
    def main(args: Array[String]): Unit = {

        for(i <- 1 until 10+1){
            println("宋宋,喜欢海狗人参丸"+i)
        }
    }
}

4.4.3 循环守卫

1.基本语法

for(i <- 1 to 3 if i != 2) {
  print(i + " ")
}
println()

说明:
(1)循环守卫,即循环保护式(也称条件判断式,守卫)。保护式为true则进入循环体内部,为false则跳过,类似于continue。
(2)上面的代码等价

for (i <- 1 to 3){
	if (i != 2) {
		print(i + "")
	}
}

2.案例实操

需求:输出1到10中,不等于5的值

object TestFor {
    def main(args: Array[String]): Unit = {

        for (i <- 1 to 10 if i != 5) {
            println(i + "")
        }
    }
}

4.4.4 循环步长

1.基本语法

for (i <- 1 to 10 by 2) {
      println("i=" + i)
    }

说明:by表示步长

for (i <- 1 to (10,2)) {
      println("i=" + i)
    }

2.案例实操

需求:输出1到10以内的所有奇数

for (i <- 1 to 10 by 2) {
println("i=" + i)
}
输出结果
i=1
i=3
i=5
i=7
i=9

4.4.5 嵌套循环

1.基本语法

for(i <- 1 to 3; j <- 1 to 3) {
    println(" i =" + i + " j = " + j)
}

说明:没有关键字,所以范围后一定要加;来隔断逻辑
上面的代码等价

for (i <- 1 to 3) {
    for (j <- 1 to 3) {
        println("i =" + i + " j=" + j)
    }
}

4.4.6 引入变量

1.基本语法

for(i <- 1 to 3; j = 4 - i) {
    println("i=" + i + " j=" + j)
}

说明:
(1)for推导式一行中有多个表达式时,所以要加;来隔断逻辑
(2)for推导式有一个不成文的约定:当for推导式仅包含单一表达式时使用圆括号,当包含多个表达式时,一般每行一个表达式,并用花括号代替圆括号,如下

for {
    i <- 1 to 3
j = 4 - i
} {
    println("i=" + i + " j=" + j)
}

2.案例实操

上面的代码等价于

for (i <- 1 to 3) {
    var j = 4 - i
    println("i=" + i + " j=" + j)
}

4.4.7 循环返回值

1.基本语法

val res = for(i <- 1 to 10) yield i
println(res)

说明:将遍历过程中处理的结果返回到一个新Vector集合中,使用yield关键字

2.案例实操

需求:将原数据中所有值乘以2,并把数据返回到一个新的集合中。

object TestFor {
    def main(args: Array[String]): Unit = {

        var res = for( i <-1 to 10 ) yield {
            i * 2
        }
        println(res)
    }
}
输出结果:
Vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)

4.5 While循环控制

1.基本语法

循环变量初始化
while (循环条件) {
循环体(语句)
循环变量迭代
}
说明:
(1)循环条件是返回一个布尔值的表达式
(2)while循环是先判断再执行语句
(3)与if语句不同,while语句没有返回值,即整个while语句的结果是Unit类型()
(4)因为while中没有返回值,所以当要用该语句来计算并返回结果时,就不可避免的使用变量,而变量需要声明在while循环的外部,那么就等同于循环的内部对外部的变量造成了影响,也就违背了函数式编程的重要思想(输入=>函数=>输出,不对外界造成影响),所以不推荐使用,而是推荐使用for循环。

2.案例实操

需求:输出10句 “宋宋,喜欢海狗人参丸”

object TestWhile {  
    def main(args: Array[String]): Unit = {

        var i = 0

        while (i < 10) {
            println("宋宋,喜欢海狗人参丸" + i)
            i += 1
        }
    }
}

4.6 do…while循环控制

1.基本语法

循环变量初始化;
do{
循环体(语句)
循环变量迭代
} while(循环条件)
说明
(1)循环条件是返回一个布尔值的表达式
(2)do…while循环是先执行,再判断

2.案例实操

需求:输出10句 “宋宋,喜欢海狗人参丸”

object TestWhile {
    def main(args: Array[String]): Unit = {

        var i = 0
        do {
            println("宋宋,喜欢海狗人参丸" + i)
            i += 1
        } while (i < 10)
    }
}

4.7 多重循环控制

1.基本说明

(1)将一个循环放在另一个循环体内,就形成了嵌套循环。其中,for,while,do…while均可以作为外层循环和内层循环。【建议一般使用两层,最多不要超过3层】
(2)实质上,嵌套循环就是把内层循环当成外层循环的循环体。当只有内层循环的循环条件为false时,才会完全跳出内层循环,才可结束外层的当次循环,开始下一次的循环。
(3)设外层循环次数为m次,内层为n次,则内层循环体实际上需要执行m*n次。

2.案例实操

需求:打印出九九乘法表

object TestWhile {
    def main(args: Array[String]): Unit = {

        var max = 9

        for (i <- 1 to max) {

            for (j <- 1 to i) {
                print(j + "*" + i + "=" + (i * j) + "\t")
            }

            println()
        }
    }
}

输出结果:
在这里插入图片描述

4.8 While循环中断

1.基本说明

Scala内置控制结构特地去掉了break和continue,是为了更好的适应函数式编程,推荐使用函数式的风格解决break和continue的功能,而不是一个关键字。scala中使用breakable控制结构来实现break和continue功能。

2.案例实操

需求一:循环遍历10以内的所有数据,数值为5,结束循环(break)

import util.control.Breaks._
object TestBreak {
    def main(args: Array[String]): Unit = {

        var n = 1

        breakable {

            while (n < 10) {

                println("n=" + n)
                n += 1

                if (n == 5) {
                    break()
                }
            }
        }

        println("exit")
    }
}

需求二:循环遍历10以内的所有数据,奇数打印,偶数跳过(continue)

import util.control.Breaks._
object TestBreak {
    def main(args: Array[String]): Unit = {

        var n = 0

        while (n < 10) {

            breakable {

                n += 1

                if (n % 2 != 0) {
                    println(n)
                } else {
                    println("continue")
                    break()
                }
            }
        }
    }
}

3.注意事项

(1)break:breakable放在循环外
(2)continue:breakable放在循环内

5.函数式编程

1.面向对象编程

解决问题,分解对象,行为,属性,然后通过对象的关系以及行为的调用来解决问题。
对象:用户
行为:登录、连接jdbc、读取数据库
属性:用户名、密码
Scala语言是一个完全面向对象编程语言。万物皆对象

2.函数式编程

解决问题时,将问题分解成一个一个的步骤,将每个步骤进行封装(函数),通过调用这些封装好的步骤,解决问题。
例如:请求->用户名、密码->连接jdbc->读取数据库
Scala语言是一个完全函数式编程语言。万物皆函数

3.在Scala中函数式编程和面向对象编程融合在一起了

4.标配

  • 高阶函数 :如果一个函数a接受一个或多个函数作为参数,或返回值是函数,函数a就叫做高阶函数
  • 闭包
  • 柯里化

5.1 函数基本语法

1.基本语法

在这里插入图片描述

2.案例实操

需求:定义一个函数,实现将传入的名称打印出来。

object TestFunction {
    def main(args: Array[String]): Unit = {
        // 1 函数声明
        def f(arg: String): Unit = {
            println(arg)
        }

        // 2 函数调用
        // 函数名(参数)
        f("hello world")
    }
}

5.2 函数和方法的区别

  1. 定义方式不一样
    a: 方法定义
    def 方法名(参数类别): 返回值类型 = { // 方法的实现}

     def foo(a: Int, b:Int) = {
     
     }
    

    b: 函数定义
    (参数列表) => { //函数体}
    (a: Int, b:Int) => a + b

  2. 有匿名函数, 但是没有匿名方法
    匿名函数 (a: Int, b:Int) => a + b

  3. 函数也可以有名字
    val/var f: (Int, Int) => Int = (a: Int, b: Int) => a + b
    f就是函数名

  4. 方法调用的时候, 如果参数只有一个或者没有参数则圆括号可以省略
    函数的圆括号不能省略

    // foo2是方法
    MethodFunction foo2 10
    MethodFunction.foo2(10)
    this foo2 10

    // f1是函数, 则调用的不能省略圆括号
    val f1 = () => println(“无参函数…”)
    f1()

  5. 函数可以作为值传递和作为返回值返回, 但是方法不行
    换句话说, 在给高阶函数传递参数的时候, 只能传函数, 不能传方法.

  6. 在使用的时候, 不产生歧义的情况下, scala会自动的根据需要把方法转成函数!!!
    手动转:
    有方法 add10
    转函数: val a = add10 _

     ''
    
  7. 方法可以重载/覆写, 函数不能重载/覆写

  8. 以后使用的时候, 不用关注他们的区别, 就把函数和方法当成一个东西来时候.
    如果发现编译不通过, 就改成比较完整的写法

5.3 函数声明

1.函数声明

(1)函数1:无参,无返回值
(2)函数2:无参,有返回值
(3)函数3:有参,无返回值
(4)函数4:有参,有返回值
(5)函数5:多参,无返回值

2.案例实操

package com.jaffe.chapter06
object TestFunctionDeclare {
    def main(args: Array[String]): Unit = {

        // 函数1:无参,无返回值
        def test(): Unit ={
            println("无参,无返回值")
        }
        test()

        // 函数2:无参,有返回值
        def test2():String={
            return "无参,有返回值"
        }
        println(test2())

        // 函数3:有参,无返回值
        def test3(s:String):Unit={
            println(s)
        }
        test3("jinlian")

        // 函数4:有参,有返回值
        def test4(s:String):String={
            return s+"有参,有返回值"
        }
        println(test4("hello "))

        // 函数5:多参,无返回值
        def test5(name:String, age:Int):Unit={
            println(s"$name, $age")
        }
        test5("dalang",40)
    }
}

5.4 函数参数

1.注意事项

  • 不可以直接给可变参数传递数组

2.案例实操

(1)可变参数
(2)如果参数列表中存在多个参数,那么可变参数一般放置在最后
(3)参数默认值
(4)带名参数

object TestFunction {
    def main(args: Array[String]): Unit = {

        // (1)可变参数
        def test( s : String* ): Unit = {
            println(s)
        }

        // 有输入参数:输出 Array
        test("Hello", "Scala")

        // 无输入参数:输出List()
        test()

        // (2)如果参数列表中存在多个参数,那么可变参数一般放置在最后
        def test2( name : String, s: String* ): Unit = {
            println(name + "," + s)
        }
        /*
        可变参数一般放置在最后
        def test2( s: String*,name : String ): Unit = {
            println(name + "," + s)
        }
        */
        test2("jinlian", "dalang")

        // (3)参数默认值
        def test3( name : String, age : Int = 30 ): Unit = {
            println(s"$name, $age")
        }

        // 如果参数传递了值,那么会覆盖默认值
        test3("jinlian", 20)

        // 如果参数有默认值,在调用的时候,可以省略这个参数
        test3("dalang")


        def test4( sex : String = "男", name : String ): Unit = {
            println(s"$name, $sex")
        }

        // scala函数中参数传递是,从左到右
        // 一般情况下,将有默认值的参数放置在参数列表的后面
//        test4("wusong")

        //(4)带名参数
        test4(name="ximenqing")
    }
}

5.4.1 扩展

  1. 函数的形参参数的默认情况都是 val的, 常量! 如果想改, 则应该自己重新声明一个新的 变量, 更改自己的新的变量
 def foo(a: Int) = {
     println(a)
     a = 100  // 错误.  a是val的, 不能修改
     var b = a // 定义新的变量,去修改新定义的变量
     b = 100
 }
  1. 可以在函数内定义新的函数.

    public void foo(int a){
        a = 100;
    }
    

    注意: 在函数内定义的函数, 一般需要先定义再调用!!!

  2. 定义函数的时候, 可以给函数设置默认值, 当传递参数的时候, 如果没有给这个有默认值的传递,则会使用默认值作为这个参数的值.

    def add(a: Int, b: Int = 100) = a + b
    
  3. 命名参数

    位置参数/命名参数

  def foo(a: Int = 10, b: Int) = {}
  foo(b = 100)

注意:

  1. 默认值出现在函数声明的处. 位置参数出现在函数调用的地方
  2. 命名参数和默认值会配合起来使用
  1. 可变参数

    def foo(ss: Int*) = {
        // 把ss当做一个数组(集合)来使用
        /*var sum = 0
            for (i <- ss) {
                sum += i
            }
            sum*/
        ss.sum
    }
    

    注意:

    1. 在java中可以把数组直接传给可变参数

    2. 在scala中, 不能把数组直接传给可变参数

    3. 需要把数组展开之后, 再传foo(arr:_*)

5.5 函数至简原则

函数至简原则:能省则省

1.至简原则细节
1.如果没有return, 则自动把最后一行的值返回
2.如果函数实现只有一行代码, 则 { }可以省略
3.如果内部没有使用return, 则返回值的类型也可以省略,scala编译器会根据最后一行得到值的类型进行自动推导
4.如果在调用函数的时候, 如果没有参数则圆括号可以省略

关于返回值:
5. 如果没有return, 则自动把最后一行的值返回
6. 如果有return, 则代码执行到return会结束函数, 返回return后面的值.
这个时候返回值类型就 不能推导了!!!
7.如果函数的返回值声明为 Unit, 那么在函数内部, 不管你return的值是什么,
返回的就用于是Unit
8. 如果你的返回值是 Unit, 那么这个时候,其实可以省略掉 :Unit=

关于声明:
9. 其实在声明的时候, 如果参数的个数是 0, 那么声明的时候, 圆括号也可以省略.
这个时候调用的时候, 圆括号就必须省略!!!

2.案例实操

object TestFunction {
    def main(args: Array[String]): Unit = {

        // 0)函数标准写法
        def f1( s : String ): String = {
            return s + " jinlian"
        }
        println(f1("Hello"))

        // 至简原则:能省则省

        //(1) return可以省略,scala会使用函数体的最后一行代码作为返回值
        def f2( s : String ): String = {
            s + " jinlian"
        }
        println(f2("Hello"))

        // 如果函数名使用return关键字,那么函数就不能使用自行推断了,需要声明返回值类型
        /*
        def f22(s:String)={
            return "jinlian"
        }
        */
        
        //(2)返回值类型如果能够推断出来,那么可以省略
        def f3( s : String ) = {
            s + " jinlian"
        }

        println(f3("Hello"))

        //(3)如果函数体只有一行代码,可以省略花括号
        //def f4(s:String) = s + " jinlian"
        //def f4(s:String) = "jinlian"
        def f4() = " dalang"

        // 如果函数无参,但是声明参数列表,那么调用时,小括号,可加可不加。
        println(f4())
        println(f4)

        //(4)如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
        def f5 = "dalang"
        // val f5 = "dalang"

        println(f5)

        //(5)如果函数明确声明unit,那么即使函数体中使用return关键字也不起作用
        def f6(): Unit = {
            //return "abc"
            "dalang"
        }
        println(f6())

        //(6)scala如果想要自动推断无返回值,可以省略等号
        // 将无返回值的函数称之为过程
        def f7() {
            "dalang"
        }
        println(f7())

        //(7)如果不关心名称,只关系逻辑处理,那么函数名(def)可以省略
        //()->{println("xxxxx")}
        val f = (x:String)=>{"wusong"}

        // 万物皆函数 : 变量也可以是函数
        println(f("ximenqing"))

        //(8)如果函数明确使用return关键字,那么函数返回就不能使用自行推断了,需要声明返回值类型
        def f8() :String = {
            return "ximenqing"
        }
        println(f8())

    }
}

5.6 高阶函数

1.定义

参数有函数或者返回值是函数的函数就是高阶函数!!!

2.案例实操

  1. foreach
  2. map
  3. reduce
  4. filter
  • 1.foreach案例
(1) foreach(arr, (a: Int) => println(a))
(2)foreach(arr, x => {
            println(x)
            println(x * x)
            println(x * x * x)
        })     
(3)foreach(arr, println)

   def foreach(arr: Array[Double], op: Double => Unit) = {
        // 可以遍历, 但是你得给我个函数, 我遍历到元素之后, 我去调用这个函数
        for (ele <- arr) {
            op(ele)
        }
    }
  • 2.map案例
// map:映射,一进一出
def map(arr: Array[Int], op: Int => Double): Array[Double] = {
        // for推导
        for (ele <- arr) yield op(ele)
    }


val arr = Array(3, 5, 70, 6, 10, 20)
val arr1 = map(arr, x => x + 10)
foreach(arr1, println)
  • 3.reduce案例
def reduce(arr: Array[Int], op: (Int, Int) => Int): Int = {
    if(arr.length <= 0) return 0
    // 聚合操作
    // lastResult 表示上一次的聚合结果
    var lastResult = arr(0)
    for (i <- 1 until arr.length) {
        lastResult = op(lastResult, arr(i))
    }
    lastResult
}


val arr1 = Array(30, 50, 70, 60, 10, 20)
println(reduce(arr1, (last, current) => last + current))


  • 4.filter案例
/*
    过滤操作:
        只留下来满足  条件  的元素, 不满足的去掉.
        关于条件: 应该是一个 Int => Boolean 值
        最后返回的是过滤后的数组
     */
    def filter(arr: Array[Int], condition: Int => Boolean) = {
        // for 推导
        for (ele <- arr if condition(ele)) yield ele
    }

val arr = Array(3, 5, 70, 6, 10, 20)
val arr1 = filter(arr, x => x % 2 == 0)
val arr1 = filter(arr, _ % 2 == 0)
foreach(arr1, println)

5.7 匿名函数

1.说明

没有名字的函数就是匿名函数,可以直接通过函数字面量(表达式)来设置匿名函数,函数字面量定义格式如下:
在这里插入图片描述
2.案例实操

  1. 作为值存储在变量中

    val f = (a: Int) => a + 10
    val f1 = (a: Int, b:Int) => a + b
    val f2 = () => println(".....")
    
  2. 给高阶函数传值

    // 定义高阶函数
    def foo(op: (Int, Int) => Int) = op(10, 20)
    
    val r = foo( (a: Int, b:Int) => a + b  )  // 30
    
    val r1 = foo( (a, b) => a + b )
    
    val r2 = foo( _ + _ )
    

5.8 函数柯里化&闭包

1.说明

函数柯里化:将一个参数列表的多个参数转化成多个参数列表的过程

闭包:一个匿名函数和这个匿名函数所处的环境就叫闭包,闭包可以延长外部局部变量的声明周期

2.案例实操

(1)闭包

def foo1(a: Int) = {
        (b: Int) => a + b
    }
val f: Int => Int = foo1(10)
        println(f(20)) //30
        println(f(30)) //40


(2)柯里化
object TestFunction {

  val sum = (x: Int, y: Int, z: Int) => x + y + z

  val sum1 = (x: Int) => {
    y: Int => {
      z: Int => {
        x + y + z
      }
    }
  }

  val sum2 = (x: Int) => (y: Int) => (z: Int) => x + y + z

  def sum3(x: Int)(y: Int)(z: Int) = x + y + z


  def main(args: Array[String]): Unit = {

    sum(1, 2, 3)
    sum1(1)(2)(3)
    sum2(1)(2)(3)
    sum3(1)(2)(3)
    
  }
}

5.9 递归

1.说明

一个函数/方法在函数/方法体内又调用了本身,我们称之为递归调用

2.案例实操

object TestFunction {
    def main(args: Array[String]): Unit = {

        // 阶乘
        // 递归算法
        // 1) 方法调用自身
        // 2) 方法必须要有跳出的逻辑
        // 3) 方法调用自身时,传递的参数应该有规律
        // 4) scala中的递归必须声明函数返回值类型

        println(test(5))
    }

    def test( i : Int ) : Int = {
        if ( i == 1 ) {
            1
        } else {
            i * test(i-1)
        }
    }
}

5.10 控制抽象

1. 名调用

   一般如果接受的是一个无参的函数的情况, 可以改成名调用

   def foo(a: () => Int) = {
       a()
       a()
       a()
   }
   // 使用名调用进行改造
   
   def foo(a: => Int) = {
       a
       a
       a
   }

2. 值调用

 def foo1(a: Int) = {
        a
        a
        a
    }

5.11 惰性求值

1.说明

当函数返回值被声明为lazy时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数。

注意: 惰性求值只能用在val上

2.案例实操

def main(args: Array[String]): Unit = {

    lazy val res = sum(10, 30)
    println("----------------")
    println("res=" + res)
}

def sum(n1: Int, n2: Int): Int = {
    println("sum被执行。。。")
    return n1 + n2
}

输出结果:
----------------
sum被执行。。。
res=40

6.面向对象

6.1 Scala包

1.基本语法
package 包名

2.Scala包的三大作用(和Java一样)
(1)区分相同名字的类
(2)当类很多时,可以很好的管理类
(3)控制访问范围

6.1.1 包的命名

1.命名规则
只能包含数字、字母、下划线、小圆点.,但不能用数字开头,也不要使用关键字。

2.案例实操
demo.class.exec1 //错误,因为 class 关键字
demo.12a //错误,数字开头

3.命名规范
一般是小写字母+小圆点
com.公司名.项目名.业务模块名

4.案例实操
com.jaffe.oa.model
com.jaffe.oa.controller
com.sohu.bank.order

6.1.2 包说明

1.说明
scala有两种包的管理风格,一种方式和java的包管理风格相同,每个源文件一个包,包名用“.”进行分隔以表示包的层级关系,如com.jaffe.scala。另一种风格,通过嵌套的风格表示层级关系,如下

package com{
	package jaffe{
		package scala{

		}
	}
}

第二种风格有以下特点:
(1)一个源文件中可以声明多个package
(2)子包中的类可以直接访问父包中的内容,而无需导包

2.案例实操

package com {

  import com.jaffe.Inner //父包访问子包需要导包

  object Outer {
    val out: String = "out"

    def main(args: Array[String]): Unit = {
      println(Inner.in)
    }
  }

  package jaffe {

    object Inner {
      val in: String = "in"

      def main(args: Array[String]): Unit = {
        println(Outer.out) //子包访问父包无需导包
      }
    }
  }
}

package other {
  
}

6.1.3 包对象

公共方法的处理:

java中一般搞工具类, 在工具类中写静态方法. 因为java中所有的方法都需要依附于类或者对象

scala中可以为每个包定义一个同名的包对象,定义在包对象中的成员,作为其对应包下所有class和object的共享变量,可以被直接访问。

1.定义

package com.jaffe.scala

package object pack {
    def foo1() = {
        println("foo...")
    }
    def eat1() = {
        println("eat...")
    }
}
//在`com.jaffe.scala.pack`包下所有的类可以直接使用这些方法.

2.说明
(1)若使用java的包管理风格,则包对象一般定义在其对应包下的package.scala文件中,包对象名与包名保持一致。

(2)如采用嵌套方式管理包,则包对象可与包定义在同一文件中,但是要保证包对象与包声明在同一作用域中。

package com {

  object Outer {
    val out: String = "out"

    def main(args: Array[String]): Unit = {
      println(name)
    }
  }
}

package object com {
  val name: String = "com"
}

6.1.4 导包说明

1.说明

  1. 导入和java一样, 在文件最顶层导入, 整个文件的任何位置都可以使用(掌握)

    import java.util.HashMap
    
  2. scala中其实在代码任何位置都可以导入. (掌握)

    def main(args: Array[String]): Unit = {
        
        import java.io.FileInputStream
        // 只能在main函数中使用
        val is = new FileInputStream("c:/users.json")
    }
    
  3. 导入类的时候, 防止和现有的冲突, 可以给类起别名

    import java.io.{FileInputStream => JFI}
    
  4. 如何批量导入(掌握)

    import java.io._  // 导入java.io包下所有的类   (java是*)
    
  5. 屏蔽某个类

    import java.io.{FileInputStream => _, _}  //屏蔽 FileInputStream 
    
  6. java中有静态导入

    只能导入静态成员.

    scala

    import java.lang.Math._	
    
  7. scala还支持导入对象的成员(掌握)

    val u = new User
    // 把对象u的成员导入
    import u._
    foo()
    eat()
    
导包说明
import com.jaffe.Fruit引入com.jaffe包下Fruit(class和object)
import com.jaffe._引入com.jaffe下的所有成员
import com.jaffe.Fruit._引入Fruit(object)的所有成员
import com.jaffe.{Fruit,Vegetable}引入com.jaffe下的Fruit和Vegetable
import com.jaffe.{Fruit=>Shuiguo}引入com.jaffe报下的Fruit并更名为Shuiguo
import com.jaffe.{Fruit=>Shuiguo,_}引入com.jaffe包下的所有成员,并将Fruit更名为Shuiguo
import com.jaffe.{Fruit=>,}引入com.jaffe包下除去Fruit的所有成员

2.注意
scala中的三个默认导入分别是
import java.lang._
import scala._
import scala.Predef._

6.1.5 访问权限

1.说明
在Java中,访问权限分为:public,private,protected和默认。在Scala中,你可以通过类似的修饰符达到同样的效果。但是使用上有区别。

java的权限修饰符:

  1. 用在外部上:

    • public

      1. 所有地方都可以找到这个类

      2. 这个类的类名要文件名保持一致(.java)

    • 默认

  2. 用在类的内部成员(属性, 方法, 内部类)上

    四种修饰符

    • public

      所有地方都可以访问

    • protected

      同包和子父类中可以访问

      在子类中访问protected的方法的时候:

      super.foo();

    • 默认(friendly)

      同包中访问

    • private

      只能在当前类中访问

scala 的权限修饰符:

  1. 用在外部类上:

    • 默认(public, 没有public关键字)

      class A

    • private 只能在当前表使用, 其他地方无法使用

  2. 用在类的内部成员(属性, 方法, 内部类)上:

    • 默认(public, 没有public关键字)

    • protected

      这个限制更加严格. 只能在子父类中访问, 即使在同包中也不能访问

      super.foo();
      
    • private

      只能在当前类中访问

      scala中做了一些改造, 精细化的控制!!!

      private[mode] def speak() = println("speak...")

      mode包和它的子包中可以访问.

2.案例实操

package com.jaffe.scala.test

class Person {

  private var name: String = "bobo"
  protected var age: Int = 18
  private[test] var sex: String = "男"

  def say(): Unit = {
    println(name)
  }
}


object Person {

  def main(args: Array[String]): Unit = {

    val person = new Person
    
    person.say()

    println(person.name)

    println(person.age)
  }
}


class Teacher extends Person {

  def test(): Unit = {
    this.age
    this.sex
  }
}

class Animal {
  def test: Unit = {
    new Person().sex
  }
}

6.2 类和对象

6.2.1 定义类

1.基本语法
[修饰符] class 类名 {
类体
}
说明
(1)Scala语法中,类并不声明为public,所有这些类都具有公有可见性(即默认就是public)
(2)一个Scala源文件可以包含多个类

2.特征

  1. 类和java一样, 默认有空参构造器, 但是一旦有定义构造器, 则不会再默认提供空构造器了

  2. 给类定义的所有的属性都是私有.

  3. 但是会给这些私有属性添加公共的gettersetter

    public class com.jaffe.scala1128.day04.obj.User2 {
      private java.lang.String name;
      private final int age;
      public java.lang.String name();   // getter
      public void name_$eq(java.lang.String);  // setter
      public int age();
      public com.jaffe.scala1128.day04.obj.User2(java.lang.String, int);
    }
    
    
  4. 在访问属性的时候, 约定是访问公共的getter方法

    println(user.name)
    其实是访问的
    public java.lang.String name();
    
  5. 在修改属性值的时候, 会默认访问setter方法

    user.name = "zs"
    其实访问的是
    public void name_$eq(java.lang.String)
    
  6. 在主构造中, 如果没有val/var, 那么这个参数在有些情况(在类内部地方用到)下也会成为属性, 但是私有, 没有提供公共的getter和setter

  7. scala自动提供的setter和getter 不符合标准的java bean 规范

    java bean:

    public String getName(){ }

    public void SetName(String name){ }

    由于scala的生态不完善, scala大量的使用专门为java准备的那些类库, 这些类库, 他们在底层一般要用到标准的javagetter和setter

    所以, 要添加标准的javagetter和setter

  8. 使用注解添加标准bean

    class User2(@BeanProperty var name: String, @BeanProperty val age: Int, @BeanProperty sex: String)
    

    如果要用到java的类库, 最好加上.

6.2.2 属性

属性是类的一个组成部分

1.基本语法
[修饰符] var 属性名称 [:类型] = 属性值
注:Bean属性(@BeanPropetry),可以自动生成规范的setXxx/getXxx方法

2.案例实操

package com.jaffe.scala.test

import scala.beans.BeanProperty

class Person {

  var name: String = "bobo" //定义属性

  var age: Int = _ // _表示给属性一个默认值

  //Bean属性(@BeanProperty)
  @BeanProperty var sex: String = "男"
}

object Person {
  def main(args: Array[String]): Unit = {

    var person = new Person()
    println(person.name)

    person.setSex("女")
    println(person.getSex)
  }
}

6.2.3 方法

1.基本语法
def 方法名(参数列表) [:返回值类型] = {
方法体
}

2.案例实操

class Person {

    def sum(n1:Int, n2:Int) : Int = {
        n1 + n2
    }
}

object Person {

    def main(args: Array[String]): Unit = {

        val person = new Person()

        println(person.sum(10, 20))
    }
}

6.2.4 创建对象

1.基本语法
val | var 对象名 [:类型] = new 类型()

2.案例实操
(1)val修饰对象,不能改变对象的引用(即:内存地址),可以改变对象属性的值。
(2)var修饰对象,可以修改对象的引用和修改对象的属性值

class Person {
    var name: String = "canglaoshi"
}

object Person {

    def main(args: Array[String]): Unit = {
        //val修饰对象,不能改变对象的引用(即:内存地址),可以改变对象属性的值。
        val person = new Person()
        person.name = "bobo"

        // person = new Person()// 错误的

        println(person.name)
    }
}

6.2.5 构造器

和Java一样,Scala构造对象也需要调用构造方法,并且可以有任意多个构造方法。
Scala类的构造器包括:主构造器(紧跟着类名)和辅助构造器(和主构造构成了重载关系,功能相比比较弱)

1.基本语法
class 类名(形参列表) { // 主构造器
// 类体
def this(形参列表) { // 辅助构造器
}
def this(形参列表) { //辅助构造器可以有多个…
}
}

注意:
(1)辅助构造器,函数的名称this,可以有多个,编译器通过参数的个数来区分。
(2)辅助构造方法不能直接构建对象,必须直接或者间接调用主构造方法。
(3)只能后定义的调用先定义的
(4)辅助构造函数的参数, 仅仅是一个普通的参数, 不会成为类的属性

2.案例实操
如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略。

// 定义一个无参辅助构造器
def this() = {

// 注意: 首行必须是调用自己的主构造器
this("lisi")
}

def this(age: Int) = {
    this("lisi")
    this.age = age
}
def this(a: Int) = {
    this()
}

6.2.6 构造器参数

1.说明
Scala类的主构造器函数的形参包括三种类型:未用任何修饰、var修饰、val修饰
(1)未用任何修饰符修饰,这个参数就是一个局部变量
(2)var修饰参数,作为类的成员属性使用,可以修改
(3)val修饰参数,作为类只读属性使用,不能修改

2.案例实操

class Person(name: String, var age: Int, val sex: String) {

}

object Test {

  def main(args: Array[String]): Unit = {

    var person = new Person("bobo", 18, "男")

    // (1)未用任何修饰符修饰,这个参数就是一个局部变量
    // println(person.name)

    // (2)var修饰参数,作为类的成员属性使用,可以修改
    person.age = 19
    println(person.age)

    // (3)val修饰参数,作为类的只读属性使用,不能修改
    // person.sex = "女"
    println(person.sex)
  }
}

6.3 封装

封装就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作。java封装操作如下,
(1)将属性进行私有化
(2)提供一个公共的set方法,用于对属性赋值
(3)提供一个公共的get方法,用于获取属性的值
scala中的public属性,底层实际为private,并通过get方法(obj.field())和set方法(obj.field_=(value))对其进行操作。所以scala并不推荐将属性设为private,再为其设置public的get和set方法的做法。但由于很多java框架都利用反射调用getXXX和setXXX方法,有时候为了和这些框架兼容,也会为scala的属性设置getXXX和setXXX方法(通过@BeanProperty注解实现)

6.4 继承

1.基本语法
class 子类名 extends 父类名 { 类体 }
(1)子类继承父类的属性和方法
(2)scala是单继承

2.案例实操
(1)子类继承父类的属性和方法
(2)继承的调用顺序:父类构造器->子类构造器

class Person(nameParam: String) {

  var name = nameParam
  var age: Int = _

  def this(nameParam: String, ageParam: Int) {
    this(nameParam)
    this.age = ageParam
    println("父类辅助构造器")
  }

  println("父类主构造器")
}


class Emp(nameParam: String, ageParam: Int) extends Person(nameParam, ageParam) {

  var empNo: Int = _

  def this(nameParam: String, ageParam: Int, empNoParam: Int) {
    this(nameParam, ageParam)
    this.empNo = empNoParam
    println("子类的辅助构造器")
  }

  println("子类主构造器")
}

object Test {
  def main(args: Array[String]): Unit = {
    new Emp("z3", 11,1001)
  }
}

6.4.1 方法的覆写

java`中方法的覆写规则:

两同

  • 方法名
  • 参数列表

两小

  • 返回值类型: 子类的返回值类型应该等于或小于父类的返回值类型
  • 抛的异常: 子类的方法抛的异常要小于父类抛的异常

一大

  • 访问权限: 子类要大于父类访问权限

scala中遵守同样的规则!!!

不同点
  1. javaOverride注解(1.6版本开始)是可选的.
  2. scala中, override是一个关键字, 必须添加

6.4.3 多态

如何理解多态?

定义: 如果一个对象的编译时类型和运行时类型不一致, 我就说发生了多态!

  1. 编译时类型:

    =左边就是 编译时类型

    ​ 编译的时候, 是否可以通过要看编译时类型.

  2. 运行时类型:

    ​ 创建对象的时候, 使用类型就是运行时类型

    ​ 运行的时候, 方法的具体表现要看运行时类型

java中, 属性没有多态!(方法才有多态)

属性覆写(scala独有)

scala中, 属性也可以覆写, 也具有多态!!!

属性覆写的规则:

  1. val只能覆写和没有参数的 def

  2. var只能覆写抽象var

    var只能覆写抽象字段(属性)

6.4.2 继承的时候构造的处理

  1. 在子类的辅构造器中, 必须先调用自己的主构造, 不能主动去调用父类的构造器.
  2. 只有主构造器才有权力去调用父类的构造器!!!

6.5 抽象类、属性和抽象方法

6.5.1 抽象类、属性和抽象方法

1.基本语法
(1)定义抽象类:abstract class Person{} //通过abstract关键字标记抽象类,抽象类不能直接创建对象, 必须创建子类的对象
(2)定义抽象属性:val|var name:String //一个属性没有初始化,就是抽象属性
(3)定义抽象方法:def hello():String //只声明而没有实现的方法,就是抽象方法

案例实操:

abstract class Person {
  
  val name: String

  def hello(): Unit
}

class Teacher extends Person {

  override val name: String = "teacher"

  override def hello(): Unit = {
    println("hello teacher")
  }
}

2.继承&重写
(1)如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类
(2)重写非抽象方法需要用override修饰,重写抽象方法则可以不加override
(3)子类中调用父类的方法使用super关键字
(4)属性重写只支持val类型,而不支持var
(5)scala中属性和方法都是动态绑定,而java中只有方法为动态绑定

案例实操(对比java与scala的重写)

scala


class Person {
  val name: String = "person"

  def hello(): Unit = {
    println("hello person")
  }
}

class Teacher extends Person {

  override val name: String = "teacher"

  override def hello(): Unit = {
    println("hello teacher")
  }
}

object Test {
  def main(args: Array[String]): Unit = {
    val teacher: Teacher = new Teacher()
    println(teacher.name)
    teacher.hello()

    val teacher1:Person = new Teacher
    println(teacher1.name)
    teacher1.hello()
  }
}

java

class Person {

    public String name = "person";
    public void hello() {
        System.out.println("hello person");
    }

}
class Teacher extends Person {

    public String name = "teacher";
    @Override
    public void hello() {
        System.out.println("hello teacher");
    }

}
public class TestDynamic {
    public static void main(String[] args) {
        Teacher teacher = new Teacher();
        Person teacher1 = new Teacher();

        System.out.println(teacher.name);
        teacher.hello();

        System.out.println(teacher1.name);
        teacher1.hello();
    }
}

结果对比:

scala
在这里插入图片描述

java
在这里插入图片描述

6.5.2 匿名子类

1.说明
Java一样,可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类

2.案例实操

abstract class Person {

  val name: String

  def hello(): Unit
}

object Test {

  def main(args: Array[String]): Unit = {

    val person = new Person {

      override val name: String = "teacher"

      override def hello(): Unit = println("hello teacher")
    }
  }
}

6.6 单例对象(伴生对象)和伴生类

Scala语言是完全面向对象的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。但是为了能够和Java语言交互(因为Java中有静态概念),就产生了一种特殊的对象来模拟类对象,该对象为单例对象。若单例对象名与类名一致,则称该单例对象是这个类的伴生对象,这个类的所有“静态”内容都可以放置在它的伴生对象中声明

6.6.1 单例对象语法

1.基本语法

object User{
    // 也可以写代码
    def main(args: Array[String]){
        // 入口
    }
}
class User(val age: Int){
    
}

2.说明

  1. class的名字和object的名字相同, 就是伴生类和伴生对象
  2. 他们可以互相访问对方的私有成员
  3. 伴生类和 伴生对象必须在同一个.scala文件中
  4. 将来编译成字节码之后, 站在java的角度, 伴生对象中的成员都会成为静态成员, 伴生类中的成员都会成为非静态成员.

3.案例实操

//(1)伴生对象采用object关键字声明
object Person {
  var country: String = "China"
}

//(2)伴生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。
class Person {
  var name: String = "bobo"
}

object Test {
  def main(args: Array[String]): Unit = {
    //(3)伴生对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。
    println(Person.country)
  }
}

6.6.2 apply方法

scala中, ***任何对象***也可以像函数一样去调用执行

//函数调用:
函数名(参数 )  // 函数名.apply(参数)
// 对象调用
对象名(参数)   // 等价于去调用对象的 apply方法

1.说明
(1)通过伴生对象的apply方法,实现不使用new方法创建对象。
(2)如果想让主构造器变成私有的,可以在()之前加上private。
(3)apply方法可以重载。
(4)Scala中obj(arg)的语句实际是在调用该对象的apply方法,即obj.apply(arg)。用以统一面向对象编程和函数式编程的风格。

2.案例实操

object Test {

  def main(args: Array[String]): Unit = {

    //(1)通过伴生对象的apply方法,实现不使用new关键字创建对象。
    val p1 = Person()
    println("p1.name=" + p1.name)

    val p2 = Person("bobo")
    println("p2.name=" + p2.name)
  }
}

//(2)如果想让主构造器变成私有的,可以在()之前加上private
class Person private(cName: String) {
  var name: String = cName
}

object Person {
  def apply(): Person = {
    println("apply空参被调用")
    new Person("xx")
  }

  def apply(name: String): Person = {
    println("apply有参被调用")
    new Person(name)
  }
}

6.7 特质(Trait)

Scala语言中,采用特质trait(特征)来代替接口的概念,也就是说,多个类具有相同的特征(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明。
Scala中的trait中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质。
Scala引入trait特征,第一可以替代Java的接口,第二个也是对单继承机制的一种补充。

成员:

抽象类能有的成员, 那么特质都可有

  1. 属性
  2. 方法
  3. 抽象属性
  4. 抽象方法
  5. 构造器(主/辅)

java接口:

  1. 1.8之前: 是抽象方法和常量的集合
  2. 从1.8开始, 默认方法

6.7.1 特质声明

1.基本语法
trait 特质名 {
trait体
}

2.案例实操

trait PersonTrait {

    // 声明属性
    var name:String = _

    // 声明方法
    def eat():Unit={

    }

    // 抽象属性
    var age:Int
    
    // 抽象方法
    def say():Unit
}

6.7.2 特质基本语法

一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了extends关键字,如果有多个特质或存在父类,那么需要采用with关键字连接。

1.基本语法:
没有父类:class 类名 extends 特质1 with 特质2 with 特质3 …
有父类:class 类名 extends 父类 with 特质1 with 特质2 with 特质3…

2.说明
(1)类和特质的关系:使用继承的关系。
(2)当一个类去继承特质时,第一个连接词是extends,后面是with。
(3)如果一个类在继承特质和父类时,应当把父类写在extends后。

3.案例实操
(1)特质可以同时拥有抽象方法和具体方法
(2)一个类可以混入(mixin)多个特质
(3)所有的Java接口都可以当做Scala特质使用
(4)动态混入:可灵活的扩展类的功能
(4.1)动态混入:创建对象时混入trait,而无需使类混入该trait
(4.2)如果混入的trait中有未实现的方法,则需要实现

trait PersonTrait {

  //(1)特质可以同时拥有抽象方法和具体方法
  // 声明属性
  var name: String = _

  // 抽象属性
  var age: Int

  // 声明方法
  def eat(): Unit = {
    println("eat")
  }

  // 抽象方法
  def say(): Unit
}

trait SexTrait {
  var sex: String
}

//(2)一个类可以实现/继承多个特质
//(3)所有的Java接口都可以当做Scala特质使用
class Teacher extends PersonTrait with java.io.Serializable {

  override def say(): Unit = {
    println("say")
  }

  override var age: Int = _
}


object TestTrait {

  def main(args: Array[String]): Unit = {

    val teacher = new Teacher

    teacher.say()
    teacher.eat()

    //(4)动态混入:可灵活的扩展类的功能
    val t2 = new Teacher with SexTrait {
      override var sex: String = "男"
    }

    //调用混入trait的属性
    println(t2.sex)
  }
}

6.7.3 特质叠加

由于一个类可以混入(mixin)多个trait,且trait中可以有具体的属性和方法,若混入的特质中具有相同的方法(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。冲突分为以下两种:

第一种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法
在这里插入图片描述
第二种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait继承自相同的trait(TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala采用了特质叠加的策略
在这里插入图片描述

所谓的特质叠加,就是将混入的多个trait中的冲突方法叠加起来,案例如下,

trait Ball {
  def describe(): String = {
    "ball"
  }
}

trait Color extends Ball {
  override def describe(): String = {
    "blue-" + super.describe()
  }
}

trait Category extends Ball {
  override def describe(): String = {
    "foot-" + super.describe()
  }
}

class MyBall extends Category with Color {
  override def describe(): String = {
    "my ball is a " + super.describe()
  }
}

object TestTrait {
  def main(args: Array[String]): Unit = {
    println(new MyBall().describe())
  }
}

结果如下:
在这里插入图片描述

动态叠加

java中所有的继承关系都应该在定义类是确定好.

scala支持特质的动态叠加. 在创建对象的时候, 可以临时只针对整个对象来叠加特质

object Trait5 {
    def main(args: Array[String]): Unit = {
        val h = new H with F1
        h.foo()
    }
}

class H

trait F1 {
    def foo() = println("f1 foo...")
}

以后真正用的时候, 就把trait当做一个普通的接口来用!!!

6.7.4 特质叠加执行顺序

思考:上述案例中的super.describe()调用的是父trait中的方法吗?
当一个类混入多个特质的时候,scala会对所有的特质及其父特质按照一定的顺序进行排序,而此案例中的super.describe()调用的实际上是排好序后的下一个特质中的describe()方法。,排序规则如下:

在这里插入图片描述
结论:
(1)案例中的super,不是表示其父特质对象,而是表示上述叠加顺序中的下一个特质,即,MyClass中的super指代Color,Color中的super指代Category,Category中的super指代Ball。
(2)如果想要调用某个指定的混入特质中的方法,可以增加约束:super[],例如super[Category].describe()。

6.7.5 特质自身类型

1.说明
自身类型可实现依赖注入的功能

2.案例实操

class User(val name: String, val age: Int)

trait Dao {
  def insert(user: User) = {
    println("insert into database :" + user.name)
  }
}

trait APP {
  _: Dao =>
  def login(user: User): Unit = {
    println("login :" + user.name)
    insert(user)
  }
}

object MyApp extends APP with Dao {
  def main(args: Array[String]): Unit = {
    login(new User("bobo", 11))
  }
}

6.8 扩展

6.8.1 类型检查和转换

1.说明
(1)obj.isInstanceOf[T]:判断obj是不是T类型。
(2)obj.asInstanceOf[T]:将obj强转成T类型。
(3)classOf获取对象的类名。

2.案例实操

class Person{

}

object Person {
    def main(args: Array[String]): Unit = {

        val person = new Person

        //(1)判断对象是否为某个类型的实例
        val bool: Boolean = person.isInstanceOf[Person]

        if ( bool ) {
            //(2)将对象转换为某个类型的实例
            val p1: Person = person.asInstanceOf[Person]
            println(p1)
        }

        //(3)获取类的信息
        val pClass: Class[Person] = classOf[Person]
        println(pClass)
    }
}

6.8.2 枚举类、密封类和应用类

1.说明
枚举类:需要继承Enumeration
应用类:需要继承App

密封类:他的子类将来只能出现在当前这个文件中
sealed abstract class  Season
object Spring extends Season
object Summer extends Season
object autumn extends Season
object Winter extends Season

2.案例实操

object Test {
    def main(args: Array[String]): Unit = {

        println(Color.RED)
    }
}

// 枚举类
object Color extends Enumeration {
    val RED = Value(1, "red")
    val YELLOW = Value(2, "yellow")
    val BLUE = Value(3, "blue")
}

// 应用类
object Test20 extends App {
    println("xxxxxxxxxxx");
}

6.8.3 Type定义新类型

1.说明
使用type关键字可以定义新的数据类型名称,本质上就是类型的一个别名

2.案例实操

object Test {

    def main(args: Array[String]): Unit = {
        
        type S=String
        var v:S="abc"
        def test():S="xyz"
    }
}

7.集合

7.1 集合简介

有序:

取出顺序和放入顺序一致 叫做有序

无序:

遍历顺序和存入顺序不一致.

1.说明
(1)Scala的集合有三大类:序列Seq、集Set、映射Map,所有的集合都扩展自Iterable特质
(2)对于几乎所有的集合类,Scala都同时提供了可变和不可变的版本,分别位于以下两个包
不可变集合:scala.collection.immutable
可变集合: scala.collection.mutable

2.案例实操
(1)Scala不可变集合,就是指该集合对象不可修改,每次修改就会返回一个新对象,而不会对原对象进行修改。
(2)可变集合,就是这个集合可以直接对原对象进行修改,而不会返回新的对象。

object TestList {

  def main(args: Array[String]): Unit = {

    //不可变List
    val immutableList: List[Int] = List(1, 2, 3, 4, 5)

    //对不可变List进行修改,在头部添加一个元素0
    val newImmutableList: List[Int] = 0 +: immutableList

    println(immutableList)

    println(newImmutableList)


    //可变List
    val mutableList: ListBuffer[Int] = ListBuffer(1, 2, 3, 4, 5)

    //对可变List进行修改,在头部添加一个元素0
    val newMutableList: ListBuffer[Int] = 0 +=: mutableList

    println(System.identityHashCode(mutableList)) //地址值一样,输出也一样
    println(System.identityHashCode(newMutableList)) //地址值一样,输出也一样
  }
}

7.1.1 不可变集合继承图

在这里插入图片描述
1)Set、Map是Java中也有的集合
2)Seq是Java没有的,我们发现List归属到Seq了,因此这里的List就和Java不是同一个概念了
3)我们前面的for循环有一个 1 to 3,就是IndexedSeq下的Vector
4)String也是属于IndexeSeq
5)我们发现经典的数据结构比如Queue和Stack被归属到LinerSeq
6)大家注意Scala中的Map体系有一个SortedMap,说明Scala的Map可以支持排序
7)IndexSeq和LinearSeq的区别:
(1)IndexSeq是通过索引来查找和定位,因此速度快,比如String就是一个索引集合,通过索引即可定位
(2)LineaSeq是线型的,即有头尾的概念,这种数据结构一般是通过遍历来查找

7.1.2 可变集合继承图

在这里插入图片描述

7.2 数组

7.2.1 不可变数组

底层其实本质就是java的数组:长度不能变, 元素可以变换

几个数组可用的的运算符

  1. :+ 用来在数组的尾部添加元素

  2. +: 用来在数组的头部添加元素

    运算符的结合性:

    a + b   左结合
    a = 30  右结合
    潜规则:
    	如果一个运算符以 : 结尾,那么他就是右结合
    
    100 +: arr 等价于 arr.+:(100)
    
  3. ++ 合并两个数组

1.第一种方式定义数组(定长数组)
定义:val arr1 = new Array [Int] (10)
(1)new是关键字
(2)[Int]是指定可以存放的数据类型,如果希望存放任意数据类型,则指定Any
(3)(10),表示数组的大小,确定后就不可以变化

2.案例实操

import scala.collection.mutable.ArrayBuffer

object TestArray{

    def main(args: Array[String]): Unit = {

        //(1)数组定义
        val arr01 = new Array[Int](4)
        println(arr01.length) // 4

        //(2)数组赋值
        //(2.1)修改某个元素的值
        arr01(3) = 10
        //(2.2)采用方法的形式给数组赋值
        arr01.update(0,1)

        //(3)遍历数组
        //(3.1)查看数组
        println(arr01.mkString(","))

        //(3.2)普通遍历
        for (i <- arr01) {
            println(i)
        }

        //(3.3)简化遍历
        def printx(elem:Int): Unit = {
            println(elem)
        }
         arr01.foreach(printx)
        // arr01.foreach((x)=>{println(x)})
        // arr01.foreach(println(_))
        arr01.foreach(println)

        //(4)增加元素(由于创建的是不可变数组,增加元素,其实是产生新的数组)
        println(arr01)
        val ints: Array[Int] = arr01 :+ 5
        println(ints)
    }
}

3.第二种方式定义数组
val arr1 = Array(1, 2)
(1)在定义数组时,直接赋值
(2)使用apply方法创建数组对象

4.案例实操

object TestArray{

    def main(args: Array[String]): Unit = {

        var arr02 = Array(1, 3, "bobo")
        for (i <- arr02) {
            println(i)
        }
    }
}

7.2.2 可变数组

1.定义可变数组
val arr01 = ArrayBuffer[Any] (3, 2, 5)
(1)[Any]存放任意数据类型
(2)(3, 2, 5)初始化好的三个元素
(3)ArrayBuffer需要引入scala.collection.mutable.ArrayBuffer

2.创建可变数组

// 创建可变数组
val buffer = ArrayBuffer(10, 20, 30, 40, 10.3)
// 创建一个空的ArrayBuffer
new ArrayBuffer[Int]()
// 创建一个空的ArrayBuffer
ArrayBuffer[Int]()

+= 在可变数组的尾部增加元素(没有产生新的集合)

+=: 在可变数组的头部增加元素(没有删除新的集合)

buffer.append(1000)  // java式的写法
buffer.prepend(2000)
buffer.insert(0, 200, 3000, 4000)
buffer1 ++= buffer2  // 把buffer2的内容加到buffer1的内部  buffer1发生变化
buffer1 ++=: buffer2   // buffer1不变, buffer2发生了变化

扩展

其实定长数组也可以使用:+=运算符

var arr1 = Array(30, 50, 70, 60, 10, 20)
// 等价于 arr1 = arr1 :+= 100
arr1 :+= 100

3.案例实操
(1)ArrayBuffer是有序的集合
(2)增加元素使用的是append方法(),支持可变参数

import scala.collection.mutable.ArrayBuffer

object TestArrayBuffer {

    def main(args: Array[String]): Unit = {

        //(1)创建并赋值可变数组
        val arr01 = ArrayBuffer[Any](1, 2, 3)

        //(2)遍历数组
        for (i <- arr01) {
            println(i)
        }
        println(arr01.length) // 3
        println("arr01.hash=" + arr01.hashCode())

        //(3)增加元素
        //(3.1)追加数据
        arr01.+=(4)
        //(3.2)向数组最后追加数据
        arr01.append(5,6)
        //(3.3)向指定的位置插入数据
        arr01.insert(0,7,8)
        println("arr01.hash=" + arr01.hashCode())

        //(4)修改元素
        arr01(1) = 9 //修改第2个元素的值
        println("--------------------------")

        for (i <- arr01) {
            println(i)
        }
        println(arr01.length) // 5
    }
}

7.2.3 不可变数组与可变数组的转换

1.说明
arr1.toBuffer //不可变数组转可变数组
arr2.toArray //可变数组转不可变数组
(1)arr2.toArray返回结果才是一个不可变数组,arr2本身没有变化
(2)arr1.toBuffer返回结果才是一个可变数组,arr1本身没有变化

2.案例实操

object TestArrayBuffer {

    def main(args: Array[String]): Unit = {

        //(1)创建一个空的可变数组
        val arr2 = ArrayBuffer[Int]()

        //(2)追加值
        arr2.append(1, 2, 3)
        println(arr2) // 1,2,3

        //(3)ArrayBuffer ==> Array
        //(3.1)arr2.toArray 返回的结果是一个新的定长数组集合
        //(3.2)arr2它没有变化
        val newArr = arr2.toArray
        println(newArr)
        
        //(4)Array ===> ArrayBuffer
        //(4.1)newArr.toBuffer 返回一个变长数组 newArr2
        //(4.2)newArr 没有任何变化,依然是定长数组
        val newArr2 = newArr.toBuffer
        newArr2.append(123)

        println(newArr2)
    }
}

7.2.4 多维数组

1.多维数组定义
val arr = Array.ofDim[Double] (3,4)
说明:二维数组中有三个一维数组,每个一维数组中有四个元素

2.案例实操

object DimArray {

    def main(args: Array[String]): Unit = {
        
        //(1)创建了一个二维数组, 有三个元素,每个元素是含有4个元素一维数组()
        val arr = Array.ofDim[Int](3, 4)
        arr(1)(2) = 88

        //(2)遍历二维数组
        for (i <- arr) { //i 就是一维数组

            for (j <- i) {
                print(j + " ")
            }

            println()
        }
    }
}

7.3 Seq集合(List)

7.3.1 不可变List

1.说明
(1)List默认为不可变集合
(2)创建一个List(数据有顺序,可重复
(3)遍历List
(4)List增加数据
(5)集合间合并:将一个整体拆成一个一个的个体,称为扁平化
(6)取指定数据
(7)空集合Nil

::List的头部添加元素(List专用)

::: 把两个List的元素合并到以前(List专用)

2.案例实操

object TestList {

    def main(args: Array[String]): Unit = {

        //(1)List默认为不可变集合
        //(2)创建一个List(数据有顺序,可重复)
        val list: List[Int] = List(1,2,3,4,3)
        
        //(7)空集合Nil
        val list5 = 1::2::3::4::Nil

        //(4)List增加数据
        //(4.1)::的运算规则从右向左
        //val list1 = 5::list
        val list1 = 7::6::5::list
        //(4.2)添加到第一个元素位置
        val list2 = list.+:(5)

        //(5)集合间合并:将一个整体拆成一个一个的个体,称为扁平化
        val list3 = List(8,9)
        //val list4 = list3::list1
        val list4 = list3:::list1

        //(6)取指定数据
        println(list(0))

        //(3)遍历List
        //list.foreach(println)
        //list1.foreach(println)
        //list3.foreach(println)
        //list4.foreach(println)
        list5.foreach(println)
    }
}

7.3.2 可变ListBuffer

1.说明
(1)创建一个可变集合ListBuffer
(2)向集合中添加数据
(3)打印集合数据

2.案例实操

import scala.collection.mutable.ListBuffer

object TestList {

    def main(args: Array[String]): Unit = {

        //(1)创建一个可变集合
        val buffer = ListBuffer(1,2,3,4)

        //(2)向集合中添加数据
        buffer.+=(5)

        //(3)打印集合数据
        buffer.foreach(println)
    }
}

7.4 Set集合

默认情况下,Scala使用的是不可变集合,如果你想使用可变集合,需要引用 scala.collection.mutable.Set 包

7.4.1 不可变Set

1.说明
(1)Set默认是不可变集合,数据无序
(2)数据不可重复
(3)遍历集合

2.案例实操

object TestSet {

    def main(args: Array[String]): Unit = {

        //(1)Set默认是不可变集合,数据无序
        val set = Set(1,2,3,4,5,6)

        //(2)数据不可重复
        val set1 = Set(1,2,3,4,5,6,3)

        //(3)遍历集合
        for(x<-set1){
            println(x)
        }
    }
}

7.4.2 可变mutable.Set

1.说明
(1)创建可变集合mutable.Set
(2)打印集合
(3)集合添加元素
(4)向集合中添加元素,返回一个新的Set
(5)删除数据

2.案例实操

object TestSet {

    def main(args: Array[String]): Unit = {

        //(1)创建可变集合
        val set = mutable.Set(1,2,3,4,5,6)

        //(3)集合添加元素
        set += 8

        //(4)向集合中添加元素,返回一个新的Set
        val ints = set.+(9)
        println(ints)
        println("set2=" + set)

        //(5)删除数据
        set-=(5)

        //(2)打印集合
        set.foreach(println)
        println(set.mkString(","))
    }
}

7.5 Map集合

Scala中的Map和Java类似,也是一个散列表,它存储的内容也是键值对(key-value)映射,Scala中不可变的Map是有序的,可变的Map是无序的。

7.5.1 不可变Map(有序)

1.说明
(1)创建不可变集合Map
(2)循环打印
(3)访问数据
(4)如果key不存在,返回0

2.案例实操

object TestMap {

    def main(args: Array[String]): Unit = {
        // Map
        //(1)创建不可变集合Map
        val map = Map( "a"->1, "b"->2, "c"->3 )

        //(3)访问数据
        for (elem <- map.keys) {
            // 使用get访问map集合的数据,会返回特殊类型Option(选项):有值(Some),无值(None)
            println(elem + "=" + map.get(elem).get)
        }

        //(4)如果key不存在,返回0
        println(map.get("d").getOrElse(0))
        println(map.getOrElse("d", 0))

        //(2)循环打印
        map.foreach((kv)=>{println(kv)})
    }
}

7.5.2 可变Map

1.说明
(1)创建可变集合
(2)打印集合
(3)向集合增加数据
(4)删除数据
(5)修改数据

2.案例实操

object TestSet {

    def main(args: Array[String]): Unit = {

        //(1)创建可变集合
        val map = mutable.Map( "a"->1, "b"->2, "c"->3 )

        //(3)向集合增加数据
        map.+=("d"->4)

        // 将数值4添加到集合,并把集合中原值1返回
        val maybeInt: Option[Int] = map.put("a", 4)
        println(maybeInt.getOrElse(0))

        //(4)删除数据
        map.-=("b", "c")

        //(5)修改数据
        map.update("d",5)

        //(2)打印集合
        map.foreach((kv)=>{println(kv)})
    }
}

7.6 元组

1.说明
元组也是可以理解为一个容器,可以存放各种相同或不同类型的数据。说的简单点,就是将多个无关的数据封装为一个整体,称为元组。
注意:元组中最大只能有22个元素。

元组不可变, 不能修改
元组不能直接遍历, 其实没有这样的需求

2.案例实操
(1)声明元组的方式:(元素,元素2,元素3)
(2)访问元组
(3)Map中的键值对其实就是元组,只不过元组的元素个数为2,称之为对偶

object TestTuple {

    def main(args: Array[String]): Unit = {

        //(1)声明元组的方式:(元素,元素2,元素3)
        val tuple: (Int, String, Boolean) = (40,"bobo",true)

        //(2)访问元组
        //(2.1)通过元素的顺序进行访问,调用方式:_顺序号
        println(tuple._1)
        println(tuple._2)
        println(tuple._3)

        //(2.2)通过索引访问数据
        println(tuple.productElement(0))

        //(2.3)通过迭代器访问数据
        for (elem <- tuple.productIterator) {
            println(elem)
        }

        //(3)Map中的键值对其实就是元组,只不过元组的元素个数为2,称之为对偶
        val map = Map("a"->1, "b"->2, "c"->3)

        map.foreach(tuple=>{println(tuple._1 + "=" + tuple._2)})
    }
}

7.7 集合常用函数

7.7.1 基本属性和常用操作

1.说明
(1)获取集合长度
(2)获取集合大小
(3)循环遍历
(4)迭代器
(5)生成字符串
(6)是否包含

2.案例实操

object TestList {

  def main(args: Array[String]): Unit = {

    val list: List[Int] = List(1, 2, 3, 4, 5, 6, 7)

    //(1)获取集合长度
    println(list.length)

    //(2)获取集合大小
    println(list.size)

    //(3)循环遍历
    list.foreach(println)

    //(4)迭代器
    for (elem <- list.iterator) {
      println(elem)
    }

    //(5)生成字符串
    println(list.mkString(","))

    //(6)是否包含
    println(list.contains(3))
  }
}

7.7.2 衍生集合

1.说明
(1)获取集合的头head
(2)获取集合的尾(不是头就是尾)tail
(3)集合最后一个数据 last
(4)集合初始数据(不包含最后一个)
(5)反转
(6)取前(后)n个元素
(7)去掉前(后)n个元素
(8)并集
(9)交集
(10)差集
(11)拉链
(12)滑窗

2.案例实操

object TestList {

  def main(args: Array[String]): Unit = {

    val list1: List[Int] = List(1, 2, 3, 4, 5, 6, 7)
    val list2: List[Int] = List(4, 5, 6, 7, 8, 9, 10)

    //(1)获取集合的头
    println(list1.head)

    //(2)获取集合的尾(不是头的就是尾)
    println(list1.tail)

    //(3)集合最后一个数据
    println(list1.last)

    //(4)集合初始数据(不包含最后一个)
    println(list1.init)

    //(5)反转
    println(list1.reverse)

    //(6)取前(后)n个元素
    println(list1.take(3))
    println(list1.takeRight(3))

    //(7)去掉前(后)n个元素
    println(list1.drop(3))
    println(list1.dropRight(3))

    //(8)并集
    println(list1.union(list2))

    //(9)交集
    println(list1.intersect(list2))

    //(10)差集
    println(list1.diff(list2))

    //(11)拉链 注:如果两个集合的元素个数不相等,那么会将同等数量的数据进行拉链,多余的数据省略不用
    println(list1.zip(list2))

    //(12)滑窗
    list1.sliding(2, 5).foreach(println)
  }
}

7.7.3 集合计算初级函数

1.说明
(1)求和
(2)求乘积
(3)最大值
(4)最小值
(5)排序

2.实操

object TestList {

  def main(args: Array[String]): Unit = {

    val list: List[Int] = List(1, 5, -3, 4, 2, -7, 6)

    //(1)求和
    println(list.sum)

    //(2)求乘积
    println(list.product)

    //(3)最大值
    println(list.max)

    //(4)最小值
    println(list.min)

    //(5)排序
    // (5.1)按照元素大小排序
    println(list.sortBy(x => x))

    // (5.2)按照元素的绝对值大小排序
    println(list.sortBy(x => x.abs))

    // (5.3)按元素大小升序排序
	println(list.sortWith((x, y) => x < y))

	// (5.4)按元素大小降序排序
    println(list.sortWith((x, y) => x > y))
    
  }
}

7.7.4 集合计算高级函数

在这里插入图片描述
1.说明
(1)过滤
(2)转化/映射
(3)扁平化
(4)扁平化+映射 注:flatMap相当于先进行map操作,在进行flatten操作
(5)分组
(6)简化(规约)
(7)折叠

2.实操

object TestList {

  def main(args: Array[String]): Unit = {

    val list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
    val nestedList: List[List[Int]] = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9))
    val wordList: List[String] = List("hello world", "hello jaffe", "hello scala")

    //(1)过滤
    println(list.filter(x => x % 2 == 0))

    //(2)转化/映射
    println(list.map(x => x + 1))

    //(3)扁平化
    println(nestedList.flatten)

    //(4)扁平化+映射 注:flatMap相当于先进行map操作,在进行flatten操作
    println(wordList.flatMap(x => x.split(" ")))

    //(5)分组
    println(list.groupBy(x => x % 2))
  }
}

3.Reduce方法
Reduce简化(规约) :通过指定的逻辑将集合中的数据进行聚合,从而减少数据,最终获取结果。

案例实操:

object TestReduce {

    def main(args: Array[String]): Unit = {

        val list = List(1,2,3,4)

        // 将数据两两结合,实现运算规则
        val i: Int = list.reduce( (x,y) => x-y )
        println("i = " + i)

        // 从源码的角度,reduce底层调用的其实就是reduceLeft
        //val i1 = list.reduceLeft((x,y) => x-y)

        // ((4-3)-2-1) = -2
        val i2 = list.reduceRight((x,y) => x-y)
        println(i2)
    }
}

4.Fold方法
Fold折叠:化简的一种特殊情况。

  1. 案例实操:fold基本使用
object TestFold {

    def main(args: Array[String]): Unit = {

        val list = List(1,2,3,4)

        // fold方法使用了函数柯里化,存在两个参数列表
        // 第一个参数列表为 : 零值(初始值)
        // 第二个参数列表为:

        // fold底层其实为foldLeft
        val i = list.foldLeft(1)((x,y)=>x-y)

        val i1 = list.foldRight(10)((x,y)=>x-y)

        println(i)
        println(i1)
    }
}
  1. 案例实操:两个集合合并
object TestFold {

    def main(args: Array[String]): Unit = {

        // 两个Map的数据合并
        val map1 = mutable.Map("a"->1, "b"->2, "c"->3)
        val map2 = mutable.Map("a"->4, "b"->5, "d"->6)

        val map3: mutable.Map[String, Int] = map2.foldLeft(map1) {
            (map, kv) => {
                val k = kv._1
                val v = kv._2

                map(k) = map.getOrElse(k, 0) + v

                map
            }
        }

        println(map3)
    }
}

7.7.5 普通WordCount案例

1.需求
单词计数:将集合中出现的相同的单词,进行计数,取计数排名前三的结果
2.需求分析
在这里插入图片描述
3.案例实操

object TestWordCount {

    def main(args: Array[String]): Unit = {

        // 单词计数:将集合中出现的相同的单词,进行计数,取计数排名前三的结果
        val stringList = List("Hello Scala Hbase kafka", "Hello Scala Hbase", "Hello Scala", "Hello")

        // 1) 将每一个字符串转换成一个一个单词
        val wordList: List[String] = stringList.flatMap(str=>str.split(" "))
        //println(wordList)

        // 2) 将相同的单词放置在一起
        val wordToWordsMap: Map[String, List[String]] = wordList.groupBy(word=>word)
        //println(wordToWordsMap)

        // 3) 对相同的单词进行计数
        // (word, list) => (word, count)
        val wordToCountMap: Map[String, Int] = wordToWordsMap.map(tuple=>(tuple._1, tuple._2.size))

        // 4) 对计数完成后的结果进行排序(降序)
        val sortList: List[(String, Int)] = wordToCountMap.toList.sortWith {
            (left, right) => {
                left._2 > right._2
            }
        }

        // 5) 对排序后的结果取前3名
        val resultList: List[(String, Int)] = sortList.take(3)

        println(resultList)
    }
}

7.7.6 复杂WordCount案例

方式一
案例实操

object TestWordCount {

    def main(args: Array[String]): Unit = {

        // 第一种方式(不通用)
        val tupleList = List(("Hello Scala Spark World ", 4), ("Hello Scala Spark", 3), ("Hello Scala", 2), ("Hello", 1))

        val stringList: List[String] = tupleList.map(t=>(t._1 + " ") * t._2)

        //val words: List[String] = stringList.flatMap(s=>s.split(" "))
        val words: List[String] = stringList.flatMap(_.split(" "))

        //在map中,如果传进来什么就返回什么,不要用_省略
        val groupMap: Map[String, List[String]] = words.groupBy(word=>word)
        //val groupMap: Map[String, List[String]] = words.groupBy(_)

        // (word, list) => (word, count)
        val wordToCount: Map[String, Int] = groupMap.map(t=>(t._1, t._2.size))

        val wordCountList: List[(String, Int)] = wordToCount.toList.sortWith {
            (left, right) => {
                left._2 > right._2
            }
        }.take(3)

        //tupleList.map(t=>(t._1 + " ") * t._2).flatMap(_.split(" ")).groupBy(word=>word).map(t=>(t._1, t._2.size))
        println(wordCountList)
    }
}

方式二
案例实操

object TestWordCount {

    def main(args: Array[String]): Unit = {

        val tuples = List(("Hello Scala Spark World", 4), ("Hello Scala Spark", 3), ("Hello Scala", 2), ("Hello", 1))

        // (Hello,4),(Scala,4),(Spark,4),(World,4)
        // (Hello,3),(Scala,3),(Spark,3)
        // (Hello,2),(Scala,2)
        // (Hello,1)
        val wordToCountList: List[(String, Int)] = tuples.flatMap {
            t => {
                val strings: Array[String] = t._1.split(" ")
                strings.map(word => (word, t._2))
            }
        }

        // Hello, List((Hello,4), (Hello,3), (Hello,2), (Hello,1))
        // Scala, List((Scala,4), (Scala,3), (Scala,2)
        // Spark, List((Spark,4), (Spark,3)
        // Word, List((Word,4))
        val wordToTupleMap: Map[String, List[(String, Int)]] = wordToCountList.groupBy(t=>t._1)

        val stringToInts: Map[String, List[Int]] = wordToTupleMap.mapValues {
            datas => datas.map(t => t._2)
        }
        stringToInts

        /*
        val wordToCountMap: Map[String, List[Int]] = wordToTupleMap.map {
            t => {
                (t._1, t._2.map(t1 => t1._2))
            }
        }

        val wordToTotalCountMap: Map[String, Int] = wordToCountMap.map(t=>(t._1, t._2.sum))
        println(wordToTotalCountMap)
        */
    }
}

7.8 队列

1.说明
scala也提供了队列(Queue)的数据结构,队列的特点就是先进先出。进队和出队的方法分别为enqueue和dequeue

2.案例实操

object TestQueue {

    def main(args: Array[String]): Unit = {

        val que = new mutable.Queue[String]()

        que.enqueue("a", "b", "c")

        println(que.dequeue())
        println(que.dequeue())
        println(que.dequeue())
    }
}

7.9 并行集合

1.说明
Scala为了充分使用多核CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算

2.案例实操

object TestPar {

    def main(args: Array[String]): Unit = {

        val result1 = (0 to 100).map{case _ => Thread.currentThread.getName}
        val result2 = (0 to 100).par.map{case _ => Thread.currentThread.getName}
        println(result1)
        println(result2)
    }
}

7.10 sort排序

排序不涉及任何的具体排序算法!!!(冒泡, 选择, 插入, 希尔, 快速, 归并)

如何让scala给我们排序!!!

提供了3个算子来给我们排序:

  1. sorted
  2. sortBy(必须掌握)
  3. sortWith

另外掌握一个: Ordering的使用

注意: 不管是对可变还是不可变, 都不会修改原集合, 都是返回一个新的排好序.

java中的排序, 涉及到元素的比较大小

  1. 元素自己可以和其他的兄弟比较 1 < 2

    让类实现Comparable接口

  2. 找一个比较器进行比较两个元素大小 c.compare(a. b) 优先级高

    Comparator

    • compare(a, b)

      如果返回值小于零 a < b

      返回值等于零 a == b

      返回值大于零 a > b

scala中, 如果要排序, 那么集合应该满足以下两点之一:

  1. 让你的集合中的元素有比较的能力

    class User1(val age: Int, val name: String) extends Ordered[User1] {
        
        override def toString: String = s"[age = $age, name = $name]"
        
        override def compare(that: User1): Int = this.age - that.age
    }
    
  2. 使用比较器.(必须要会写)

    方式1不够灵活, 如果想更改比较的逻辑, 需要去更改自定义类型的定义, 不够灵活.

    比较器就比较灵活!!!

    // 在java中使用的Comparator
    // 在scala中, 有一个更加牛逼的Comparator的子类: Ordering
    

    内置一些常见类型的Ordering, 但是对自定含义没有内置!!!

sortBy

就用这个sortBy, 他可以解决99.99%的排序

sortWith

val list2 = list1.sortWith(_ > _)

8.模式匹配

Scala中的模式匹配类似于Java中的switch语法,但是更加强大。
模式匹配语法中,采用match关键字声明,每个分支采用case关键字进行声明,当需要匹配时,会从第一个case分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有case都不匹配,那么会执行case _分支,类似于Java中default语句。

8.1 基本语法

object TestMatchCase {

  def main(args: Array[String]): Unit = {

    var a: Int = 10
    var b: Int = 20
    var operator: Char = 'd'

    var result = operator match {
      case '+' => a + b
      case '-' => a - b
      case '*' => a * b
      case '/' => a / b
      case _ => "illegal"
    }

    println(result)
  }
}

说明
(1)如果所有case都不匹配,那么会执行case _ 分支,类似于Java中default语句,若没有case _ 分支,那么会抛出MatchError。
(2)每个case中,不用break语句,自动中断case。
(3)match case语句可以匹配任何类型,而不只是字面量。
(4)=> 后面的代码块,是作为一个整体执行,可以使用{}括起来,也可以不括。

8.2 模式守卫

1.说明
如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫

2.案例实操

object TestMatchGuard {

  def main(args: Array[String]): Unit = {

    def abs(x: Int) = x match {
      case i: Int if i >= 0 => i
      case j: Int if j < 0 => -j
      case _ => "type illegal"
    }

    println(abs(-5))
  }
}

8.3 模式匹配类型

8.3.1 匹配常量

1.说明
scala中,模式匹配可以匹配所有的字面量,包括字符串,字符,数字,布尔值等等

2.实操

object TestMatchVal {

  def main(args: Array[String]): Unit = {

    println(describe(6))

  }



  def describe(x: Any) = x match {

    case 5 => "Int five"

    case "hello" => "String hello"

    case true => "Boolean true"

    case '+' => "Char +"

  }

}

8.3.2 匹配类型

1.说明
需要进行类型判断时,可以使用前文所学的isInstanceOf[T]和asInstanceOf[T],也可使用模式匹配实现同样的功能

2.案例实操

object TestMatchClass {

  def describe(x: Any) = x match {

    case i: Int => "Int"
    case s: String => "String hello"
    case m: List[_] => "List"
    case c: Array[Int] => "Array[Int]"
    case someThing => "something else " + someThing
  }

  def main(args: Array[String]): Unit = {

    //泛型擦除
    println(describe(List(1, 2, 3, 4, 5)))

    //数组例外,可保留泛型
    println(describe(Array(1, 2, 3, 4, 5, 6)))
    println(describe(Array("abc")))
  }
}

8.3.3 匹配数组

1.说明
scala模式匹配可以对集合进行精确的匹配,例如匹配只有两个元素的、且第一个元素为0的数组

2.案例实操

object TestMatchArray {

  def main(args: Array[String]): Unit = {

    for (arr <- Array(Array(0), Array(1, 0), Array(0, 1, 0), Array(1, 1, 0), Array(1, 1, 0, 1), Array("hello", 90))) { // 对一个数组集合进行遍历

      val result = arr match {
        case Array(0) => "0" //匹配Array(0) 这个数组
        
        case Array(x, y) => x + "," + y //匹配有两个元素的数组,然后将将元素值赋给对应的x,y
        
        case Array(0, _*) => "以0开头的数组" //匹配以0开头和数组
        
        case _ => "something else"
      }

      println("result = " + result)
    }
  }
}

8.3.4 匹配列表

1)方式一

object TestMatchList {
  def main(args: Array[String]): Unit = {

    //list是一个存放List集合的数组
    //请思考,如果要匹配 List(88) 这样的只含有一个元素的列表,并原值返回.应该怎么写
    for (list <- Array(List(0), List(1, 0), List(0, 0, 0), List(1, 0, 0), List(88))) {

      val result = list match {

        case List(0) => "0" //匹配List(0)
        case List(x, y) => x + "," + y //匹配有两个元素的List
        case List(0, _*) => "0 ..."
        case _ => "something else"
      }

      println(result)
    }
  }
}

2)方式二

object TestMatchList {

  def main(args: Array[String]): Unit = {

    val list: List[Int] = List(1, 2, 5, 6, 7)

    list match {
      case first :: second :: rest => println(first + "-" + second + "-" + rest)
      case _ => println("something else")
    }
  }
}

8.3.5 匹配元组

object TestMatchTuple {

  def main(args: Array[String]): Unit = {

    //对一个元组集合进行遍历
    for (tuple <- Array((0, 1), (1, 0), (1, 1), (1, 0, 2))) {

      val result = tuple match {
        case (0, _) => "0 ..." //是第一个元素是0的元组
        case (y, 0) => "" + y + "0" // 匹配后一个元素是0的对偶元组
        case (a, b) => "" + a + " " + b
        case _ => "something else" //默认

      }
      println(result)
    }
  }
}

8.3.6 匹配对象及样例类

1.基本语法

class User(val name: String, val age: Int)

object User {

  def apply(name: String, age: Int): User = new User(name, age)

  def unapply(user: User): Option[(String, Int)] = {
    if (user == null)
      None
    else
      Some(user.name, user.age)
  }
}

object TestMatchUnapply {
  def main(args: Array[String]): Unit = {
    val user: User = User("zhangsan", 11)
    val result = user match {
      case User("zhangsan", 11) => "yes"
      case _ => "no"
    }

    println(result)
  }
}
  • val user = User(“zhangsan”,11),该语句在执行时,实际调用的是User伴生对象中的apply方法,因此不用new关键字就能构造出相应的对象。

  • 当将User(“zhangsan”, 11)写在case后时[case User(“zhangsan”, 11) => “yes”],会默认调用unapply方法(对象提取器),user作为unapply方法的参数,unapply方法将user对象的name和age属性提取出来,与User(“zhangsan”, 11)中的属性值进行匹配

  • case中对象的unapply方法(提取器)返回Some,且所有属性均一致,才算匹配成功,属性不一致,或返回None,则匹配失败。

  • 若只提取对象的一个属性,则提取器为unapply(obj:Obj):Option[T]
    若提取对象的多个属性,则提取器为unapply(obj:Obj):Option[(T1,T2,T3…)]
    若提取对象的可变个属性,则提取器为unapplySeq(obj:Obj):Option[Seq[T]]

2.样例类
(1)语法:
case class Person (name: String, age: Int)
(2)说明
样例类仍然是类,和普通类相比,只是其自动生成了伴生对象,并且伴生对象中自动提供了一些常用的方法,如apply、unapply、toString、equals、hashCode和copy。
样例类是为模式匹配而优化的类,因为其默认提供了unapply方法,因此,样例类可以直接使用模式匹配,而无需自己实现unapply方法。
构造器中的每一个参数都成为val,除非它被显式地声明为var(不建议这样做)

3.实操
上述匹配对象的案例使用样例类会节省大量代码

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

object TestMatchUnapply {
  def main(args: Array[String]): Unit = {
    val user: User = User("zhangsan", 11)
    val result = user match {
      case User("zhangsan", 11) => "yes"
      case _ => "no"
    }

    println(result)
  }
}

8.4 变量声明中的模式匹配

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

object TestMatchVariable {
  def main(args: Array[String]): Unit = {

    val (x, y) = (1, 2)
    println(s"x=$x,y=$y")

    val Array(first, second, _*) = Array(1, 7, 2, 9)
    println(s"first=$first,second=$second")

    val Person(name, age) = Person1("zhangsan", 16)
    println(s"name=$name,age=$age")
  }
}

8.5 for表达式中的模式匹配

object TestMatchFor {

  def main(args: Array[String]): Unit = {

    val map = Map("A" -> 1, "B" -> 0, "C" -> 3)
    for ((k, v) <- map) { //直接将map中的k-v遍历出来
      println(k + " -> " + v) //3个
    }
    println("----------------------")

    //遍历value=0的 k-v ,如果v不是0,过滤
    for ((k, 0) <- map) {
      println(k + " --> " + 0) // B->0
    }

    println("----------------------")
    //if v == 0 是一个过滤的条件
    for ((k, v) <- map if v >= 1) {
      println(k + " ---> " + v) // A->1 和 c->33
    }
  }
}

8.6 偏函数中的模式匹配

偏函数也是函数的一种,通过偏函数我们可以方便的对输入参数做更精确的检查。例如该偏函数的输入类型为List[Int],而我们需要的是第一个元素是0的集合,这就是通过模式匹配实现的。

1.偏函数定义
用一对大括号括起来的case语句, 就是一个偏函数

val second: PartialFunction[List[Int], Option[Int]] = {
  case x :: y :: _ => Some(y)
}

注:该偏函数的功能是返回输入的List集合的第二个元素

2.偏函数原理
上述代码会被scala编译器翻译成以下代码,与普通函数相比,只是多了一个用于参数检查的函数——isDefinedAt,其返回值类型为Boolean。

 val second = new PartialFunction[List[Int], Option[Int]] {

    //检查输入参数是否合格
    override def isDefinedAt(list: List[Int]): Boolean = list match {
      case x :: y :: _ => true
      case _ => false
    }

    //执行函数逻辑
    override def apply(list: List[Int]): Option[Int] = list match {
      case x :: y :: _ => Some(y)
    }
  }

3.偏函数使用
偏函数不能像second(List(1,2,3))这样直接使用,因为这样会直接调用apply方法,而应该调用applyOrElse方法,如下
second.applyOrElse(List(1,2,3), (_: List[Int]) => None)
applyOrElse方法的逻辑为 if (ifDefinedAt(list)) apply(list) else default。如果输入参数满足条件,即isDefinedAt返回true,则执行apply方法,否则执行defalut方法,default方法为参数不满足要求的处理逻辑。

4.案例实操
(1)需求
将该List(1,2,3,4,5,6,“test”)中的Int类型的元素加一,并去掉字符串。
(2)实操
方法一:
List(1,2,3,4,5,6,“test”).filter(.isInstanceOf[Int]).map(.asInstanceOf[Int] + 1).foreach(println)
方法二:
List(1, 2, 3, 4, 5, 6, “test”).collect { case x: Int => x + 1 }.foreach(println)

9.泛型

9.1 协变和逆变

1.语法

class MyList[+T]{ //协变
} 
class MyList[-T]{ //逆变
}
class MyList[T] //不变

2.说明
协变:Son是Father的子类,则MyList[Son] 也作为MyList[Father]的“子类”。
逆变:Son是Father的子类,则MyList[Son]作为MyList[Father]的“父类”。
不变:Son是Father的子类,则MyList[Father]与MyList[Son]“无父子关系”。

3.实操

class MyList[T]

class Father
class Son extends Father

不变

MyList[Son] 和 MyList[Father] 没有任何的"父子关系"

错误:  val fs: MyList[Father] = new MyList[Son] 
默认情况下, 所有泛型都是不变的!!!
class MyList[T]

协变

val fs: MyList[Father] = new MyList[Son]  // 正确的

class MyList[+T]

逆变

val fs: MyList[Son] = new MyList[Father]  // 正确的

class MyList[-T]

9.2 泛型上下限

[T <: Ordered[T]]  // 上限

[T >: Pet]  // 下限. 推导的时候,不能和上限一样

9.3 上下文限定

1.语法
def f[A : B](a: A) = println(a) //等同于def fA(implicit arg:B[A])=println(a)

2.说明
上下文限定是将泛型和隐式转换的结合产物,以下两者功能相同,使用上下文限定[A : Ordering]之后,方法内无法使用隐式参数名调用隐式参数,需要通过implicitly[Ordering[A]]获取隐式变量。

3.实操

def f[A:Ordering](a:A,b:A) =implicitly[Ordering[A]].compare(a,b)
def f[A](a: A, b: A)(implicit ord: Ordering[A]) = ord.compare(a, b)

上下文泛型的本质其实是对***隐式参数和隐式值***的封装!!!

9.4 视图界定

视图界定是对象***隐式转换函数***的封装!!!

def max[T <% Ordered[T]](x: T, y: T) :T = {
    if(x > y) x
    else y
}


/*def max[T](x: T, y: T)(implicit f:  T => Ordered[T]) :T = {
        if(x > y) x
        else y
    }*/

10.隐式转换

10.1 隐式转函数

1.说明
隐式转换是scala一个高级特性, 可以再不需改任何代码的情况下,扩展某个类的功能

2.案例实操

// 在作用域内只能有一个, 如果有多个 Double=> Int 那么这个时候, 隐式转换就不会发生
implicit def double2Int(d: Double): Int = d.toInt

val a: Int = 10.1 // Double=> Int
 

重要功能: 对已有的类做功能扩展

object ImplicitDemo2 {
    def main(args: Array[String]): Unit = {
        // 读取文本文件中的内容  java: IO
        implicit def file2RichFile(file: File) = new RichFile(file)
        
        val content = new File("C:\\Users\\lzc\\Desktop\\class_code\\2019_11_28\\01_scala\\scala1128\\src\\main\\scala\\com\\jaffe\\scala1128\\day06\\implicitdemo\\ImplicitDemo2.scala").readContent
        println(content)
    }
}

class RichFile(file: File) {
    
    // 这个方法要真正的去封装读 文件 内容的代码
    def readContent: String = {
        // Array(1,2,3,4)  => arr.mkString   "1234"
        // Array(1,2,3,4)  => arr.mkString(",")   "1,2,3,4"
        Source.fromFile(file, "utf-8").mkString
    }
}

3.总结

  1. 什么时候会用到隐式转换

    • A类型的值, 赋值给B类型的时候,AB没有任何的关系, 这个时候会去找一个隐式转换(A => B),如果还有有, 则编译成功,将来自动把 A转成B之后, 赋值给B

      val a: A = new A
      val b:B = a   // 会找隐式转换
      
    • 在调用一个对象a的方法(foo)的时候, 如果这个对象a没有这样的方法, 那么会找一个隐式转换, 这个隐式接收一个 A类型的对象, 返回一个新的对象, 新的对象中有foo, 就成功了

      implicit def a(file:File) = new RichFile(file)
      val content = new File("..").readContent
      //RichFile中有一个readContent方法
      
  2. 隐式转换需要做什么

    • 写一个隐式转换函数

      • 函数名没有限制, 随意
      • 参数一定是你原来的类型
      • 返回值一定是你新定义的类型
      • implicit def a(file:File) = new RichFile(file)
    • 写一个自定义类型(能够完成以前的类没有的功能)

      • 自定义一个类: 主构造接收已有的类型

      • 在定义一个你需要的方法

class RichFile(file: File){
	def readContent = {}
}

10.2 隐式类

implicit class RichFile(file: File) {
    // 这个方法要真正的去封装读 文件 内容的代码
    def readContent: String = {
        // Array(1,2,3,4)  => arr.mkString   "1234"
        // Array(1,2,3,4)  => arr.mkString(",")   "1,2,3,4"
        Source.fromFile(file, "utf-8").mkString
    }
}

10.3 隐式参数和隐式值

配合使用

implicit val aaa: Int = 100

def main(args: Array[String]): Unit = {
    foo
}

// a是隐式参数, 将来调用的时候根据需要,可以不传
// 让scala自动帮助我们传递, 找一个隐式值
def foo(implicit a: Int) = {
    println(a)
}

注意

  • 找隐式值的时候, 只看类型, 不看变量的名字

  • 在作用域内, 只能有一个同类型的隐式值

  • 一个隐式参数列表内, 如果有多个参数, 则这些参数都是隐式的

  • 如果一个函数, 有很多参数, 有些用是隐式参数, 有些不是, 怎么办?

    对函数左柯里化

    • 使用两个参数列表
    • 第一个是必须要传
    • 第二个里面是隐式参数
  • 如果使用隐式值, 则圆括号也要省略,省略括号, 表示在使用 隐式值, 加上括号, 表示使用默认值

10.4 隐式转换函数,隐式类,隐式值 的查找路径问题

  1. 在当前作用域中查找
  2. 相关类型伴生对象中查找!!! 如果有泛型, 泛型也是相关的.

11.异常

  • 运行时异常

    只有在运行的时候才有可能产生, 在源码中,可以处理, 也可以不处理

  • 编译时异常(受检异常)

    在源码的时候,必须处理. IO, 网络

11.1 Java异常处理

public class ExceptionDemo {

    public static void main(String[] args) {

        try {
            int a = 10;
            int b = 0;
            int c = a / b;
        }catch (ArithmeticException e){
// catch时,需要将范围小的写到前面
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            System.out.println("finally");
        }
    }
}

注意事项
(1)Java语言按照try—catch—finally的方式来处理异常
(2)不管有没有异常捕获,都会执行finally,因此通常可以在finally代码块中释放资源。
(3)可以有多个catch,分别捕获对应的异常,这时需要把范围小的异常类写在前面,把范围大的异常类写在后面,否则编译错误。

11.2 Scala异常处理

scala中, 所有的异常, 都可以处理, 也可以不处理.

用法:

  1. 处理

    • 使用try catch finally

    • 抛出异常类型(java: throws scala: 注解)

      @throws(classOf[RuntimeException])
      @throws(classOf[IllegalArgumentException])
      
  2. 主动抛出

    throw new IllegalArgumentException
    

    scalac: 编译scala源码

    scala: 进行运行


    scala 源码.scala

    当脚本用来(内部会先编译, 再运行)

def main(args: Array[String]): Unit = {

    try {
        var n= 10 / 0
    }catch {
        case ex: ArithmeticException=>{
            // 发生算术异常
            println("发生算术异常")
        }
        case ex: Exception=>{
            // 对异常处理
            println("发生了异常1")
            println("发生了异常2")
        }
    }finally {
        println("finally")
    }
}

1)我们将可疑代码封装在try块中。在try块之后使用了一个catch处理程序来捕获异常。如果发生任何异常,catch处理程序将处理它,程序将不会异常终止。
2)Scala的异常的工作机制和Java一样,但是Scala没有“checked(编译期)”异常,即Scala没有编译异常这个概念,异常都是在运行的时候捕获处理。
3)异常捕捉的机制与其他语言中一样,如果有异常发生,catch子句是按次序捕捉的。因此,在catch子句中,越具体的异常越要靠前,越普遍的异常越靠后,如果把越普遍的异常写在前,把具体的异常写在后,在Scala中也不会报错,但这样是非常不好的编程风格。
4)finally子句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作,这点和Java一样。
5)用throw关键字,抛出一个异常对象。所有异常都是Throwable的子类型。throw表达式是有类型的,就是Nothing,因为Nothing是所有类型的子类型,所以throw表达式可以用在需要类型的地方

def test():Nothing = {
    throw new Exception("不对")
}

6)Scala提供了throws关键字来声明异常。可以使用方法定义声明异常。它向调用者函数提供了此方法可能引发此异常的信息。它有助于调用函数处理并将该代码包含在try-catch块中,以避免程序异常终止。在Scala中,可以使用throws注释来声明异常

def main(args: Array[String]): Unit = {
  	f11()
}

@throws(classOf[NumberFormatException])
def f11()={
  	"abc".toInt
}

12.总结

12.1 开发环境

要求掌握必要的Scala开发环境搭建技能。

12.2 变量和数据类型

掌握var和val的区别
掌握数值类型(Byte、Short、Int、Long、Float、Double、Char)之间的转换关系

12.3 流程控制

掌握if-else、for、while等必要的流程控制结构,掌握如何实现break、continue的功能。

12.4 函数式编程

掌握高阶函数、匿名函数、函数柯里化、函数参数以及函数至简原则。

12.5 面向对象

掌握Scala与Java继承方面的区别、单例对象(伴生对象)、特质的用法及功能。

12.6 集合

掌握常用集合的使用、集合常用的计算函数。

12.7 模式匹配

掌握模式匹配的用法

12.8 异常

掌握异常常用操作即可

12.9 隐式转换

掌握隐式方法、隐式参数、隐式类,以及隐式解析机制

12.10 泛型

掌握泛型语法

12.11 扩展

  1. 中置运算符
    1 + 2

  2. 一元运算符
    后置
    1 toString
    前置
    +5
    -5
    !false 取反
    ~2 按位取反

  3. apply方法
    任何对象都可以 调用
    对象(…) // 对象.apply(…)
    伴生对象
    普通的对象
    函数对象

  4. update方法
    user(0) = 100 // user.update(0, 100)

  5. _ 总结
    https://stackoverflow.com/questions/8000903/what-are-all-the-uses-of-an-underscore-in-scala

    1. 导包, 通配符 _
      import java.util.Math._

    2. 屏蔽类
      import java.util.{HashMap => _, _}

    3. 给可变参数传值的时候, 展开
      foo(arr:_*)

    4. 元组元素访问
      t._1

    5. 函数参数的占位符
      reduce(_ + _)

    6. 方法转函数
      val f = foo _

    7. 给属性设置默认值
      class A{
      var a: Int = _ // 给a赋值默认的0
      }

    8. 模式匹配的通配符
      case _ => // 匹配所有

    9. 模式匹配集合
      Array(a, b, rest@_*)

    10. 部分应用函数
      math.pow(_, 2)

    11. 在定义标识符的时候, 把字符和运算符隔开
      val a_+ = 10
      a+ // 错误

    12. List[_]
      泛型通配符

    13. 自身类型
      _: Exception =>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值