Scala基础

目录

一、概述

Scala是一门多范式的编程语言,一种类似java的编程语言 ,设计初衷是实现可伸缩的语言 、并集成面向对象编程和函数式编程的各种特性。Spark就是使用Scala编写的。因此为了更好的学习大数据开发, 需要掌握Scala这门语言,当然Spark的兴起,也带动Scala语言的发展!官方文档:https://www.scala-lang.org/

二、Scala发展历史

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

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

  • JDK5.0 的泛型、增 强for循 环、自动类型转换等,都是从Pizza引入的新特性。
  • JDK8.0 的类型推断、Lambda表达式就是从Scala引入的特性。

三、 Scala 和 Java 关系

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

特点:

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

四、Scala 环境搭建(window)

1)下载安装JDK

1、下载JDK

http://www.oracle.com/technetwork/java/javase/downloads/index.html

按正常下载是需要先登录的,这里提供一个不用登录下载的方法

连接如下:https://www.oracle.com/webapps/redirect/signon?nexturl=https://download.oracle.com/otn/java/jdk/8u321-b07/df5ad55fdd604472a86a45a217032c7d/jdk-8u321-windows-x64.exe

其实只要后半部分,再把标红的otn换成otn-pub就可以直接下载了

https://download.oracle.com/otn-pub/java/jdk/8u321-b07/df5ad55fdd604472a86a45a217032c7d/jdk-8u321-windows-x64.exe

下载完后就是傻瓜式安装了

2、设置环境变量

3、验证

$ java -version

2)下载安装Scala

1、下载Scala

Scala 官网地址:http://www.scala-lang.org/downloads
2.12.15版本:https://www.scala-lang.org/download/2.12.15.html


2、配置环境变量

3、验证

$ scala -version
$ scala

4、Scala 插件安装

默认情况下 IDEA 不支持 Scala 的开发,需要安装 Scala 插件。

插件怎么安装可以参考我之前的文章:大数据Hadoop之——搭建本地flink开发环境详解(window10)

五、class、object、case class、case object、trait区别

  • class 类似Java中的class;
  • object Scala不能定义静态成员,用定义单例对象代之;
  • case class被称为样例类,是一种特殊的类,常被用于模式匹配。
  • 在 Scala 中trait(特征) 相当于 Java 的接口,与接口不同的是它还可以定义属性和方法的实现,这一点又更像 Java 的抽象类。
  • 一般情况下 Scala 的类只能够继承单一父类,但是如果是 trait(特征) 的话就可以继承多个,从结果来看就是实现了多重继承。

1)class 和 object 关系

  • 单例对象不能带参数,类可以
  • 对象可以和类名一样时,object被称为伴生对象,class被称为伴生类;
  • 类和伴生对象可以相互访问其私有属性,但是它们必须在一个源文件当中;
  • 类只会被编译,不会被执行。要执行,必须在Object中。

2)case class 与 class 区别

  • case class初始化的时候可以不用new,也可以加上;但是普通类必须加new;
  • case class默认实现了equals、hashCode方法;
  • case class默认是可以序列化的,实现了Serializable;
  • case class自动从scala.Product中继承一些函数;
  • case class 构造函数参数是public的,我们可以直接访问;
  • case class默认情况下不能修改属性值;
  • case class最重要的功能,支持模式匹配,这也是定义case class的重要原因。

3)case class 和 case object 区别

  • 类中有参和无参,当类有参数的时候,用case class ,当类没有参数的时候那么用case object。

4)总结

  • object 用于定义单例对象
  • class 用于定义类
  • trait 用于定义接口
  • 至于还有两个 case class / case object 类型,主要用于支持模式匹配。

六、变量和数据类型

1)注释

Scala 注释使用和 Java 完全一样。注释是一个程序员必须要具有的良好编程习惯。将自己的思想通过注释先整理出来,再
用代码去体现。

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

2)变量和常量(重点)

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

1、常量语法(var定义变量)

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

2、常量语法(val定义常量)

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

3、总结

  • 声明变量时,类型可以省略,编译器自动推导,即类型推导
  • 类型确定后,就不能修改,说明 Scala 是强数据类型语言
  • 变量声明时,必须要有初始值
  • 在声明/定义一个变量时,可以使用 var 或者 val 来修饰,var 修饰的变量可改变,val 修饰的变量不可改。

4) 标识符的命名规范

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

5)字符串输出

1、基本语法

  • 字符串,通过+号连接
  • printf 用法:字符串,通过%传值。
  • 字符串模板(插值字符串):通过$获取变量值

2、键盘输入

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

基本语法

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

6)数据类型(重点)

  • Java基本类型:char、byte、short、int、long、float、double、boolean
  • Java引用类型:(对象类型)
  • Java基本类型的包装类:Character、Byte、Short、Integer、Long、Float、Double、Boolean

总结

  • Scala中一切数据都是对象,都是Any的子类
  • Scala中数据类型分为两大类:数值类型(AnyVal)、
    引用类型(AnyRef),不管是值类型还是引用类型都是
    对象
  • Scala数据类型仍然遵守,低精度的值类型向高精
    度值类型,自动转换
    (隐式转换)
  • Scala中的StringOps是对Java中的String增强
  • Unit:对应Java中的void,用于方法返回值的位置,表
    示方法没有返回值。Unit是 一个数据类型,只有一个对象
    就是()。Void不是数据类型,只是一个关键字
  • Null是一个类型,只 有一个对 象就 是null。它是
    所有引用类型(AnyRef)的子类。
  • Nothing,是所有数据类型的子类,主要用在一个函数没有明确返回值时使用,因为这样我们可以把抛出的返回值,返回给任何的变量或者函数。

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

【示例】

  • Scala 各整数类型有固定的表示范围和字段长度,不受具体操作的影响,以保证Scala 程序的可移植性。
object TestDataType {
 def main(args: Array[String]): Unit = {
 // 正确
 var n1:Byte = 127
 var n2:Byte = -128
 // 错误
 // var n3:Byte = 128
 // var n4:Byte = -129
 }
}
  • Scala 的整型,默认为 Int 型,声明 Long 型,须后加‘l’或‘L’
object TestDataType {
 def main(args: Array[String]): Unit = {
 var n5 = 10
 println(n5)
 var n6 = 9223372036854775807L
 println(n6)
 }
}
  • Scala 程序中变量常声明为 Int 型,除非不足以表示大数,才使用 Long

8) 浮点类型(Float、Double)

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

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

【示例】

  • Scala 的浮点型常量默认为 Double 型,声明 Float 型常量,须后加‘f’或‘F’。
