大数据组件笔记 -- Scala

文章目录

  • 一、简介
    • 1.1 环境搭建
    • 1.2 Hello World
    • 1.3 Scala in IDEA
    • 1.4 伴生对象
  • 二、变量和数据类型
    • 2.1 变量和常量 *
    • 2.2 关键字
    • 2.3 字符串输出
    • 2.4 键盘输入
    • 2.5 数据类型 *
    • 2.6 类型转换
  • 三、运算符
    • 3.1 算术运算符
    • 3.2 关系运算符
    • 3.3 逻辑运算符
    • 3.4 赋值运算符
    • 3.5 位运算符
  • 四、流程控制
    • 4.1 分支
    • 4.2 For 循环
    • 4.3 While 循环
    • 4.4 循环中断
  • 五、函数式编程
    • 5.1 函数基础
    • 5.2 至简原则
    • 5.3 高阶函数
    • 5.4 匿名函数
    • 5.5 其它
      • 5.5.1 函数柯里化&闭包
      • 5.5.2 递归
      • 5.5.3 控制抽象
      • 5.5.4 惰性函数
  • 六、面向对象
    • 6.1 包管理
    • 6.2 类和对象
      • 6.2.1 封装
      • 6.2.2 继承
      • 6.2.3 抽象
      • 6.2.4 多态
      • 6.2.5 匿名子类
    • 6.3 单例对象(伴生对象)
    • 6.4 特质(Trait)
      • 6.4.1 基本语法
      • 6.4.2 特质叠加
      • 6.4.3 自身类型(依赖注入)
      • 6.4.4 特质和抽象类的区别
    • 6.5 扩展
      • 6.5.1 类型检查和转换
      • 6.5.2 枚举类和应用类
      • 6.5.3 Type定义新类型
  • 七、集合
    • 7.1 数组
    • 7.2 集合 List
    • 7.3 集合 Set
    • 7.4 集合 Map
    • 7.5 元组
    • 7.6 衍生集合
    • 7.7 集合计算
    • 7.8 队列
    • 7.9 并行集合
    • 7.10 WordCount 案例
    • 八、模式匹配
  • 九、异常
    • 9.1 Java异常处理
    • 9.2 Scala异常处理
  • 十、隐式转换
    • 10.1 隐式函数
    • 10.2 隐式参数
    • 10.3 隐式类
    • 10.4 隐式解析机制
  • 十一、泛型
    • 11.1 Java 泛型
    • 11.2 Scala 泛型

一、简介

  • Scala、Java以及JVM关系图

大数据组件笔记 -- Scala_第1张图片

  • 语言特点

大数据组件笔记 -- Scala_第2张图片

1.1 环境搭建

  1. 下载安装

下载地址:https://scala-lang.org/download/all.html

  1. 配置环境变量

大数据组件笔记 -- Scala_第3张图片

  1. 验证

注意Java版本与Scala的兼容性,否则会出现 failed to initialize compiler: object java.lang.object in compiler mirror not found. 报错。

详见兼容性列表:https://docs.scala-lang.org/overviews/jdk-compatibility/overview.html

C:\Users\m1553>java -version
java version "1.8.0_281"
Java(TM) SE Runtime Environment (build 1.8.0_281-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.281-b09, mixed mode)

C:\Users\m1553>scala
Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_281).
Type in expressions for evaluation. Or try :help.

scala> var a = 10
a: Int = 10

scala> var b = 20
b: Int = 20

scala> var c = a + b
c: Int = 30

scala> :quit

C:\Users\m1553>

1.2 Hello World

object HelloWorld {
     
	def main(args:Array[String]):Unit = {
     
		println("Hello Scala")
	}
}
C:\Users\m1553\Desktop>scalac HelloWorld.scala

C:\Users\m1553\Desktop>dir
 驱动器 C 中的卷没有标签。
 卷的序列号是 D28D-B712

 C:\Users\m1553\Desktop 的目录

2021/03/14  15:04    <DIR>          .
2021/03/14  15:04    <DIR>          ..
2021/03/14  15:04               635 HelloWorld$.class
2021/03/14  15:04               586 HelloWorld.class
2021/03/14  14:52                92 HelloWorld.scala
               3 个文件          1,313 字节
               2 个目录 30,940,823,552 可用字节

C:\Users\m1553\Desktop>scala HelloWorld
Hello Scala

C:\Users\m1553\Desktop>

1.3 Scala in IDEA

  1. 安装插件支持
    大数据组件笔记 -- Scala_第4张图片

  2. 为项目添加框架支持

大数据组件笔记 -- Scala_第5张图片

  1. 编写程序运行

新建空白项目 -> 新建Maven子项目 -> 为子项目添加Scala语言框架支持

大数据组件笔记 -- Scala_第6张图片

1.4 伴生对象

  • Object$.class

细心点到这里会发现,每个 Scala Object 编译过后会产生两个 .class 文件 Object.class 和 Object$.class。

  1. Scala 是纯面向对象的,去除 static(不可在类上调用方法)、void、break/continue(应通过对象方法实现)关键字;
  2. Scala 通过 伴生对象 模拟 static 效果
  • 伴生对象
  1. 定义:伴随类产生的一个对象
  2. 当对源文件进行编译之后,默认会生成两个字节码文件,一个是伴生类,另一个伴生对象的所属类(带 $ 后缀)
  3. 真正的伴生对象是 伴生对象所属类中创建的单例对象
  • 如何实现 static 效果

Scala 通过将伴生对象构造成类似单例静态对象的方式实现 static 效果。

  1. HelloWorld.class
package com.simwor.scala.day01;

import scala.reflect.ScalaSignature;

@ScalaSignature(bytes="\006\001):Q!\001\002\t\002 ...")
public final class HelloWorld
{
     
  public static void main(String[] paramArrayOfString)
  {
     
    HelloWorld..MODULE$.main(paramArrayOfString);
  }
}
  1. HelloWorld$.class
package com.simwor.scala.day01;

import scala.Predef.;

public final class HelloWorld$
{
     
  public static final  MODULE$;

  static
  {
     
    new ();
  }

  public void main(String[] args)
  {
     
    Predef..MODULE$.println("Hello Scala From IDEA");
  }
  private HelloWorld$() {
     
    MODULE$ = this;
  }
}

二、变量和数据类型

2.1 变量和常量 *

package com.simwor.scala.chapter02

object TestAnnotation {
     

  def main(args: Array[String]): Unit = {
     
    // 1.声明变量时,类型可以省略,编译器自动推导,即类型推导
    var a:Int = 10
    var b = 20
    println(a)
    println(b)

    // 2.类型确定后,就不能修改,说明Scala是强数据类型语言。
    // 3.变量声明时,必须要有初始值

    // 4.在声明/定义一个变量时,可以使用var或者val来修饰,var修饰的变量可改变,val修饰的变量不可改。
    var c = 10
    val d = 20
    c = 20
    //d = 30

    // 5.val修饰的对象不可改变,但对象的状态(值)却是可以改变的。(比如:自定义对象、数组、集合等等)
  }
}

2.2 关键字

•	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 字符串输出

package com.simwor.scala.chapter02

object TestAnnotation {
     

