Scala

文章目录

1. Scala 入门

1.1 概述

1.1.1 Scala优势

  • Scala ----- Java++
    • Scala 基于JVM, 和Java完全兼容, 同样具有跨平台、可移植性好、方便的垃圾回收等特性
    • Scala 比 Java 更加面向对象
    • Scala 是一门函数式编程语言
  • Scala 更适合大数据的处理
    • Scala 对集合类型数据有非常好的支持
    • Spark 的底层用Scala编写
  • Spark 新一代内存级大数据计算框架, 是大数据的重要内容

1.1.2 Scala发展历史

联邦理工学院马丁·奥德斯基(Martin Odersky) 与 2001年开始设计 Scala

马丁·奥德斯基是编译器及编程的狂热爱好者, 长时间的编程之后, 希望发明一种语言, 能够让写程序这样的基础工作变得高效, 简单。所以当接触到Java语言后, 对Java 这门便携式, 运行在网络, 且存在垃圾回收的语言产生了极大的兴趣, 所以决定将函数式编程语言的特点融合到Java中, 由此发明了两种语言 (Pizza & Scala)

Pizza 和 Scala 极大地推动了 Java 编程语言的发展

  • JDK5.0 的泛型、增强for循环、自动类型转换等, 都是从Pizza引入的新特性。
  • JDK8.0 的类型推断、Lambda 表达式 就是从Scala引入的特性
  • JDK5.0和JDK8.0的编辑器就是马丁·奥德斯基写的

1.1.3 Scala 和 Java 关系

一般来说, 学 Scala的人, 都会Java, 而Scala是基于Java的, 因此我们需要将Scala和Java以及JVM之间的关系搞清楚

  • 可以使用Java语法(部分)
  • Scala特有的语法和类库
  • 对Java的类库做了包装

1.1.4 Scala语言特点

Scala是一门 Java虚拟机(JVM) 为运行环境并将面向对象和函数式编程的最佳特性结合在一起的静态类型编程语言 (静态语言需要提前编译如 Java、 C、 C++等, 动态语言如 javascript、python)

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

1.2 Scala环境搭建

  1. 安装步骤
    1. 首先确保 JDK1.8 安装成功
    2. 下载对应的 Scala安装文件 https://www.scala-lang.org/
    3. 解压之后配置环境变量

1.3 Hello World

package chapter01

// object: 关键字, 表示声明一个单例对象(伴生对象)
object HelloWorld {

	// main 方法: 从外部可以直接调用执行的方法
	// def 方法名称(参数): 返回值类型 = { 方法体 }
	def main(args: Array[String]): Unit = {

		print("Hello World")
		println("Hello World")
		System.out.println("Hello World");
	}
}

目录:

src/main/java(源根)

src/main/scala(源根)

1.4 官方编程指南

  1. 在线查看: https://www.scala-lang.org
  2. 离线查看: 解压 scala-docs-2.11.8.zip, 可以获得scala的API操作

2. 变量和数据类型

2.1注释

scala 注释使用和 Java 完全一样

2.2 变量和常量

常量: 在程序执行的过程中, 其值不会被改变

  1. java变量和常量语法

    变量类型 变量名称 = 初始值

    final 变量类型 变量名称 = 初始值

  2. 基本语法

    var 变量名[: 变量类型] = 初始值 var i: Int = 10

    val 常量名[: 变量类型] = 初始值 val j: Int = 20

    注意: 能用常量的地方不用变量