object TestDataType {
 def main(args: Array[String]): Unit = {
 // 建议,在开发中需要高精度小数时,请选择 Double
 var n7 = 2.2345678912f
 var n8 = 2.2345678912
 println("n7=" + n7)
 println("n8=" + n8)
 }
}

运行的结果

9)字符类型(Char)

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

  • 字符常量是用单引号 ' ' 括起来的单个字符。
  • \t :一个制表位,实现对齐的功能
  • \n :换行符 尚硅谷大数据技术之 Scala
  • \\ :表示\
  • \" :表示"

【示例】

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("\"Scala hello world!!!\"")
 }
}

10)布尔类型(Boolean)

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

【示例】

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

11)Unit 类型、Null 类型和 Nothing 类型(重点)

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

【示例】

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

object TestSpecialType {
  def main(args: Array[String]): Unit = {
    def sayOk : Unit = {// unit 表示没有返回值,即 void

    }
    println(sayOk)
  }
}

  • 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 {
}
  • Nothing,可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方法不会正常返回,而且由于 Nothing 是其他任意类型的子类,他还能跟要求返回值的方法兼容。
object TestSpecialType {
 def main(args: Array[String]): Unit = {
 def test() : Nothing={
 throw new Exception()
 }
 test
 }
}

12)类型转换

1、数值类型自动转换

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

总结

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

【示例】

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 //错误
  }
}

2、强制类型转换

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

  • 将数据由高精度转换为低精度,就需要使用到强制转换
  • 强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级

【示例】

package test

object TestForceTransfer {
  def main(args: Array[String]): Unit = {
    //(1)将数据由高精度转换为低精度,就需要使用到强制转换
    var n1: Int = 2.5.toInt // 这个存在精度损失

    //(2)强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级
    var r1: Int = 10 * 3.5.toInt + 6 * 1.5.toInt // 10 *3 + 6*1= 36
    var r2: Int = (10 * 3.5 + 6 * 1.5).toInt // 44.0.toInt = 44
    println("r1=" + r1 + " r2=" + r2)
  }
}

3、数值类型和 String 类型间转换

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

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

【示例】

object TestStringTransfer {
 def main(args: Array[String]): Unit = {
 //(1)基本类型转 String 类型(语法:将基本类型的值+"" 即可)
 var str1 : String = true + ""
 var str2 : String = 4.5 + ""
 var str3 : String = 100 +""
 //(2)String 类型转基本数值类型(语法:调用相关 API)
 var s1 : String = "12"
 var n1 : Byte = s1.toByte
 var n2 : Short = s1.toShort
 var n3 : Int = s1.toInt
 var n4 : Long = s1.toLong
 }
}

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

七、运算符

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

1)算术运算符

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

【示例】

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

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

【示例】

object TestRelation {
 def main(args: Array[String]): Unit = {
 // 测试:>、>=、<=、<、==、!=
 var a: Int = 2
 var b: Int = 1
 println(a > b) // true
 println(a >= b) // true
 println(a <= b) // false
 println(a < b) // false
 println("a==b" + (a == b)) // false
 println(a != b) // true
 }
}

  • Java 和 Scala 中关于==的区别

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

Scala:==更加类似于 Java 中的 equals,参照 jd 工具

def main(args: Array[String]): Unit = {
 val s1 = "abc"
 val s2 = new String("abc")
 println(s1 == s2)
println(s1.eq(s2))
}

输出结果:

true
false

3)逻辑运算符

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

【示例】

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

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 // 没有--
  }
}

5)位运算符

下表中变量 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

【示例】

object TestPosition {
  def main(args: Array[String]): Unit = {
    // 测试:1000 << 1 =>10000
    var n1 :Int =8
    n1 = n1 << 1
    println(n1)
  }
}

6) Scala 运算符本质

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

  • 当调用对象的方法时,点.可以省略
  • 如果函数参数只有一个,或者没有参数,()可以省略
object TestOpt {
  def main(args: Array[String]): Unit = {
    // 标准的加法运算
    val i:Int = 1.+(1)
    // (1)当调用对象的方法时,.可以省略
    val j:Int = 1 + (1)
    // (2)如果函数参数只有一个,或者没有参数,()可以省略
    val k:Int = 1 + 1

    println(1.toString())
    println(1 toString())
    println(1 toString)
  }
}

八、流程控制

1)分支控制 if-else

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

1、单分支

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

2、双分支

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

3、多分支

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

【示例】

import scala.io.StdIn

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 中 if else 表达式其实是有返回值的,具体返回值取决于满足条件的代码体的最后一行内容
import scala.io.StdIn

object TestIfElseReturn {
  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 else 实现
import scala.io.StdIn

object TestIfElse003 {
  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)
  }

}

2、嵌套分支

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

语法:

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

【示例】

import scala.io.StdIn

object TestIfElse004 {
  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)
  }

}

3) Switch 分支结构

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

4)For 循环控制

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

1、 范围数据循环(To)

for(i <- 1 to 3){
 print(i + " ")
}
println()
  • i 表示循环的变量,<- 规定 to
  • i 将会从 1-3 循环,前后闭合

2、范围数据循环(Until)

for(i <- 1 until 3) {
 print(i + " ")
}
println()
  • 这种方式和前面的区别在于 i 是从 1 到 3-1
  • 即使前闭合后开的范围

3、循环守卫

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

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

上面的代码等价

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

4、循环步长

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

【温馨提示】by 表示步长

5、嵌套循环

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

【温馨提示】没有关键字,所以范围后一定要加;来隔断逻辑

下面的写法跟上面的代码等价

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

6、引入变量

for(i <- 1 to 3; j = 4 - i) {
 println("i=" + i + " j=" + j)
}
  • for 推导式一行中有多个表达式时,所以要加;来隔断逻辑
  • for 推导式有一个不成文的约定:当 for 推导式仅包含单一表达式时使用圆括号,
    当包含多个表达式时,一般每行一个表达式,并用花括号代替圆括号,如下:
for {
 	i <- 1 to 3
	j = 4 - i
} {
 	println("i=" + i + " j=" + j)
}

下面的写法跟上面的代码等价

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

7、循环返回值

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

【温馨提示】将遍历过程中处理的结果返回到一个新 Vector 集合中,使用 yield 关键字。注意:开发中很少使用。

8、倒序打印

需求:倒序打印 10 到 1

for(i <- 1 to 10 reverse){
 println(i)
}

5)While 和 do..While 循环控制

While 和 do..While 的使用和 Java 语言中用法相同

1、While 循环控制

基本语法

while (循环条件) {
循环体(语句)
循环变量迭代
}

特点

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

2、do..while 循环控制

基本语法