  def main(args: Array[String]): Unit = {
     
    var name = "lily"
    var age = 18

    //1.通过+号连接
    println("name := " + name + ", age := " + age)

    //2.插值
    println(s"name := ${name}, age := ${age}")

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

    //4.字符串,通过$引用
    val sql =
      s"""
        |select
        |    name,
        |    age
        |from user
        |where name = ${name}
            """.stripMargin
    println(sql)

  }
}
name := lily, age := 18
name := lily, age := 18
name := lily, age := 18

select
    name,
    age
from user
where name = lily
规定符 说明
%d 十进制有符号整数
%u 十进制无符号整数
%f 浮点数
%s 字符串
%c 单个字符
%p 指针的值
%e 指数形式的浮点数
%x, %X 无符号以十六进制表示的整数
%o 无符号以八进制表示的整数
%g 把输出的值按照%e或者%f类型中输出长度较小的方式输出
%p 输出地址符
%lu 32位无符号整数
%llu 64位无符号整数

2.4 键盘输入

package com.simwor.scala.chapter02

import scala.io.StdIn

object TestAnnotation {
     

  def main(args: Array[String]): Unit = {
     
    println("type your name")
    val name = StdIn.readLine()

    println("type your age")
    val age = StdIn.readInt()

    println(s"Welcom ${name}, ${age}")
  }
}
type your name
rayslee
type your age
18
Welcom rayslee, 18

2.5 数据类型 *

大数据组件笔记 -- Scala_第7张图片

  • 整数类型(Byte、Short、Int、Long)
数据类型 描述
Byte[1] 8位有符号补码整数。数值区间为 -128 到 127
Short[2] 16位有符号补码整数。数值区间为 -32768 到 32767
Int[4] 32位有符号补码整数。数值区间为 -2147483648 到 2147483647
Long[8] 64位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807 = 2的(64-1)次方-1
  1. Scala各整数类型有固定的表示范围和字段长度,不受具体操作的影响,以保证Scala程序的可移植性。
  2. Scala的整型,默认为Int型,声明Long型,须后加‘l’或‘L’
  3. Scala程序中变量常声明为Int型,除非不足以表示大数,才使用Long
  • 浮点类型(Float、Double)

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

数据类型 描述
Float [4] 32 位, IEEE 754标准的单精度浮点数
Double [8] 64 位 IEEE 754标准的双精度浮点数
  • 字符类型(Char)
object TestCharType {
     

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

        //(1)字符常量是用单引号 ' ' 括起来的单个字符。
        var c1: Char = 'a'
        println("c1=" + c1)
		//注意:这里涉及自动类型提升,其实编译器可以自定判断是否超出范围,
    	//不过idea提示报错
		var c2:Char = 'a' + 1
		println(c2)
        
        //(2)\t :一个制表位,实现对齐的功能
        println("姓名\t年龄")

        //(3)\n :换行符
        println("西门庆\n潘金莲")

        //(4)\\ :表示\
        println("c:\\岛国\\avi")

        //(5)\" :表示"
        println("同学们都说:\"大海哥最帅\"")
    }
}
  • 布尔类型(Boolean)

boolean类型占1个字节。

object TestBooleanType {
     

    def main(args: Array[String]): Unit = {
     
        
        var isResult : Boolean = false
        var isResult2 : Boolean = true
    }
}
  • Unit类型、Null类型和Nothing类型(重点)
数据类型 描述
Unit 表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。
Null null , Null 类型只有一个实例值null
Nothing Nothing类型在Scala的类层级最低端;它是任何其他类型的子类型。当一个函数,我们确定没有正常的返回值,可以用Nothing来指定返回类型,这样有一个好处,就是我们可以把返回的值(异常)赋给其它的函数或者变量(兼容性)
  1. Unit类型用来标识过程,也就是没有明确返回值的函数。

由此可见,Unit类似于Java里的void。Unit只有一个实例——( ),这个实例也没有实质意义。

object TestSpecialType {
     

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

        def sayOk : Unit = {
     // unit表示没有返回值,即void
            
        }
        println(sayOk)
    }
}
  1. Null类只有一个实例对象,Null类似于Java中的null引用。Null可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal)
object TestDataType {
     

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

        //null可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal)
        var cat = new Cat();
        cat = null	// 正确

        var n1: Int = null // 错误
        println("n1:" + n1)

    }
}

class Cat {
     

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

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

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

2.6 类型转换

  • 隐式转换

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

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

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

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

        //(4)byte,short,char他们三者可以计算,在计算时首先转换为int类型。
        var n6 : Byte = 1
        var c2 : Char = 1
        // var n : Short = n6 + c2 //当n6 + c2 结果类型就是int
        // var n7 : Short = 10 + 90 //错误
    }
}
  • 强制类型转换
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)
    }
}
  • 数值类型和String类型间转换

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

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
    }
}

三、运算符

在Scala中其实是没有运算符的,所有运算符都是方法。

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

3.1 算术运算符

运算符 运算 范例 结果
+ 正号 +3 3
- 负号 b=4; -b -4
+ 5+5 10
- 6-4 2
* 3*4 12
/ 5/5 1
% 取模(取余) 7%5 2
+ 字符串相加 “He”+”llo” “Hello”
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 关系运算符

运算符 运算 范例 结果
== 相等于 4==3 false
!= 不等于 4!=3 true
< 小于 4<3 false
> 大于 4>3 true
<= 小于等于 4<=3 false
>= 大于等于 4>=3 true
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
    }
}

Scala 中的 == 与 eq() 与 Java 中的 == 和 equals() 效果相反。

public static void main(String[] args) {
     
    
    String s1 = "abc";
    String s2 = new String("abc");

    System.out.println(s1 == s2);

    System.out.println(s1.equals(s2));
}
//输出结果:
//false
//true

3.3 逻辑运算符

运算符 描述 实例
&& 逻辑与 (A && B) 运算结果为 false
|| 逻辑或 (A || B) 运算结果为 true
! 逻辑非 !(A && B) 运算结果为 true
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
    }
    
	//扩展避免逻辑与空指针异常
	isNotEmpty(String s){
     
		//如果逻辑与,s为空,会发生空指针
		return s!=null && !"".equals(s.trim());
	}
}

3.4 赋值运算符

运算符 描述 实例
= 简单的赋值运算符,将一个表达式的值赋给一个左值 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中没有++、–操作符,可以通过+=、-=来实现同样的效果;

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

3.5 位运算符

运算符 描述 实例
& 按位与运算符 (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
object TestPosition {
     

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

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

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

四、流程控制

4.1 分支

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

        if (age < 18)
            println("童年")
    }
}
  • 双分支
object TestIfElse {
     
    def main(args: Array[String]): Unit = {
     
        println("input age:")
        var age = StdIn.readShort()

        if (age < 18)
            println("童年")
        else
            println("成年")
    }
}
  • 多分支
object TestIfElse  {
     
    def main(args: Array[String]): Unit = {
     
        println("input age")
        var age = StdIn.readInt()

        if (age < 18){
     
            println("童年")
        }else if(age>=18 && age<30){
     
            println("中年")
        }else{
     
            println("老年")
        }
    }
}
  • 分支返回值