2.3 标识符的命名规范

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

  1. 命名规范

    Scala 中标识符声明, 基本和Java是一致的, 但是细节上会有所变化, 有以下三种规则:

    1. 以字母或者下划线开头, 后接字母、数字、下划线
    2. 以操作符开头, 且只包含操作符 (+ - * / # !)
    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.4 字符串输出

  1. 基本语法

    1. 字符串, 通过 + 号连接
    2. printf 用法: 字符串, 通过 % 号传值
    3. 字符串模版 (插值字符串): 通过 $ 获取变量值
  2. 案例实操

    package com.chapter02
    
    object Test04String {
    
    	def main(args: Array[String]): Unit = {
    		// 1.  字符串, 通过 + 号连接
    		val name : String = "alice"
    		val age : Int = 18
    		println(age + "岁的" + name + "在学习")
    		// * 用于将一个字符串复制多次并拼接
    		println(name * 10)
    
    		// 2.  printf 用法: 字符串, 通过 % 号传值
    		printf("%d岁的%s在学习", age, name)
    
    		// 3.  字符串模版 (插值字符串): 通过 $ 获取变量值
    		println()
    		println(s"${age}岁的${name}在学习")
    
    		val num : Double = 2.345687676788678786d
    		println(f"The num is ${num}%2.2f")	// 格式化模版字符串
    		println(raw"The num is ${num}%2.2f")	// 不会解析模版字符串
    
    		// 三引号表示字符串, 保持多行字符串的原格式输出
    
    		val sql = s"""
    		   |select *
    		   |from
    		   |	student
    		   | where
    		   | 	name = ${name}
    		   |  and
    		   |  	age > ${age}
    		   |   """.stripMargin
    		println(sql)
    	}
    }
    

2.5键盘输入

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

  1. 基本语法

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

  2. 案例实操

    从控制台接收用户的信息

    package com.chapter02
    
    import scala.io.StdIn
    
    object Test05StdIn {
    
    	def main(args: Array[String]): Unit = {
    
    		// 输入信息
    		print("请输入名字>>: ")
    		val name: String = StdIn.readLine()
    		print("请输入年龄>>: ")
    		val age : Int = StdIn.readInt()
    
    		println(s"欢迎${age}岁的${name}学习")
    	}
    }
    

2.6数据类型

Java 基本数据类型: char、byte、short、int、long、float、double、boolean

Java引用类型: (对象类型)

由于Java有基本类型, 而且基本类型不是真正意义的对象, 即使后面产生了基本类型的包装类, 但是仍然存在基本数据类型, 所以Java语言并不是真正意思的面向对象

Java基本类型的包装类: Character、Byte、Short、Integer、Long、Float、Double、Boolean
注意: Java中基本类型和引用类型没有共同的祖先
Scala 数据类型
数据类型

  1. Scala 中一切数据都是对象, 都是 Any的子类
  2. Scala 中数据类型分为两大类: 数值类型(AnyVal)、引用类型(AnyRef), 不管是值类型还是引用类型都是对象
  3. Scala 数据类型仍然遵守, 低精度的值类型向高精度值类型, 自动转换(隐式转换)
  4. Scala 中的 StringOps 是对Java中的 String 增强
  5. Unit 对应Java中的void, 用于方法返回值的位置, 表示方法没有返回值, Unit 是一个数据类型, 只有一个对象就是() void不是数据类型, 只是一个关键字
  6. Null 是一个类型, 只有一个对象就是 null 它是所有引用类型(AnyRef)的子类
  7. 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位有符号补码整数 数值区间为 -9223372036854775808L 到 9223372036854775807L = 2的(64-1)次方 -1
		val a: Byte = 127
		val b: Byte = -128
		val c: Short = 32767
		val d: Short = -32768
		val e: Int = 2147483647
		val f: Int = -2147483648
		val g: Long = 9223372036854775807L
		val h: Long = -9223372036854775808L
val va: Byte = (10 + 20).toByte // 强制类型转换

2.8 浮点类型(Float、Double)

2.9 字符类型 (Char)

  1. 基本说明

    字符类型可以表示单个字符, 字符类型是Char

  2. 案例

    1. 字符常量使用 单引号 ‘’ 括起来的单个字符
    2. \t 一个制表位, 实现对齐的功能
    3. \n 换行符
    4. \\ 转义表示\
    5. \" 转义表示 "

2.10 布尔类型

  1. 基本说明
    1. 布尔类型也叫 Boolean 类型, Boolean 类型数据只允许取值 true 和 false
    2. boolean 类型 占1个字节

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

  1. 基本说明

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

2.12 类型转换

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

请添加图片描述

  1. 基本说明

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

    	def main(args: Array[String]): Unit = {
    		// 1. 自动提升原则: 有多种类型的数据混合运算时, 系统首先自动将所有数据转换成精度大的那种数据类型, 然后再进行计算
    		val a1: Byte = 10
    		val b1: Long = 2353L
    		val result1: Long = a1 + b1
    		println(result1)
    		val result2: Int = (a1 + b1).toInt
    		println(result2)
    		// 2. 把精度大的数值类型赋值给精度小的数值类型时, 就会报错, 反之就会进行自动类型转换
    		val a2: Byte = 10
    		val b2: Int = a2
    		//		val c2 : Byte = b2  error
    		val c2: Byte = b2.toByte
    		println(c2)
    		// 3. (byte, short) 和 char之间不会相互自动转换
    		val a3: Byte = 10
    		val b3: Char = 'b'
    		//		val c3: Byte = b3  error
    		val c3: Byte = b3.toByte
    		val d3: Int = b3
    		println(c3)
    		println(b3)
    		// 4. byte, short, char 他们三者之间可以计算, 在计算时首先转换为int类型
    		val a4: Byte = 12
    		val b4: Short = 25
    		val c4: Char = 'c'
    		//		val result4:Short = a4 + b4  error
    		val result4: Int = a4 + b4
    		val result44: Int = a4 + b4 + c4
    		println(result4)
    		println(result44)
    	}
    

2.12.2 强制类型转换

  1. 基本说明

    自动类型转换的逆过程, 将精度大的数值类型转换为精度小的数值类型。使用时要加上强制转函数, 但可能造成精度降低或溢出, 格外要注意

    Java int num = (int) 2.5

    Scala var num: Int = 2.7.toInt

  2. 案例实操

    1. 将数据由高精度转为低精度, 就需要使用到强制转换
    2. 强转符号只针对与最近的操作数有效, 往往会使用小括号提升优先级
    // 2. 强制类型转换
    // 1.  将数据由高精度转为低精度, 就需要使用到强制转换
    //		val n1: Int = 2.9.toInt	// 2
    val n1: Int = -2.9.toInt // -2
    println(n1)
    
    // 2.  强转符号只针对与最近的操作数有效, 往往会使用小括号提升优先级
    //		val n2 : Int = (2.6 + 3.7).toInt	// 6
    val n2: Int = 2.6.toInt + 3.7.toInt // 5
    println(n2)
    
    // 3. 数值类型和String类型的转换
    // 1. 数值转String
    val n: Int = 27
    val s: String = n + ""
    println(s)
    
    // 2. String转数值
    val m: Int = "12".toInt
    println(m)
    val f: Float = "12.3".toFloat
    val f2: Int = "12.3".toDouble.toInt
    println(f)
    println(f2)
    

3. 运算符

Scala 运算符的使用和 Java 运算符的使用基本相同, 只有个别细节上不同

3.1 算术运算符

  1. 基本语法

    运算符运算范例结果
    +正号+33
    -负号b=4; -b-4
    +5+510
    -6-42
    *3*412
    /5/51
    %取模(取余)7%52
    +字符串相加“he”+“llo”“hello”
    1. 对于 除号 ‘/’, 它的整数除和小数除是有区别的: 整数之间做除法时, 只保留整数部分而舍弃小数部分

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

  1. 基本语法

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

3.3 逻辑运算符

  1. 基本语法

    用于连接多个条件 (一般来讲就是关系表达式), 最终的结果也是一个Boolean值

    变量 A 为 true , B 为 false

    运算符描述实例
    &&(A && B) 结果为 false
    ||(A || B) 结果为true
    !!(A && B) 结果为true

3.4 赋值运算符

  1. 基本语法

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

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

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

3.5 位运算符

  1. 基本语法

    下表中变量 a为60, b为13

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

4. 流程控制

4.1 分支控制 if-else

让程序有选择的执行, 分支控制有三种: 单分支、双分支、多分支

分支语句的返回值

print("请输入年龄>>: ")
val age: Int = StdIn.readInt();

val result = if (age >= 18) {
    println("成年")
    "成年"
} else {
    println("未成年")
    "未成年"
}
val res = if (age >= 18) "成年" else "未成年"

println(result)
println(res)

4.2 嵌套分支

4.3 for循环控制

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

4.4.1 范围数据循环 to

  1. 基本语法

    for (i <- 1 to 3) {
        print(i + " ")
    }
    
    1. i 表示循环的变量, <- 规定to
    2. i 将会从 1-3 循环, 前后闭合
  2. 案例操作

    		// 1. 范围遍历
    			// 包含尾
    //		for (i <- 1 to 10) {
    //			println(i + ". Hello World")
    //		}
    
    //		for (i: Int <- 1.to(10)) {
    //			println(i + ". Hello World")
    //		}
    			// 不包含尾
    //		for (i <- Range(1, 10)) {
    //			println("Range: " + i)
    //		}
    //		for (i <- 1 until 10) {
    //			println("until: " + i)
    //		}
    		// 2. 集合遍历
    //		for (i <- Array(12, 34, 53,23)) {
    //			println(i)
    //		}
    
    //		for (i <- List(12, 34, 53,23)) {
    //			println(i)
    //		}
    
    //		for (i <- Set(12, 34, 53,23)) {
    //			println(i)
    //		}
    

4.4.2 循环守卫

  1. 基本语法

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

    说明:

    1. 循环守卫, 即循环保护式(也称条件判断式,守卫) 保护式为true则进入循环体内部,为false则跳过, 类似于continue

    2. 上面的代码等价

      for (i <- 1 to 3) {
        if (i != 2) {
          print(i + "\t")
        }
      }
      
  2. 案例实操

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

4.4.3 循环步长

  1. 基本语法
for (i <- 1 to 10 by 2) {
  print(i + "\t")
}
for (i <- 13 to 30 by 3) {
  print(i + "\t")
}
for (i <- 1.0 to 10.0 by .5) {
  // 每次半步
  print(i + "\t")
}
for (i <- 1 to 10 by 2 reverse) {	// reverse 反转
  print(i + "\t")		// 9	7	5	3	1	
}

4.4.4 嵌套循环

  1. 基本语法

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

    说明: 没有关键字, 所以范围后一定要加; 来隔断逻辑

    // 与上面代码等价
    for (i <- 1 to 3) {
      for (j <- 1 to 3) {
        println("i=" + i + "j=" + i);
      }
    }
    
  2. 九九乘法表

    for (i <- 1 to 9; j <- 1 to i) {
      print(s"${j} * ${i} = ${i * j}\t")
      if (j == i) println()
    }
    

4.4.5 引入变量

  1. 基本语法

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

    说明

    1. for 推导式一行中有多个表达式时, 所以要加 ; 来隔断逻辑

    2. for 推导式又一个不成文的约定: 当for推导式仅包含单一表达式时使用圆括号, 当包括多个表达式时, 一般每行一个表达式, 并用花括号代替圆括号,如下

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

4.4.6 循环返回值

  1. 基本语法

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

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

    注意: 开发中很少使用

  2. 案例实操

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

    var res = for (i <- 1 to 10) yield { i * 2 }
    println(res)
    

4.4 while 和 do…while 循环控制

  1. 循环条件是返回一个布尔值的表达式
  2. while循环是先判断在执行语句
  3. 与 for 语句不同, while 语句没有返回值, 即整个while语句的结果是 Unit类型()
  4. 因为 while 中没有返回值, 所以当要用该语句来计算并返回结果时, 就不可避免的使用变量, 而变量需要声明在 while循环的外部, 那么就等同于循环的内部对外部的变量造成了影响, 所以不推荐使用, 而是使用for循环

4.5 循环中断

  1. 基本说明

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

  2. 案例实操

    采用异常的方式退出循环

    try {
      for (i <- 0 until 5) {
        if (i == 3)
        throw new RuntimeException
        print(i + "\t")
      }
    } catch {
      case e: Exception => // 什么都不做, 只是退出循环
    }
    println("这是循环外的代码")
    
  3. breakable(
      for (i <- 0 until 5) {
        if (i == 3)
        break()
        print(i + "\t")
      }
    )
    println("这是循环外的代码")
    

5. 函数式编程

  1. 面向对象编程

    解决问题, 分解对象, 行为, 属性, 然后通过对象的关系以及行为的调用来解决问题

    对象: 用户

    行为: 登陆、连接 JDBC、读取数据库

    属性: 用户名、密码

    Scala 语言是一个完全面向对象编程语言, 万物皆对象

    对象的本质: 对数据和行为的一个封装

  2. 函数式编程

    解决问题时, 将问题分解成一个一个的步骤, 将每个步骤进行封装(函数),通过调用这些封装好的步骤, 解决问题

    Scala语言是一个完全函数式编程语言。万物皆函数

    函数的本质: 函数可以当作一个值进行传递

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

5.1 函数基础

5.1.1 函数基本语法

  1. 基本语法

    // 定义函数的关键字  函数名字  参数名 参数类型 	函数返回值类型
    def 							sum				(x: Int, y: Int): Int = {
      // 函数体
      x + y
    }
    

5.1.2 函数和方法的区别

  1. 核心概念

    1. 为完成某一功能的程序语句的集合, 称为函数
    2. 类中的函数称之方法
  2. 案例实操

    1. Scala语言可以在任何的语法结构中声明任何的语法
    2. 函数没有重载和重写的概念; 方法可以进行重载和重写
    3. Scala 中函数可以嵌套定义
    package com.chapter05
    
    object Test01FunctionAndMethod {
    
    	def main(args: Array[String]): Unit = {
    		// 定义函数
    		def sayHi(name: String): Unit = {
    			println(s"hi, ${name}")
    		}
    
    		sayHi("alice")
    		Test01FunctionAndMethod.sayHi("bob")
    	}
    
    	// 定义对象的方法
    	def sayHi(name: String): Unit = {
    		println(s"Hi, ${name}")
    	}
    
    	sayHi("张三")
    
    	def sayHi(name: String, age: Int): String = {
    		s"Hello ${name} ${age}"
    	}
    
    	val result: String = sayHi("cary", 18)
    	println(result)
    }
    

5.1.3 函数定义

  1. 函数定义
    • 函数1: 无参, 无返回值
    • 函数2: 无参, 有返回值
    • 函数3: 有参, 无返回值
    • 函数4: 有参, 有返回值
    • 函数5: 多参, 无返回值
    • 函数6: 多参, 有返回值

5.1.4 函数参数

  1. 案例实操

    1. 可变参数
    2. 如果参数列表中存在多个参数, 那么可变参数一般放置在最后
    3. 参数默认值, 一般将有默认值的参数放置在参数列表的后面
    4. 带名参数
    package com.chapter05
    
    object Test03FunctionParameter {
    	def main(args: Array[String]): Unit = {
    
    		// 1. 可变参数
    		def f1(str: String*): Unit = {
    			println(str)
    		}
    
    		f1("alice")
    		f1("aaa", "bbb", "ccc")
    
    		// 2. 如果参数列表中存在多个参数, 那么可变参数一般放置在最后
    		def f2(str1: String, str2: String*): Unit = {
    			println(str1)
    			println(str2)
    		}
    
    		f2("张三", "李四", "王五", "赵六")
    
    		// 3. 参数默认值, 一般将有默认值的参数放置在参数列表的后面
    		def f3(str: String = "123"): Unit = {
    			println(str)
    		}
    
    		f3("qweqw")
    		f3()
    
    		// 4. 带名参数
    		def f4(name: String = "张三", age: Int): Unit = {
    			println(s"${age}岁的${name}在学习")
    		}
    
    		f4("alex", 20)
    		f4(age = 20, name = "bob")
    		f4(age = 20)
    	}
    }
    

5.1.5 函数至简原则(重点)

函数至简原则: 能省则省

  1. return 可以省略, Scala会使用函数体的最后一行代码作为返回值
  2. 如果函数体只有一行代码, 可以省略花括号
  3. 返回值类型如果能够推断出来, 那么可以省略 (:和返回值类型一起省略)
  4. 如果有return, 则不能省略返回值类型, 必须指定
  5. 如果函数明确声明unit, 那么即使函数体中使用return关键字也不起作用
  6. Scala如果期望是无返回值类型, 可以省略等号
  7. 如果函数无参, 但是声明了参数列表, 那么调用时, 小括号, 可加可不加
  8. 如果函数没有参数列表, 那么小括号可以省略, 调用时小括号必须省略
  9. 如果不关心名称, 只关心逻辑处理, 那么函数名(def)可以省略
def main(args: Array[String]): Unit = {

  def f0(name: String): String = {
    return name
  }
  // println(f0("atguigu"))

  // 1. return 可以省略, Scala会使用函数体的最后一行代码作为返回值
  def f1(name: String): String = {
    name
  }
  // println(f1("atguigu"))

  // 2. 如果函数体只有一行代码, 可以省略花括号
  def f2(name: String): String = name

  // println(f2("atguigu"))

  // 3. 返回值类型如果能够推断出来, 那么可以省略 (:和返回值类型一起省略)
  def f3(name: String) = name

  // println(f3("atguigu"))

  // 4. 如果有return, 则不能省略返回值类型, 必须指定
  def f4(name: String) = {
    name
  }
  // println(f4("atguigu"))

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

  // 6. Scala如果期望时无返回值类型, 可以省略等号
  def f6(name: String) {
    println(name)
  }

  // println(f6("atguigu"))

  // 7. 如果函数无参, 但是声明了参数列表, 那么调用时, 小括号, 可加可不加
  def f7(): Unit = {
    println("atguigu")
  }

  // f7()
  // f7

  // 8. 如果函数没有参数列表, 那么小括号可以省略, 调用时小括号必须省略
  def f8: Unit = {
    println("atguigu")
  }

  // f8

  // 9. 如果不关心名称, 只关心逻辑处理, 那么函数名(def)可以省略
  def f9(name: String): Unit = {
    println(name)
  }
  // 匿名函数, lambda表达式
  (name: String) => { println(name) }
}

5.2 函数高级

5.2.1 高阶函数

在scala中, 函数是一等公民

对于一个函数我们可以 定义函数、调用函数

但是其实函数还有更高阶的用法

  1. 函数可以作为值进行传递
  2. 函数可以作为参数进行传递
  3. 函数可以作为函数的返回值返回
def main(args: Array[String]): Unit = {

		def f(n: Int): Int = {
			println("f调用")
			n + 1
		}

		def fun(): Int = {
			println("fun调用")
			1
		}

		val result: Int = f(123)
		// println(result)

		// 1. 函数可以作为值进行传递
		val f1 = f _
		val f2: Int => Int = f
		//		println(f1)
		//		println(f1(12))
		//		println(f2)
		//		println(f2(35))

		val f3: () => Int = fun
		val f4 = fun _

		// println(f3)
		// println(f4)

		// 2. 函数作为参数进行传递
		// 定义二元计算函数
		def dualEval(op: (Int, Int) => Int, a: Int, b: Int): Int = {
			op(a, b)
		}

		println(dualEval(_ + _, 4, 5))

		// 3. 函数作为函数的返回值返回
		def f5(): Int => Unit = {
			def f6(a: Int): Unit = {
				println("f6调用 - " + a)
			}
			f6 		// 当函数直接返回
		}
//		val f6 = f5()
//		println(f6)
//		println(f6(25))
		println(f5()(25))
	}
def main(args: Array[String]): Unit = {
  val arr: Array[Int] = Array(12, 45, 67, 32)

  // 对数组进行处理
  def arrayOperation(array: Array[Int], op: Int => Int): Array[Int] = {
    for (elem <- array) yield op(elem)
  }

  // 定义一个加一操作
  //		def addOne(elem: Int): Int = {
  //			elem + 1
  //		}

  //		val newArray : Array[Int] = arrayOperation(arr, addOne)
  //		val newArray : Array[Int] = arrayOperation(arr, _ + 1)
  val newArray : Array[Int] = arrayOperation(arr, _ * 2)

  println(newArray.mkString(", "))

}

5.2.2 匿名函数

  1. 说明

    没有名字的函数就是匿名函数

    (x: Int) => {函数体}

    x: 表示输入参数类型; Int: 表示输入参数类型; 函数体: 表示具体代码逻辑

  2. 案例实操

    传递匿名函数的之间原则

    1. 参数的类型可以省略, 会根据形参进行自动的推导
    2. 类型省略之后, 发现只有一个参数, 则圆括号可以省略; 其他情况: 没有参数和参数超过 1的永远不能省略圆括号
    3. 匿名函数如果只有一行, 则大括号也可以省略
    4. 如果参数只出现一次, 则参数省略且后面参数可以用 _ 代替
    // 定义一个函数, 以函数作为参数输入
    def f(func: String => Unit): Unit = {
      func("atguigu")
    }
    
    // f((fun))
    
    // 1. 参数的类型可以省略, 会根据形参进行自动的推导
    f((name) => {
      println(name)
    })
    
    // 2. 类型省略之后, 发现只有一个参数, 则圆括号可以省略; 其他情况: 没有参数和参数超过 1的永远不能省略圆括号
    f(name => {
      println(name)
    })
    
    // 3. 匿名函数如果只有一行, 则大括号也可以省略
    f(name => println(name))
    
    // 4. 如果参数只出现一次, 则参数省略且后面参数可以用 _ 代替
    f(println(_))
    
    // 5. 如果可以推断出, 当前传入的println是一个函数体而不是调用语句, 可以直接省略下划线
    f(println)
    
    // 实际示例 定义一个二元运算函数, 只操作 1和2两个数, 但是具体运算通过参数传入
    def dualFunctionOneAndTwo(fun: (Int, Int) => Int): Int = {
      fun(1, 2)
    }
    
    val add = (a: Int, b: Int) => a + b
    val minus = (a: Int, b: Int) => a - b
    
    println(dualFunctionOneAndTwo(add))
    println(dualFunctionOneAndTwo(minus))
    
    //  匿名函数简化
    //		println(dualFunctionOneAndTwo((a, b) => a + b))
    println(dualFunctionOneAndTwo(_ + _))
    println(dualFunctionOneAndTwo(-_ - _))
    

扩展练习

  1. 定义一个匿名函数, 并将它们作为值赋给变量fun。函数有三个参数, 类型分别为 Int, String, Char, 返回值类型为Boolean

    要求调用函数 fun(0, “”, ‘0’) 返回false, 其他情况均返回 true

  2. 定义一个函数 func, 它接收一个 Int类型的参数, 返回一个函数(计作 f1) 它返回的函数 f1 , 接收一个 String 类型的参数, 同样返回一个函数 (计作 f2) , 函数f2 接收一个 Char类型的参数, 返回一个 Boolean 的值

    要求调用函数 func(0)(""(‘0’)) 得到返回值为 false 其他情况均返回 true

    def func(i: Int): String => (Char => Boolean) = {
      def f1(s: String): Char => Boolean = {
        def f2(c: Char): Boolean = {
          if (i == 0 && s == "" && c == '0') false else true
        }
        f2
      }
      f1
    }
    
    println(func(0)("")('0'))
    
    // 匿名函数简写
    def func1(i: Int): String => (Char => Boolean) = s => c => 
    			if (i == 0 && s == "" && c == '0') false else true
    
    
    println(func1(0)("")('0'))
    
    // 柯里化
    def func2(i: Int)(s: String)(c: Char): Boolean = 
    	if (i == 0 && s == "" && c == '0') false else true
    println(func2(0)("")('0'))
    

5.2.3 函数柯里化&闭包

闭包: 函数式编程的标配

  1. 说明

    闭包: 如果一个函数, 访问到了它的外部(局部) 变量的值, 那么这个函数和他所处的环境, 称为闭包

    函数柯里化: 把一个参数列表的多个参数, 变成多个参数列表

  2. 案例实操

    1. 闭包

      def main(args: Array[String]): Unit = {
        def add(a: Int, b: Int): Int = {
          a + b
        }
      
        // 1. 考虑固定一个加数的场景
        def addByFour(b: Int): Int = {
      
          4 + b
        }
      
        // 2. 扩展固定加数改变的情况
        def addByFive(b: Int): Int = {
          5 + b
        }
      
        // 3. 将固定加数作为另一个参数传入, 但是是作为"第一层参数"传入
        def addByFour1(): Int => Int = {
          val a = 4
      
          def addB(b: Int): Int = {
            a + b
          }
      
          addB
        }
      
        def addByA(a: Int): Int => Int = {
          def addB(b: Int): Int = {
            a + b
          }
      
          addB
        }
        //		println(addByA(35)(24))
      
        val addByFour2 = addByA(4);
        val addByFive2 = addByA(5)
      
        //		println(addByFour2(13))
        //		println(addByFive2(25))
      
        // lambda 表达式简写
        def addByA1(a: Int): Int => Int = {
          (b: Int) => {
            a + b
          }
        }
      
        def addByA2(a: Int): Int => Int = {
          b => a + b
        }
      
        def addByA3(a: Int): Int => Int = a + _
      
        val addByFour3 = addByA3(4);
        val addByFive3 = addByA3(5)
      
        println(addByFour3(13))
        println(addByFive3(25))
      }
      // 柯里化
      def addCurrying(a: Int)( b: Int): Int = {
        a + b
      }
      
      println(addCurrying(2)(3))
      

5.2.4 递归

  1. 说明

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

  2. 案例实操

    def main(args: Array[String]): Unit = {
    
      // 阶乘
      // 递归算法
      // 1 方法调用自身
      // 2 方法必须要有跳出的逻辑
      // 3 方法调用自身时, 传递的参数应该有规律
      // 4 scala中的递归必须声明函数返回值类型
    
      println(fact(5))
      println(tailFact(5))
    }
    
    // 递归实现计算阶乘
    def fact(n: Int): Int = {
      if (n == 0) 1 else fact(n - 1) * n
    }
    
    // 尾递归实现
    def tailFact(n: Int): Int = {
      @tailrec
      def loop(n: Int, currRes: Int): Int = {
        if (n == 0) currRes else loop(n - 1, currRes * n)
      }
    
      loop(n, 1)
    }
    

5.2.6 控制抽象

  1. 值调用: 把计算后的值传递过去

    def f0(a: Int): Unit = {
      println("a: " + a)
      println("a: " + a)
    }
    
    f0(23)
    
    def f1(): Int = {
      println("f1调用")
      12
    }
    f0(f1())
    
  2. 名调用: 把代码传递过去

    def f1(): Int = {
      println("f1调用")
      12
    }
    def f2(a: => Int): Unit = {
      println("a: " + a)
      println("a: " + a)
    }
    f2(23)
    f2(f1())
    f2({
      println("这是一个代码块")
      13
    })
    
  3. 实现 while 循环

    def main(args: Array[String]): Unit = {
    
      var n = 10
    
      // 1. 常规的while循环
      //		while (n > 1) {
      //			println(n)
      //			n -= 1
      //		}
    
      // 2. 用闭包实现一个函数, 将代码块作为参数传入, 递归调用
      def myWhile(condition: => Boolean): (=> Unit) => Unit = {
        // 内层函数需要递归调用, 参数就是循环体
        def doLoop(op: => Unit): Unit = {
          if (condition) {
            op
            myWhile(condition)(op)
          }
        }
    
        doLoop _
      }
    
      //		myWhile(n >= 1) {
      //			println(n)
      //			n -= 1
      //		}
    
      // 3. 匿名函数实现
      def While(condition: => Boolean): (=> Unit) => Unit = {
        op =>
        if (condition) {
          op
          While(condition)(op)
        }
      }
    
      var i = 100
      While(i >= 0) {
        println(i)
        i -= 1
      }
    
      // 3. 用柯里化实现
      @tailrec
      def While2(condition: => Boolean)(op: => Unit): Unit = {
        if (condition){
          op
          While2(condition)(op)
        }
      }
      var a = 10
      While2(a >= 0) {
        println("a: " + a)
        a-=1
      }
    }
    

5.2.7 惰性加载

  1. 说明

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

  2. 案例实操

    def main(args: Array[String]): Unit = {
      lazy val result: Int = sum(13, 47)
      println("1. 函数调用")
      println("3. result= " + result)
      println("4. result= " + result)
    
    }
    
    def sum(a: Int, b: Int): Int = {
      println("2. sum调用")
      a + b
    }
    

6. 面向对象

scala 的面向对象思想和 Java的面向对象思想和概念时一致的

scala 中语法和Java不同, 补充了更多的功能

6.1 scala 包

  1. 基本语法

    package 包名

  2. Scala 包的三大作用 (和 Java一样)

    1. 区分相同名字的类
    2. 当类很多时, 可以很好的管理类
    3. 控制访问范围

6.1.1 包的命名

  1. 命名规则

    只能包含数字、字母、下划线、小圆点, 但不能用数字开头, 也不要使用关键字

  2. 命名规范

    一般是小写字母+小圆点

    com.公司名.项目名.业务模块名

    com.baidu.tieba.search

6.1.2 包说明 (包语句)

  1. 说明

    Scala有两种包的管理风格, 一种方式和Java的包管理风格相同, 每个源文件一个包(包名和源文件所在路径不要求必须一致), 包名用"." 进行分割以表示包的层级关系, 如: com.xxx.scala 另一种风格, 通过嵌套的风格表示层级关系, 如下

    package com{
      package xxx{
        package scala{
          
        }
      }
    }
    

    第二种风格有以下特点:

    1. 一个源文件中可以声明多个 package
    2. 子包中的类可以直接访问父包中的内容, 而无需导包
  2. 案例实操

    // 用嵌套风格定义包
    package com {
    
      import com.atguigu.scala.Inner
    
      // 在外层包中定义单例对象
      object Outer {
        var out: String = "out"
    
        def main(args: Array[String]): Unit = {
    
          println(Inner.in)
        }
      }
      package atguigu {
        package scala {
          // 内层包中定义单例对象
          object Inner {
            val in: String = "in"
    
            def main(args: Array[String]): Unit = {
              println(Outer.out)
              Outer.out = "outer"
              println(Outer.out)
            }
          }
        }
    
      }
    
    }
    // 在同一文件中定义多个包
    package aaa {
      package bbb {
    
        import com.atguigu.scala.Inner
    
        object Test01Package {
          def main(args: Array[String]): Unit = {
            println(Inner.in)
          }
        }
      }
    }
    

6.1.3 包对象

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

  1. 定义

    package object com {
      val shareValue="share"
      def shareMethod() = {}
    }
    
  2. 说明

    1. 若使用 Java的包管理风格, 则包对象一般定义在其对应包下的 package.scala文件中, 包对象名与包名一致

6.1.4 导包说明

  1. 和 Java一样, 可以在顶部使用 import 导入, 在这个文件中的所有类都可以使用

  2. 局部导入: 什么时候使用, 什么时候导入. 在其作用范围内都可以使用

  3. 通配符导入: import java.util._

  4. 给类起名: import java.util.{ArrayList => JL}

  5. 导入相同包的多个类: import java.util.{HashSet, ArrayList}

  6. 屏蔽类: import java.util.{ArrayList => _,_}

  7. 导入包的绝对路径: new _root_.java.util.HashMap

    package java {
      package util {
        class HashMap {
          
        }
      }
    }
    
    1. 注意

      Scala 中的三个默认导入分别是

      import java.lang._

      import scala._

      import scala.Predef._

6.2类和对象

类: 可以看成一个模版

对象: 表示具体的事物

6.2.1 定义类

  1. 回顾Java中的类

    如果类时 public 的, 则必须和文件名一致

    一般, 一个 .java 有一个 public类

    注意: Scala中没有 public, 一个 .scala 中可以写多个类

  2. 基本语法

    [修饰符] class 类名 {

    ​ 类体

    }

  3. Scala 语法中, 类并不声明为 public, 所以这些类都具有公有可见性(即默认就是 public)

  4. 一个Scala源文件可以包含多个类

6.3 封装

封装就是把抽象出的数据和对数据的操作封装在一起, 数据被保护在内部, 程序的其他部分只有通过授权的操作(成员方法), 才能对数据进行操作. Java封装操作如下

  1. 将属性私有化
  2. 提供一个公共的set方法, 用于对属性赋值
  3. 提供一个公共的get方法, 用于获取属性的值

scala中的 public 属性, 底层实际为 private, 并通过get 方法 (obj.field()) 和 set方法 (obj.field_=(value)) 对其进行操作。所以scala并不推荐将属性设为 private, 再为其设置 public 的get和set方法的做法。单由于很多 Java框架都利用反射调用 get和set方法, 有时候是为了和这些框架兼容, 也会为 scala的属性设置 get 和 set 方法(通过 @BeanProperty 注解实现)

6.3.1 访问权限

  1. 说明

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

    1. scala 中属性和方法的默认访问权限为 public, 但scala中无 public 关键字
    2. private 为私有权限, 只在类的内部和半生对象中可用
    3. protected 为受保护权限, scala中受保护权限比 Java中更严格, 同类、子类可以访问, 同包无法访问
    4. private[包名] 增加包访问权限, 包名下的其他类也可以使用

6.3.2 方法

  1. 基本语法

    def 方法名(参数列表) [:返回值类型] => {

    ​ 方法体

    }

6.3.3 构造器

和 Java一样, Scala构造对象也需要调用构造方法, 并且可以有任意多个构造方法。

Scala 类的构造器包括: 主构造器和辅助构造器

  1. 基本语法

    class 类名(形参列表) { // 主构造器

    ​ // 类体

    ​ def this(形参列表) { // 辅助构造器

    ​ …

    ​ }

    ​ def this(形参列表){ // 辅助构造器可以有多个

    ​ …

    ​ }

    }

    说明

    1. 辅助构造器, 函数的名称 this, 可以有多个, 编译器通过参数的个数及类型来区分
    2. 辅助构造方法不能直接构建对象,必须直接或者间接调用构造方法
    3. 构造器调用其他另外的构造器, 要求被调用构造器必须提前声明
  2. 案例实操

    object Test05Constructor {
    
      def main(args: Array[String]): Unit = {
    
        val test = new Test05Constructor("张三", 18)
    
      }
    }
    
    // 定义一个类
    class Test05Constructor() {
      // 定义属性
      var name:String = _
      var age: Int = _
    
      println("1. 主构造方法被调用")
    
      // 声明辅助构造方法
      def this(name:String) {
        this()	// 直接调用主构造器
        println("2. 辅助构造方法一被调用")
        this.name = name
        println(s"name: $name age: $age")
      }
    
      def this(name:String,age:Int) {
        this(name)
        println("3. 辅助构造方法二被调用")
        this.age = age
        println(s"name: $name age: $age")
      }
    
      def Test05Constructor(): Unit = {
        println("普通方法被调用")
      }
    }
    

6.3.4 构造器参数

  1. 说明

    scala 类的主构造器函数的形参包括三种类型: 未用任何修饰、var修饰、val修饰

    1. 未用任何修饰符修饰, 这个参数就是一个局部变量
    2. var 修饰参数, 作为类的成员属性使用, 可以修改
    3. val 修饰参数, 作为类只读属性使用, 不可修改
    class Test06ConstructorParams {
      // 单独定义属性
      var name: String = _
      var age: Int = _
    }
    
    // 上面定义等价于
    class Student3(var name: String, var age: Int)
    
    // 主构造器参数无修饰
    class Student4(name: String, age: Int) {
      def printlnInfo(): Unit = {
        println(s"student4: name= ${name}, age= ${age}")
      }
    }
    
    class Student5(_name: String, _age: Int) {
      var name: String = _name
      var age: Int = _age
    }
    
    class Student6(val name: String, val age: Int)
    
    class Student7(var name: String, var age: Int) {
      var school: String = _
    
      def this(name:String,age:Int,school:String) {
        this(name, age)
        this.school = school
      }
    
      def printlnInfo(): Unit = {
        println(s"student4: name= ${this.name}, age= ${this.age}, school= ${this.school}")
      }
    }
    

6.4 继承和多态

  1. 基本语法

    class 子类名 extends 父类名 { 类体 }

    1. 子类继承父类的属性和方法
    2. scala是单继承
  2. 案例实操

    1. 子类继承父类的属性和方法
    2. 继承的调用顺序: 父类构造器 > 子类构造器
    package com.chapter06
    
    object Test07Inherit {
      def main(args: Array[String]): Unit = {
    
        val student1: Person7 = new Student77("alex", 18)
        //		student1.printlnInfo()
        println("-----------------------------------------------------------------")
        val student2: Person7 = new Student77("egon", 20, "std01")
        //		student2.printlnInfo()
        println("-----------------------------------------------------------------")
        val teacher = new Teacher
        //		teacher.printlnInfo()
        println("-----------------------------------------------------------------")
    
        def personInfo(person: Person7): Unit = {
          person.printlnInfo()
        }
    
        val person = new Person7();
        personInfo(student2)
        personInfo(teacher)
        personInfo(person)
    
      }
    }
    
    // 定义一个父类
    class Person7() {
      var name: String = _
      var age: Int = _
    
      println("1. 父类的主构造器调用")
    
      def this(name: String, age: Int) {
        this()
        println("2. 父类的辅助构造器被调用")
        this.name = name
        this.age = age
      }
    
      def printlnInfo(): Unit = {
        println(s"Person7: $name $age")
      }
    }
    
    // 定义子类
    class Student77(name: String, age: Int) extends Person7(name, age) {
    
      var stdNo: String = _
      println("3. 子类的主构造器被调用")
    
      def this(name: String, age: Int, stdNo: String) {
        this(name, age)
        println("4. 子类的辅助构造器调用")
        this.stdNo = stdNo
      }
    
      override def printlnInfo(): Unit = {
        println(s"Student77: $name $age $stdNo")
      }
    }
    
    class Teacher extends Person7 {
      override def printlnInfo(): Unit = {
        println("teacher")
      }
    }
    

6.5 抽象类

6.5.1 抽象属性和抽象方法

  1. 基本语法

    1. 定义抽象类: abstract class Persion{} 通过 abstract 关键字标记抽象类
    2. 定义抽象属性: val | var name:String 一个属性没有初始化, 就是抽象属性
    3. 定义抽象方法: def hello():String 只声明而没有实现的方法, 就是抽象方法
    4. 案例实操
  2. 继承 & 重写

    1. 如果父类为抽象类, 那么子类需要将抽象的属性和方法实现, 否则子类也需声明为抽象类

    2. 重写非抽象方法需要用 override 修饰, 重写抽象方法则可以不加override

    3. 子类中调用父类的方法使用 super关键字

    4. 子类对抽象属性进行实现, 父类抽象属性可以用var修饰

      子类对非抽象属性重写, 父类非抽象属性只支持 val 类型, 而不支持 var

      因为var修饰的可为变量, 子类继承之后可以直接使用, 没有必要重写

// 定义抽象类
abstract class Person9 {
  // 非抽象属性
  val name: String = "person"

  // 抽象属性
  var age: Int

  // 非抽象方法
  def eat(): Unit = println("person eat")

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

// 定义具体的实现子类
class Student9 extends Person9 {
  // 实现抽象属性和方法
  var age: Int = 18

  def sleep(): Unit = println("student sleep")

  // 重写非抽象属性和方法
  override val name: String = "student"

  override def eat(): Unit = {
    super.eat()
    println("person eat")
  }
}

6.5.2 匿名子类

  1. 说明

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

  2. 案例实操

    object Test10AnonymousClass {
      def main(args: Array[String]): Unit = {
        val person: Person10 = new Person10 {
          override var name: String = "alice"
    
          override def eat(): Unit = println(s"person ${sex}")
        }
    
        person.eat()
    
      }
    }
    
    // 定义抽象类
    abstract class Person10 {
      var name: String
    
      val sex: String = "男"
    
      def eat(): Unit
    }
    

6.6 单例对象(伴生对象)

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

6.6.1 单例对象语法

  1. 基本语法

    object Person{
      val country: String = "China"
    }
    
  2. 说明

    1. 单例对象采用 object 关键字声明
    2. 单例对象对应的类称之为伴生类, 伴生对象的名称应该和伴生类名一致
    3. 单例对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。
  3. 案例实操

    package com.chapter06
    
    object Test11Object {
    
      def main(args: Array[String]): Unit = {
        //		val student: Student11 = new Student11("alice", 18)
        //		student.printlnInfo()
    
        val student1 = Student11.newStudent("alice", 18)
        student1.printlnInfo()
    
        val student2 = Student11("ob", 12)
        student2.printlnInfo()
      }
    }
    
    // 定义类
    class Student11 private(val name: String, val age: Int) {
    
      def printlnInfo(): Unit = {
        println(s"student: name=${name}, age=${age}, school=${Student11.school}")
      }
    }
    
    // 定义伴生对象
    object Student11 {
      val school: String = "atguigu"
    
      // 定义一个类的对象示例的创建方法
      def newStudent(name: String, age: Int): Student11 = new Student11(name, age)
    
      def apply(name: String, age: Int): Student11 = new Student11(name, age)
    }
    

6.7 特质(Trait)

Scala 语言中, 采用特质 trait (特征) 来代替接口的概念, 也就是说, 多个类具有相同的 特质时, 就可以将这个特质独立出来, 采用关键字 trait 声明

Scala 中的 trait 中即可以有抽象属性和方法, 也可以有具有的属性和方法, 一个类可以混入(mixin) 多个特质。这种感觉类似于 Java 中的抽象类

Scala 引入 trait 特征, 第一可以替代Java的接口, 第二个也是对单继承机制的一种补充

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…

  2. 说明

    1. 类和特质的关系: 使用继承的关系
    2. 当一个类去继承特质时, 第一个连接词是 extends, 后面是 with
    3. 如果一个类在同时继承特质和父类时, 应当把父类写在 extends 后
// 动态混入
val talent = new Student14 with Talent {
  override def singing(): Unit = ???
  override def dancing(): Unit = ???
}

6.7.3 特质叠加

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

冲突分以下两种

第一种, 一个类混入的两个 trait 中具有相同的具体方法, 且两个trait之间没有任何关系, 解决类冲突问题, 直接在类中重写冲突方法

第二种, 一个类混入的两个 trait中具有相同的具体方法, 且两个 trait继承自相同的 trait 及所谓的 钻石问题, 解决这类冲突问题, scala采用了特质叠加的策略

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

// 定义球类特征
trait Ball {
	def describe(): String = "ball"
}

// 定义颜色特征
trait ColorBall extends Ball {
	val color: String = "red"

	override def describe(): String = color + "-" + super.describe()
}

// 定义种类特征
trait CategoryBall extends Ball {
	var category: String = "foot"

	override def describe(): String = category + "-" + super.describe()
}

// 定义一个自定义球的类
class MyFootBall extends CategoryBall with ColorBall {
  override def describe(): String = "my ball is a " +  super.describe()
  // my ball is a red-foot-ball
  
  // 调用指定super方法
  // override def describe(): String = "my ball is a " +  super[CategoryBall].describe()
}

6.7.4 特质叠加执行顺序

上述案例中的 super.describe() 调用的是父 trait 中的方法吗

当一个类混入多个特质的时候, scala 会对所有的特质及其父特质按照一定的顺序进行排序, 而此案例中的 super.describe() 调用的实际上是排好序后的下一个特质中的 descrive() 方法, 排序规则如下
请添加图片描述

6.6.5 特质自身类型

  1. 说明

    自身类型可实现依赖注入的功能

  2. 案例实操

    package com.chapter06
    
    object Test16TraitSelfType {
    
    	def main(args: Array[String]): Unit = {
    
    		val user = new RegisterUser("alice", "123456")
    
    		user.insert()
    	}
    }
    
    // 定义一个用户类
    class User(val name: String, val password: String)
    
    trait UserDao {
    	_: User =>
    	// 向数据库插入数据
    	def insert(): Unit = {
    		println(s"insert into db: ${this.name}")
    	}
    }
    
    // 定义注册用户类
    class RegisterUser(name:String, password:String) extends User(name, password) with UserDao
    

6.7.6 特质和抽象类的区别

  1. 优先使用特质. 一个类扩展多个特质是很方便的, 但却只能扩展一个抽象类
  2. 如果你需要构造函数参数, 使用抽象类. 因为抽象类可以定义带参数的构造函数 而特质不行 (有无参构造)

6.8 扩展

6.8.1 类型检查和转换

  1. 说明

    1. obj.isInstanceOf[T] 判断 obj 是否是 T 类型
    2. obj.asInstanceOf[T] 将 obj 强转成 T 类型
    3. classOf 获取对象的类名
  2. 案例实操

    package com.chapter06
    
    object Test17Extends {
      def main(args: Array[String]): Unit = {
        // 1. 类型的检测和转换
        val student: Student17 = new Student17("alice", 18)
        student.study()
        student.sayHi()
    
        val person: Person17 = new Student17("bob", 20)
        person.sayHi()
    
        // 类型判断
        println("student is Student17: " + student.isInstanceOf[Student17]) // true
        println("student is Person17: " + student.isInstanceOf[Person17]) // true
        println("person is Person17: " + person.isInstanceOf[Person17]) // true
        println("person is Student17: " + person.isInstanceOf[Student17]) // true
    
        val person2: Person17 = new Person17("cary", 35)
        println(s"person2 is Student17: ${person2.isInstanceOf[Student17]}") // true
        println("----------------------------------------")
        // 类型转换
        if (person.isInstanceOf[Student17]) {
          val newStudent = person.asInstanceOf[Student17]
          newStudent.study()
        }
    
        println(classOf[Student17])
      }
    }
    
    class Person17(val name: String, val age: Int) {
      def sayHi(): Unit = {
        println(s"hi from person ${name}")
      }
    }
    
    class Student17(name: String, age: Int) extends Person17(name, age) {
      override def sayHi(): Unit = {
        println(s"hi from student ${name}")
      }
    
      def study(): Unit = {
        println("student study")
      }
    }
    

6.8.2 枚举类和应用类

  1. 说明

    枚举类: 需要继承 Enumeration

    应用类: 需要继承 App

  2. 案例实操

    // 定义枚举类对象
    object WorkDay extends Enumeration {
    	val MONDAY = Value(1, "Monday")
    	val TUESDAY = Value(2, "Tuesday")
    }
    
    // 定义应用类
    object TestApp extends App {
    	println(WorkDay.MONDAY)
    
    	println("app start")
    }
    

6.8.3 Type定义新类型

  1. 说明

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

  2. 案例实操

    // 源码中
    type String = java.lang.String
    
    type myString = String
    val a: myString = "abc"
    

7. 集合

7.1 集合简介

  1. Scala 的集合有三大类: 序列 Seq、集 Set、映射 Map, 所有的集合都扩展自 Iterable 特质

  2. 对于几乎所有的集合类, Scala 都同时提供了 可变和不可变 的版本, 分别位于一下两个包

    不可变集合: scala.collection.immutable

    可变集合: scala.collection.mutable

  3. Scala 不可变集合, 就是指该集合对象不可修改, 每次就该就会返回一个新对象, 而不会对原对象进行修改, 类似于 java重的 String 对象

  4. 可变集合, 就是这个集合可以直接对原对象进行修改, 而不会返回新的对象, 类似于 java 中 StringBuilder 对象

建议: 在操作集合的时候, 不可变用符号, 可变用方法

7.1.1 不可变集合继承图

请添加图片描述

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

7.1.2 可变集合继承图

请添加图片描述

7.2 数组

7.2.1 不可变数组

  1. 第一种方式定义数组

    定义: val arr1 = new Array[Int](10)

    1. new 是关键字
    2. [Int] 是指定可以存放的数据类型, 如果虚妄存放任意数据类型, 则指定 Any
    3. (10), 表示数组的大小, 确定后就不可以变化
  2. 案例实操

    def main(args: Array[String]): Unit = {
      // 1. 创建数组
      val arr: Array[Int] = new Array[Int](5)
    
      // 另一种创建的方式 调用 apply 方法
      val arr2: Array[Int] = Array(5, 28, 17, 28, 97)
    
      // 2. 访问元素
      println(arr(0))
      println(arr(2))
      println(arr(4))
    
      arr(0) = 29
      arr(1) = 34
      arr(2) = 21
      arr(3) = 45
      arr(4) = 60
      
      println("-------1-----------")
      // 3. 数组的遍历
    
      // (1) 普通for循环
      for (i <- 0 until arr.length) println(arr(i))
    
      println("-------2-----------")
    
      for (i <- arr.indices) println(arr(i))
    
      println("-------3-----------")
    
      // (2) 直接遍历所有元素 增强for循环
      for (elem <- arr) println(elem)
    
      println("------4------------")
    
      // (3) 迭代器
      val iter = arr.iterator
      while (iter.hasNext) println(iter.next())
    
      println("------5------------")
    
      // (4) 调用 foreach 方法
      arr.foreach((element: Int) => println(element))
    
      println("-------6-----------")
    
      arr.foreach(println(_))
      arr.foreach( println )
    
      println("-------7-----------")
    
      println(arr.mkString("-"))
    
      println("-------8-----------")
      // 4. 添加元素 (返回新数组)
      // 在尾部添加
      val newArr = arr.:+(66)
      println(newArr.mkString("-"))
    
      // 在头部添加
      val newArr2 = newArr.+:(18)
      println(newArr2.mkString("-"))
    
      // 省略方法调用 连续添加
      val newArr3 = newArr2 :+ 15 :+ 22
      val newArr4 = 19 +: 2 +: newArr3 :+ 23 :+ 24 :+ 28
      println(newArr4.mkString("-"))
    }
    

7.2.2 可变数组

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

  // 1. 创建可变数组
  val arr1: ArrayBuffer[Int] = new ArrayBuffer[Int]()
  val arr2 = ArrayBuffer[Int](23, 35, 65)

  println(arr1.mkString("-"))
  println(arr2)

  // 2. 访问元素
  println(arr2(1))
  arr2(1) = 29
  println(arr2(1))

  // 3. 添加元素
  val newArr1 = 34 +: arr1 :+ 2 :+ 34
  println(newArr1)
  println(newArr1 == arr1) // false 引用不同

  val newArr2 = arr1 += 23
  println(arr1)
  println(newArr2 == arr1) // true 引用相等

  newArr2 += 13
  println(newArr2) //
  19 +=: 14 +=: newArr2 += 27 += 217
  println(newArr2)
  println(arr1)

  // 向后追加
  arr1.append(12, 29)
  // 向前
  arr1.prepend(12, 19)
  println(arr1)

  // 指定任意位置添加
  arr1.insert(0, 2)
  println(arr1)
  // 添加一个集合
  arr1.insertAll(1, newArr2)
  println(arr1)

  // 4. 删除元素
  arr1.remove(3)
  println(arr1)

  // 从指定位置往后删除指定数量
  arr1.remove(0, 11)
  println(arr1)
  arr1 -= 19
  println(arr1)
}

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

  // 可变数组转换为不可变数组
  val arr :ArrayBuffer[Int] = ArrayBuffer[Int](24, 21, 34)
  val newArr: Array[Int] = arr.toArray
  println(newArr.mkString("-"))
  println(arr)

  // 不可变数组转换为可变数组
  val buffer: mutable.Buffer[Int] = newArr.toBuffer

7.2.4 多维数组

def main(args: Array[String]): Unit = {
  // 1. 创建二维数组
  val array: Array[Array[Int]] = Array.ofDim[Int](2, 3)

  // 2. 访问元素
  array(0)(2) = 19
  array(1)(0) = 25

  for (i <- 0 until array.length; j <- 0 until array(i).length) {
    print(array(i)(j) + "\t")
    if (j == array.length) {
      println()
    }
  }
  println("----------")
  for (i <- array; j <- i) {
    println(j)
  }
  println("----------")
  array.foreach(line => line.foreach( println ))
  array.foreach(_.foreach( println ))
}

7.3 列表 List

7.3.1 不可变 List

  1. 说明

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

    def main(args: Array[String]): Unit = {
      // 1. 创建一个 List
      val list1 = List(23, 65, 87)
      println(list1)
    
      // 2. 访问元素
      println(list1(1))
      println("------------")
      list1.foreach(println)
    
      // 3. 添加元素
      val list2: List[Int] = 10 +: list1
      val list3: List[Int] = list1 :+ 10
      println(list1)
      println(list2)
      println(list3)
    
      println("------------")
    
      val list4 = list2.::(52)
      println(list4)
      println("------------")
    
      val list5 = Nil.::(13)
      println(list5)
      println("------------")
    
      val list6 = 73 :: 32 :: Nil
      val list7 = 17 :: 28 :: 59 :: 16 :: Nil
      println(list7)
    
      val list = Nil
      println(list)
      println("------------")
    
      // 合并 List
      val list8 = list6 :: list7	// 不正确
      println(list8)
    
      val list9 = list6 ::: list7
      println(list9)
    
      val list10 = list6 ++ list7
      println(list10)
    }
    

7.3.2 可变 List

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

  // 1. 创建可变列表
  val list1: ListBuffer[Int] = new ListBuffer[Int]()
  val list2 = ListBuffer(12, 53, 75)
  println(list1)
  println(list2)
  println("===========================")

  // 2. 添加元素
  list1 append(15, 62)
  list2 prepend (20)
  list1 insert(1, 19)
  println(list1)
  println(list2)
  println("===========================")

  31 +=: 96 +=: list1 += 25 += 11
  println(list1)
  println("===========================")

  // 3. 合并 ListBuffer 返回新ListBuffer对象
  val list3: ListBuffer[Int] = list1 ++ list2
  println(list1)
  println(list2)
  println(list3)
  println("===========================")
  // 合并赋值给list1
  list1 ++= list2 ++= list3
  println(list1)
  println(list2)
  println(list3)

  println("===========================")
  // 4. 修改元素
  list2(3) = 30
  list2 update (0, 89)
  println(list2)

  // 5. 删除元素
  list2 remove(2)
  list2 -= 12
  println(list2)
}