do{
循环体(语句)
循环变量迭代
} while(循环条件)

特点

  • 循环条件是返回一个布尔值的表达式
  • do..while 循环是先执行,再判断

6)循环中断

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

【示例】

  • 采用异常的方式退出循环
object BreakableTest001 {
  def main(args: Array[String]): Unit = {
    try {
      for (elem <- 1 to 10) {
        println(elem)
        if (elem == 5) throw new RuntimeException
      }
    }catch {
      case e =>
    }
    println("正常结束循环")
  }
}
  • 采用 Scala 自带的函数,退出循环
object BreakableTest002 {

  import scala.util.control.Breaks
  
  def main(args: Array[String]): Unit = {
    Breaks.breakable(
      for (elem <- 1 to 10) {
        println(elem)
        if (elem == 5) Breaks.break()
      }
    )
    println("正常结束循环")
  }
}
  • 循环遍历 10 以内的所有数据,奇数打印,偶数跳过(continue)
object BreakableTest003 {
  def main(args: Array[String]): Unit = {
    for (elem <- 1 to 10) {
      if (elem % 2 == 1) {
        println(elem)
      } else {
        println("continue")
      }
    }
  }
}

7)多重循环

  • 将一个循环放在另一个循环体内,就形成了嵌套循环。其中,for,while,do…while均可以作为外层循环和内层循环。【建议一般使用两层,最多不要超过 3 层】

  • 设外层循环次数为 m 次,内层为 n 次,则内层循环体实际上需要执行 m*n 次。

【示例】

  • 打印出九九乘法表
object TestWhile001 {
  def main(args: Array[String]): Unit = {
    for (i <- 1 to 9) {
      for (j <- 1 to i) {
        print(j + "*" + i + "=" + (i * j) + "\t")
      }
      println()
    }
  }
}

九、函数式编程

1)概述

1、面向对象编程

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

【示例分析】

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

特点

  • Scala 语言是一个完全面向对象编程语言。万物皆对象
  • 对象的本质:对数据和行为的一个封装

2、函数式编程

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

【示例分析】

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

特点

  • Scala 语言是一个完全函数式编程语言。万物皆函数。
  • 函数的本质:函数可以当做一个值进行传递

2)函数基础

1、函数基本语法

【示例】

  • 定义一个函数,实现将传入的名称打印出来。
object TestFunctionTest001 {
  def main(args: Array[String]): Unit = {
    // (1)函数定义
    def f(arg: String): Unit = {
      println(arg)
    }
    // (2)函数调用
    // 函数名(参数)
    f("hello world")
    
  }

}

2、函数和方法的区别

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

3)函数定义

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

【示例】

object TestFunctionDeclare {
  def main(args: Array[String]): Unit = {
    // 函数 1:无参,无返回值
    def test1(): Unit ={
      println("无参,无返回值")
    }
    test1()
    // 函数 2:无参,有返回值
    def test2():String={
      return "无参,有返回值"
    }
    println(test2())
    // 函数 3:有参,无返回值
    def test3(s:String):Unit={
      println(s)
    }
    test3("jinlian")
    // 函数 4:有参,有返回值
    def test4(s:String):String={
      return s+"有参,有返回值"
    }
    println(test4("hello "))
    // 函数 5:多参,无返回值
    def test5(name:String, age:Int):Unit={
      println(s"$name, $age")
    }
    test5("dalang",40)
  }

}

4)函数参数

  • 可变参数
  • 如果参数列表中存在多个参数,那么可变参数一般放置在最后
  • 参数默认值,一般将有默认值的参数放置在参数列表的后面
  • 带名参数
object TestFunction {
  def main(args: Array[String]): Unit = {
    // (1)可变参数
    def test( s : String* ): Unit = {
      println(s)
    }
    // 有输入参数:输出 Array
    test("Hello", "Scala")
    // 无输入参数:输出 List()
    test()
    // (2)如果参数列表中存在多个参数,那么可变参数一般放置在最后
    def test2( name : String, s: String* ): Unit = {
      println(name + "," + s)
    }
    test2("jinlian", "dalang")
    // (3)参数默认值
    def test3( name : String, age : Int = 30 ): Unit = {
      println(s"$name, $age")
    }
    // 如果参数传递了值,那么会覆盖默认值
    test3("jinlian", 20)
    // 如果参数有默认值,在调用的时候,可以省略这个参数
    test3("dalang")
    // 一般情况下,将有默认值的参数放置在参数列表的后面
    def test4( sex : String = "男", name : String ): Unit = {
      println(s"$name, $sex")
    }
    // Scala 函数中参数传递是,从左到右
    //test4("wusong")
    //(4)带名参数
    test4(name="ximenqing")
    
  }

5)函数至简原则(重点)

【总结】函数至简原则:能省则省

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

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

    test2("jinlian", "dalang")

    // (3)参数默认值
    def test3(name: String, age: Int = 30): Unit = {
      println(s"$name, $age")
    }
    // 如果参数传递了值,那么会覆盖默认值
    test3("jinlian", 20)
    // 如果参数有默认值,在调用的时候,可以省略这个参数
    test3("dalang")

    // 一般情况下,将有默认值的参数放置在参数列表的后面
    def test4(sex: String = "男", name: String): Unit = {
      println(s"$name, $sex")
    }
    // Scala 函数中参数传递是,从左到右
    //test4("wusong")
    //(4)带名参数
    test4(name = "ximenqing")

  }
}

3)函数高级

1、高阶函数

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

object TestFunction {
 def main(args: Array[String]): Unit = {
 // 调用函数
foo()
 }
 // 定义函数
 def foo():Unit = {
 println("foo...")
 }
}

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

  • 函数可以作为值进行传递
object TestFunction002 {
  def main(args: Array[String]): Unit = {
    //(1)调用 foo 函数,把返回值给变量 f
    //val f = foo()
    val f = foo
    println(f)
    //(2)在被调用函数 foo 后面加上 _,相当于把函数 foo 当成一个整体,传递给变量 f1
    val f1 = foo _
    foo()
    f1()
    //(3)如果明确变量类型,那么不使用下划线也可以将函数作为整体传递给变量
    var f2:()=>Int = foo
  }
  def foo():Int = {
    println("foo...")
    1
  }
}
  • 函数可以作为参数进行传递
def main(args: Array[String]): Unit = {

    // (1)定义一个函数,函数参数还是一个函数签名;f 表示函数名称;(Int,Int)表示输入两个 Int 参数;Int 表示函数返回值
    def f1(f: (Int, Int) => Int): Int = {
      f(2, 4)
    }

    // (2)定义一个函数,参数和返回值类型和 f1 的输入参数一致
    def add(a: Int, b: Int): Int = a + b

    // (3)将 add 函数作为参数传递给 f1 函数,如果能够推断出来不是调用,_可以省略
    println(f1(add))
    println(f1(add _))
    //可以传递匿名函数
  }
  • 函数可以作为函数返回值返回