Scala中返回值类型不一致,取它们共同的祖先类型。

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

        val res :String = if (age < 18){
     
            "童年"
        }else if(age>=18 && age<30){
     
            "中年"
        }else{
     
            "老年"
        }

        println(res)
    }
}
  • 三元运算符

如果大括号{}内的逻辑代码只有一行,大括号可以省略。如果省略大括号,if只对最近的一行逻辑代码起作用。

object TestIfElse {
     
    def main(args: Array[String]): Unit = {
     
        // Java
		// int result = flag?1:0

        // Scala
        println("input age")
        var age = StdIn.readInt()
        val res:Any = if (age < 18)  "童年" else "成年"

        println(res)
    }
}

4.2 For 循环

  • 范围数据循环(To)
for(i <- 1 to 3){
     
    print(i + " ")
}
println()
//1 2 3
  • 范围数据循环(Until)
for(i <- 1 until 3) {
     
    print(i + " ")
}
println()
//1 2
  • 循环守卫(类似Java continue)
for(i <- 1 to 3 if i != 2) {
     
    print(i + " ")
}
println()
//1 3
  • 循环步长
for (i <- 1 to 10 by 2) {
     
    println("i=" + i)
}
//1 3 5 7 9
  • 嵌套循环
for(i <- 1 to 3; j <- 1 to 3) {
     
    println(" i =" + i + " j = " + j)
}
  • 倒序打印
for(i <- 1 to 5 reverse){
     
    println(i)
}
//5 4 3 2 1

4.3 While 循环

  • while
var i = 0

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

4.4 循环中断

Breaks.breakable 以及 Breaks.break 都是对象封装的方法。

import scala.util.control.Breaks._

object TestBreak {
     

    def main(args: Array[String]): Unit = {
     
    
        breakable {
     
            for (elem <- 1 to 10) {
     
                println(elem)
                if (elem == 5) break
            }
        }
    
        println("正常结束循环")
    }
}

五、函数式编程

  • 面向对象编程

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

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

对象:用户
行为:登录、连接JDBC、读取数据库
属性:用户名、密码

对象的本质:对数据和行为的一个封装
  • 函数式编程

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

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

例如:请求->用户名、密码->连接JDBC->读取数据库

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

5.1 函数基础

  • 基本语法
    大数据组件笔记 -- Scala_第8张图片

  • 函数和方法的区别

为完成某一功能的程序语句的集合,称为函数,类中的函数称之方法。

  1. 函数定义在方法或函数的内部,方法定义在类下。
object TestFunction {
     

    def test1(): Unit = {
      }

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

        def test2(): Unit ={
      }
    
    }
}
  1. 函数没有重载和重写的概念;方法可以进行重载和重写
def main(args: Array[String]): Unit = {
     
	//函数没有重载和重写的概念,程序报错
	def test(age:Int): Unit ={
     
	    println("无参,无返回值")
	}

	def test(name:String): Unit={
     
	    println()
	}
}
  • Scala中函数可以嵌套定义
def main(args: Array[String]): Unit = {
     
    //Scala中函数可以嵌套定义
    def test2(): Unit ={
     

        def test3(name:String):Unit={
     
            println("函数可以嵌套定义")
        }
        
    }
}

5.2 至简原则

函数至简原则:能省则省

  • 标准写法
def f( s : String ): String = {
     
    return s + " jinlian"
}
println(f("Hello"))
  • return可以省略,Scala会使用函数体的最后一行代码作为返回值
def f1( s : String ): String =  {
     
    s + " jinlian"
}
println(f1("Hello"))
  • 如果函数体只有一行代码,可以省略花括号
def f2(s:String):String = s + " jinlian"
println(f2("Hello"))
  • 返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
def f3( s : String ) = s + " jinlian"
println(f3("Hello3"))
  • 如果有return,则不能省略返回值类型,必须指定
def f4() :String = {
     
    return "ximenqing4"
}
println(f4())
  • 如果函数明确声明unit,那么即使函数体中使用return关键字也不起作用
def f5(): Unit = {
     
    return "dalang5"
}
println(f5())
//()
  • Scala如果期望是无返回值类型,可以省略等号
// 将无返回值的函数称之为过程
def f6() {
     
    "dalang6"
}
println(f6())
//()
  • 如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
def f7() = "dalang7"
println(f7())
println(f7)
  • 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
def f8 = "dalang"
//println(f8())
println(f8)
  • 如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
def f9 = (x:String)=>{
     println("wusong")}
def f10(f:String=>Unit) = {
     
    f("")
}
f10(f9)

//省略函数名(def),类似匿名函数的定义方式
println(f10((x:String)=>{
     println("wusong")}))

5.3 高阶函数

  • 函数作为值进行传递
object Test {
     

  def foo():Int = {
     
    println("foo")
    1
  }

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

    //(1)调用foo函数,把返回值给变量f
    //val f = foo()
    val f = foo
    println(f)

    //(2)在被调用函数foo后面加上 _,相当于把函数foo当成一个整体,传递给变量f1
    val f1 : () => Int = foo _
    // val f1 = foo _   //函数的类型 '() => Int' 可以省略
    f1()

    //(3)如果明确变量类型,那么不使用下划线也可以将函数作为整体传递给变量
    val f2:()=>Int = foo
    f2()
  }

}
  • 函数作为参数进行传递
def main(args: Array[String]): Unit = {
     

  def calculator(a: Int, b: Int, op: (Int,Int)=>Int): Int = {
     
    op(a, b)
  }

  // 1. 函数作为参数传递
  def add(a: Int, b: Int): Int = {
     
    a + b
  }
  println(calculator(10, 20, add))

  // 2. 匿名函数作为参数传递
  println(calculator(10, 20, (a: Int, b: Int) => {
     
    a * b
  }))

  // 2.1 匿名函数作为参数传递的简洁写法
  println(calculator(20, 10, _ - _))
}
  • 函数作为函数返回值返回
  def main(args: Array[String]): Unit = {
     
    def f1() = {
     
      def f2() = {
     
        println("f2 invoked")
      }
      f2 _
    }

//    val f = f1()
//    f()
    f1()()
  }

5.4 匿名函数

  • 指导思想
  1. 参数的类型可以省略,会根据形参进行自动的推导
  2. 型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过1的永远不能省略圆括号。
  3. 匿名函数如果只有一行,则大括号也可以省略
  4. 如果参数只出现一次,则参数省略且后面参数可以用_代替
  • 单个参数
object Test {
     

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

    // (1)定义一个函数:参数包含数据和逻辑函数
    def operation(arr: Array[Int], op: Int => Int): Array[Int] = {
     
      for (elem <- arr) yield op(elem)
    }

    // (2)定义逻辑函数
    def op(ele: Int): Int = {
     
      ele + 1
    }

    // (3)标准函数调用
    val arr = operation(Array(1, 2, 3, 4), op)
    println(arr.mkString(","))

    // (4)采用匿名函数
    val arr1 = operation(Array(1, 2, 3, 4), (ele: Int) => {
     
      ele + 1
    })
    println(arr1.mkString(","))

    // (4.1)参数的类型可以省略,会根据形参进行自动的推导;
    val arr2 = operation(Array(1, 2, 3, 4), (ele) => {
     
      ele + 1
    })
    println(arr2.mkString(","))