7.4 Set 集合

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

7.4.1 不可变 Set

  1. 说明

    1. Set 默认是不可变集合, 数据无序
    2. 数据不可重复
    3. 遍历集合
  2. 案例实操

    def main(args: Array[String]): Unit = {
      // 1. 创建Set
      val set1 = Set(13, 23, 53, 12, 13, 23, 78)
      println(set1)
    
      println("=========================")
    
      // 2. 添加元素
      val set2 = set1 + 20
      println(set1)
      println(set2)
    
      // 3. 合并 Set
      val set3 = Set(19, 13, 23, 53, 67, 99)
      val set4 = set3 ++ set2
      println(set4)
      println("=========================")
      // 4. 删除元素
      val set5 = set3 - 13
      println(set3)
      println(set5)
    }
    

7.4.2 可变 Set

def main(args: Array[String]): Unit = {
  // 1. 创建Set
  val set1: mutable.Set[Int] = mutable.Set(13, 23, 53, 12, 13, 23, 78)

  println(set1)

  println("===========================")

  // 2. 添加元素
  val set2 = set1 + 11
  println(set2)

  set1 += 111
  println(set1)
  println("===========================")

  val flag: Boolean = set1 add 10
  println(flag)
  val flag2: Boolean = set1 add 10
  println(flag2)
  println(set1)

  // 3. 删除元素
  set1 -= 111
  println(set1)
  val flag3: Boolean = set1 remove 78
  println(flag3)
  println(set1)

  println("===========================")

  // 4. 合并两个 Set
  val set3 = mutable.Set(13, 12, 13, 27, 98, 29)
  println(set1)
  println(set3)
  val set4 = set1 ++ set3
  println(set1)
  println(set3)
  println(set4)

  println("===========================")

  set1 ++= set3
  println(set1)
  println(set3)
}