def main(args: Array[String]): Unit = {
    def f1() = {
      def f2() = {
      }
      f2 _
    }

    val f = f1()
    // 因为 f1 函数的返回值依然为函数,所以可以变量 f 可以作为函数继续调用
    f()
    // 上面的代码可以简化为
    f1()()
  }

2、匿名函数

  • 没有名字的函数就是匿名函数。
  • (x:Int)=>
  • x:表示输入参数类型;Int:表示输入参数类型;函数体:表示具体代码逻辑

传递匿名函数原则

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

【示例】

  • 传递的函数有一个参数
def main(args: Array[String]): Unit = {
    // (1)定义一个函数:参数包含数据和逻辑函数
    def operation(arr: Array[Int], op: Int => 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(","))
  }
  • 传递的函数有两个参数
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, _ + _))
  }

4)函数柯里化&闭包

  • 闭包:函数式编程的标配,如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境,称为闭包
  • 函数柯里化:把一个参数列表的多个参数,变成多个参数列表。

【示例】

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) = {
      a + b
    }

    println(f3()(3))
  }

5)递归

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

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

6)控制抽象

  • 值调用:把计算后的值传递过去
def main(args: Array[String]): Unit = {
    def f = ()=>{
      println("f...")
      10
    }
    foo(f())
  }
  def foo(a: Int):Unit = {
    println(a)
    println(a)
  }
  • 名调用:把代码传递过去
def main(args: Array[String]): Unit = {
 def f = ()=>{
 println("f...")
 10
 }
 foo(f())
 }
//def foo(a: Int):Unit = {
 def foo(a: =>Int):Unit = {//注意这里变量 a 没有小括号了
 println(a)
 println(a)
 }

【温馨提示】Java 只有值调用;Scala 既有值调用,又有名调用。

7)惰性加载

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

object LazyTest001 {
  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
  }
}

【温馨提示】lazy 不能修饰 var 类型的变量

十、面向对象

  • Scala 的面向对象思想和 Java 的面向对象思想和概念是一致的。
  • Scala 中语法和 Java 不同,补充了更多的功能。

1)Scala 包

基本语法

package 包名

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

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

1、包的命名

命名规则

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

【示例】

demo.class.exec1 //错误,因为 class 关键字
demo.12a //错误,数字开头

命名规范

一般是小写字母+小圆点

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

【示例】

com.bigdata.oa.model 
com.bigdata.oa.controller
com.bigdata.bank.order 

2、 包说明(包语句)

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

package com{
	package atguigu{
		package scala{
		}
	}
}

第二种风格有以下特点:

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

【示例】

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

3、包对象

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

定义

package object com{
	val shareValue="share"
	def shareMethod()={}
}

说明

若使用 Java 的包管理风格,则包对象一般定义在其对应包下的 bigdata.test文件中,包对象名与包名保持一致。

  • 如采用嵌套方式管理包,则包对象可与包定义在同一文件中,但是要保证包对象与包声明在同一作用域中
package test {
  object Outer {
    val out: String = "out"
    def main(args: Array[String]): Unit = {
      println(name)
    }
  }
}

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

4、导包说明

  • 和 Java 一样,可以在顶部使用 import 导入,在这个文件中的所有类都可以使用。
  • 局部导入:什么时候使用,什么时候导入。在其作用范围内都可以使用
  • 通配符导入:import java.util._
  • 给类起名:import java.util.
  • 导入相同包的多个类:import java.util.
  • 屏蔽类:import java.util.{ArrayList =>,}
  • 导入包的绝对路径:new root.java.util.HashMap
package java {
 package util {
 class HashMap {
 }
 }
}
import com.bigdata.Fruit 引入 com.atguigu 包下 Fruit(class 和 object)
import com.bigdata._ 引入 com.bigdata 下的所有成员
import com.bigdata.Fruit._ 引入 Fruit(object)的所有成员
import com.bigdata. 引入 com.bigdata 下的 Fruit 和 Vegetable
import com.bigdata. 引入 com.bigdata 包下的 Fruit 并更名为 Shuiguo
import com.bigdata. 引入 com.bigdata 包下的所有成员,并将 Fruit 更名为 Shuiguo
import com.atguigu.{Fruit=>,} 引入 com.atguigu 包下屏蔽 Fruit 类
new root.java.util.HashMap 引入的 Java 的绝对路径

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

  • import java.lang._
  • import scala._
  • import scala.Predef._

2)类和对象

  • 类:可以看成一个模板
  • 对象:表示具体的事物

1、定义类

【温馨提示】Scala 中没有 public,一个.scala 中可以写多个类。

基本语法

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

【示例】

package com.bigdata.test

class Test001 {
  //(1)Scala 语法中,类并不声明为 public,所有这些类都具有公有可见性(即默认就是 public)
  class Person {
  }
  //(2)一个 Scala 源文件可以包含多个类
  class Teacher{
  }
}

2、属性

属性是类的一个组成部分

基本语法

[修饰符] var|val 属性名称 [:类型] = 属性值

【温馨提示】Bean 属性(@BeanPropetry),可以自动生成规范的 setXxx/getXxx 方法

【示例】

package com.bigdata.test

import scala.beans.BeanProperty
class Person {
  var name: String = "bobo" //定义属性
  var age: Int = _ // _表示给属性一个默认值
  //Bean 属性(@BeanProperty)
  @BeanProperty var sex: String = "男"
  //val 修饰的属性不能赋默认值,必须显示指定
}
object Person {
  def main(args: Array[String]): Unit = {
    var person = new Person()
    println(person.name)
    person.setSex("女")
    println(person.getSex)
  }
}

3、封装

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

  • 将属性进行私有化
  • 提供一个公共的 set 方法,用于对属性赋值
  • 提供一个公共的 get 方法,用于获取属性的值

4、访问权限

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

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

【示例】

package com.bigdata.test

class Person {
  private var name: String = "bobo"
  protected var age: Int = 18
  private[test] var sex: String = "男"
  def say(): Unit = {
    println(name)
  }
}
object Person {
  def main(args: Array[String]): Unit = {
    val person = new Person
    person.say()
    println(person.name)
    println(person.age)
  }
}
class Teacher extends Person {
  def test(): Unit = {
    this.age
    this.sex
  }
}
class Animal {
  def test: Unit = {
    new Person().sex
  }
}