    // (4.2)类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过1的永远不能省略圆括号。
    val arr3 = operation(Array(1, 2, 3, 4), ele => {
     
      ele + 1
    })
    println(arr3.mkString(","))

    // (4.3) 匿名函数如果只有一行,则大括号也可以省略
    val arr4 = operation(Array(1, 2, 3, 4), ele => ele + 1)
    println(arr4.mkString(","))

    //(4.4)如果参数只出现一次,则参数省略且后面参数可以用_代替
    val arr5 = operation(Array(1, 2, 3, 4), _ + 1)
    println(arr5.mkString(","))
  }
}
  • 多个参数
object Test {
     

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

    def calculator(a: Int, b: Int, op: (Int, Int) => Int): Int = {
     
      op(a, b)
    }

    // (1)标准版
    println(calculator(2, 3, (x: Int, y: Int) =>  {
     x + y}))

    // (2)如果只有一行,则大括号也可以省略
    println(calculator(2, 3, (x: Int, y: Int) =>  x + y))

    // (3)参数的类型可以省略,会根据形参进行自动的推导;
    println(calculator(2, 3, (x , y) =>  x + y))

    // (4)如果参数只出现一次,则参数省略且后面参数可以用_代替
    println(calculator(2, 3,   _ + _))
  }

}

5.5 其它

5.5.1 函数柯里化&闭包

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

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

object TestFunction {
     

  def main(args: Array[String]): Unit = {
     
    def f1() = {
     
      var a:Int = 10
      def f2(b:Int)={
     
        a + b
      }
      f2 _
    }

    // 在调用时,f1函数执行完毕后,局部变量a应该随着栈空间释放掉
    val f = f1()

    // 但是在此处,变量a其实并没有释放,而是包含在了f2函数的内部,形成了闭合的效果
    println(f(3))
    println(f1()(3))

    // 函数柯里化,其实就是将复杂的参数逻辑变得简单化,函数柯里化一定存在闭包
    def f3()(b:Int)={
     
      var a:Int = 10
      a + b
    }

    println(f3()(3))
  }
}

5.5.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.5.3 控制抽象

  • 值调用:把计算后的值传递过去
object TestControl {
     

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

        def f = ()=>{
     
            println("f...")
            10
        }

        foo(f())
    }

    def foo(a: Int):Unit = {
     
        println(a)
        println(a)
    }
}
//f...
//10
//10
  • 名调用:把 代码块 传递过去
object TestControl {
     

    def main(args: Array[String]): Unit = {
     
        def f = ()=>{
     
            println("f...")
            10
        }
        foo(f())
    }

    def foo(a: =>Int):Unit = {
     //注意这里变量a右侧多了 =>
        println(a)
        println(a)
    }
}
//f...
//10
//f...
//10 
object TestFunction {
     

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

        // (1)传递代码块
        foo({
     
            println("aaa")
        })

        // (2)小括号可以省略
        foo{
     
            println("aaa")
        }
    }

    def foo(a: =>Unit):Unit = {
     
        println(a)
        println(a)
    }
}
//aaa
//()
//aaa
//()
//aaa
//()
//aaa
//()
  • 使用案例:自定义一个While循环
object TestFunction {
     

  def main(args: Array[String]): Unit = {
     
    var i:Int = 1
    myWhile(i <= 10){
     
      println(i)
      i +=1
    }
  }

  def myWhile(condition: =>Boolean)(op: =>Unit):Unit={
     
    if (condition){
     
      op
      myWhile(condition)(op)
    }
  }
}

5.5.4 惰性函数

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

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.1 包管理

  • 嵌套风格