7.5 Map 集合

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

7.5.1 不可变 Map

  1. 说明

    1. 创建不可变集合 Map
    2. 循环打印
    3. 访问数据
    4. 如果 key 不存在, 返回 0
  2. 案例实操

    def main(args: Array[String]): Unit = {
    
      // 1. 创建 Map
      val map1: Map[String, Int] = Map("a" -> 13, "b" -> 25, "hello" -> 3)
    
      println(map1)
      println(map1.getClass)
      println("==============================")
      // 2.遍历元素
      map1.foreach(println)
      map1.foreach((kv: (String, Int)) => println(kv))
    
      println("==============================")
    
      // 3. 取map中所有的 key 或者 value
      for (key <- map1.keys) {
        println(s"${key} ---> ${map1.get(key)}")
      }
    
      println("==============================")
    
      // 4. 访问 某一个 key 的value
      val v1: Option[Int] = map1.get("a")
      val v2: Int = map1.get("a").get
      val v3: Int = map1("a")
      println(v1)
      println(v2)
      println(v3)
      // 为 None 给予默认值
      println(map1.get("c")) // None
      println(map1.getOrElse("c", 0)) // 0
    }
    

7.5.2 可变 Map

def main(args: Array[String]): Unit = {
  // 1. 创建map
  val map1: mutable.Map[String, Int] = mutable.Map("a" -> 13, "b" -> 25, "hello" -> 3)
  println(map1)
  println(map1.getClass)

  // 2. 添加元素
  map1 put("c", 5)
  map1 put("d", 9)
  map1 += "e" -> 2 += "f" -> 33
  println(map1)
  println("====================")

  // 3. 删除元素
  map1 remove ("d")
  map1 -= "e" -= "f"
  println(map1)

  println("====================")

  // 4. 修改
  map1 update("a", 33)
  println(map1)

  println("====================")
  // 5. 合并两个 map
  val map2: Map[String, Int] = Map("aa" -> 13, "bb" -> 25, "hello" -> 5)
  // map2 覆盖 map1
  map1 ++= map2
  println(map1)

  println("====================")
  val map3: Map[String, Int] = map2 ++ map1
  println(map1)
  println(map2)
  println(map3)
}