3) 方法

基本语法

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

【示例】

class Person {
 def sum(n1:Int, n2:Int) : Int = {
 n1 + n2
 }
}
object Person {
 def main(args: Array[String]): Unit = {
 val person = new Person()
 println(person.sum(10, 20))
 }
}

4)创建对象

基本语法

val | var 对象名 [:类型] = new 类型()
  • val 修饰对象,不能改变对象的引用(即:内存地址),可以改变对象属性的值。
  • var 修饰对象,可以修改对象的引用和修改对象的属性值
  • 自动推导变量类型不能多态,所以多态需要显示声明

【示例】

package com.bigdata.test

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

5) 构造器

  • 和 Java 一样,Scala 构造对象也需要调用构造方法,并且可以有任意多个构造方法。
  • Scala 类的构造器包括:主构造器和辅助构造器

基本语法

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

说明:

  • 辅助构造器,函数的名称 this,可以有多个,编译器通过参数的个数及类型
    来区分。
  • 辅助构造方法不能直接构建对象,必须直接或者间接调用主构造方法。
  • 构造器调用其他另外的构造器,要求被调用构造器必须提前声明。
  • 如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略。

【示例】

package com.bigdata.test

//(1)如果主构造器无参数,小括号可省略
//class Person002 (){
class Person002 {
  var name: String = _
  var age: Int = _
  def this(age: Int) {
    this()
    this.age = age
    println("辅助构造器")
  }
  def this(age: Int, name: String) {
    this(age)
    this.name = name
  }
  println("主构造器")
}
object Person002 {
  def main(args: Array[String]): Unit = {
    val person2 = new Person002(18)
  }
}

6)构造器参数

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

  • 未用任何修饰符修饰,这个参数就是一个局部变量
  • var 修饰参数,作为类的成员属性使用,可以修改
  • val 修饰参数,作为类只读属性使用,不能修改

【示例】

package com.bigdata.test

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

}
object Test {
  def main(args: Array[String]): Unit = {
    var person = new Person003("bobo", 18, "男")
    // (1)未用任何修饰符修饰,这个参数就是一个局部变量
    // printf(person.name)
    // (2)var 修饰参数,作为类的成员属性使用,可以修改
    person.age = 19
    println(person.age)
    // (3)val 修饰参数,作为类的只读属性使用,不能修改
    // person.sex = "女"
    println(person.sex)
  }
}

7)继承和多态

基本语法

class 子类名 extends 父类名 { 类体 }
  • 子类继承父类的属性和方法
  • scala 是单继承

【示例】

package com.bigdata.test

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

class Emp(nameParam: String, ageParam: Int) extends Person004(nameParam, ageParam) {
  var empNo: Int = _
  def this(nameParam: String, ageParam: Int, empNoParam: Int) {
    this(nameParam, ageParam)
    this.empNo = empNoParam
    println("子类的辅助构造器")
  }
  println("子类主构造器")
}

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

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

Scala

package com.bigdata.test

class Person005 {
  val name: String = "person"
  def hello(): Unit = {
    println("hello person")
  }
}
class Teacher005 extends Person005 {
  override val name: String = "teacher"
  override def hello(): Unit = {
    println("hello teacher")
  }
}
object Test005 {
  def main(args: Array[String]): Unit = {
    val teacher: Teacher005 = new Teacher005()
    println(teacher.name)
    teacher.hello()
    val teacher1:Person005 = new Teacher005
    println(teacher1.name)
    teacher1.hello()
  }
}

Java

package bigdata.test;
// Person005.java

class Person005 {
    public String name = "person";
    public void hello() {
        System.out.println("hello person");
    }
}
class Teacher005 extends Person005 {
    public String name = "teacher";
    @Override
    public void hello() {
        System.out.println("hello teacher");
    }
}
package bigdata.test;
// TestDynamic.java

public class TestDynamic {
    public static void main(String[] args) {
        Teacher005 teacher = new Teacher005();
        Person005 teacher1 = new Teacher005();
        System.out.println(teacher.name);
        teacher.hello();
        System.out.println(teacher1.name);
        teacher1.hello();
    }
}

8)抽象类

1、 抽象属性和抽象方法

  • 定义抽象类:abstract class Person{} //通过 abstract 关键字标记抽象类
  • 定义抽象属性:val|var name:String //一个属性没有初始化,就是抽象属性
  • 定义抽象方法:def hello():String //只声明而没有实现的方法,就是抽象方法

【示例】

package com.bigdata.test

abstract class Person006 {
  val name: String
  def hello(): Unit
}
class Teacher006 extends Person006 {
  val name: String = "teacher"
  def hello(): Unit = {
    println("hello teacher")
  }
}

// 测试
object Test006 {
  def main(args: Array[String]): Unit = {
    var t = new Teacher006
    t.hello()
  }
}

2、继承&重写

  • 如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类
  • 重写非抽象方法需要用 override 修饰,重写抽象方法则可以不加 override。
  • 子类中调用父类的方法使用 super 关键字
  • 子类对抽象属性进行实现,父类抽象属性可以用 var 修饰;
    1. 子类对非抽象属性重写,父类非抽象属性只支持 val 类型,而不支持 var。
    2. 因为 var 修饰的为可变变量,子类继承之后就可以直接使用,没有必要重写

9)匿名子类

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

package com.bigdata.test

abstract class Person007 {
  val name: String
  def hello(): Unit
}
object Test007 {
  def main(args: Array[String]): Unit = {
    val person = new Person007 {
      override val name: String = "teacher"

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

10)单例对象(伴生对象)

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

1、单例对象语法

基本语法

object Person{
	val country:String="China"
}

说明

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

【示例】

package com.bigdata.test

//(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)
  }
}

2、apply 方法

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

【示例】

package com.bigdata.test

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)
  }
  //注意:也可以创建其它类型对象,并不一定是伴生类对象
}

11)特质(Trait)

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

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

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

1、声明

trait 特质名 {
	trait 主体
}

【示例】

trait PersonTrait {
 // 声明属性
 var name:String = _
 // 声明方法
 def eat():Unit={
 }
 // 抽象属性
 var age:Int
 
 // 抽象方法
 def say():Unit
}

2、特质基本语法

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

基本语法

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

说明

  • 类和特质的关系:使用继承的关系。
  • 当一个类去继承特质时,第一个连接词是 extends,后面是 with。
  • 如果一个类在同时继承特质和父类时,应当把父类写在 extends 后。

【示例】

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

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

特质叠加

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

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

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

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

package com.bigdata.test

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

3、特质叠加执行顺序

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

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

4、特质和抽象类的区别

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

十一、集合