除类似Java包管理方式外,还可以通过嵌套的风格表示层级关系。

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

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

    object Outer {
     
        val out: String = "out"

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

    package atguigu {
     
        object Inner {
     
            val in: String = "in"

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

package other {
     

}
  • 包对象

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

package object com{
     
	val shareValue="share"
	def shareMethod()={
     }
}
  • 导包
  1. 和Java一样,可以在顶部使用import导入,在这个文件中的所有类都可以使用。
  2. 局部导入:什么时候使用,什么时候导入。在其作用范围内都可以使用
  3. 通配符导入:import java.util._
  4. 给类起名:import java.util.{ArrayList=>JL}
  5. 屏蔽类:import java.util.{ArrayList =>_, _}
  6. 导入相同包的多个类:import java.util.{HashSet, ArrayList}
  7. 导入包的绝对路径:new root.java.util.HashMap
  • 三个默认导入包
  1. import java.lang._
  2. import scala._
  3. import scala.Predef.

6.2 类和对象

  • 基本语法
  1. Scala语法中,类并不声明为public,所有这些类都具有公有可见性(即默认就是public)
  2. 一个Scala源文件可以包含多个类
package com.atguigu.chapter06

//(1)Scala语法中,类并不声明为public,所有这些类都具有公有可见性(即默认就是public)
class Person {
     

}

//(2)一个Scala源文件可以包含多个类
class Teacher{
     

}
  • 属性
  1. 属性默认为 public
  2. Bean属性(@BeanPropetry),可以自动生成规范的setXxx/getXxx方法
  3. val修饰的属性不能赋默认值,必须显示指定
  4. 可以使用 _ 标识为属性自动赋默认值(0, 0.0, false, null)
package com.atguigu.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.1 封装

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

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

Java 访问权限:

  1. private:只能在当前类中被访问
  2. default:当前类以及同包的其它类
  3. protected:当前类、同包的其它类以及子类
  4. public:所有类

Scala 访问权限:

  1. public:类、方法、属性的默认修饰符,但 Scala 中无此关键字
  2. private:只能在当前类以及伴生对象中访问
  3. protected:只能在当前类以及子类中访问
  4. private[包名]:可以在当前类、子类以及指定的包中访问
  • 构造器
  1. 定义类的本身就定义默认构造器(主构造器)
  2. 可以通过函数的名称this 定义辅助构造器
  3. 辅助构造方法不能直接构建对象,必须直接或者间接调用主构造方法
object Test {
     
  def main(args: Array[String]): Unit = {
     
    val person2 = new Person(18, "jingjing")
  }
}

//(1)如果主构造器无参数,小括号可省略
//class Person (){
     
class Person {
     

  var name: String = _

  var age: Int = _

  def this(age: Int) {
     
    this()
    this.age = age
    println("1-辅助构造器")
  }

  def this(age: Int, name: String) {
     
    this(age)
    this.name = name
    println("2-辅助构造器")
  }

  println("0-主构造器")
}
//0-主构造器
//1-辅助构造器
//2-辅助构造器
  • 构造器参数

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

  1. 未用任何修饰符修饰,这个参数就是一个局部变量
  2. var修饰参数,作为类的成员属性使用,可以修改
  3. val修饰参数,作为类只读属性使用,不能修改
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.2.2 继承

object Test {
     
  def main(args: Array[String]): Unit = {
     
    val stu1: Person = new Student("jingjing", 18)
    println("-------")
    var stu2: Person = new Student("huanhuan", 19, 20210318)
  }
}

class Person {
     
  println("1-父主构造器")

  var name: String = _
  var age: Int = _

  def this(name: String, age: Int) {
     
    this()
    this.name = name
    this.age = age
    println("2-父辅构造器")
  }
}

class Student(name:String, age:Int) extends Person(name, age) {
     
  println("3-子主构造器")

  var stuNo:Int = _

  def this(name:String, age:Int, stuNo:Int) {
     
    this(name, age)
    this.stuNo = stuNo
    println("4-子辅构造器")
  }
}
//1-父主构造器
//2-父辅构造器
//3-子主构造器
//-------
//1-父主构造器
//2-父辅构造器
//3-子主构造器
//4-子辅构造器

6.2.3 抽象

如果类中存在抽象属性/方法,这个类就是抽象的;如果类是抽象的,其中不一定包含抽象属性/方法。

  • 定义方法
  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 {
     

    val name: String = "teacher"

    def hello(): Unit = {
     
        println("hello teacher")
    }
}
  • 抽象的继承与重写
  1. 对抽象属性|方法重写,关键字 override 可以省略
  2. 只能对 val 类型的非抽象属性进行重写
  3. 对非抽象方法重写,关键字 override 不可以省略
abstract class Person {
     
  //非抽象属性
  var name:String = "jingjing"
  val planet:String = "earth"

  //抽象属性
  var age:Int

  //非抽象方法
  def eat(): Unit = {
     
    println("Person's eat")
  }

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

class Student extends Person {
     
  //对抽象属性|方法重写,关键字 override 可以省略
  var age:Int = 18
  def sleep(): Unit = {
     
    println("Student's sleep")
  }

  //只能对 val 类型的非抽象属性进行重写
  override val planet: String = "mars"

  //对非抽象方法重写,关键字 override 不可以省略
  override def eat(): Unit = {
     
    super.eat()
    println("Person's eat")
  }

}

6.2.4 多态

Scala中属性和方法都是动态绑定,而Java中只有方法为动态绑定。

  • Java
  1. 属性:静态/编译期绑定,编译时即把属性所属的类型确定下来;
  2. 方法:动态/运行期绑定,实际创建的什么类型运行时就调用对应类型的方法。
public class Test {
     
    public static void main(String[] args) {
     
        Person p  = new Student();

        System.out.println(p.getName());
        p.hello();
    }
}
//person
//student hello

class Person {
     
    String name = "person";

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

class Student extends Person {
     
    String name = "student";

    public void hello() {
     
        System.out.println("student hello");
    }
}
  • Scala
object Test {
     
  def main(args: Array[String]): Unit = {
     
    val t:Person = new Student

    println(t.name)
    t.hello()
  }
}
//student
//student hello

abstract class Person {
     
  val name:String = "person"

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

class Student extends Person {
     
  override val name:String = "student"

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

6.2.5 匿名子类

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.3 单例对象(伴生对象)

  • 伴生对象
  1. Scala语言是完全面向对象的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。
  2. 但是为了能够和Java语言交互(因为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)
    }
}
  • apply方法
  1. 通过伴生对象的apply方法,实现不使用new方法创建对象。
  2. 如果想让主构造器变成私有的,可以在()之前加上private。
  3. apply方法可以重载。
  4. Scala中obj(arg)的语句实际是在调用该对象的apply方法,即obj.apply(arg)。用以统一面向对象编程和函数式编程的风格。
  5. 当使用new关键字构建对象时,调用的其实是类的构造方法,当直接使用类名构建对象时,调用的其实时伴生对象的apply方法。
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)
	}
}
  • 单例设计模式
object Test {
     
  def main(args: Array[String]): Unit = {
     
      val p1 = Person.getInstance()
      val p2 = Person.getInstance()

      println(p1)//com.simwor.scala.chapter06.Person@23223dd8
      println(p2)//com.simwor.scala.chapter06.Person@23223dd8
  }
}

class Person private() {
     }

object Person {
     
  private var p:Person = _

  def getInstance(): Person = {
     
    if(p == null)
      p = new Person()
    p
  }
}

6.4 特质(Trait)

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

通过查看字节码,可以看到特质 = 抽象类+接口。

trait PersonTrait {
     

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

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

    }

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

6.4.1 基本语法

没有父类:class 类名 extends 特质1 with 特质2 with 特质3 …

有父类:class 类名 extends 父类 with 特质1 with 特质2 with 特质3…

  1. 特质可以同时拥有抽象方法和具体方法
  2. 一个类可以混入(mixin)多个特质
  3. 所有的Java接口都可以当做Scala特质使用
  4. 动态混入:可灵活的扩展类的功能;创建对象时混入trait,而无需使类混入该trait;如果混入的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.4.2 特质叠加

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

  • 异类冲突
  1. 一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait之间没有任何关系;
  2. 解决这类冲突问题,直接在类(Sub)中重写冲突方法。

大数据组件笔记 -- Scala_第9张图片

  • 祖类冲突
  1. 一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait继承自相同的trait(TraitC),及所谓的“钻石问题”;
  2. 解决这类冲突问题,Scala采用了特质叠加的策略。

大数据组件笔记 -- Scala_第10张图片

  • 特质叠加

所谓的特质叠加,就是将混入的多个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())
   }
}
// my ball is a blue-foot-ball
  • 特质叠加执行顺序

大数据组件笔记 -- Scala_第11张图片

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

6.4.3 自身类型(依赖注入)

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

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.4.4 特质和抽象类的区别

  1. 抽象类中可以定义抽象属性、抽象方法、非抽象属性、非抽象方法
  2. 特质中可以定义抽象属性、抽象方法、非抽象属性、非抽象方法
  3. 抽象类和特质都不能被实例化
  4. 抽象类和特质都有无参构造方法,但特质不支持带参构造方法
  5. 抽象类针对的是 ‘is a’,而特质针对的是 ‘has a’。
  6. 优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类。

6.5 扩展

6.5.1 类型检查和转换

  1. obj.isInstanceOf[T]:判断obj是不是T类型。
  2. obj.asInstanceOf[T]:将obj强转成T类型。
  3. classOf 获取对象的类名。
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.5.2 枚举类和应用类

  1. 枚举类:需要继承Enumeration
  2. 应用类:需要继承App
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")
}

// 应用类:相当于 main 方法,点击直接可以运行,调试用
object Test20 extends App {
     
    println("xxxxxxxxxxx");
}

6.5.3 Type定义新类型

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

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

七、集合

  • 简介

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

  1. Scala的集合有三大类:序列Seq、集Set、映射Map,所有的集合都扩展自Iterable特质。
  2. 对于几乎所有的集合类,Scala都 同时提供了可变和不可变的版本,分别位于以下两个包:不可变集合:scala.collection.immutable;可变集合:scala.collection.mutable
  3. 不可变集合,就是指该集合对象不可修改,每次修改就会返回一个新对象,而不会对原对象进行修改。类似于java中的String对象
  4. 可变集合,就是这个集合可以直接对原对象进行修改,而不会返回新的对象。类似于java中StringBuilder对象。
  • 不可变集合继承图

大数据组件笔记 -- Scala_第12张图片

  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可以支持排序
  • 可变集合继承图

大数据组件笔记 -- Scala_第13张图片