7.6 元组

  1. 说明

    元组也是可以理解为一个容器, 可以存放各种相同或不同类型的数据。说的简单点, 就是将多个无关的数据封装为一个整体, 称之为元组

    注意: 元组中最大智能有 22 个元素

  2. 案例实操

    1. 声明元组的方式: (元素1, 元素2, 元素3)
    2. 访问元组
    3. Map中的键值对其实就是元组, 只不过元组的元素个数为 2, 称之为 对偶
    def main(args: Array[String]): Unit = {
      // 1. 创建元组
      val tuple: (String, Int, String, Boolean) = ("hello", 100, "a", true)
    
      println(tuple)
    
      // 2. 访问数据
      println(tuple._1)
      println(tuple._2)
      println(tuple._3)
      println(tuple._4)
    
      println(tuple.productElement(0))
      println("===========================")
      // 3. 遍历元组数据
      for (elem <- tuple.productIterator) println(elem)
    
      // 4. 嵌套元组
      val mulTuple = (12, .3, "hello", (23, "scala"), 29)
      println(mulTuple)
      println(mulTuple._4._2)
    }
    

7.7 集合常用函数

7.7.1 基本属性和常用操作

  1. 说明

    1. 获取集合长度
    2. 获取集合大小
    3. 循环遍历
    4. 迭代器
    5. 生成字符串
    6. 是否包含
  2. 案例实操

    def main(args: Array[String]): Unit = {
      val list = List(1, 3, 5, 7, 2, 89)
      val set = Set(23, 34, 423, 75)
    
      // 1. 获取集合长度
      println(list.length)
    
      // 2. 获取集合大小
      println(set.size)
    
      // 3. 循环遍历
      for (elem <- list) println(elem)
      println("=========")
    
      set.foreach(println)
      println("=========")
      // 4. 迭代器
      for (elem <- list.iterator) println(elem)
    
      // 5. 生成字符串
      println(list)
      println(set)
      println(list.mkString("-"))
      println(set.mkString("-"))
    
      // 6. 是否包含
      println(list.contains(89))
      println(set.contains(232))
    }
    