1)集合简介

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

【温馨提示】在操作集合的时候,不可变用符号,可变用方法。

1、不可变集合继承图

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

2、可变集合继承图

2) 数组

1、不可变数组

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

【示例】

package com.bigdata.test

object TestArray{
  def main(args: Array[String]): Unit = {
    //(1)数组定义
    val arr01 = new Array[Int](4)
    println(arr01.length) // 4
    //(2)数组赋值
    //(2.1)修改某个元素的值
    arr01(3) = 10
    //(2.2)采用方法的形式给数组赋值
    arr01.update(0,1)
    //(3)遍历数组
    //(3.1)查看数组
    println(arr01.mkString(","))
    //(3.2)普通遍历
    for (i <- arr01) {
      println(i)
    }
    //(3.3)简化遍历
    def printx(elem:Int): Unit = {
      println(elem)
    }
    arr01.foreach(printx)
    // arr01.foreach((x)=>{println(x)})
    // arr01.foreach(println(_))
    arr01.foreach(println)
    //(4)增加元素(由于创建的是不可变数组,增加元素,其实是产生新的数组)
    println(arr01)
    val ints: Array[Int] = arr01 :+ 5
    println(ints)
  }
}

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

【示例】

package com.bigdata.test

object TestArray002 {
  def main(args: Array[String]): Unit = {
    var arr02 = Array(1, 3, "bobo")
    println(arr02.length)
    for (i <- arr02) {
      println(i)
    }

  }
}

2、可变数组

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

【示例】

package com.bigdata.test

import scala.collection.mutable.ArrayBuffer

object TestArrayBuffer {
  def main(args: Array[String]): Unit = {
    //(1)创建并初始赋值可变数组
    val arr01 = ArrayBuffer[Any](1, 2, 3)
    //(2)遍历数组
    for (i <- arr01) {
      println(i)
    }
    println(arr01.length) // 3
    println("arr01.hash=" + arr01.hashCode())
    //(3)增加元素
    //(3.1)追加数据
    arr01.+=(4)
    //(3.2)向数组最后追加数据
    arr01.append(5,6)
    //(3.3)向指定的位置插入数据
    arr01.insert(0,7,8)
    println("arr01.hash=" + arr01.hashCode())
    //(4)修改元素
    arr01(1) = 9 //修改第 2 个元素的值
    println("--------------------------")
    for (i <- arr01) {
      println(i)
    }
    println(arr01.length) // 5
  }
}

2)不可变数组与可变数组的转换
  • arr2.toArray 返回结果才是一个不可变数组,arr2 本身没有变化
  • arr1.toBuffer 返回结果才是一个可变数组,arr1 本身没有变化

arr1.toBuffer //不可变数组转可变数组
arr2.toArray //可变数组转不可变数组

【示例】

package com.bigdata.test

import scala.collection.mutable.ArrayBuffer

object TestArrayBuffer002 {
  def main(args: Array[String]): Unit = {
    //(1)创建一个空的可变数组
    val arr2 = ArrayBuffer[Int]()
    //(2)追加值
    arr2.append(1, 2, 3)
    println(arr2) // 1,2,3
    //(3)ArrayBuffer ==> Array
    //(3.1)arr2.toArray 返回的结果是一个新的定长数组集合
    //(3.2)arr2 它没有变化
    val newArr = arr2.toArray
    println(newArr)

    //(4)Array ===> ArrayBuffer
    //(4.1)newArr.toBuffer 返回一个变长数组 newArr2
    //(4.2)newArr 没有任何变化,依然是定长数组
    val newArr2 = newArr.toBuffer
    newArr2.append(123)
    println(newArr2)
  }
}

3、多维数组

多维数组定义

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

【示例】

package com.bigdata.test

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

    //(1)创建了一个二维数组, 有三个元素,每个元素是,含有 4 个元素一维数组()
    val arr = Array.ofDim[Int](3, 4)
    arr(1)(2) = 88
    //(2)遍历二维数组
    for (i <- arr) { //i 就是一维数组
      for (j <- i) {
        print(j + " ")
      }
      println()
    }
  }
}

3)列表 List

1、不可变 List

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

【示例】

package com.bigdata.test

object TestList {
  def main(args: Array[String]): Unit = {
    //(1)List 默认为不可变集合
    //(2)创建一个 List(数据有顺序,可重复)
    val list: List[Int] = List(1,2,3,4,3)

    //(7)空集合 Nil
    val list5 = 1::2::3::4::Nil
    //(4)List 增加数据
    //(4.1)::的运算规则从右向左
    //val list1 = 5::list
    val list1 = 7::6::5::list
    //(4.2)添加到第一个元素位置
    val list2 = list.+:(5)
    //(5)集合间合并:将一个整体拆成一个一个的个体,称为扁平化
    val list3 = List(8,9)
    //val list4 = list3::list1
    val list4 = list3:::list1
    //(6)取指定数据
    println(list(0))
    //(3)遍历 List
    //list.foreach(println)
    //list1.foreach(println)
    //list3.foreach(println)
    //list4.foreach(println)
    list5.foreach(println)
  }
}

2、可变 ListBuffer

  • 创建一个可变集合 ListBuffer
  • 向集合中添加数据
  • 打印集合数据

【示例】

package com.bigdata.test

import scala.collection.mutable.ListBuffer

object TestList002 {
  def main(args: Array[String]): Unit = {
    //(1)创建一个可变集合
    val buffer = ListBuffer(1,2,3,4)
    //(2)向集合中添加数据
    buffer.+=(5)
    buffer.append(6)
    buffer.insert(1,2)
    //(3)打印集合数据
    buffer.foreach(println)
    //(4)修改数据
    buffer(1) = 6
    buffer.update(1,7)
    //(5)删除数据
    buffer.-(5)
    buffer.-=(5)
    buffer.remove(5)
  }

}

4)Set 集合

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

1、不可变 Set

  • Set 默认是不可变集合,数据无序
  • 数据不可重复
  • 遍历集合

【示例】

package com.bigdata.test

object TestSet {
  def main(args: Array[String]): Unit = {
    //(1)Set 默认是不可变集合,数据无序
    val set = Set(1,2,3,4,5,6)
    //(2)数据不可重复
    val set1 = Set(1,2,3,4,5,6,3)
    //(3)遍历集合
    for(x<-set1){
      println(x)
    }
  }
}

2、可变 mutable.Set

  • 创建可变集合 mutable.Set
  • 打印集合
  • 集合添加元素
  • 向集合中添加元素,返回一个新的 Set
  • 删除数据

【示例】

package com.bigdata.test

import scala.collection.mutable