7.1 数组

  • 不可变数组
  def main(args: Array[String]): Unit = {
     
    //创建数组
    val arr = new Array[Int](5) //new Object Method
    //val applyMethod = Array(1, 2, 3, 4, 5)

    //修改、访问数组
    println(arr(1)) // 0
    arr(1) = 10
    println(arr(1)) // 10
    arr.update(1,20)
    println(arr(1)) // 20

    //遍历数组
    for(ele <- arr)
      println(ele)
    //arr.foreach((ele:Int) => {println(ele)})
    arr.foreach(println)
    println(arr.mkString(",")) //0,20,0,0,0
  }

Array的size是不可以改变的,所以没有直接删除其元素的方法。

  def main(args: Array[String]): Unit = {
     
    val arr = new Array[Int](5)
    println(arr) //[I@23223dd8
    println(arr.mkString(",")) //0,0,0,0,0

    val newArr = 30 +: arr
    println(newArr) //[I@7a0ac6e3
    println(newArr.mkString(",")) //30,0,0,0,0,0

    val newArr2 = arr :+ 30
    println(newArr2.mkString(",")) //0,0,0,0,0,30
  }
  • 可变数组
  def main(args: Array[String]): Unit = {
     
    //var arr = new ArrayBuffer[Int]()
    var arr = ArrayBuffer(1,2,3)

    //操作这个数组
    var newArr = arr. += (30)
    println(arr) //ArrayBuffer(1, 2, 3, 30)
    println(newArr) //ArrayBuffer(1, 2, 3, 30)
    arr.insert(0,72)
    arr.append(13)
    arr.remove(3)
    println(arr) //ArrayBuffer(72, 1, 2, 30, 13)

    //返回新的数组
    var newArr2 = arr.+:(30)
    var newArr3 = arr.:+(9)
    println(arr)//ArrayBuffer(72, 1, 2, 30, 13)
    println(newArr2)//ArrayBuffer(30, 72, 1, 2, 30, 13)
    println(newArr3)//ArrayBuffer(72, 1, 2, 30, 13, 9)
  }
  • 可变与不可变的转换
  def main(args: Array[String]): Unit = {
     
    val arr1 = Array(1,2,3)
    var arr2 = ArrayBuffer(4,5,6)
    
    var arr3 = arr1.toBuffer
    val arr4 = arr2.toArray
  }
  • 多维数组
  def main(args: Array[String]): Unit = {
     
    val array: Array[Array[Int]] = Array.ofDim[Int](2, 3)
    
    array(1)(0) = 1
    
    for(i <- 0 until array.length) {
     
      for(j <- 0 until array(i).length)
        print(array(i)(j))
      println()
    }
  }

7.2 集合 List

  • 不可变集合 List
  def main(args: Array[String]): Unit = {
     
    val list = List[Int](1,2,3)
    val listNew1 = list.+:(10)
    val listNew2 = list.:+(20)
    val listNew3 = list.::(30)
    val listColn = 7 :: 6 :: 5 :: Nil //Nil = 空集合
    println(list) //List(1, 2, 3)
    println(listNew1)//List(10, 1, 2, 3)
    println(listNew2)//List(1, 2, 3, 20)
    println(listNew3)//List(30, 1, 2, 3)
    println(listColn)//List(7, 6, 5)

    val list00 = List[Int](1,2,3)
    val list11 = List[Int](5,6)
    val listNN = list00 ::: list11
    println(listNN)//List(1, 2, 3, 5, 6)

    list.foreach(println)//1 2 3
  }
  • 可变集合 ListBuffer
  def main(args: Array[String]): Unit = {
     
    //var value = new ListBuffer[Int]()
    var listBuffer = ListBuffer[Int](1,2,3)

    listBuffer.append(10)
    println(listBuffer.mkString(",")) //1,2,3,10

    listBuffer.insert(1, 20)
    println(listBuffer.mkString(",")) //1,20,2,3,10

    listBuffer.remove(0)
    println(listBuffer.mkString(",")) //20,2,3,10
    
    listBuffer.update(2,30)
    println(listBuffer.mkString(",")) //20,2,30,10
  }

7.3 集合 Set

  def main(args: Array[String]): Unit = {
     
    val set = Set[Int](1, 2, 3, 3, 4, 5, 6, 6, 7, 9)
    val newSet = set.+(10)
    println(set.mkString(",")) //5,1,6,9,2,7,3,4
    println(newSet.mkString(",")) //5,10,1,6,9,2,7,3,4

    var set00 = mutable.Set[Int](1,2,3)
    set00.add(2)
    set00.add(10)
    set00.remove(1)
    println(set00.mkString(",")) //2,3,10
  }

7.4 集合 Map

  def main(args: Array[String]): Unit = {
     
    //---- 不可变集合
    val map3 = Map[Char,Int]('a'->1,'b'->2,'c'->3)
    val hashMap = Map[Char,Int]('a'->1,'b'->2,'c'->3,'d'->4,'e'->5)
    println(map3.getClass) //class scala.collection.immutable.Map$Map3
    println(hashMap.getClass) //class scala.collection.immutable.HashMap$HashTrieMap

    hashMap.foreach(println)

    for(key <- hashMap.keys)
      println(key)

    println(hashMap.getOrElse('e', 0))//5
    println(hashMap.getOrElse('f', 0))//0

    //---- 可变集合
    val charToInt = mutable.Map[Char, Int]('a' -> 1, 'b' -> 2, 'c' -> 3)

    charToInt.put('d', 4)
    charToInt.remove('b')
    charToInt.update('a', 10)

    println(charToInt.mkString(","))//d -> 4,a -> 10,c -> 3
  }

7.5 元组

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

object TestTuple {
     

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

        //(1)声明元组的方式:(元素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)
        val map1 = Map(("a",1), ("b",2), ("c",3))

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

7.6 衍生集合

  def main(args: Array[String]): Unit = {
     
    val list = List[Int](1,2,3,4,5)

    println(list.head)//1
    println(list.init)//List(1, 2, 3, 4)
    println(list.last)//5
    println(list.tail)//List(2, 3, 4, 5)
    println(list.take(3))//List(1, 2, 3)
    println(list.takeRight(3))//List(3, 4, 5)
    println(list.drop(2))//List(3, 4, 5)
    println(list.dropRight(2))//List(1, 2, 3)

    val list2 = List[Int](3,4,5,6,7)
    println(list.union(list2))//List(1, 2, 3, 4, 5, 3, 4, 5, 6, 7)
    println(list.intersect(list2))//List(3, 4, 5)
    println(list.diff(list2))//List(1, 2)
    println(list.zip(list2))//List((1,3), (2,4), (3,5), (4,6), (5,7))

    println(list)
    list.sliding(3).foreach(println)
    //List(1, 2, 3, 4, 5)
    //List(1, 2, 3)
    //List(2, 3, 4)
    //List(3, 4, 5)

    list.sliding(3,3).foreach(println)
    //List(1, 2, 3)
    //List(4, 5)
  }

7.7 集合计算