7.7.2 衍生集合

  1. 说明

    1. 获取集合的头
    2. 获取集合的尾 (去掉头剩下的都是尾)
    3. 集合最后一个数据
    4. 集合初始数据 (不包含最后一个)
    5. 反转
    6. 取前(后) n 个元素
    7. 去掉前(后) n 个元素
    8. 并集
    9. 交集
    10. 差集
    11. 拉链
    12. 滑窗
  2. 案例实操

    def main(args: Array[String]): Unit = {
      val list1 = List(1, 3, 5, 7, 2, 89)
      val list2 = List(3, 7, 2, 45, 4, 8, 19)
    
      // 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(4))
    
      // 7. 去掉前(后) n 个元素
      println(list1.drop(3))
      println(list1.dropRight(4))
    
      // 8. 并集 (合并)
      println(list1 union list2)
      println(list1 ::: list2)
    
      // 如果set做并集, 会去重
      val set1 = Set(1, 3, 5, 7, 2, 89)
      val set2 = Set(3, 7, 2, 45, 4, 8, 19)
      println(set1 union set2)
      println(set1 ++ set2)
    
      // 9. 交集 (取公共元素)
      println(list1 intersect list2)
    
      // 10. 差集
      println(list1 diff list2)	// 属于 list1 但不属于 list2 的元素
      println(list2 diff list1)	// 属于 list2 但不属于 list1 的元素
    
      // 11. 拉链
      println(list1 zip list2)
      println(list2 zip list1)
    
      // 12. 滑窗
      println("==========")
      var iter: Iterator[List[Int]] = list1 sliding 3
      iter.foreach( println )
      println("=====第二个参数是步数=====")
      iter = list2 sliding(4, 2)
      iter.foreach( println )
    }
    