object TestSet002 {
  def main(args: Array[String]): Unit = {
    //(1)创建可变集合
    val set = mutable.Set(1,2,3,4,5,6)
    //(3)集合添加元素
    set += 8
    //(4)向集合中添加元素,返回一个新的 Set
    val ints = set.+(9)
    println(ints)
    println("set2=" + set)
    //(5)删除数据
    set-=(5)
    //(2)打印集合
    set.foreach(println)
    println(set.mkString(","))
  }

}

5)Map 集合

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

1、不可变 Map

  • 创建不可变集合 Map
  • 循环打印
  • 访问数据
  • 如果 key 不存在,返回 0

【示例】

package com.bigdata.test

object TestMap {
  def main(args: Array[String]): Unit = {
    // Map
    //(1)创建不可变集合 Map
    val map = Map( "a"->1, "b"->2, "c"->3 )
    //(3)访问数据
    for (elem <- map.keys) {
      // 使用 get 访问 map 集合的数据,会返回特殊类型 Option(选项):有值(Some),无值(None)
      println(elem + "=" + map.get(elem).get)
    }
    //(4)如果 key 不存在,返回 0
    println(map.get("d").getOrElse(0))
    println(map.getOrElse("d", 0))
    //(2)循环打印
    map.foreach((kv)=>{println(kv)})
  }
}

2、可变 Map

  • 创建可变集合
  • 打印集合
  • 向集合增加数据
  • 删除数据
  • 修改数据

【示例】

package com.bigdata.test

import scala.collection.mutable

object TestMap002 {
  def main(args: Array[String]): Unit = {
    //(1)创建可变集合
    val map = mutable.Map( "a"->1, "b"->2, "c"->3 )
    //(3)向集合增加数据
    map.+=("d"->4)
    // 将数值 4 添加到集合,并把集合中原值 1 返回
    val maybeInt: Option[Int] = map.put("a", 4)
    println(maybeInt.getOrElse(0))
    //(4)删除数据
    map.-=("b", "c")
    //(5)修改数据
    map.update("d",5)
    map("d") = 5
    //(2)打印集合
    map.foreach((kv)=>{println(kv)})
  }
  
}

6)元组

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

【示例】

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

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)集合常用函数

1、基本属性和常用操作

  • 获取集合长度
  • 获取集合大小
  • 循环遍历
  • 迭代器
  • 生成字符串
  • 是否包含

【示例】

package com.bigdata.test

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

}

2、衍生集合

  • 获取集合的头 尚硅谷大数据技术之 Scala
  • 获取集合的尾(不是头的就是尾)
  • 集合最后一个数据
  • 集合初始数据(不包含最后一个)
  • 反转
  • 取前(后)n 个元素
  • 去掉前(后)n 个元素
  • 并集
  • 交集
  • 差集
  • 拉链
  • 滑窗

【示例】

package com.bigdata.test

object TestList0002 {
  def main(args: Array[String]): Unit = {
    val list1: List[Int] = List(1, 2, 3, 4, 5, 6, 7)
    val list2: List[Int] = List(4, 5, 6, 7, 8, 9, 10)
    //(1)获取集合的头
    println(list1.head)
    //(2)获取集合的尾(不是头的就是尾)
    println(list1.tail)
    //(3)集合最后一个数据
    println(list1.last)
    //(4)集合初始数据(不包含最后一个)
    println(list1.init)
    //(5)反转
    println(list1.reverse)
    //(6)取前(后)n 个元素
    println(list1.take(3))
    println(list1.takeRight(3))
    //(7)去掉前(后)n 个元素
    println(list1.drop(3))
    println(list1.dropRight(3))
    //(8)并集
    println(list1.union(list2))
    //(9)交集
    println(list1.intersect(list2))
    //(10)差集
    println(list1.diff(list2))
    //(11)拉链 注:如果两个集合的元素个数不相等,那么会将同等数量的数据进行拉链,多余的数据省略不用
    println(list1.zip(list2))
    //(12)滑窗
    list1.sliding(2, 5).foreach(println)
  }

}

3、集合计算简单函数

  • 求和
  • 求乘积
  • 最大值
  • 最小值
  • 排序

【示例】

package com.bigdata.test

object TestList0003 {
  def main(args: Array[String]): Unit = {
    val list: List[Int] = List(1, 5, -3, 4, 2, -7, 6)
    //(1)求和
    println(list.sum)
    //(2)求乘积
    println(list.product)
    //(3)最大值
    println(list.max)
    //(4)最小值
    println(list.min)
    //(5)排序
    // (5.1)按照元素大小排序
    println(list.sortBy(x => x))
    // (5.2)按照元素的绝对值大小排序
    println(list.sortBy(x => x.abs))
    // (5.3)按元素大小升序排序
    println(list.sortWith((x, y) => x < y))
    // (5.4)按元素大小降序排序
    println(list.sortWith((x, y) => x > y))
  }

}

  • sorted:对一个集合进行自然排序,通过传递隐式的 Ordering
  • sortBy:对一个属性或多个属性进行排序,通过它的类型。
  • sortWith:基于函数的排序,通过一个 comparator 函数,实现自定义排序的逻辑。

4、集合计算高级函数

  • 过滤:遍历一个集合并从中获取满足指定条件的元素组成一个新的集合
  • 转化/映射(map):将集合中的每一个元素映射到某一个函数
  • 扁平化
  • 扁平化+映射 注:flatMap 相当于先进行 map 操作,在进行 flatten 操作集合中的每个元素的子元素映射到某个函数并返回新集合
  • 分组(group):按照指定的规则对集合的元素进行分组
  • 简化(归约)
  • 折叠

【示例】

package com.bigdata.test

object TestList004 {
  def main(args: Array[String]): Unit = {
    val list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
    val nestedList: List[List[Int]] = List(List(1, 2, 3), List(4,
      5, 6), List(7, 8, 9))
    val wordList: List[String] = List("hello world", "hello bigdata", "hello scala")
      //(1)过滤
      println(list.filter(x => x % 2 == 0))
      //(2)转化/映射
      println(list.map(x => x + 1))
      //(3)扁平化
      println(nestedList.flatten)
      //(4)扁平化+映射 注:flatMap 相当于先进行 map 操作,在进行 flatten操作
      println(wordList.flatMap(x => x.split(" ")))
      //(5)分组
      println(list.groupBy(x => x % 2))
      }
}

十二、异常

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

1)Java 异常处理

  • Java 语言按照 try—catch—finally 的方式来处理异常
  • 不管有没有异常捕获,都会执行 finally,因此通常可以在 finally 代码块中释放资源。
  • 可以有多个 catch,分别捕获对应的异常,这时需要把范围小的异常类写在前面,把范围大的异常类写在后面,否则编译错误。