  • 初级函数
  def main(args: Array[String]): Unit = {
     
    val list = List[Int](5,1,3,2,4)

    println(list.sum)//15
    println(list.product)//120
    println(list.max)
    println(list.min)

    val list1 = List[Int](-3,1,2,3,-34,72)
    println(list1.sorted)//List(-34, -3, 1, 2, 3, 72)
    println(list1.sorted.reverse)//List(72, 3, 2, 1, -3, -34)
    println(list1.sortBy(ele => -ele))//List(72, 3, 2, 1, -3, -34)
    println(list1.sortBy(ele => ele.abs))//List(1, 2, -3, 3, -34, 72)
    //println(list1.sortBy(_.abs))
    println(list1.sortWith((a,b) => a<b))//List(-34, -3, 1, 2, 3, 72)
    println(list1.sortWith(_>_))//List(72, 3, 2, 1, -3, -34)
  }
  • 高级运算
  def main(args: Array[String]): Unit = {
     
    val list = List[Int](1,2,3,4,5,6,7,8,9,10)
    
    //1.过滤
    println(list.filter(_%2 == 0))//List(2, 4, 6, 8, 10)
    //2. 转化
    println(list.map(_*2))//List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)

    //3.扁平化
    val nestedList = List(List(1,2,3),List(532,2,5),List(6,82,45))
    println(nestedList.mkString(","))//List(1, 2, 3),List(532, 2, 5),List(6, 82, 45)
    println(nestedList.flatten.mkString(","))//1,2,3,532,2,5,6,82,45

    //4.扁平化+映射
    val longStrings = List("hello Duo", "Hallo Dao", "Hi Doo")
    val splitStrings: List[Array[String]] = longStrings.map(_.split(" "))
    println(splitStrings.flatten)//List(hello, Duo, Hallo, Dao, Hi, Doo)

    println(longStrings.flatMap(_.split(" ")))
    //List(hello, Duo, Hallo, Dao, Hi, Doo)

    //5. 分组
    val list2 = List[Int](1,2,3,4,5,6,7,8,9,10)
    println(list.groupBy(_ % 3))
    //Map(2 -> List(2, 5, 8), 1 -> List(1, 4, 7, 10), 0 -> List(3, 6, 9))

    //6. 简化/归约:通过指定的逻辑将集合中的数据进行聚合,从而减少数据量
    val list3 = List[Int](1,2,3)
    //reduce 参数类型必须一致
    println(list3.reduce(_ + _)) //6
    //reduceLeft 参数类型可以不一致,从左向右计算
    println(list3.reduceLeft(_ - _))//-4
    //reduceRight 参数类型可以不一致,从右向左计算
    //def reduceRight[B >: A](op: (A, B) => B): B =
    //    if (isEmpty) throw new UnsupportedOperationException("Nil.reduceRight")
    //    else if (tail.isEmpty) head
    //    else op(head, tail.reduceRight(op))
    println(list3.reduceRight(_ - _))//2
    // 1 - List(2,3)
    // 1 - (2 - List(3))
    // 1 - (2 - 3) = 2

    // 7. 折叠:集合外元素和集合内元素进行聚合
    var list4 = List(1,2,3,4,5)
    println(list4.fold(10)(_+_)) //25
    println(list4.foldLeft(10)(_-_))//-5
    println(list4.foldRight(10)(_-_))//-7
  }
  • Map 间操作
  def main(args: Array[String]): Unit = {
     
    var map1 = mutable.Map('a'->1, 'b'->2, 'c'->3)
    var map2 = mutable.Map('a'->4, 'b'->5, 'd'->7)

    //两个集合之间操作,使用折叠
    val mapFold = map1.foldLeft(map2) {
     
      (m2, m1kv) => {
     
        val m1k = m1kv._1
        val m1v = m1kv._2
        m2(m1k) = m2.getOrElse(m1k, m1v)
        m2
      }
    }
    println(mapFold)//Map(b -> 5, d -> 7, a -> 4, c -> 3)
  }

7.8 队列

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 并行集合

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

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)
    }
}
//Vector(main, main, main, main)
//ParVector(ForkJoinPool-1-worker-13, ForkJoinPool-1-worker-13, ForkJoinPool-1-worker-11, ForkJoinPool-1-worker-11)

7.10 WordCount 案例

  def main(args: Array[String]): Unit = {
     
    val stringList = List("Hello Scala Hbase kafka", "Hello Scala Hbase", "Hello Scala", "Hello")

    val strings = stringList.flatMap(_.split(" "))
    println(strings)
    //List(Hello, Scala, Hbase, kafka, Hello, Scala, Hbase, Hello, Scala, Hello)

    val stringsMap = strings.groupBy(ele=>ele)
    println(stringsMap)
    //Map(Hello -> List(Hello, Hello, Hello, Hello), Hbase -> List(Hbase, Hbase), kafka -> List(kafka), Scala -> List(Scala, Scala, Scala))

    val countMap = stringsMap.map(kv=>(kv._1,kv._2.size))
    println(countMap)
    //Map(Hello -> 4, Hbase -> 2, kafka -> 1, Scala -> 3)

    val countList = countMap.toList
    println(countList)
    //List((Hello,4), (Hbase,2), (kafka,1), (Scala,3))

    val sortedList = countList.sortBy(_._2).reverse
    println(sortedList)
    //List((Hello,4), (Scala,3), (Hbase,2), (kafka,1))

    println(sortedList.take(3))
    //List((Hello,4), (Scala,3), (Hbase,2))

    println(stringList.flatMap(_.split(" "))
                      .groupBy(ele=>ele)
                      .map(kv=>(kv._1,kv._2.size))
                      .toList
                      .sortBy(_._2)
                      .reverse
                      .take(3))
    //List((Hello,4), (Scala,3), (Hbase,2))
  }

八、模式匹配

  • 简单说明
  1. 如果所有case都不匹配,那么会执行case _ 分支,类似于Java中default语句,若此时没有case _ 分支,那么会抛出MatchError。
  2. 每个case中,不需要使用break语句,自动中断case。
  3. match case语句可以匹配任何类型,而不只是字面量。
  4. => 后面的代码块,直到下一个case语句之前的代码是作为一个整体执行,可以使用{}括起来,也可以不括。
  def main(args: Array[String]): Unit = {
     
    var a: Int = 10
    var b: Int = 20
    var op: Char = '+'

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

    println(res)//30
  }
  • 模式守卫

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

  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))//5
  }
  • 匹配常量

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

    def describe(x: Any) = x match {
     
        case 5 => "Int five"
        case "hello" => "String hello"
        case true => "Boolean true"
        case '+' => "Char +"
    }
  • 匹配类型

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

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")))
    }
}
  • 匹配数组

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

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)
        }
    }
}
  • 匹配列表
object TestMatchList {
     
    def main(args: Array[String]): Unit = {
     
        //list是一个存放List集合的数组
        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)
        }
    }
}
  • 匹配元组
	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)
        }
    }