7.7.3 集合计算简单函数

  1. 说明

    1. 求和
    2. 求乘积
    3. 最大值
    4. 最小值
    5. 排序
  2. 实操

    def main(args: Array[String]): Unit = {
      val list = 5 :: 1 :: 8 :: 2 :: -3 :: 4 :: Nil
      val list2 = ("a", 5) :: ("b", 1) :: ("c", 8) :: ("d", 2) :: ("e", -3) :: ("f", 4) :: Nil
    
      // 1. 求和
      var sum = 0
      for (elem <- list) sum += elem
      println(sum)
    
      println(list.sum)
    
      // 2. 求乘积
      println(list.product)
    
      // 3. 最大值
      println(list.max)
      println(list2.max)
      println(list2.maxBy((tuple: (String, Int)) => tuple._2))
      // 简化:
      println(list2.maxBy(_._2))
    
      // 4. 最小值
      println(list.min)
      println(list2.minBy(_._2))
    
      // 5. 排序
      println(list.sorted)
      // 从大到小排序
      println(list.sorted.reverse)
      println(list.sorted(Ordering[Int].reverse))
    
      println(list2.sorted)
    
      // sortBy
      println(list2.sortBy(_._2))
      println(list2.sortBy(_._2)(Ordering[Int].reverse))
    
      // sortWith  相当于 Java中的比较器
      println(list.sortWith((a: Int, b: Int) => {a < b}))
      // 简化:
      println(list.sortWith( _ < _))
      println(list.sortWith( _ > _))
    }
    

7.7.4 集合计算高级函数

  1. 说明

    1. 过滤

      遍历一个集合并从中获取满足指定条件的元素组成一个新的集合

    2. 转化/映射 (map)

      将集合中的每一个元素映射到某一个函数

    3. 扁平化

    4. 扁平化 + 映射 注: flatMap 相当于先进行map 操作, 在进行 flatten 操作集合中的每个元素映射到某个函数并返回新集合

    5. 分组 (group)

      按照指定的规则对集合的元素进行分组

    6. 简化 (归约)

    7. 折叠

  2. map

    def main(args: Array[String]): Unit = {
      val list = 1 :: 2 :: 3 :: 4 :: 5 :: 6 :: 7 :: 8 :: 9 :: Nil
    
      // 1. 过滤
      // 选取偶数
      val e = list.filter((elem: Int) => {
        elem % 2 == 0
      })
      // 简化
      val evenList = list.filter(_ % 2 == 0)
      println(evenList)
    
      println("==============================")
    
      // 2. map
      // 把集合中每个数乘 2
      println(list.map(elem => elem * 2))
      println(list.map(_ * 2))
    
      println("==============================")
    
      // 3. 扁平化
      val nestedList: List[List[Int]] = List(1, 2, 3) :: List(4, 5) :: List(6, 7, 8, 9) :: Nil
      val flatList = nestedList(0) ::: nestedList(1) ::: nestedList(2)
    
      println(flatList)
      // flatten
      println(nestedList.flatten)
    
      // 4. 扁平映射
      // 将一组字符串进行分词, 并保存成单词的列表
      val strings: List[String] = List("hello world", "hello scala", "hello java", "we study")
      val splitList: List[Array[String]] = strings.map(_.split(" "))
      val flattenList: List[String] = splitList.flatten
    
      println(flattenList)
    
      val flattenMapList = strings.flatMap(_.split(" "))
      println(flattenMapList)
    
      // 5. 分组 groupBy
      // 分成奇偶两组
      val groupMap: Map[Int, List[Int]] = list.groupBy(_ % 2)
      val groupMap2: Map[String, List[Int]] = list.groupBy(data => if (data % 2 == 0) "偶数" else "奇数")
      println(groupMap)
      println(groupMap2)
    
      // 给定一组词汇, 按照单词的首字母进行分组
      val wordList = List("china", "america", "alice", "canada", "cary", "bob", "japan")
      println(wordList.groupBy(_.charAt(0)))
    
    }
    
  3. reduce

    def main(args: Array[String]): Unit = {
      val list = List(1, 2, 3, 4)
    
      // 1. 归约 reduce
      println(list.reduce(_ + _))		// 底层调用 reduceLeft
      println(list.reduceLeft(_ + _))	// 从左往右
      println(list.reduceRight(_ + _)) // 从右往左
    
      println("====================")
      val list2 = List(3, 4, 5, 8, 10)
      println(list2.reduce(_ - _))
      println(list2.reduceRight(_ - _))    // 3 - (4 - (5 - (8 - 10)))
    
      println("====================")
    
      // 2. 折叠 fold
      println(list.fold(10)(_ + _))  // 10 + 1 + 2 + 3 + 4
      println(list.foldLeft(10)(_ - _)) // 10 -1 -2 - 3 - 4
    
      println(list2.foldRight(11)(_-_)) // 3 - (4 - (5 - (8 - (10 - 11))))
    }
    
  4. Map 合并案例

    def main(args: Array[String]): Unit = {
      val map1 = Map("a" -> 1, "b" -> 3, "c" -> 6)
      val map2 = mutable.Map("a" -> 6, "b" -> 2, "c" -> 9, "d" -> 3)
    
      // println(map1 ++ map2)  // Map(a -> 6, b -> 2, c -> 9, d -> 3)
    
      val map3: mutable.Map[String, Int] = map1.foldLeft(map2)(
        (mergedMap, kv) => {
          val key = kv._1
          val value = kv._2
          mergedMap(key) = mergedMap.getOrElse(key, 0) + value
          mergedMap
        }
      )
    
      println(map3)
    }
    

7.7.5 普通 WordCount 案例

  1. 需求

    单词计数: 将集合中出现的相同的单词, 进行计数, 取计数排名前三的结果

  2. 案例实操

    def main(args: Array[String]): Unit = {
      val stringList: List[String] = List(
        "hello",
        "hello word",
        "hello scala",
        "hello spark from scala",
        "hello flink from scala"
      )
    
      // 1. 对字符串进行切分, 得到一个打散所有单词的列表
      //		val wordList1: List[Array[String]] = stringList.map(_.split(" "))
      //		val wordList2: List[String] = wordList1.flatten
      //		println(wordList2)
    
      val wordList = stringList.flatMap(_.split(" "))
      // println(wordList)
    
      // 2. 分组
      val groupMap: Map[String, List[String]] = wordList.groupBy(word => word)
    
      // println(groupMap)
    
      // 3. 对分组之后的list取长度, 得到每个单词的个数
      val countMap: Map[String, Int] = groupMap.map(kv => (kv._1, kv._2.length))
      println(countMap)
    
      // 4. 将 map转换为 list, 并排序 取前三
      val sortList: List[(String, Int)] = countMap.toList
      .sortWith(_._2 > _._2)
      .take(3)
      println(sortList)
    }
    

7.7.6 复杂 WordCount案例

def main(args: Array[String]): Unit = {
  val tupleList: List[(String, Int)] = List(
    ("hello", 1),
    ("hello word", 2),
    ("hello scala", 3),
    ("hello spark from scala", 1),
    ("hello flink from scala", 2)
  )

  // 思路一, 直接展开为普通版本
  val newStringList: List[String] = tupleList.map(
    kv => {
      (kv._1.trim + " ") * kv._2
    }
  )
  println(newStringList)

  // 接下来操作与普通版本完全一致
  val wordCountList: List[(String, Int)] = newStringList
  .flatMap(_.split(" ")) // 空格分词
  .groupBy(word => word) // 按照单词分组
  .map(kv => (kv._1, kv._2.length)) // 统计出每个单词的个数
  .toList // 转list
  .sortBy(_._2)(Ordering[Int].reverse) // 排序
  .take(3) // 取前三
  println(wordCountList)

  // 思路二, 直接基于预统计的结果进行转换
  // 将字符串打散为单词, 并结合对应的个数包装成二元组
  val preCountList: List[(String, Int)] = tupleList.flatMap(
    tuple => {
      val strings: Array[String] = tuple._1.split(" ")
      strings.map(word => (word, tuple._2))
    }
  )
  // println(preCountList)

  // 2. 对二元组按照单词进行分组
  // val preCountMap: Map[String, List[(String, Int)]] = preCountList.groupBy(_._1)
  // println(preCountMap)

  // 3. 叠加每个单词预统计的个数值
  val countMap = preCountList
  .groupBy(_._1) // 分组
  .map( // 累加
    kv => {
      (kv._1, kv._2.map(_._2).sum)
    }
  )
  .toList // 转换成 List
  .sortWith(_._2 > _._2) // 排序
  .take(3) // 取前三
  println(countMap)

}

7.8 队列

  1. 说明

    scala 也提供了队列 Queue 的数据结构, 队列的特点就是先进先出, 进队和出队的方法分别为 enqueue 和 deqieie

  2. 案例实操

    def main(args: Array[String]): Unit = {
    
      // 创建一个可变序列
      val queue: mutable.Queue[String] = mutable.Queue[String]()
    
      queue.enqueue("a", "b", "c")
    
      println(queue)
    
      println(queue.dequeue())
      println(queue)
      println(queue.dequeue())
      println(queue)
    
      queue.enqueue("d", "e")
    
      println(queue)
      println(queue.dequeue())
    
      println(queue)
    
      println("====================")
      // 不可变队列
      val queue2: Queue[String] = Queue("a", "b", "c")
    
      val queue3: Queue[String] = queue2.enqueue("d")
      println(queue3)
    
    }
    

7.9 并行集合

  1. 说明

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

  2. 案例实操

    
    def main(args: Array[String]): Unit = {
    
      // 串行
      val result: IndexedSeq[Long] = (1 to 100).map(
        x => Thread.currentThread().getId
      )
      println(result)
    
    
      // 并行
      val result2: ParSeq[Long] = (1 to 100)
      .par
      .map(
        x => Thread.currentThread().getId
      )
      println(result2)
    }
    

8. 模式匹配

Scala 中的模式匹配 类似于 Java 中的 switch 语法

但是 scala 从语法中补充了更多功能, 所以更强大

8.1 基本语法

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

val x: Int = 5
val y: String = x match {
  case 1 => "one"
  case 2 => "two"
  case 3 => "three"
  case _ => "other"
}

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

8.2 模式守卫

  1. 说明

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

  2. 案例实操

    def abs(num: Int): Int = {
      num match {
        case i if i >= 0 => i
        case i if i < 0 => -i
        case _ => 0
      }
    }
    println(abs(-5))
    

8.3 模式匹配类型