package bigdata.test;

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");
        }
    }
}

2)Scala 异常处理

  • 我们将可疑代码封装在 try 块中。在 try 块之后使用了一个 catch 处理程序来捕获异常。如果发生任何异常,catch 处理程序将处理它,程序将不会异常终止。
  • Scala 的异常的工作机制和 Java 一样,但是 Scala 没有“checked(编译期)”异常,即 Scala 没有编译异常这个概念,异常都是在运行的时候捕获处理。
  • 异常捕捉的机制与其他语言中一样,如果有异常发生,catch 子句是按次序捕捉的。因此,在 catch 子句中,越具体的异常越要靠前,越普遍的异常越靠后,如果把越普遍的异
    常写在前,把具体的异常写在后,在 Scala 中也不会报错,但这样是非常不好的编程风格。
  • finally 子句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作,这点和 Java 一样。
  • 用 throw 关键字,抛出一个异常对象。所有异常都是 Throwable 的子类型。throw 表达式是有类型的,就是 Nothing,因为 Nothing 是所有类型的子类型,所以 throw 表达式可以用在需要类型的地方。
def test():Nothing = {
 throw new Exception("不对")
}
  • java 提供了 throws 关键字来声明异常。可以使用方法定义声明异常。它向调用者函数提供了此方法可能引发此异常的信息。它有助于调用函数处理并将该代码包含在 try-catch块中,以避免程序异常终止。在 Scala 中,可以使用 throws 注解来声明异常
def main(args: Array[String]): Unit = {
 f11()
}
@throws(classOf[NumberFormatException])
def f11()={
 "abc".toInt
}

【示例】

package com.bigdata.test

object ExceptionDemo {
  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)协变和逆变

1、语法

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

说明

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

【示例】

//泛型模板
//class MyList<T>{}
//不变
//class MyList[T]{}
//协变
//class MyList[+T]{}
//逆变
//class MyList[-T]{}
class Parent{}
class Child extends Parent{}
class SubChild extends Child{}
object Scala_TestGeneric {
 def main(args: Array[String]): Unit = {
 //var s:MyList[Child] = new MyList[SubChild]
 
 }
}

2)泛型上下限

1、语法

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

Class PersonList[T <: Person]{ //泛型上限
}
Class PersonList[T >: Person]{ //泛型下限
}

【示例】

package com.bigdata.test

class Parent{}
class Child extends Parent{}
class SubChild extends Child{}
object Scala_TestGeneric {
  def main(args: Array[String]): Unit = {
    //test(classOf[SubChild])
    //test[Child](new SubChild)
  }
  //泛型通配符之上限
  //def test[A <: Child](a:Class[A]): Unit ={
  // println(a)
  //}
  //泛型通配符之下限
  //def test[A >: Child](a:Class[A]): Unit ={
  // println(a)
  //}
  //泛型通配符之下限 形式扩展
  def test[A >: Child](a:A): Unit ={
    println(a.getClass.getName)
  }
}

3)上下文限定

语法

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

说明

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

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)

十四、实战(WordCount)

1)普通 WordCount 案例

需求

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

需求分析

package com.bigdata.test

object TestWordCount {
  def main(args: Array[String]): Unit = {
    // 单词计数:将集合中出现的相同的单词,进行计数,取计数排名前三的结果
    val stringList = List("Hello Scala Hbase kafka", "Hello Scala Hbase", "Hello Scala", "Hello")
    // 1) 将每一个字符串转换成一个一个单词
    val wordList: List[String] =
      stringList.flatMap(str => str.split(" "))
    //println(wordList)
    // 2) 将相同的单词放置在一起
    val wordToWordsMap: Map[String, List[String]] =
    wordList.groupBy(word => word)
    //println(wordToWordsMap)
    // 3) 对相同的单词进行计数
    // (word, list) => (word, count)
    val wordToCountMap: Map[String, Int] =
    wordToWordsMap.map(tuple => (tuple._1, tuple._2.size))
    // 4) 对计数完成后的结果进行排序(降序)
    val sortList: List[(String, Int)] =
      wordToCountMap.toList.sortWith {
        (left, right) => {
          left._2 > right._2
        }
      }
    // 5) 对排序后的结果取前 3 名
    val resultList: List[(String, Int)] = sortList.take(3)
    println(resultList)
  }
}

2)复杂 WordCount 案例

package com.bigdata.test

object TestWordCount001 {
  def main(args: Array[String]): Unit = {
    // 第一种方式(不通用)
    val tupleList = List(("Hello Scala Spark World ", 4), ("Hello Scala Spark", 3), ("Hello Scala", 2), ("Hello", 1))
    val stringList: List[String] = tupleList.map(t => (t._1 + "") * t._2)
    //val words: List[String] =stringList.flatMap(s => s.split(" "))
    val words: List[String] = stringList.flatMap(_.split(" "))
    //在 map 中,如果传进来什么就返回什么,不要用_省略
    val groupMap: Map[String, List[String]] = words.groupBy(word => word)
    //val groupMap: Map[String, List[String]] = words.groupBy(_)
    // (word, list) => (word, count)
    val wordToCount: Map[String, Int] = groupMap.map(t => (t._1,
      t._2.size))
    val wordCountList: List[(String, Int)] =
      wordToCount.toList.sortWith {
        (left, right) => {
          left._2 > right._2
        }
      }.take(3)
    //tupleList.map(t=>(t._1 + " ") * t._2).flatMap(_.split("")).groupBy(word=>word).map(t=>(t._1, t._2.size))
    println(wordCountList)
  }
}

十五、总结

1)开发环境

要求掌握必要的 Scala 开发环境搭建技能。

2)变量和数据类型

掌握 var 和 val 的区别

掌握数值类型(Byte、Short、Int、Long、Float、Double、Char)之间的转换关系

3)流程控制

掌握 if-else、for、while 等必要的流程控制结构,掌握如何实现 break、continue 的功能。

4)函数式编程

掌握高阶函数、匿名函数、函数柯里化、闭包、函数参数以及函数至简原则。

5)面向对象

掌握 Scala 与 Java 继承方面的区别、单例对象(伴生对象)、构造方法、特质的用法及功能。

6) 集合

掌握常用集合的使用、集合常用的计算函数

7)异常

掌握异常常用操作即可

8)泛型

掌握泛型语法

到此为此,Scala的基础就到这里了,小伙伴如果有什么疑问的话,欢迎给我留言~

posted @ 2022-05-14 18:08  大数据老司机  阅读(441)  评论(0编辑  收藏  举报