def main(args: Array[String]): Unit = {
     
    //特殊的模式匹配1   打印元组第一个元素
    for (elem <- Array(("a", 1), ("b", 2), ("c", 3)))  println(elem._1)
    for ((word,count) <- Array(("a", 1), ("b", 2), ("c", 3)))  println(word)
    for ((word,_) <- Array(("a", 1), ("b", 2), ("c", 3)))  println(word)
    for (("a",count) <- Array(("a", 1), ("b", 2), ("c", 3))) println(count)
    println("--------------")

    //特殊的模式匹配2 给元组元素命名
    var (id,name,age): (Int, String, Int) = (100, "zs", 20)
    println((id,name,age))
    println("--------------")

    //特殊的模式匹配3   遍历集合中的元组,给count * 2
    var list: List[(String, Int)] = List(("a", 1), ("b", 2), ("c", 3))
    //println(list.map(t => (t._1, t._2 * 2)))
    println(list.map{
     case (word,count)=>(word,count*2)})
  }
  • 变量声明中的模式匹配
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")
    }
}
  • for 表达式中的模式匹配
  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
    }
  }
  • 偏函数中的模式匹配
  def main(args: Array[String]): Unit = {
     
    val list = List(1,2,3,4,5,6,"test")

    list.filter(_.isInstanceOf[Int]).map(_.asInstanceOf[Int] + 1).foreach(println)

    list.collect {
      case x: Int => x + 1 }.foreach(println)
  }

九、异常

9.1 Java异常处理

  1. Java语言按照try—catch—finally的方式来处理异常
  2. 不管有没有异常捕获,都会执行finally,因此通常可以在finally代码块中释放资源。
  3. 可以有多个catch,分别捕获对应的异常,这时需要把范围小的异常类写在前面,把范围大的异常类写在后面,否则编译错误。
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");
        }
    }
}

9.2 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("不对")
}
  1. java提供了throws关键字来声明异常。可以使用方法定义声明异常。它向调用者函数提供了此方法可能引发此异常的信息。它有助于调用函数处理并将该代码包含在try-catch块中,以避免程序异常终止。在Scala中,可以使用throws注解来声明异常
def main(args: Array[String]): Unit = {
     
  	f11()
}

@throws(classOf[NumberFormatException])
def f11()={
     
  	"abc".toInt
}

十、隐式转换

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

10.1 隐式函数

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

class MyRichInt(val self: Int) {
     
    def myMax(i: Int): Int = {
     
        if (self < i) i else self
    }

    def myMin(i: Int): Int = {
     
        if (self < i) self else i
    }
}

object TestImplicitFunction {
     
   	// 使用implicit关键字声明的函数称之为隐式函数
    implicit def convert(arg: Int): MyRichInt = {
     
        new MyRichInt(arg)
    }

	def main(args: Array[String]): Unit = {
     
    	// 当想调用对象功能时,如果编译错误,那么编译器会尝试在当前作用域范围内查找能调用对应功能的转换规则
    	//这个调用过程是由编译器完成的,所以称之为隐式转换。也称之为自动转换
        println(2.myMax(6))
    }
}

10.2 隐式参数

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

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

    implicit val str: String = "hello world!"

    def hello(implicit arg: String="good bey world!"): Unit = {
     
        println(arg)
    }

    def main(args: Array[String]): Unit = {
     
        hello//hello world!
    }
}

10.3 隐式类

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

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

    implicit class MyRichInt(arg: Int) {
     

        def myMax(i: Int): Int = {
     
            if (arg < i) i else arg
        }

        def myMin(i: Int) = {
     
            if (arg < i) arg else i
        }
    }

    def main(args: Array[String]): Unit = {
     
        println(1.myMax(3))
    }
}

10.4 隐式解析机制

  1. 首先会在当前代码作用域下查找隐式实体(隐式方法、隐式类、隐式对象)。(一般是这种情况)
  2. 如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。类型的作用域是指与该类型相关联的全部伴生对象以及该类型所在包的包对象。
//(2)如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。
//类型的作用域是指与该类型相关联的全部伴生模块。
object TestTransform extends PersonTrait {
     
    def main(args: Array[String]): Unit = {
     
        //(1)首先会在当前代码作用域下查找隐式实体
        val teacher = new Teacher()
        teacher.eat()
        teacher.say()
    }

    class Teacher {
     
        def eat(): Unit = {
     
            println("eat...")
        }
    }
}

trait PersonTrait {
      }
object PersonTrait {
     
    // 隐式类 : 类型1 => 类型2
    implicit class Person5(user:Teacher) {
     
        def say(): Unit = {
     
            println("say...")
        }
    }
}

十一、泛型

11.1 Java 泛型

public class Test {
     
    public static void main(String[] args) {
     
        //1. 泛型的不可变性
        //Person s1 = new Person();
        Person<Son> s2 = new Person<Son>();
        //Person s3 = new Person();

        //2.泛型的下限
        printSuperClass(Father.class);
        printSuperClass(Son.class);
        //printSuperClass(GrandSon.class);

        //3. 泛型的上限
        //printExtendsClass(Father.class);
        printExtendsClass(Son.class);
        printExtendsClass(GrandSon.class);
    }

    public static void printSuperClass(Class<? super Son> c) {
      System.out.println(c.getName()); }
    public static void printExtendsClass(Class<? extends Son> c) {
      System.out.println(c.getName()); }
}

class Person<T> {
      }
class Father {
     }
class Son extends Father {
     }
class GrandSon extends Son {
     }

11.2 Scala 泛型

object Test {
     

  def main(args: Array[String]): Unit = {
     
    //1.不变:Son是Father的子类,则MyList[Father]与MyList[Son]“无父子关系”。
    //var s1:MyList[Son] = new MyList[Father];
    var s2:MyList[Son] = new MyList[Son];
    //var s3:MyList[Son] = new MyList[GrandSon];

    //2. 协变:Son是Father的子类,则MyList[Son] 也作为MyList[Father]的“子类”。
    //var ss1:MyList2[Son] = new MyList2[Father];
    var ss2:MyList2[Son] = new MyList2[Son];
    var ss3:MyList2[Son] = new MyList2[GrandSon];

    //3. 逆变:Son是Father的子类,则MyList[Son]作为MyList[Father]的“父类”。
    var sss1:MyList3[Son] = new MyList3[Father];
    var sss2:MyList3[Son] = new MyList3[Son];
    //var sss3:MyList3[Son] = new MyList3[GrandSon];
  }

}

//泛型模板的不可变性
class MyList[T] {
     }
//协变
class MyList2[+T] {
     }
//逆变
class MyList3[-T] {
     }

class Father {
     }
class Son extends Father {
     }
class GrandSon extends Son {
     }
  • 泛型上下限
object Test {
     

  def main(args: Array[String]): Unit = {
     
    //1. 泛型通配符的下界
    test(classOf[Father])
    test(classOf[Son])
    //test(classOf[GrandSon])

    //2. 泛型通配符的上界
    //test2(classOf[Father])
    test2(classOf[Son])
    test2(classOf[GrandSon])

  }

  //下界
  def test[A >: Son](a:Class[A]): Unit = {
     
    println(a)
  }

  //上界
  def test2[A <: Son](a:Class[A]): Unit = {
     
    println(a)
  }

}

class Father {
     }
class Son extends Father {
     }
class GrandSon extends Son {
     }
  • 上下文限定(了解)

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

  1. 上下文限定是将泛型和隐式转换的结合产物,以下两者功能相同;
  2. 使用上下文限定[A : Ordering]之后,方法内无法使用隐式参数名调用隐式参数,需要通过implicitly[Ordering[A]]获取隐式变量;
  3. 如果此时无法查找到对应类型的隐式变量,会发生出错误。
implicit val x = 1
val y = implicitly[Int]
val z = implicitly[Double]
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)

你可能感兴趣的:(大数据组件学习笔记)