8.3.1 匹配常量

  1. 说明

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

  2. 实操

    def describeConst(x: Any): String = x match {
      case 1 => "Int one"
      case "hello" => "String hello"
      case true => "Boolean true"
      case '+' => "Char +"
      case _ => "Unit"
    }
    
    println(describeConst("hello"))
    println(describeConst(true))
    println(describeConst("sad"))
    

8.3.2 匹配类型

def describeType(x: Any): String = x match {
  case i: Int => s"Int $i"
  case s: String => s"String $s"
  case list: List[String] => s"List $list"
  case array: Array[Int] => s"List ${array.mkString("-")}"
  case a => s"Something $a"
}

println(describeType("hello" :: "world" :: Nil))
println(describeType(21))
println(describeType(true))

8.3.3 匹配数组

for (arr <- List(
  Array(0),
  Array(1, 0),
  Array(0, 1, 0),
  Array(1, 1, 0),
  Array(2, 3, 7, 15),
  Array("hello", 20, 30)
)) {
  val result = arr match {
    case Array(0) => "0"
    case Array(1, 0) => "Array(1, 0)"
    case Array(x, y) => s"Array: $x , $y"
    case Array(0, _*) => s"以0开头的数组"
    case Array(x, 1, z) => s"中间为1的三元素数组"
    case _ => "something else"
  }
  println(result)
}

8.3.4 匹配列表

// 方式一
for (list <- List(
  List(0),
  List(1, 0),
  List(0, 0, 0),
  List(1, 1, 0),
  List(88),
  List("hello")
)) {
  val result = list match {
    case List(0) => "0"
    case List(x, y) => s"list: $x , $y"
    case List(0, _*) => "List(0, ...)"
    case List(a) => s"List a = $a"
    case _ => "something else"
  }
  println(result)
}
// 方式二
val list = List(1, 2, 5, 7, 24)

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

8.3.5 匹配元组

for (tuple <- List(
  (0, 1),
  (0, 0),
  (0, 1, 0),
  (0, 1, 1),
  (1, 23, 56),
  ("hello", true, .5),
)) {
  val result = tuple match {
    case (a, b) => "" + a + ", " + b
    case (0, _) => "(0, _)"
    case (a, 1, _) => "(a, 1, _)" + a
    case (x, y, z) => s"($x-$y-$z)"
    case _ => "something else"
  }
  println(result)
}

8.3.6 匹配对象及样例类

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

    val student = Student("alice", 18)

    // 针对对象实例的内容进行匹配

    val result = student match {
      case Student("alice", 18) => "alice, 18"
      case _ => "else"
    }

    println(result)
  }
}

// 定义类
class Student(val name:String, val age: Int) 

// 定义伴生对象
object Student {

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

  // 必须实现一个 unapply 方法, 用来对对象属性进行拆解
  def unapply(student: Student): Option[(String, Int)] = {
    if (student == null)
    None
    else
    Some((student.name, student.age))
  }
}

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

    val student = Student1("alice", 18)

    val result = student match {
      case Student1("alice", 18) => "alice, 18"
      case _ => "else"
    }

    println(result)
  }
}

// 定义样例类
case class Student1(name: String, age: Int)

8.4 变量声明中的模式匹配

val (x, y) = (10, "hello")

println(s"x $x , y $y")

val List(first, second, _*) = List(23, 15, 9, 78)
val fir :: sec :: rest = List(23, 15, 9, 78)
println(s"first $first second $second ")
println(s"fir $fir sec $sec rest $rest ")

println("===============")

8.5 for表达式中的模式匹配

// for 推导式中 进行模式匹配
val list: List[(String, Int)] = List(("a", 12), ("b", 35), ("c", 27), ("a", 122))

// 原本的遍历方式
for (elem <- list) {
  println(elem._1 + " " + elem._2)
}

// 将list的元素直接定义为元组, 对变量赋值
for ((word, count) <- list) {
  println(word + " : " + count)
}

// 可以不考虑某个位置的变量 只遍历 key, 或者 value
for ((word, _) <- list) {
  println(word)
}

// 可以指定某个位置的值必须是多少
for (("a", count) <- list) {
  println(count)
}

8.6 偏函数中的模式匹配 (了解)

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

  1. 偏函数定义

    // 偏函数名		偏函数类型				参数类型			返回值类型
    val second: PartialFunction[List[Int], Option[Int]] = {
      // case 语句
      case x :: y :: _ => Some(y)
    }
    

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

  2. 实操

    def main(args: Array[String]): Unit = {
    
      val list: List[(String, Int)] = List(("a", 12), ("b", 35), ("c", 27), ("a", 13))
    
      // 1. map 转换 实现key不变, value * 2
      val newList = list map (tuple => (tuple._1, tuple._2 * 2))
    
      // 2. 使用模式匹配
      val newList2: List[(String, Int)] = list map ({
        tuple => {
          tuple match {
            case (word, count) => (word, count * 2)
          }
        }
      })
    
      // 3. 省略 lambda 表达式的写法进行简化
      val newList3: List[(String, Int)] = list map {
        case (word, count) => (word, count * 2)
      }
    
      println(newList)
      println(newList2)
      println(newList3)
    
      // 偏函数的应用, 求绝对值
      // 对输入数据分为不同的情形: 正、负、零
      val positiveAbs: PartialFunction[Int, Int] = {
        case x if x > 0 => x
      }
      val negativeAbs: PartialFunction[Int, Int] = {
        case x if x < 0 => -x
      }
      val zeroAbs: PartialFunction[Int, Int] = {
        case 0 => 0
      }
    
      def abs(x: Int): Int = (positiveAbs orElse negativeAbs orElse zeroAbs) (x)
    
      println(abs(-12))
    
    }
    

9. 异常

语法处理上和 Java类似, 但是又不尽相同

9.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, 分别捕获对应的异常, 这时需要把范围小的异常类写在前面, 把范围大的异常类写在后面, 否则编译错误

9.2 Scala 异常处理

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

  try {
    val c = 10 / 0
  } catch {
    case e: ArithmeticException => println("发生算数异常")
    case e: Exception => println("发生一般异常")
  } finally println("处理结束")
}
  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. java 提供了 throws 关键字来声明异常. 可以使用方法定义声明异常。它向调用者函数提供了此方法可能引发此异常的信息, 它有助于调用函数处理并将该代码包含在 try-catch 块中, 以避免程序异常终止, 在 Scala 中, 可以使用 throws 注解来声明异常

    @throws(classOf[NumberFormatException])
    def fil(): Int = "abc".toInt
    

10. 隐式转换

当编译器第一次编译失败的时候, 会在当前的环境中查找能让代码编译通过的方法, 用于将类型进行转换, 实现二次编译

10.1 隐式函数

  1. 说明

    隐式转换可以在不需要改任何代码的情况下, 扩展某个类的功能

  2. 案例实操

    // 通过隐式转化为 Int 类型增加方法
    object Test02Implicit {
    	def main(args: Array[String]): Unit = {
    
    		val new12 = new MyRichInt(12)
    		println(new12.myMax(15))
    
    		// 1. 隐式函数
    		implicit def convert(num: Int) : MyRichInt = new MyRichInt(num)
    		// 自动执行转化方法
    		println(16.myMax(15))
    	}
    }
    
    // 自定义类
    class MyRichInt(val self: Int) {
    	// 自定义比较大小的方法
    	def myMax (n: Int) : Int = if (n < self) self else n
    	def myMin (n: Int) : Int = if (n < self) n else self
    
    }
    

10.2 隐式参数

普通方法或者函数中的参数可以通过 implicit 关键字声明为隐式参数, 调用该方法时, 就可以传入该参数, 编译器会在相应的应用域寻找符合条件的隐式值

  1. 说明

    1. 同一个作用域中, 相同的隐式值只能有一个
    2. 编译器按照隐式参数的类型去找对应类型的隐式值, 与隐式值的名称无关
    3. 隐式参数优先于默认参数
  2. 案例实操

    implicit val str: String = "alice"
    implicit val num: Int = 18
    
    def sayHello(implicit name: String): Unit = println(s"hello $name")
    
    def sayHi(implicit name: String = "atguigu"): Unit = println(s"hi $name")
    
    sayHello
    sayHi
    
    // 简便写法
    def hiAge(): Unit = println(s"hi, ${implicitly[Int]}")
    
    hiAge()
    

10.3 隐式类

在 scala 2.10 后提供了隐式类, 可以使用 implicit 声明类, 隐式类的非常强大, 同样可以扩展类的功能, 在集合中隐式类会发挥重要的作用

  1. 隐式类说明

    1. 其所带的构造参数有且只能有一个
    2. 隐式类必须被定义在 “类” 或 “半生对象” 或 “包对象” 里, 即隐式类不能是顶级的
  2. 案例实操

    def main(args: Array[String]): Unit = {
    
      val new12 = new MyRichInt(12)
      println(new12.myMax(15))
    
      // 1. 隐式函数
      implicit def convert(num: Int): MyRichInt = new MyRichInt(num)
    
      println(16.myMax(15))
    
      println("====================")
    
      // 2. 隐式类
      implicit class MyRichInt2(val self: Int) {
        // 自定义比较大小的方法
        def myMax2(n: Int): Int = if (n < self) self else n
    
        def myMin2(n: Int): Int = if (n < self) n else self
      }
    
      println(12.myMin2(19))
    }
    

10.4 隐式解析机制

  1. 说明
    1. 首先会在当前代码作用域下查找隐式实体(隐式方法、隐式类、隐式对象)。(一般是这种情况)
    2. 如果第一条规则查找隐式实体失败, 会继续在隐式参数的类型的作用域里查找。类型的作用域是指与 该类型相关联的全部伴生对象 以及 该类型所在包的包对象

11. 泛型

11.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. 实操

    object Test03Generics {
      def main(args: Array[String]): Unit = {
        // 1. 协变和逆变
        val child: Parent = new Child
    
        // +
        // val childList: MyCollection[Parent] = new MyCollection[Child]
    
        // -
        val childList: MyCollection[SubChild] = new MyCollection[Child]
    
      }
    }
    // 定义继承关系
    class Parent
    class Child extends Parent
    class SubChild extends Child
    
    // 定义带泛型的集合类型
    class MyCollection[-E]
    

11.2 泛型上下限

  1. 语法

    class PersonList[T <: Person] {} 泛型上限

    class PersonList[T >: Person] {} 泛型下限

  2. 说明

    泛型的上下限的作用是对传入的泛型进行限定

  3. 实操

11.3 上下文限定

  1. 语法

    def f[A : B](a: A) = println(a) 等同于 def f[A](a:A)(implicit arg: B[A]) = println(a)

  2. 说明

    上下文限定是将泛型和隐式转换的结合产物, 以下两者功能相同, 使用上下文限定[A: Ordering] 之后, 方法内无法使用隐式参数名称调用隐式参数, 需要通过 implicit[Ordering[A]] 获取隐式变量, 如果此时无法查找到对应类型的隐式变量, 会发生错误

    implicit val x = 1
    val y = implicitly[Int]
    val y = implicitly[Double]
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值