题外话:
为什么要学习Scala语言?
Scala语言是Spark语言的基础,不会scala就无法入门spark语言。spark语言是大数据开发必备的语言,通过RDD运算对离线数据或者微批次数据进行处理。学大数据就一定要学Spark!
1、scala语言特点:
1. 面向对象的语言
2. 面向函数式编程的语言
3. 静态的语言
4. 扩展性良好
5. 支持Actor并发模型(多线程)
2、函数式编程
1. 函数式编程,也是一种编程思想
2. 纯粹的函数式编程语言编写的函数没有变量,输入的值确定,那么输出的结果是确定的。没有副作用
3. 可以使用变量的程序设计语言,是有副作用
一个带有副作用的函数不仅有一个返回值,还可能做了:
修改一个变量
直接修改数据结构
设置一个对象的成员
打印到终端或者读取用户输入
读取或写入一个文件
在屏幕上绘画
抛出一个异常或以一个错误终止
4. 函数是一等公民、是有返回值的,和基本类型的值、对象的地位是一样的,可以直接拿来使用。
5. 以表达式为中心
函数式编程的优势:
- 代码简洁,开发速度快
- 接近自然语言,易理解
- 易于代码管理
- 适合并发编程
- 适用于热升级
3、scala的安装
1)上传、解压、更名
[root@qianfeng01 ~]# tar -xvf scala-2.11.8.tar -C /usr/java
[root@qianfeng01 ~]# cd /usr/local
[root@qianfeng01 local]# mv scala-2.11.8 scala
2)配置系统环境变量
[root@qianfeng01 local]# vi /etc/profile
........省略......
export SCALA_HOME=/usr/local/scala
export PATH=$SCALA_HOME/bin:$PATH
3)保存退出后重新加载
[root@qianfeng01 local]# source /etc/profile
4)验证
[root@qianfeng01 local]# scala -version
4、scala语言的简介
Scala的REPL就是scala的命令行交互界面。
R->read 表示可以接受用户的输入数据
E->evaluate 可以计算数据
P->print 可以打印数据
L->loop 可以循环操作
演示
scala> 1
res0: Int = 1 # res0 是内置提供的变量,用来存储回车后的值
scala> res0
res1: Int = 1 # 可以打印变量的值 值又存到res1里了
scala> res0+res1 # 表达式
res3: Int = 2
scala> val age:Int = 28 #使用val定义变量age 同时指定类型 并赋值
age: Int = 28
scala> 1+1*10
res4: Int = 11
scala> val num = 34 # scala可以推断没有指定类型的变量的类型,也可以证明数字的默认类型是Int
num :Int = 34
上面那个是windows中安装了scala的环境,在cmd中演示的
下面这种是在windows编译。个人喜好在idea中整合scala敲。
1. 在D盘的dir目录下,创建一个hello.scala源文件,内容如下
object HelloWorld{
def main(args:Array[String]):Unit ={
print("you are best")
}
}
2. 编译源文件: 语法格式:scalac 源文件名
D:\dir> scalac hello.scala
注意: 结果会产生两个class文件
3. 运行字节码文件: 语法格式:scala 字节码文件名(不带后缀)
D:\dir> scala HelloWorld
scala和idea的整合
1. file-->settings-->plugins-->搜索scala
2. 选择install, 读条
3. apply,如果有重启,那就重启
5、scala编程语言的规范
1. 源文件扩展名(后缀)必须是.scala
2. 源文件名和里面的代码的最顶层的类名一致,区分大小写、所有源文件编码必须是 UTF-8
3. scala中的标识符要符合驼峰命名法、尽量做到见名知意(望文知意)
4. scala的语法缩进要使用空格、不要使用tab键(注意:Idea自动将tab键转成了2个空格,可以百度自行调整四个空格)
5. 单行长度不建议超出150字符、也就是不要超出可视化范围
6. 花括号的用法和java一致、该换行就换行。
7. 空行的目的,用于逻辑分组。比如 方法和成员变量之间应该多一个空行
8. 在简单的表达式上,能省略花括号就省略。
9. Scala的每句话说完,不需要使用分号,换行即可。
10. 注释风格,和java一样
// 单行注释
/* 多行注释*/
/** 文档注释*/
6、scala的类型体系(重点!)
1. scala的超类(父类,基类)是Any
2. Any下有两个直接子类型,分别是AnyVal 和AnyRef
Byte-->Short-->Int-->Long-->Float-->Double
Char
boolean
Unit
4. AnyRef包括了java的所有引用类型、Scala的所有集合类型、以及Scala的其他引用类型,以及Null类型
5. Nothing是Scala的所有其他类型的子类型,位于类型层次的最底层
6. Null类型只有一个实例值是null
scala> val a = null
a: Null = null
7. Unit和java的void相似,是一种类型,也只有一个实例()
scala> val a = ()
a: Unit = ()
7、变量的用法:
1. 需要使用val和var来定义、而且必须同时使用等号,进行初始化
2. 定义变量时、可以指定类型、也可以不指定类型(scala编译器会自动推断类型)
val num1 = 10 scala会自动推断类型
指定类型的写法: val num2:Long = 10
3. val声明变量时、可以使用lazy修饰、还有特点就是变量不能再次赋值,相当于用了final修改该变量
4. var声明变量时,不能使用lazy修饰、变量可以再次赋值
5. 变量一旦初始化后、类型确定,不能再赋值其他类型的值
6. scala推荐使用val来定义变量,因为:
- 更安全
- 可读性更高
- 节省内存资源
val 对应的单词是value,即值,常量的含义,因此可以理解为是final修饰的。实际上在转成.class时,确实是final修饰的变量
var 对应的单词是variable,即可变的,易变的,多变的含义。 因此对应的变量可以多次赋值。
8、数值类型
计算所遵循的原则:
1. 不同的数值类型计算时,需要转为同一种类型进行计算
2. 如果是小于Int类型的两种类型计算、都转为Int类型计算
3. 如果是其他情况的两种不同类型,那么就会转为较大类型的数据进行计算
数值类型有八种、可以细分如下:
整型四种:Byte 、Short 、Int、Long、
- 整型的默认类型是Int
- Long的值需要添加L/l
- 也可以赋值16进制的数字 如: val num = [0x|0X]ffff 就是255的16进制
浮点型两种:Float、Double
- 浮点型的默认类型是Double
- Float的数字需要添加F/f
- 赋值时也可以这样:
省略0的写法: .512 .34F
科学计数法: 5.12e2
字符类型: Char
- 赋值时需要使用单引号,单引号里只能有且只有一个字符
- 也可以赋值整数、范围是0~65535
var ch1 : Char = 65
- 也可以赋值16进制形式
如: var ch1 = '\u0041' 是字符A的unicode编码
布尔类型: Boolean
- 只有两个值,true和false,用于判断条件是否成立
- 一般用于分支或者是循环结构中
String类型
1. Scala的String的本质就是java的String
“zhangsan”.getClass.toString
2. 赋值使用双引号
3. 也可以使用三引号,进行换行赋值
var name = """
|zhangsan
|lisi
|"""
Option类型
用来表示一个值是可选的,实际上就是有值和无值两种。
有值的话,是Some(value)
无值的话,就是None
总之:Option类型就两个值:Some(value)、None
9、类型转换
1. 数值类型
-- 隐式转换: 小类型的变量赋值给大类型的变量,会自动在前面添加相应位数的0或1
比如: var a: Byte = 10 二进制: 0000 1010
var b: Short = a 二进制: 0000 0000 0000 1010
b变量是16位, 此时是Byte类型的10在前面自动添加8个0,进行补全
-- 强制转换: 大类型的变量赋值给小类型的变量时,必须强制转换
java中语法: 小类型名 变量名 = (小类型名)大类型变量
scala中,必须调用相关的转换方法,如
val num1 = 10;
val num2:Short = num1.toShort
2. 数值类型与字符串之间的转换
- 数值类型转字符串,直接使用拼接符号+,拼接即可
var age = 28
var str = age+"" // "28"
var str = age+"hello" // "28hello"
- 字符串转数值类型,需要掉相应的转换方法
var str = "28";
str.toInt // 转成了Int类型的28
注意:非纯数字的字符串无法转成数字类型,NumberFormatException
10、常用的运算符
1. 算数运算符: + 、- 、* 、/ 、%
计算原则:
--(1) 不同类型的数据做运算时,会转成较大类型的数据,再运算
--(2) 两个类型都小于Int时,转成Int类型,再运算
比如: 18.0/6 运算逻辑:6要先转成double的6.0, 然后和18.0做运算。结果类型是Double,所以结果是3.0
18/7 结果类型是Int,所以结果是2
也可以这样理解:两个整形运算,除法符号是取整操作
只要有一个是浮点型,就是正常除法运算
2. 关系运算符: >,>=,<,<=,==,!=
-- 用来判断两边的数值的关系是否成立,只有两种结果,true、false
-- 通常用于分支结构和循环结构,充当条件
3. 赋值运算符: = 、+=、-=、*=、/=、%=
4. 短路逻辑运算符: 一般用于连接多个关系表达式的,结果也是true、false
&& : 一假则假,全真为真
|| : 一真则真, 全假为假
! : 非真即假,非假即真
短路逻辑: 当前面的条件可以确定结果后,后面的条件不再执行
5. 位运算符 : 1为 真 0为假
& : 与 口诀: 一0则0,全1为1
| : 或 口诀: 有1则1,全0为0
^ : 异或 口诀: 相同为0,不同为1
>> : 有符号右移, 原来是正,结果就是正,原来是负,结果就是负数
<< :有符号左移, 原来是正,结果就是正,原来是负,结果就是负数
有符号移动时,正数高位补0,负数高位补1
>>>:无符号右移: 表示所有二进制位上的数向右移动,高位补0
11、流程控制语句
11.1 Scala表达式(重要)
1. 在scala中,你所看到的完整的语句,都是一个表达式
2. 只要是表达式,那么一定有返回值
比如:
a + b 返回的就是两个变量的和
println("aaaa") 打印语句也是一个表达式,返回Unit类型,值为()
throw new Exception(); 也是一个表达式,返回Nothing
3. 常见的表达式:
- 块表达式
- if表达式
- 循环表达式
- 异常表达式
- Actor并发模型表达式
4. 块表达式:
-- 即{}整体,有返回值,返回值为{}中的最后一个表达式的返回值
5. 赋值表达式
-- 有返回值,返回值为Unit类型
scala> var c = (b+=a)
c: Unit = ()
11.2 分支结构
if分支的语法与java语法一样,有以下三种写法:
1. 只有一条分支的写法
if(条件表达式){
逻辑体
}
2. 两条分支带else的写法
if(条件表达式){
逻辑体
}else{
逻辑体
}
3. 带else if的写法
if(条件表达式){
逻辑体
}else if(条件表达式){
逻辑体
}else if(条件表达式){
}.....
小贴士:
1. 只要某一个分支的条件表达式成立,那么剩下的分支直接跳过不执行。
2. 如果带有else模块,那么一定会执行其中一条分支,如果不带else,可能一条分支都不执行。
3. 针对于scala来说,if分支结构本身就是表达式,表达式的值是结构中所有的花括号的最后一个表达式的共同的父类
11.3循环结构
说明:
1. 循环结构本身也是一个表达式,有值。
2. 循环结构的种类:
-- while : 条件成立时,才会执行循环体
-- do-while :先执行一次循环体,再判断条件是否成立
-- for循环:
11.3.1while和do-while
while(条件表达式){
循环体
}
do{
}while(条件表达式)
练习
package com.qf.scala.day03
/**
* 计算100以内的奇数之和
*/
object _02WhileDemo {
def main(args: Array[String]): Unit = {
var num = 1;
var sum = 0;
while(num<100){
if(num%2==1){
sum+=num;
}
num+=1;
}
println(sum)
}
}
package com.qf.scala.day03
import scala.io.StdIn
object _03DoWhileDemo {
def main(args: Array[String]): Unit = {
var password = "";
do{
println("请输入密码")
password = StdIn.readLine();
}while(password!="123456")
println("密码正确")
}
}
11.3.2 for循环
说明:
循环时的范围表示方式,有两种,分别是使用to或者是until
1. to的写法:
1 to 10
1.to(10)
特点: 包含10. 是一个闭区间
2. until的写法
1 until 10
1.until(10)
特点: 不包含10, 是一个左闭右开区间
for循环的语法结构
小贴士: java中的for循环结构有两种写法
一种是经典for循环写法 :
for(循环因子的声明和初始化;循环条件;向着循环结束的方向变化的表达式){}
一种是增强for循环写法
for(类型名 变量名:集合|数组){}
注意:scala里的for循环,相当于java中的增强for循环写法
for( 变量名 <- 集合|数组|Range|表达式 ){
循环体
}
练习
package com.qf.scala.day03
object _04ForDemo {
def main(args: Array[String]): Unit = {
//练习1: 打印 1 到 10
for(num <- 1 to 10){
print(num+"\t")
}
println
for(num <- 1 until 11){
print(num+"\t")
}
println
//练习2: 遍历数组
val names = Array("zhangsan","lisi","wangwu","zhaoliu")
for(name <- names){
println(name)
}
//练习3: 打印一个字符串中的所有的字符
val str = "helloworld"
for( i <- 0 until str.length){
print(str(i)+" ")
}
println
//练习4: 计算1到10的和
var sum = 0;
for(i<-1 to 10){
sum+=i;
}
println(sum)
//练习5:
// for循环本身就是一个表达式,因此有值,
// 值比较特殊,是一个Vector集合, 比如循环10次,则是10个循环的最后一行的值的集合
// 那么如何获取这个集合的值, 需要使用yield,
// yield的位置位于{}的前面,小括号的后面
// 而下面的循环结构的最后一行,返回的都是(),因此集合里有10个()
var sum1 = 0;
val result = for(i<-1 to 10) yield{
sum1+=i;
}
//遍历for循环的结果
for(i<-result){
println(i)
}
}
}
for循环的多层嵌套
package com.qf.scala.day03
/**
* for循环可以进行嵌套
*/
object _05ForDemo {
def main(args: Array[String]): Unit = {
/**
* 练习1: 打印
* *****
* *****
* *****
* *****
*/
for(i<- 1 to 4){
for(j<- 1 to 5){
print("*")
}
println
}
/**
* 练习1: 打印
* *
* **
* ***
* ****
* *****
*/
for(i <- 1 to 5){
for(j <- 1 to i){
print("*")
}
println
}
/**
* 练习3: 打印乘法口诀表
*/
for(i <- 1 to 9){
for(j <- 1 to i){
print(i+"*"+j+"="+i*j+"\t")
}
println
}
}
}
多层简写
package com.qf.scala.day03
/**
* for循环可以进行嵌套
*/
object _06ForDemoSimple {
def main(args: Array[String]): Unit = {
/**
* 练习1: 打印
* *****
* *****
* *****
* *****
*/
for(i<- 1 to 4;j<- 1 to 5){
print("*")
if(j==5){
println
}
}
/**
* 练习1: 打印
* *
* **
* ***
* ****
* *****
*/
for(i <- 1 to 5;j <- 1 to i){
print("*")
if(i==j){
println
}
}
/**
* 练习3: 打印乘法口诀表
*/
for(i <- 1 to 9;j <- 1 to i){
print(i+"*"+j+"="+i*j+"\t")
if(i==j){
println
}
}
/**
* 练习4: 打印10次10以内的所有奇数
*/
for( i <- 1 to 10;j <- 1 to 10;if j%2==1){
print(j+" ")
}
}
}
循环结构的终止
说明:java中的循环结构可以使用break,表示结束循环结构,continue结束当次循环,
特殊情况:也可以使用return来结束整个方法
那么,scala中也可以使用以下方式来结束循环:
1. 循环条件 , 就是控制循环的条件表达式
2. return关键字: 注意,该方式会结束整个方法
3. break: 但是需要配合breakable语句块来使用
练习:
package com.qf.scala.day03
import scala.util.control.Breaks._
object _07ForDemo {
def main(args: Array[String]): Unit = {
println("-------练习1:------------")
// 使用循环条件来结束循环
var i = 0;
while(i<10){
println(i);
i += 1
}
println("-------练习2:------------")
// 使用return 关键字来结束循环,注意,会结束整个方法
// 当num等于5时,结束循环结构
// for( num <- 1 to 10){
// println(num)
// if(num==5){
// return
// }
// }
// println("-------over------------")
println("-------练习3:------------")
/**
* 使用break 来结束循环结构, 需要配合breakable语句块
* 练习: 当num等于5,结束循环
*/
breakable{
for( num <- 1 to 10){
println(num)
if(num==5){
break
}
}
}
println("-------练习4:使用break来表达continue的用法------------")
/**
* 练习: 当num等于5时,跳过,不打印
*/
for( num <- 1 to 10){
breakable{
if(num==5){
break
}
println(num)
}
}
}
}
11.3.3字符串插值器
字符串插值器的应用, 功能是让字符串中的变量生效,换成变量的值
s: 就是让字符串中的变量生效
f: 除了有s功能外,还具有格式化的效果
%[Num]s: 对字符串进行右对齐,左补空格,总长度为Num
%[Num]d: 对整形进行右对齐,左补空格,总长度为Num
%[.Num]f:对浮点数进行保留Num个小数位。
raw: 除了具有s功能外,其他任何字符都被当做普通字符输出 \t\n\s 和lua的[[....]] 以及xml里的<![CDATA[ ]]>功能一样
注意: 变量名前需要添加$variableName符号,或者使${variableName}
代码
package com.qf.scala.day03
object _08Interpolation {
def main(args: Array[String]): Unit = {
var name = "michael"
var age = 34
var salary = 55555.1234;
//java中一般使用+拼接
println("有一个帅哥叫"+name+",他的年龄是"+age);
//使用scala的插值器 s
println(s"有一个帅哥叫${name},他的年龄是${age}");
//使用scala的插值器 f
println(f"有一个帅哥叫$name%10s,他的年龄是$age%10.2f,他的工资是$salary%.2f");
println(f"有一个帅哥叫$name%10s,他的年龄是$age%10d,她的工资是$salary%.2f");
//使用raw插值器
println("有一个帅哥叫$name,\t他的年龄使$age")
println(raw"有一个帅哥叫$name,\t他的年龄使$age")
}
}
11.3.4文件IO
用来读写磁盘或者使网络上的字符
package com.qf.scala.day03
import scala.io.Source
object _09ScalaIO {
def main(args: Array[String]): Unit = {
println("----------练习1: 读取文件,按照字符进行输出----------");
//读取磁盘上的文件,返回的是一个字符迭代器
var file1 = Source.fromFile("D:/input/dept.txt");
for(ch <- file1){
print(ch) //打印的使每一个字符
}
println("----------练习2: 读取文件,按照行进行输出----------");
//读取磁盘上的文件,返回的是一个字符迭代器
var file2 = Source.fromFile("D:/input/dept.txt");
//调用getLines()方法,返回多行的迭代器
var lines = file2.getLines()
for(line <- lines){
println(line)
}
//读取磁盘上的文件,返回的是一个字符迭代器
println("---------练习3:测试mkString方法---------------")
var file3 = Source.fromFile("D:/input/dept.txt");
/**
* mkString: 该方法的作用是将数组或者集合中的元素拼接到一起。
* 重载方法mkString(x:String): 该方法的作用是将数组或者集合中的元素使用x拼接到一起。
*/
println(file3.mkString)
// 也可以使用将非空迭代器变成数组类型
var a = file3.getLines().toArray
for(ele <- a){
println(ele)
}
println("----------读取文件,按照单词进行输出----------");
var file4 = Source.fromFile("D:/input/dept.txt"); // 字符迭代器
var lines1 = file4.getLines();
for(line <- lines1){
var words = line.mkString.split(",") //切出来的是多个单词
for(word <- words){
println(word)
}
}
println("----------读取文件,按照单词进行输出----------");
var file5 = Source.fromFile("D:/input/dept.txt"); // 字符迭代器
val contents = file5.mkString.split(",")
for(word <- contents)
println(word)
println("----------读取网络的网页信息,按照行进行输出----------");
var contentall = Source.fromURL("http://www.baidu.com")
var lines2 = contentall.getLines();
for(line <- lines2){
println(line)
}
}
}
11.3.4## 正则表达式
package com.qf.scala.day03
import scala.util.matching.Regex
object _10ScalaReg {
def main(args: Array[String]): Unit = {
/**
* scala中比较正规的写法,是使用Regex的类型来定义,
* var reg = new Regex("([\\d]{18}|[\\d]{17}[X|x]|[\\d]{15})")
*
* 但是scala不建议这样写,而是要使用最简单的写法,如下:
* var reg = "正则表达式".r
*/
var reg = "[sS]cala".r
var words = "scala is a simple language,java php Scala C++";
// 找到第一个符合正则表达式的单词,如果有就返回Some(单词),如果没有返回None
var word = reg.findFirstIn(words)
println(word)
var content = reg.findAllIn(words)
for(w <- content){
println(w)
}
val pattern = "(S|s)cala".r
val str = "Scala is scalable and cool"
println(pattern replaceFirstIn(str, "Java"))
println(pattern replaceAllIn(str, "Java"))
}
}
12方法与函数
相较于java中只有方法的概念,Scala既有方法Method的概念,也有函数Function的概念.
java中的方法的定义结构: 修饰词 返回值类型 方法名(参数类型列表){ 方法体 }
方法:
package com.qf.scala.day03
/**
* 方法的基本定义语法:
* 写法1:
* def 方法名(参数类型列表):返回值类型={方法体}
* 写法2:
* 如果方法体比较简单,就一句话,那么可以省略{}以及返回值类型,返回值类型会自动推断
* 写法3:去掉返回值类型和等号, 一定返回的是Unit, 相当于java的void
* def 方法名(参数类型列表){方法}
*
*/
object _11ScalaMethod {
def main(args: Array[String]): Unit = {
println(sum(2, 5))
println(sum(1, 2, 3))
sum(3, 5L)
//调用四个参数的sum方法
// sum(1,2,3,4)
sum(10L,10)(100.0,100.0)
//调用无参数方法,注意定义期间是有小括号的
println(m1())
println(m1)
println(m2)
println(m3(10, 1.3, 1.4, 1.5))
}
/*定义一个方法: 指定两个Int类型的参数,返回值类型为Long*/
def sum(m:Int,n:Int):Int = {
val x = m*2
val y = n+3
x+y
}
/*定义一个方法: 省略{}和返回值类型的写法*/
def sum(a:Int,b:Int,c:Int)=a+b+c
/*定义一个方法: 省略等号的写法, 一定要省略返回值类型,返回值是固定的,就是Unit 相当于java的void*/
def sum(m:Int,n:Long){
val x = m*2
val y = n+3
println((x+y).toDouble)
}
//定义一个四个参数的方法
// def sum(a:Long,b:Int,m:Double,n:Double): Unit ={
// val x1 = a+10;
// val x2 = a*b
// val y1 = m*x2
// val y2 = y1+x1
// println(y1+y2)
// }
/**
* 使用多参数类型列表的定义方法来修改上面的方法 ,
* 注意事项:
* 1. 但是要将上面的方法注释掉,如果不注释,就冲突了,因为效果一样
* 2. 重载方法时,要注意方法的参数列表,不要与多参数类型列表那个方法的的第一个列表重复,否则,冲突
*/
def sum(a:Long,b:Int)(m:Double,n:Double): Unit ={
val x1 = a+1;
val x2 = a*b
val y1 = m*x2
val y2 = y1+x1
println(y1+y2)
}
/**
* 无参数类型列表的写法:
* 1. 只有小括号,没有参数的写法
* 调用期间,可以写括号,也可以省略括号
* 2. 没有小括号的方法定义
* 调用期间,不能写括号
*/
def m1():Int={
val x = 5
val y = 6
x + y
}
def m2: Int ={
val x = 10
val y = 6
x + y
}
/**
* 可变参数的方法定义
*/
def m3(a:Int,b:Double*): Double ={
var sum:Double = 0;
for(i <- b){
sum +=i
}
sum+=a
sum
}
}
方法的总结:
1. scala的方法定义语法如下:
def 方法名([形参名:形参类型],........): 方法返回值类型 = {
方法体
}
2. 简化语法1: 省略方法的返回值类型和=以及冒号, 因为可以推断出来类型
def 方法名([形参名:形参类型],........) {
方法体
}
3. 简化语法2: 如果方法体的逻辑比较简单,就一句话,则可以省略{} 等号不能省略
4. 可以设置多参数列表:
其实:就是将一个参数列表中的所有参数,拆分成多个列表。
调用时,所有的列表都要赋值。
5. 方法为无参方法时:
定义期间带小括号, 调用时可以带括号,也可以不带括号
定义期间不小括号, 调用时一定不能带括号
方法和函数的总结
1. 在对应语法上的区别
(1)关键字上的区别
- 方法需要使用def定义
- 函数使用val|var
(2) 方法里没有=>, 函数中有=>
2. 方法的定义语句如下:
-- def 方法名(参数类型列表):返回值类型={方法体}
-- 省略返回值类型的写法,返回值类型会自动推断,等号不能省略
即:def 方法名(参数类型列表)={方法体}
-- 如果方法的返回值为Unit,那么就可以去掉返回值类型和等号, 相当于java的void
def 方法名(参数类型列表){
方法体
}
3. 函数的定义语法如下:
-- 带=号写法
val|var 函数名 = ((参数类型列表) => {函数体})
val|var 函数名 = (参数类型列表) => {函数体} 这种方法比较通用
val|var 函数名 = 函数体(函数体中的形参要使用神奇的下划线)
val f3 = (_:Int) * (_:Int) + (_:Double) * (_:Double)
println(f3(2, 3, 4, 5))
-- 带冒号写法
val|var 函数名: ((参数类型列表)=>返回值类型) = { (参数名称列表)=>{函数体}}
val|var 函数名: (参数类型列表)=>返回值类型 = (参数名称列表)=>{函数体}
val|var 函数名:(参数类型列表)=>返回值类型 = 神奇的下划线进行运算
val f6 : (Int,Int) =>Long = _ * _
println(f6(1, 2))
--两者区别:
1. 使用等号的没有返回值类型,使Scala自动推断出来的
参数类型列表中包含 参数名和类型名 :(a:Int,b:Long)
2. 使用冒号的需要定义返回值类型
参数类型列表和参数名称列表是独立的,不在一起
(Int,Int) (a,b)
4. 方法一般定义在某个类、特质、或者object中
方法可以共享使用所在类内的属性
5. 函数可以作为方法的参数进入定义
def m2(a:(Int,Int)=>Unit)(m:Int,n:Int):Unit={
a(m,n)
}
val f1 = (a:Int,b:Int) => println(a + b)
调用:
m2(f1)(2,3)
6. 方法转函数:
只需要使用 神奇的下划线
方法名 _
7. 函数的本质
其实所有自定义的函数都是实现了函数的23个特质(trait)
特质的源码如下:
trait Function1[ -T1, +R] extends AnyRef {
def apply(v1: T1): R
}
-T1 是参数的泛型,如果其他特质,则对应多个相应的泛型
+R 使返回值类型的泛型。
apply方法 是特质中的一个抽象方法,而且使scala中的一个方法糖(方法糖就是方便的意思)
所有自定义的函数体,其实底层都是写到了apply方法中
13、SCALA的集合框架(重点)
13.1集合框架的简介
1. 集合的作用,用来存储一堆数据的,是一个容器
2. 集合框架的所有类型分两大类,一类是不可变集合类型,一类是可变集合类型
不可变的集合类型都存储在 scala.collection.immutable下
可变的集合类型都存储在 scala.collection.mutable下
3. 不可变集合类型,在并发访问时,相对安全,但是效率低
可变集合类型,并发访问时效率高,但是不安全
4. 不可变集合对象,也可以进行添加、移除、更新、但是返回的都是新的集合对象
5. 集合框架中常用的类型分三大类,
-- Map
(1) HashMap
(2) TreeMap
-- Set
(1) HashSet
(2) TreeSet
-- Seq
(1) Array
(2) List
(3) stack
(4) LinkedList
13.2.1 Array的说明
1. 数组是用来存储一堆相同类型的数据
2. 数组也分为不可变数组和可变数组
默认使用的是不可变数组
回顾:java的数组
int[] array = {} :静态初始化
int[] arr = new int[length] : 动态初始化,规定长度, 特点有length个默认值
int[] arr = new int[]{....} :动态初始化,不能规定长度,直接使用{}赋值。
13.2.2 数组的使用
不可变数组的定义
第一种: 使用new关键字定义,应该规定泛型,必须规定长度
val names = new Array[泛型](length)
第二种:不使用new关键字定义, 不能规定长度,必须初始化元素
val names = Array[泛型](元素1,.......)
val names = Array(元素1,.......) 泛型可以省略,scala会自动推断
val names = Array() 省略元素,即是空数组
不可变数组的遍历
package com.qf.scala.day04.array
object _01ArrayDemo {
def main(args: Array[String]): Unit = {
var names = new Array[String](5);
println(names) //直接打印数组遍历,返回的使一个带有句柄的地址值
//遍历方式1:
// for( name <- names){
// println(name)
// }
//遍历方式2:
names.foreach(println)
//遍历方式3: 传入匿名函数
names.foreach(x=>print(x+"\t"))
//遍历方式4:
var b = names.toBuffer //转成可变数组类型,
println(b) //打印时,可以直接看到可变数组中的元素
//遍历方式5: 使用下标, 下标从0开始, 语法: 变量名(下标)
for(i <- 0 until names.length)
println(s"第${i}个元素是${names(i)}")
//访问第三个元素,并修改为"zhangsan"
names(2) = "zhangsan"
names.foreach(x=>print(x+"\t"))
}
}
可变数组的定义
注意: 需要导入包 import scala.collection.mutable.ArrayBuffer
第一种: 使用new关键字定义,应该规定泛型,长度可以不规定,即使定义了也不生效。
val names = new ArrayBuffer[泛型](length)
第二种:不使用new关键字定义, 不能规定长度,必须初始化元素
val names = ArrayBuffer[泛型](元素1,.......)
val names = ArrayBuffer(元素1,.......) 泛型可以省略,scala会自动推断
val names = ArrayBuffer() 省略元素,即是空数组
可变数组的练习
package com.qf.scala.day04.array
import scala.collection.mutable.ArrayBuffer
object _02ArrayBufferDemo {
def main(args: Array[String]): Unit = {
//定义方式
//var nums = new ArrayBuffer[Int]() // 使用new关键字,需要规定泛型,长度可以不写
//var nums = ArrayBuffer() //直接写类型名,小括号写元素,不写就是空数组
var nums = ArrayBuffer(10,20)
//遍历nums
nums.foreach(x=>print(x+"\t"))
println
//修改第二个元素为10倍关系
nums(1) = nums(1)*10
nums.foreach(x=>print(x+"\t"))
println
//扩容方式1:
nums += 30
nums.foreach(x=>print(x+"\t"))
println
//扩容方式2:
nums+=(40,50,60)
nums.foreach(x=>print(x+"\t"))
println
//扩容方式3: 向数组中添加一个集合对象
nums ++=List(40,50,60)
nums.foreach(x=>print(x+"\t"))
println
//删除元素
nums -= 200
nums.foreach(x=>print(x+"\t"))
println
nums -= 60
nums.foreach(x=>print(x+"\t"))
println
nums -= (50,50)
nums.foreach(x=>print(x+"\t"))
println
nums --= Array(40,60,100)
nums.foreach(x=>print(x+"\t"))
println
//测试常用方法 追加
nums.append(10) // 10 30 40 10
nums.append(1,2) // 10 30 40 10 1 2
nums.foreach(x=>print(x+"\t"))
println
//插入 insert(index:Int,elem:Int*) 下标对应的元素以及后面的元素都要向后移动
nums.insert(1,0,0)
println(nums)
//移除 trimEnd(length:Int) 移除后面的length个元素
nums.trimEnd(2)
println(nums)
//移除 trimStart(length:Int) 移除前面的length个元素
nums.trimStart(2)
println(nums)
// 移除方法 remove(index:Int) 移除下标对应的那个元素
nums.remove(2)
println(nums)
// 移除方法 remove(index:Int,count:Int) 从下标对应的那个元素开始移除,向后移除count个
nums.remove(1,2)
println(nums)
}
}
数组的其他方法
package com.qf.scala.day04.array
/**
* 数组的其他练习
*/
object _04ArrayDemo {
def main(args: Array[String]): Unit = {
//遍历数组,使用yield 取出循环结果,返回新的数组
val a = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)
val res1 = for (elem <- a) yield 2 * elem
for (elem <- res1)
print(elem + " ")
println()
//对原数组元素过滤后生成一个新的数组
//将偶数取出乘以10后再生成一个新的数组
val res2 = for(elem <- a if elem%2==0 ) yield 10*elem
for(elem <- res2)
print(elem+" ")
println()
/**
* 使用filter函数,map函数,来达到上述需求
*/
var b = a.filter( x=> x%2==0).map(_*10)
b.foreach(println)
/**
* 测试其他方法
*/
println(a.sum)
println(a.max)
println(a.min)
println(a.length)
println(a.indices) //获取索引的信息 Range(0, 1, 2, 3, 4, 5, 6, 7, 8)
var nums = Array(2,1,4,3,3,6,7,5)
// println(nums.sorted.toBuffer) // sorted 是一个升序排序方法
println(nums.sortWith( _ > _).toBuffer) // 自定义排序规则: 两个参数x,y x<y就是升序,x>y就是降序
//创建一个二维数组
// [
// [null,null,null],
// [null,null,null]
// ]
var hobbys = Array.ofDim[String](2,3)
for(i <- hobbys){
var hobby = i; //获取每个人的所有爱好
for(j <- hobby){
print(j+"\t")
}
println
}
hobbys(1)(1) = "basketball"
for(i <- hobbys){
var hobby = i; //获取每个人的所有爱好
for(j <- hobby){
print(j+"\t")
}
println
}
//定义一个一维数组,长度为2,泛型是一个数组
hobbys = Array.ofDim[Array[String]](2)
hobbys(0) = Array("a","b")
hobbys(1) = Array("e","f","g")
for(i <- hobbys){
var hobby = i; //获取每个人的所有爱好
for(j <- hobby){
print(j+"\t")
}
println
}
}
}
13.3List
说明
1.List里的元素有序、并且可以重复
2. List也分两类,一种是不可变list、一种是可变list
3. 一个列表整体就分两部分,一部分是head,一部分就是tail
head指的是头元素
tail指的就是除了头元素的其他所有元素
4. Nil是空列表的意思
13.3.1
不可变List的应用
1)获取不可变数组
1. val 变量名 = List() // 获取一个空列表对象
2. val 变量名 = List("zhangsan","lisi","wangwu") //直接初始化元素信息,泛型使String
3. val 变量名 = List("zhangsan",23,true) //列表的泛型是Any
4. val 变量名 = Nil //Nil表示空列表
5. val 变量名 = 5::Nil //使用Nil和元素拼接成一个新的集合, 元素必须在前面,Nil在最后
6. val 变量名 = 1::2::3::Nil
7. val 变量名 = List(List(1,2),List(3,4),List(5,6))
8. val 变量名 = List(1,2)::List(3,4)::List(5,6)::Nil
9. val 变量名 = (3::4::Nil)::(5::6::Nil)::Nil
:: 是拼接符号,需要拼接的元素只能拼接到列表的头部位置,原有的元素都变成了tail
2)元素的访问
1. 使用下标访问,下标从0开始,
格式: 变量名(index)
2. 元素不可以被修改
小贴士: 不可变List指的是长度不可变,值也不可变
3)遍历
第一种:
scala> a.foreach(println)
10
20
30
第二种:
scala> a.foreach(x=>print(x+"\t"))
10 20 30
第三种:
scala> for(i <- a) println(i)
10
20
30
第四种:
scala> for(i <- 0 until a.size) println(a(i))
10
20
30
scala>
4)元素的拼接
第一种方式:
双冒号
第二种方式:
+:
:+
总结: 挨着冒号的是List 挨着+号的是要添加的元素
.:+()
第三种:
++
++:
第四种:
:::
5)集合的其他方法
val nums = List(1,2,3)
nums.head //1
nums.tail // 2,3
nums.last // 3
nums.init // 1,2
List.concat(p1,p2)
List.fill(length)(element) : 返回length个element元素的集合
nums.reverse
nums.length
nums.isEmpty
nums.size
nums.take(num) 取出前num个元素,返回一个新的集合对象
nums.drop(num) 删除前num个元素,剩下的元素返回一个新的集合对象
nums.splitAt(index) 切成两个集合对象,下标前的所有元素构成一个,下标和下标后构成一个集合
nums.zip(other) 与其他集合产生拉链,
val other = List("a","b","c")
nums.zip(other) ---> List((1,"a"),(2,"b"),(3,"c"))
注意:拉链后,元素使一个tuple ,如果长度不一致,以短的个数为元素个数
模式匹配进行拉链
val fruit = List("apples", "oranges", "pears")
val List(a, b, c) = fruit
a: String = apples
b: String = oranges
c: String = pears
注意:给出的元素个数与列表的元素个数需要一致,否则会报错。
为了避免报错,在元素个数不可知的情况下,最好使用::匹配。
比如:
val fruit = List("apples", "oranges", "pears","banana")
val a :: b :: c = fruit
a: String = apples
b: String = oranges
c: List[String] = List(pears,banana)
flatMap方法: 展开映射方法,作用是元素变成集合,并展开到一个集合中
val fruit = List("apples", "oranges", "pears","banana")
fruit.flatMap(_.toList)
res49: List[Char] = List(a, p, p, l, e, s, o, r, a, n, g, e, s, p, e, a, r, s, b, a, n, a, n, a)
nums.partition(_%2==0) : 按照条件进行分区,分成2个集合
nums.find(_>3) : 返回为Some(value) 或者是None
takeWhile(_<3) : 只要不满足条件就停止循环,停止前的元素构成新集合
dropWhile(_<3) : 只要不满足条件就停止循环 删除停止前的满足条件的所有元素,剩下的元素构成新集合
span(_<3) : 将集合分成两段,只要遇到一个不满足条件的就截断
nums.sortWith(_<_) : 升序排序
4.3.3 可变List的应用
1)如何获取可变List
1. 需要导包 import scala.collection.mutable.ListBuffer
2. val nums = ListBuffer()
3. val nums = ListBuffer(1,2,3,4)
4. val nums = new ListBuffer[Int]()
2)可变List的练习
1. 拼接符号
+=
.append()
++= List(4)
注意:以上的操作都是在原有的集合上追加元素
2. 以下拼接符号,都会产生新的集合对象
+:
:+
++List(4)
注意: 单+号的 只能添加元素, 双+号的 只能添加集合
不可变列表里的方法,在可变列表里,基本上都可以使用
13.4Set 说明
1.Set是一个无序,不能重复的集合对象
2.Set 也分两种,一种不可变,一种可变
13.4.1不可变Set的使用
1. 需要导包
import scala.collection.mutable.HashSet
2. 获取方式:
val nums = new HashSet[Int]()
val nums = Set()
val nums = Set(1,2,3)
3. 拼接
nums + 元素
nums ++ Set()
nums ++:Set()
4. 也可以移除
nums - 元素
nums -- Set()
nums --:Set()
总结:
1. 不可变Set 指的是 元素不可变 长度不可变
可以通过Set没有提供+=,++=,-=,--+这样的方法 证实 如上特点
13.4.2可变Set的使用
1. 包:
import scala.collection.mutable.Set
import scala.collection.mutable.HashSet
2. 获取方式:
val nums = Set()
val nums = Set(1,2,3)
println(nums.getClass.getName) //scala.collection.mutable.HashSet
3. 操作:
nums += 元素
nums.add(元素)
nums -= 元素
nums.remove(元素)
总结:
1. 可变set 在操作上 同不可变Set的操作基本相似
也有自己独立的功能,比如 += ++=,-=,--= 。
可以证明:可变Set 指的是长度可变。
13.5元组的使用(重点)
1. 作用:用来存储一堆数据的,可以是不同类型的,个数最多是22个。
2. 如何获取一个tuple对象
方式1: val 变量名 = (,,,,,,,,)
方式2: val 变量名= new TupleN(,,,,,) N从1到22, n是多少,小括号内就要有多少个元素
3. 如何访问一个tuple呢?
方式1: 使用_N n是1到22
val nums = (10,20,30,40)
val element = nums._4 //获取第四个元素
方式2: 定义期间,直接给一个key的组合,也就是变量, 变量可以直接使用
val nums,(a,b,c,d) = (10,20,30,40)
a=10
b=20
c=30
c=40
方式3: 使用下标,0~N-1
nums.productElement(index)
4. 遍历
方式1:
for (elem <- tuple1.productIterator) {
print(elem)
}
println()
方式2:
tuple1.productIterator.foreach(i => println(i))
tuple1.productIterator.foreach(print(_))
13.6Map的使用
说明
1. Scala的Map也是用于存储多个键值对
2. 也分两种类型,一种是不可变scala.collection.immutable._
一种是可变scala.collection.mutable._
3. 不可变的Map有序
可变的Map无序
获取对象的方式
1. 默认情况下使用的是不可变Map
val address = Map("a"->"10","b"->20)
val hobbys = Map(("a",10),("b",20))
2. 使用可变的Map, 别忘记导包
val hobbys = scala.collection.mutable.Map(("a",10),("b",20))
访问元素
1. 直接在变量名后添加小括号,小括号里添加key
nums("a")
注意: 如果key不存在,会报错
2. 避免报错的话,可以使用get方法
nums.get("a")
注意:如果有这个key,则返回Some(value),如果想要获取具体value,可以这样nums.get("a").get
3. nums.getOrElse(key,defaultValue)
推荐使用这样的方式,如果key不存在,可以使用默认值
遍历Map
package com.qf.scala.day05.map
object _01MapDemo {
def main(args: Array[String]): Unit = {
val nums = Map("a"->10,"b"->20,"c"->30)
//获取所有的key
val keySet = nums.keySet
for(key<-keySet){
println(key+","+nums(key))
}
//另外一种获取Key的迭代器
val keys = nums.keys
for(key<-keys){
println(key+","+nums(key))
}
//再来一种方式
for((k,v)<-nums){
println(k+","+v)
}
//直接获取value的集合
val values = nums.values
values.foreach(println)
}
}
使用其他子类型获取对象
1. HashMap : 无序
import scala.collection.mutable
object MutMapDemo extends App{
val map1 = new mutable.HashMap[String, Int]()
//向map中添加数据
map1("spark") = 1
map1 += (("hadoop", 2))
map1.put("storm", 3)
println(map1)
//从map中移除元素
map1 -= "spark"
map1.remove("hadoop")
println(map1)
}
2. TreeMap : 有序
scala> import scala.collection.immutable.TreeMap
scala> var tm = TreeMap(3 -> 'x', 1 -> 'x', 4 -> 'x')
tm: scala.collection.immutable.SortedMap[Int,Char] =
Map(1 -> x, 3 -> x, 4 -> x)
scala> tm += (2 -> 'x')
scala> tm
res38: scala.collection.immutable.SortedMap[Int,Char] =
Map(1 -> x, 2 -> x, 3 -> x, 4 -> x)
聚合的其他操作(重要)
1.1 拉链操作
两个集合对象,可以使用zip方法,组成一一对应的pair对,也可以理解为键值对。
方法有如下几个:
1. zip: 两个集合对象进行拉链,长度不统一时,以短的为基础,多于的去掉
2. zipAll:
使用方式:
A.zipAll(B,defaultA,defaulB)
解析: A和B是两个集合对象
defaultA是调用者A的默认值
defaultB是调用者B的默认值
当长度不统一时,元素少的就会用到默认值,来填充
3. zipWithIndex:
作用:将集合的元素与对应的下标构成键值对
scala> a
res86: List[Int] = List(10, 20, 30)
scala> a.zipWithIndex
res87: List[(Int, Int)] = List((10,0), (20,1), (30,2))
scala> a.zip(Stream from 1) //指定下标从1开始
res88: List[(Int, Int)] = List((10,1), (20,2), (30,3))
4. unzip
scala> a
res93: List[Int] = List(10, 20, 30)
scala> c
res94: List[String] = List(x, y, z)
scala> val f = a zip c
f: List[(Int, String)] = List((10,x), (20,y), (30,z))
scala> f unzip
warning: there was one feature warning; re-run with -feature for details
res96: (List[Int], List[String]) = (List(10, 20, 30),List(x, y, z))
1.2交集、并集、差集等方法
1. intersect
2. union
3. diff
4. forall(_<3) :
对集合中的元素条件进行过滤,只有当所有元素都满足要要求是,才会返回true 否则是false
5. list.flatten : 如果集合的元素是一个集合,那么就展开所有的元素信息,变成一个集合
6. fold(defaultValue:B)((x,y)=>B)
两个变种方式:
foldLeft
foldRight
7. reduce((x,y)=>B): 归约运算
两个变种方式:
reduceLeft
reduceRight
练习
package com.qf.scala.day05.map
object _02Exercise {
def main(args: Array[String]): Unit = {
val nums = List(1,2,3,4,5,1)
/**
* reduce((x, y) => T)
* x :最开始指向集合的第一个元素
* y :指向剩下的每一个元素
*/
println(nums.reduce((x, y) => x - y))
/**
* 1 2,3,4,5 2 3,4,5 3 4,5 4 5
* 运行逻辑:
* 右边的两个元素先计算,然后再往左走
* x表示两个元素的左边元素,y最初是最右边的元素,然后是计算的结果
*/
println(nums.reduceRight((x, y) => (x - y)))
/**
* fold(默认值)(x,y)
* x用于接收默认值,并且接收计算结果
* y指向的是每一个元素
*
* 底层调用的是foldLeft方法 foldLeft[B](z: B)(op: (B, A) => B)
*/
// List(1,2,3,4,5,1) 0 4
println(nums.fold(0)((x, y) => x - y))
/**
* 向右折叠方法: foldRight(2)((x, y) => (x - y)
* 2是默认值,y接收默认值,y再接收计算结果
* x接收的使从右到左的每一个元素
*/
println(nums.foldRight(2)((x, y) => (x - y)))
}
}
小技巧:
fold <--> foldLeft
foldRight
上述三个方式:都是双参数列表,第一个参数列表是默认值
fold(0)(x,y) 向左折叠,则左边的变量x用于接收默认值和计算结果
foldRight(0)(x,y) 向右折叠,则右边的变量y用于接收默认值和计算结果
reduce <--> reduceLeft
reduceRight
上述三个方式:就是一个参数列表
reduce(x,y) : 向左归约,则左边的变量用于接收元素的第一个值和计算结果,右边变量指向的是除了第一个元素外的每一个元素
reduceRight(x,y) :向右归约,则右边的变量用于接收元素的最后一个值和计算结果,左边变量指向的是除了最后一个元素外的每一个元素
14 类型结构(重要)
14.1 定义
1)基本结构
class 类名(主构造器的参数列表){
属性的定义和初始化
构造方法(辅助构造方法)
普通方法(方法和函数)
}
2)说明
-1. class默认是public修饰的,可以省略不写
-2. 一个源文件中,可以有多个class
-3. scala的类体中包含的就是各种表达式。
因此可以直接在类体中书写输出语句、for循环、if分支、方法、函数、构造器等
14.2类体的属性定义
通用方式
package com.qf.scala.day05
/**
* 定义属性的规范:
* 1. 属性可以使用val或者var定义,必须初始化
* -- val定义的属性,不能被再次修改, 因此属性一般用var定义
* 2. 当初始化的值为null时,应该指定类型,否则变量的类型被会推断为Null类型
* 3. 当使用var定义属性时,初始化值可以使用_,scala会自动推断为该类型的默认值
* 4. 可以使用修饰词 protected 或者 private 修饰每一个属性
*
* 5. scala源文件的反编译查看结果
* --(1). 所有的属性在编译时,都是私有的
* --(2). var声明的变量,底层提供公有的getter方法和setter方法
* val声明的变量,底层有final, 并提供公有的getter方法
* --(3). scala中使用private var修饰的变量,提供了私有的getter和setter方法
* private val修饰的变量,提供了私有的getter方法
* 6. 有一个特殊的属性,叫私有对象属性,底层编译时,没有任何方法与之对应
*/
class Student{
//属性的定义
var id:Int= 0
val id1:Int = 0
var name:String = null
var gender:Char = _ //因为是Char, 所以scala会将_推断为'\u0000'
protected var age = 0
private var classno:String = _
private val teacher = ""
private[this] var friend = "gaoyuanyuan"
//辅助构造器
def showInfo(): Unit ={
println(id+"\t"+id1+"\t"+name+"\t"+gender+"\t"+age+"\t"+classno+"\t"+teacher+"\t"+friend)
}
}
object Scala_01_Class_1 {
def main(args: Array[String]): Unit = {
val s1 = new Student()
s1.showInfo()
}
}
反编译的内容如下
package com.qf.scala.day05;
import scala.Predef.;
import scala.collection.mutable.StringBuilder;
import scala.reflect.ScalaSignature;
import scala.runtime.BoxesRunTime;
public class Student
{
private int id = 0;
private final int id1 = 0;
private String name = null;
private char gender;
private int age = 0;
private String classno;
private final String teacher = "";
private String friend = "gaoyuanyuan";
public int id(){return this.id; } // get方法
public void id_$eq(int x$1) { this.id = x$1; } //set方法
public int id1() { return this.id1; }
public String name() { return this.name; }
public void name_$eq(String x$1) { this.name = x$1; }
public char gender() { return this.gender; }
public void gender_$eq(char x$1) { this.gender = x$1; }
public int age() { return this.age; }
public void age_$eq(int x$1) { this.age = x$1; }
private String classno() { return this.classno; }
private void classno_$eq(String x$1) { this.classno = x$1; }
private String teacher() { return this.teacher; }
public void showInfo()
{
Predef..MODULE$.println(new StringBuilder().append(id()).append("\t").append(BoxesRunTime.boxToInteger(id1())).append("\t").append(name()).append("\t").append(BoxesRunTime.boxToCharacter(gender())).append("\t").append(BoxesRunTime.boxToInteger(age())).append("\t").append(classno()).append("\t").append(teacher()).append("\t").append(this.friend).toString());
}
}
自定义属性的get和set方法
package com.qf.scala.day05
/**
* 重要提示:自己手动创建变量的getter和setter方法需要遵循以下原则:
* 1. 成员变量必须私有化
* 2. 字段属性名以“_”作为前缀,如定义:_XXX XXX换成自己的具体属性名
* 3. getter方法定义为:def XXX = _XXX
* 4. setter方法定义时,方法名为属性名去掉前缀,并加上后缀,后缀是:“XXX_=”,如例子所示
*/
class Teacher {
println("--------start----------")
var _name = "test"
private var _age = 0
private var _salary = 1000.0
// get方法
def name = _name
// set方法
def name_=(name:String): Unit ={
this._name = name
}
def age = _age
def age_=(age:Int): Unit ={
this._age = age
}
def salary = _salary
def salary_=(salary:Double): Unit ={
this._salary = salary
}
println("--------end----------")
def showInfo = println(s"这个人叫${_name},他的年龄是${_age},工资是${_salary}")
}
object Scala_02_Class_2{
def main(args: Array[String]): Unit = {
val t = new Teacher()
println(t.name) // 调用的是自定义的get方法
t.name = "michael" //调用的是自定义的set方法
t.age =23 //调用的是自定义的set方法
t.salary =1000000
t.showInfo
}
}
使用注解方式定义属性
package com.qf.scala.day05
import scala.beans.BeanProperty
object Scala_03_Class_3 {
def main(args: Array[String]): Unit = {
var m = new Manager()
m.setAge(23)
m.setAge(39)
m.showInfo()
m.name = "aaaaaa"
m.showInfo()
}
}
/**
* 当你将Scala字段标注为 @BeanProperty时,getter和setter方法会自动生成。
* 生成的方法和java规范一样,为 getXXX() setXXX()
*
* 注意:
* 1. 需要导包
* 2. 如果属性私有化, 那么提供的getter和setter方法都是私有的,只能在本类的内部访问
*/
class Manager {
@BeanProperty var name = "michael"
@BeanProperty var age = 25
def showInfo(): Unit ={
println(name+"\t"+age)
}
}
14.2 类体的构造器
scala中的构造器分为两类,一类是主构造器,位于类名处,一类是辅助构造器,位于类体中。
14.2.1主构造器的应用
主构造器位于类名处,如果不提供,scala会提供一个无参的主构造器
1)语法如下
class 类型名(形参列表){
.....
}
2)案例演示如下
package com.qf.scala.day05
object Scala_04_Constructor_1 {
def main(args: Array[String]): Unit = {
val e1 = new Employee("michael",23,'f',1001,"gaoyuanyuan","tom")
e1.showInfo
// gender 和 empno 用了var 和val 修饰 所以能被访问,但是empno是val修饰,只能赋值一次
e1.gender
//e1.empno = 1002 // 报错,是应该的
e1.showInfo
}
}
/**
* 1. 如果没有提供主构造器,系统默认提供一个无参主构造器
* 2. 参数列表的特点:
* -- 可以类似方法一样,添加普通的参数列表
* -- 也可以用var|val修饰
* -- 也可以使用修饰词修饰
* -- 还可以初始化
* 3. val|var声明的形参,都会在底层转成私有属性,并包含公有的getter/setter方法
* private var 声明的形参 会提供私有的getter/setter方法
* private val 声明的形参 会提供私有的getter方法
* 4. 如果没有val|var声明,生成的是对象私有属性,原因:因为在底层反编译时,没有相关的getter/setter方法
* 5. 当调用主构造器时,会执行整个类体
* 6. 调用主构造器时,如果形参有默认值,并且在列表的最后位置,那么调用时,可以不用给这个参数赋值
*/
class Employee(name:String,age:Int=100,var gender:Char,val empno:Int,protected var girlFriend:String,private val mgr:String,isMarry:Boolean=true){
println("-----start-----")
def showInfo = println(name+","+age+","+gender+","+empno+","+girlFriend+","+mgr+","+isMarry)
for(i <- 1 to 10){
println(i)
}
println("-----end------")
}
14.2.2辅助构造器的应用
scala的辅助构造器,可以像java一样,提供多个构造器,名字统一为this.
1)语法
class 类型名(形参列表){
//.......
def this(参数列表){
逻辑体
}
//......
}
举例
package com.qf.scala.day06.constuctor
object _02ConstructorDemo {
def main(args: Array[String]): Unit = {
val p1 = new Person() //调用主构造器获取对象
p1.showInfo
val p2 = new Person("michael") // 调用辅助构造器 this (String)
p2.showInfo
val p3 = new Person(name="michael",age=23) //调用辅助构造器 this (String,Int)
p3.showInfo
val p4 = new Person("michael",23,'f') //调用辅助构造器 this (String,Int,Char)
p4.showInfo
val p14 = new Person1()
p14.showInfo
}
}
/**
* 1. 辅助构造器的形参列表一定不能和主构造器的形参列表相同
* 2. 辅助构造器的第一行,必须使用this(有参传参)调用本类的主构造器或者使其他辅助构造器
* 方法签名: 方法名+参数类型列表 sum(int a,int b) 方法签名就是:sum int int
*/
class Person(){
var name:String = ""
var age:Int = 0
var gender:Char = 'm'
def this(name:String){
this() // 调用主构造器
this.name = name
}
def this(name:String,age:Int){
this(name) //调用this(String) 辅助构造器
this.age = age
}
//辅助构造器
def this(name:String,age:Int,gender:Char){
this(name,age)
this.gender = gender
}
def showInfo = println(s"这个人叫$name,年龄是$age,性别$gender")
}
class Person1(var name:String,var age:Int,var gender:Char){
var girlFriend:String = _
def this(){
this("大乔",23,'m') //调用主构造器
}
def this(name:String){
this() //调用辅助构造器
this.name = name
}
def this(name:String,age:Int){
this(name) //调用辅助构造器
this.age = age
}
def this(name:String,age:Int,gender:Char,girlFriend:String){
this(name,age) //调用辅助构造器
this.gender = gender
this.girlFriend = girlFriend
}
def showInfo = println(s"这个人叫$name,年龄是$age,性别$gender")
}
案例
package com.qf.scala.day05
object Scala_06_Constructor_3 {
def main(args: Array[String]): Unit = {
val c1 = new Cat(10) //调用一个参数的构造器
val c2 = new Cat(10,"黄色") //调用前两个参数的构造器
val c3 = new Cat(10,weight = 30,color="黄色")// 当将形参名写出来指定值时,怎么个顺序都可以
val c4 = new Cat(10,"橘黄色",4,"小花")
//将形参名称写出进行调用, 顺序无所谓
val d1 = new Dog(gender='f',id=1001,name="旺财");
}
}
/**
* 当主构造器的参数提供了初始化,那么就可以减少辅助构造器的定义
*/
class Cat(id:Int,color:String="白色",var weight:Int=2,var name:String="小白"){
}
class Dog(id:Int,name:String,gender:Char)
14.3 抽象类和特质
14.3.1 抽象类
14.3.1.1抽象类的特点
-1. 使用abstract修饰的类,就是抽象类
-2. 抽象类里可以没有抽象方法
-3. 有抽象方法的类,一定是抽象类
-4. 抽象方法指的是没有方法体的方法,默认使用abstract修饰
-5. 抽象类中可以提供抽象字段,即不用初始化的字段
-6. 抽象类不能实例化,没有任何意义 ,但是可以有构造器。
-7. 如果一个类继承了抽象类,那么必须要实现抽象方法和抽象字段,否则它也是一个抽象类
-8. 抽象方法和抽象字段不能使用 private、final修饰,不符合继承原理
案例演示
package com.qf.scala.day05
object Scala_07_Abstract_1 {
def main(args: Array[String]): Unit = {
var a:Animal = new Dog("黑色","藏獒")
a.noise
println(a.calculate(3, 4))
//a.sum(5,6) // 报编译错误,父类的私有方法、私有属性,子类不可见
}
}
abstract class Animal(color:String,kind:String){
//抽象类中可以提供抽象字段
var name:String
var color1 = "" // 也可以提供普通属性, 注意属性不能和主构造器的形参相同,因为主构造器的形参会被编译成类的属性
def this(color:String){
this(color,"")
}
def this(){
this("白色")
}
def showInfo = println(s"$color,$kind")
// 抽象方法,不提供方法体,不能提供abstract关键字,会默认提供
def noise
def calculate(a:Int,b:Int):Int
private def sum(a:Int,b:Int) = a+b
}
// 父类的主构造器只能被子类调用, 如何调用?子类的主构造器必须和父类的主构造器一模一样,就是调用
// 如果调用的是父类的无参构造器,那么子类的主构造器可以任意个参数
class Dog(color:String,kind:String) extends Animal(color, kind){
override var name: String = _
override def calculate(a: Int, b: Int): Int=a+b
override def noise: Unit = {
println("汪汪汪")
}
}
14.3.2 特质(trait)
scala的trait相当于java的接口
1. 关键词是trait
2. 不能实例化、是scala特殊的抽象类,不能提供构造器
3. 作用用来被子类实现(实现使用extends,不能使用implements)的,封装不同子类共同特征、共同行为
4. 定义期间,和抽象类一样,可以有普通字段,抽象字段、普通方法、抽象方法
5. 普通子类可以多继承trait, 第一个使用extends 其他的都要使用with,
但是,如果继承中,父类中有一个是普通类,那么一定要放在extends的后面
案例演示
package com.qf.scala.day05
object Scala_08_trait_1 {
}
/**
* 1. 关键词是trait
* 2. 不能实例化、是scala特殊的抽象类,不能提供构造器
* 3. 作用用来被子类实现(实现使用extends,不能使用implements)的,封装不同子类共同特征、共同行为
* 4. 定义期间,和抽象类一样,可以有普通字段,抽象字段、普通方法、抽象方法
* 5. 普通子类可以多继承trait, 第一个使用extends 其他的都要使用with,
* 但是,如果继承中,父类中有一个是普通类,那么一定要放在extends的后面
*/
trait A{
var id = 0 //普通字段
var name:String //抽象字段,必须指定类型
//def this(){} // 报错,因为不能提供构造器
def showInfo(){} // 普通方法
def showMessage // 抽象方法
}
trait B{}
trait C{}
class D{}
class E extends A with B with C{
override var name: String = _ //抽象字段的实现
override def showMessage: Unit ={} // 抽象方法的实现
}
// 因为D是普通类,只能放在extends后
class F extends D with A{
override var name: String = _
override def showMessage: Unit = ???
}
特殊的app特质
package com.qf.scala.day06.object1
/**
* 1. App 是一个trait 里面提供了一个main方法,程序的入口
* 2. 因此继承了App的object,就不需要再提供main方法,可以直接运行
*/
object _03Test03 extends App {
println("you are best")
}
14.4继承体系
scala编程语言,为了减少代码的冗余、以及提高代码的重复使用,以及扩展性,也引入继承的概念
-1. 继承的关键字是extends
-2. scala中的继承也是单继承
-3. 继承体系里,提供了抽象类、trait(特质)这种特殊类型:
--(1)抽象类用来封装子类的共同特质和行为的,用于被子类继承
--(2)trait和抽象类的作用基本一样。
-4. scala可以通过继承trait来达到多继承的目录,多继承的话需要使用extends和with
-5. 继承体系中,父类的所有属性和方法(除了构造器)都能被继承,只不过父类中的私有成员在子类中不可见(不可访问)
-6. 构造器不能被继承、只能被子类调用,只有主构造器才能调用父类构造器,而辅助构造器无法直接调用父类构造器
-7. 可以使用super.父类方法名,调用父类里的方法
-8. 子类可以重写父类里的方法,前面应该添加override 可以表示该方法是重写方法
-9. 子类中可以有自己独有的属性和方法,提高代码的扩展性(二次开发)
-10. final修饰词
--(1)修饰的类,无法被继承,
--(2)final修饰的方法和属性,不能被子类重写(override)
-11. 可以使用isInstanceOf[类型名] 和asInstanceOf[类型名],用于判断某一个变量所引用的对象是否属于某一个类型,并转换
案例演示
package com.qf.scala.day05
object Scala_14_extend_1 {
def main(args: Array[String]): Unit = {
val s1 = new Sub();
s1.showInfo
println(s1.isInstanceOf[M]) // 子类是父类型的一个实例 面向对象的多态特点
val m1 = s1.asInstanceOf[M] // 转成父类型变量引用
println(m1.sum(10, 9)) //向上造型时,编译期间看类型,运行期间看对象
}
}
abstract class M(a:Int,b:Int){
var name = ""
private var Account = ""
var idcard:String
def this(name:String){
this(1,2)
this.name = name
}
def sum(a:Int,b:Int) = a+b
def showInfo
}
trait X{
var gender:Char
def showMessage
}
// 子类的主构造器,要调用父类的构造器, 如何调用,需要在继承时,指定父类的某一个构造器
class Sub(a:Int,b:Int) extends M(a:Int,b:Int) with X {
var gender: Char = _
def showMessage: Unit = {}
var idcard: String = _
//重写父类的sum方法,改为减法
override def sum(a:Int,b:Int) = a-b
override def showInfo: Unit = {
//调用自己的sum方法
println(sum(5,1))
//调用父类的sum方法
println(super.sum(5,1))
}
def showSelf(message:String) = println(message)
}
14.5单例object
scala不能像Java那样定义static成员,我们想要定义类似于Java的static变量、static方法,就要使用到scala中的object
,其特点如下:
-1. scala的object是特殊的类型,被称为单例对象(在全局范围内,有且只有一个),
-2. 定义格式:只需要将定义类的格式中的class关键字替换成object即可
-3. object属于惰性加载的,当第一次被调用的时候才会创建到内存中。
-4. object没有形式参数,不能使用new关键字创建,直接使用其标识符即可
-5. object可以划分为两类:
--(1) 伴生对象 companion object
当object的名字与本文件中的某一个类的名字相同时,那么这个object就是该类的伴生对象,该类就是该object的伴生类。
类和它的伴生对象可以互相访问其私有成员。
--(2) 独立对象 standalone object
不与伴生类共享名称的单例对象称为独立对象。它可以用在很多地方,例如作为相关功能方法的工具类,或者定义Scala应用的入口点。
-6. object也可以定义在一个类的内部
standalone object
1)说明
不与伴生类共享名称的单例对象称为独立对象。它可以用在很多地方,例如作为相关功能方法的工具类,或者定义Scala应用的入口点。
2)案例演示
package com.qf.scala.day06.object1
import scala.collection.mutable.ArrayBuffer
object SingletonDemo {
def main(args: Array[String]): Unit = {
val s = SessionFactory
println(s.getSession)
println(s.getSession.size)
println(s.removeSession)
println(s.getSession.size)
}
}
object SessionFactory {
println("单利对象SessionFactory被执行")
var i = 5 // 计数器
// 存放Session对象的数组
val sessions = new ArrayBuffer[Session]()
// 向sessions里添加Session对象,最多添加5个Session对象
while (i > 0){
println("while被执行")
sessions.append(new Session)
i -= 1
}
// 获取Session对象
def getSession = sessions
// 删除Session对象
def removeSession {
val session = sessions(0)
sessions.remove(0)
println("session对象被移除" + session)
}
}
class Session{}
companion object
1)概念
正常情况下, 私有成员只能在本类的类体被访问,不可以在其他地方被访问。不过,伴生类和伴生对象的私有成员可以被对方访问到
测试
package com.qf.scala.day05
object Scala_11_companion_object {
def main(args: Array[String]): Unit = {
// 调用AccountInfo 私有方法,发现报错,即私有方法在其他的地方不能被访问到
// AccountInfo.newUniqueNumber()
// 调用AccountInfo 私有属性,发现报错,即私有属性在其他的地方不能被访问到
//AccountInfo.lastNumber;
//创建一个伴生类的对象(这个类的某一个实例) 要与伴生对象区分开
val a1 = new AccountInfo
//println(a1.name) // 因为name是AccountInfo的私有属性,因此不能在其他的地方被访问到
//a1.showInfo // 因为showInfo是AccountInfo的私有方法,因此不能在其他的地方被访问到
}
}
class AccountInfo {
/**
* 类的伴生对象的功能特性并不在类的作用域
* 以不能直接调用伴生对象里的方法和属性
* 需要使用伴生对象.
* */
//访问伴生对象的私有方法
var id = AccountInfo.newUniqueNumber()
//访问私有属性
var number = AccountInfo.lastNumber;
//自己的私有属性
private var name = "michael"
//对象私有属性,
private[this] var cardId = "123456"
// 自己的私有方法
private def showInfo = println(s"$name")
def getCardId = cardId
}
object AccountInfo {
private var lastNumber = 0 // 私有属性
private def newUniqueNumber() = { // 私有方法
lastNumber += 1;
lastNumber
}
def main(args: Array[String]) {
//相当于Java中的静态方法调用
println(AccountInfo.newUniqueNumber())
//获取伴生类对象
val a = new AccountInfo()
//访问伴生类对象里的私有属性
println(a.name)
//访问伴生类对象里的私有方法
a.showInfo
//println(a.cardId) // 报错,对象私有属性,即使是伴生对象,也不能共享,访问
println(a.getCardId)
}
}
有关伴生对象的apply方法
apply语法糖
-1. 在伴生对象里提供apply方法,可以简化伴生类的对象的创建,
-2. 也可以实现伴生类的单例模式
案例
package com.qf.scala.day05
/**
* 伴生对象里可以提供apply方法这个语法糖,可以更加方便伴生类的对象的创建
*/
object Scala_12_companion_object_2 {
def main(args: Array[String]): Unit = {
//构建Horse对象,正常应该调用主构造器,或者辅助构造器,但是该类有伴生对象,并提供了apply语法糖
var h1 = Horse("michael",23) //省略了new关键, 其实调用的是apply(String,Int)
var h2 = Horse("tom") //省略了new关键, 其实调用的是apply(String)
var h3 = Horse(33) //省略了new关键, 其实调用的是apply(Int)
}
}
class Horse(name:String,age:Int){
}
object Horse{
def apply(name:String,age:Int):Horse=new Horse(name,age)
def apply(name:String):Horse=new Horse(name,0)
def apply(age:Int):Horse=new Horse("",age)
}
package com.qf.scala.day05
/**
* 利用伴生对象里的apply方法 来设计伴生类的单例模式
* 1. 伴生类的主构造器需要私有化
* 2. 在伴生对象中提供一个成员变量,返回值类型为伴生类,采用懒汉式,那么就赋值null
* 3. 再在伴生对象中提供apply语法糖,给成员变量赋值
*
* 回顾java的单例模式:
* 1. 构造器私有化
* 2. 提供本类的静态成员变量
* 3. 提供一个静态方法,调用私有构造器创建对象,给静态成员赋值
*/
object Scala_13_companion_object_3 {
def main(args: Array[String]): Unit = {
//获取伴生类的对象
//val m1 = new Man("f","michael") //不能直接new,因为主构造器私有化了
val m1 = Man("michael") //利用伴生对象的语法糖apply获取伴生类的对象
m1.describe
}
}
//伴生类的主构造器私有化
class Man private(val sex: String, name: String) {
def describe = {
println("Sex:" + sex + "\t name:" + name)
}
}
//伴生对象,可以调用伴生类里的私有构造器
object Man {
var instance: Man = null
def apply(name: String) = {
if(instance == null) {
instance = new Man("男", name) // 因为是伴生对象,可以访问伴生类的私有成员
}
instance
}
}
14.6模式匹配
scala中,没有switch-case分支语句,但是提供了一个强大的模式匹配功能,可以进行判断一个变量是属于某一个值、类型等,可以匹配各种情况,比如变量的类型、集合的元素、有值或无值
scala的模式匹配,没有穿透效果,不需要提供break
1)scala的模式匹配语法
变量名 match{
case 固定值 => 分支逻辑
case 变量 => 分支逻辑
case 集合 => 分支逻辑
case _ => 分支逻辑 // 当上面的所有分支都没有匹配成功时,该分支必须添加,否则运行异常
}
回顾java的switch-case
switch(变量名){ //变量名的类型只能是 整型(byte,short,int,char),字符串,枚举
case 值1:
if(变量名 instanceOf 类型名){
}
case 值2: break;
default:
}
匹配字符串
package com.qf.scala.day06.match1
import scala.io.StdIn
object PatternDemo {
def main(args: Array[String]): Unit = {
var sign = 0
var input = StdIn.readLine()
//match 是表达式
input match {
case "+" => sign = 1
case "-" => sign = -1
//使用|分割多个选项
case "*"|"x" => sign = 2
//可以使用变量
case other => sign = 3 ;println("other:"+other)
//case _ 类似Java中的default
// 如果没有模式能匹配,会抛出MacthError
case _ => sign=4
}
println("sign = "+ sign)
}
}
匹配类型
注意事项
1. 进行类型匹配时,case 后必须要带变量,以及类型
2. 进行类型匹配时, 泛型会被编译器忽略。因此泛型是什么,无所谓
案例
package com.qf.scala.day05
import scala.util.Random
/**
* 如果想要进行类型匹配( 判断某一个对象是什么类型), 那么 在case后的变量名后必须指定类型。
*/
object Scala_16_match_Type extends App{
//定义一个数组,里面存储不同的类型的对象
val arr = Array("hs", 1, 2.0, 'a',List(1,2,3),(1,2,3,4,5),Map(("a",1),("b",2)))
// 随机一个下标,获取对应的元素
val obj = arr(Random.nextInt(7))
println(obj) // 偷偷的查看一下
obj match {
case x => println("xxxx === "+x)
case y:Int => println("Int:"+y)
case s: String => println("String:"+s.toUpperCase)
case _: Double => println("Double:"+Int.MaxValue)
case a:List[_] => println("List:"+a)
case m:Map[String,Int] => println("Map:"+m)
case t:(_) => println("tuple:"+t) // 变量名:_ 后的下划线表示任意类型 可以理解为是一个统配符 相当于 else 没有必要写case _
}
}
匹配数组
注意:匹配时,先检查长度是否一致,如果一致,就匹配每一个元素,是否对应。
package com.qf.scala.day05
object Scala_17_match_array extends App{
val arr1 = Array(1,2,3)
var res = arr1 match {
//匹配是否就是Array(0)这个数组
case Array(0) => "0"
//匹配是否arr1是否是1开头的,并且是三个长度的数组,如果是,x接受第二个元素,y接受第三个元素
case Array(1,x, y) => s"1开头的并且是三个长度的数组:$x $y"
//匹配任何以0开始的数组
case Array(0, _*) => "0..."
case _ => "something else"
}
println(res)
}
匹配集合
package com.qf.scala.day05
object Scala_18_match_list extends App{
val list = List(0,1,2)
val res2 = list match {
case 0 :: Nil => "0" // 匹配只有一个0元素集合,不是,匹配不上
case x :: y :: Nil => x + " " + y // 匹配两个长度的集合,也不是,匹配不上
case 0 :: tail => "0 ..." // 匹配head是0的集合,是,所以匹配成功
case _ => "something else"
}
println(res2)
}
匹配元组
package com.qf.scala.day05
object Scala_19_match_tuple extends App{
var pair = (1,2)
val res3 = pair match {
case (0, _) => "0 ..." // 匹配0开头的两个长度的元组
case (y, 0) => s"$y 0" // 匹配两个长度的第二个元素是0的元组
case _ => "neither is 0" // 匹配成功
}
println(res3)
}
14.7样例类型
样例类是scala中的一种特殊的类型,主要就是用于模式匹配
-1. 定义语法:case class 类型名(参数列表){}
-2. 主构造器中的参数列表默认是val声明
-3. 样例类会自动提供一个伴生对象,伴生对象里提供apply方法,用于简化样例类的对象创建
还提供了unapply方法,用于模式匹配
-4. 样例类也会默认提供toString、hashCOde、equals、copy等方法
-5. 样例类可以提供参数列表,因此样例类对象是多例的,如果获取的对象的值是一模一样,则是同一个对象
演示
package com.qf.scala.day06.match1
object _06TemplateClass {
def main(args: Array[String]): Unit = {
val point = Point(1, 2)
val anotherPoint = Point(1, 2)
val yetAnotherPoint = Point(2, 2)
if (point == anotherPoint) {
println(point + " and " + anotherPoint + " are the same.")
} else {
println(point + " and " + anotherPoint + " are different.")
}
if (point == yetAnotherPoint) {
println(point + " and " + yetAnotherPoint + " are the same.")
} else {
println(point + " and " + yetAnotherPoint + " are different.")
}
}
}
case class Point(x: Int, y: Int){}
package com.qf.scala.day05
object Scala_21_case_class_2 {
//定义样例类
abstract class Notification
case class Email(sender: String, title: String, body: String) extends Notification
case class SMS(caller: String, message: String) extends Notification
case class VoiceRecording(contactName: String, link: String) extends Notification
//基于样例类的模式匹配
def showNotification(notification: Notification): String = {
notification match {
case Email(email, title, _) =>
s"You got an email from $email with title: $title"
case SMS(number, message) =>
s"You got an SMS from $number! Message: $message"
case VoiceRecording(name, link) =>
s"you received a Voice Recording from $name! Click the link to hear it: $link"
}
}
def main(args: Array[String]): Unit = {
//获取一个SMS对象
val someSms = SMS("12345", "Are you there")
//获取一个VoiceRecording
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")
println(showNotification(someSms)) //结果:You got an SMS from 12345! Message: Are you there?
println(showNotification(someVoiceRecording)) //结果:you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123
}
}
unapply方法
办事对象中的apply方法是为了方便构建伴生类的实例
package com.qf.scala.day05
import scala.util.Random
/**
* 该案例演示 主要用来证明模式匹配执行的是unapply方法
*/
object Scala_22_case_class_unapply {
def main(args: Array[String]): Unit = {
//创建一个对象,此时会触发apply方法
val c = CustomerID("小白")
//模式匹配 用来判断Customer对象是否创建成功
c match{
/*当将CustomerID(name)写在case后时 就会默认触发伴生对象中unapply方法
name 值会被传递到 def unapply(name:String)方法中就相当于将name赋值给了形参的name
如果判断对象是否创建成功 -->只要返回Some(value)就认为创建成功后
开始执行 if判断语句
name.nonEmpty(不是空的) 返回some(value)
为空 返回None
case语句用来判断是否创建成功 */
case CustomerID(name) => println(name)
case _ => println("没有")
/**
* 通过以上的证明,可以直到样例类的模式匹配其实执行就是样例类自动提供的伴生对象里的unapply方法
*/
}
}
}
//伴生类
class CustomerID { }
//伴生对象
object CustomerID{
// apply方法的作用,就是简化对象的创建
def apply(name:String):String = name+Random.nextLong()
def unapply(name:String): Option[String] ={
if(name.nonEmpty) Some("小黑") else None
}
}
14.8 偏函数
简介
被包在花括号内没有match的一组case语句是一个偏函数,它是PartialFunction[A, B]的一个实例,A代表参数类型,B代表返回类型,常用作输入模式匹配
说的通俗易懂点,就是用于判断,输入的值,属于哪一个定义域(哪一个case分支)
1)定义语法
def 函数名: PartialFunction[String, String] = {
case "one" => 1
case "two" => 2
case _ => -1
}
解析:
-- 返回值必须是scala的特质PartialFunction[T, R]
-- 特质需要规定两个泛型,一个是输入的值的类型,一个是返回的值的类型
-- 函数体是一个没有match关键字的模式匹配,也就是case的组合
演示
package com.qf.scala.day05
import scala.util.Random
/**
* 偏函数的匹配规则:
* 1. 当输入的值,只要匹配到某一个分支,就不报错,并返回分支对应的值
* 没有匹配到任何一个分支就报错
* 2. 调用方式:
* --(1) 偏函数名(输入值)
* --(2) 偏函数名.apply(输入值)
* 3. 为了避免上述报错的情况,应该先使用下面的方法
* isDefinedAt(输入值)
* 该方法的作用:使用于判断输入的值,是否匹配某一个分支,如果匹配到某一个分支就返回true,否则返回false
*/
object Scala_23_PartialFunction_1 {
// 定义一个偏函数f1
// 比如 x 有三个定义域 (-无穷 -1] (-1,1] (1,正无穷),判断x属于哪一个定义域
def f1: PartialFunction[Int, String] = {
case x if(x>1) => " (1,正无穷) "
case a if(a <= -1) => "(-无穷 -1]"
//case _ => "(-1,1]"
}
def main(args: Array[String]): Unit = {
//判断某一个值属于哪一个定义域
val num = Random.nextInt(7)-3
println(num) // 偷看一下
if(f1.isDefinedAt(num)){ //判断num是否能匹配上某一个分支,如果能,则返回true
println(f1(num))
//另外一种调用方式,可以apply方法
println(f1.apply(num))
}else{
println("没有匹配上某一个分支")
}
}
}
15 泛型机制
java的泛型就是规定类型的
在定义类型或者是方法时,传入类型名称
与方法的形式参数比较: 方法的形式参数应该被传入一个具体的对象或者值。 而泛型应该被传入一个具体的类名。
案例演示
package com.qf.day05;
/**
* 定义Teacher的一些泛型机制, 可以更加灵活的应用各种类型
*/
public class Teacher<T,E> {
public static void main(String[] args) {
//创建一个Teacher对象
Teacher<String,Integer> t1 = new Teacher<>("michael",24);
//再创建一个其他类型的Teacher对象
Teacher<Double,String> t2 = new Teacher<>(3.14,"tom");
}
private T t;
private E e;
public Teacher(T t,E e){
this.t = t;
this.e = e;
}
public T getT(){
return t;
}
public void setT(T t){
this.t = t;
}
public E getE(){
return e;
}
public void setE(E e){
this.e = e;
}
}
案例
package com.qf.day05;
import java.util.ArrayList;
import java.util.List;
public class TestF {
public static void main(String[] args) {
//定义一个集合,用于存储P类型的对象
List<P> list = new ArrayList<>();
//存储三个p对象
list.add(new P());
list.add(new P());
list.add(new P());
//该集合中是否可以存储T和S对象 可以的 因为T和S是P的子类型 scala也遵循这样的规则
list.add(new T());
list.add(new S());
//该集合中是否可以存储Human对象 不可以 因为Human是P的父类型
//list.add(new Human());
/**
* 在java中 List<P> 和List<T> 是父子关系吗?????
*
* 答案: 不是 两者没有任何关系。
* 这就好比 一个纸箱A里装的是一堆P
* 一个纸箱B里装的是一堆T
* 两个纸箱A和B的关系 没有任何关系
*/
}
}
class Human{}
class P extends Human{}
class T extends P{}
class S extends P{}
泛型机制简要
在Scala中也可以使用类型参数来实现类和函数,这样的类和函数可以用于多种类型。比如Array[T] 你可以存放任意指定类型T的数据。类、特质、函数都可以有类型参数;将类型参数放在名字后面用方括号括起来
[T]: 普通用法,和java一样
[B <: A] upper bounds 上限或上界或上边界:B类型在传类型名时,可以向上追溯成A类型
A应该是B的祖宗类
[B >: A] lower bounds 下限或下界或下边界:B类型在传类型名时,可以向下追溯成A类型
A应该是B的子类型
[-A]:逆变,作为参数类型。是指实现的参数类型是接口定义的参数类型的父类
[+B]:协变,作为返回类型。是指返回类型是接口定义返回类型的子类
以下两个不常用
[B <% A] view bounds 视界:表示B类型要转换为A类型,需要一个隐式转换函数
[B : A] context bounds 上下文界定(用到了隐式参数的语法糖):需要一个隐式转换的值
泛型的基本使用
package com.qf.scala.day05
object Scala_24_generic_1 {
def main(args: Array[String]): Unit = {
val list = new MyList[Int]
list.add(1)
list.add(2)
list.add(3)
list.add(4)
list.showInfo
println
//去掉头部
val head = list.remove();
println("移除的元素是:"+head)
list.showInfo
}
}
class MyList[T]{
//定义一个属性,空集合
var elements:List[T] = Nil
//添加元素的方法
def add(t:T): Unit ={
elements = t::elements
}
//删除头元素的方法,并返回头元素
def remove(): T ={
val head = elements.head
elements = elements.tail
head
}
def showInfo: Unit ={
for(e <- elements) print(e+"\t")
}
}
[B <: A] 的应用
泛型参数B可以向上追溯到具体的A类型 。 也就是说参数B的具体类型可以是A的子类,也可以是A类型本身。定义期间A是具体类名
S1
S2 extends S1
S3 extends S2
S4 extends S3
S5 extends S4
class MyList[T <: S2]{ 规范泛型的具体类型只能是S2的子类型,向上追述最高只能是S2本身
}
T 可以是S5,S4,S3,S2, 就不能是S1
演示
package com.qf.scala.day05
/**
* 泛型的上边界的应用
* 语法:
* 类名[T <: A] A在定义期间是具体类名
*/
object Scala_25_generic_2 {
def main(args: Array[String]): Unit = {
val arr1 = new MyArray[Person] // 因为在定义期间规定了上边界是Person,因此泛型只能是Person或者是Person的子类型
arr1.add(new Person())
arr1.add(new Teacher) // 可以添加泛型的子类型
val arr2 = new MyArray[Student]
//arr2.add(new Person) 报错,不符合多态的知识点,只能添加本身类型或者子类型
val arr3 = new MyArray[Teacher]
//val arr1 = new MyArray[Human] // 不行,报错
//val arr1 = new MyArray[Int] // 不行报错
}
}
case class Human(){}
class Person() extends Human{}
class Student extends Person{}
class Teacher extends Person{}
/**
* 自定义一个数组类型,同时指定泛型的上界到Person
*/
class MyArray[T <: Person]{
var elements:List[T] = Nil
//添加元素
def add(t:T): Unit ={
elements = t::elements
}
//删除头元素元素,并返回头元素
def remove(): T ={
val head = elements.head
elements = elements.tail
head
}
def showInfo: Unit ={
for(e <- elements) print(e+"\t")
}
}
[B >: A]的应用
泛型参数B可以向下追溯到具体的A类型 。 也就是说参数B的具体类型是A的父类,或者是A类型
S1
S2 extends S1
S3 extends S2
S4 extends S3
S5 extends S4
class MyList[T >: S2]{ 规定泛型的具体类型只能是S2的父类型,向下追述最低只能是S2本身
}
T 可以是S1,S2,就不能是S3,s4,s5
package com.qf.scala.day05
/**
* 泛型的下边界的应用:
* 语法: [A >: B] 在定义期间 B是确定的某一个类型名称, A是类型参数
*/
object Scala_26_generic_3 {
def main(args: Array[String]): Unit = {
val arr1= new MyArr[X1] //因为下边界指定了X2,所以泛型只能使用X2或者是X2的父类型
arr1.add(new X1())
arr1.add(new X2())
val arr2= new MyArr[X2]
arr2.add(new X2())
//arr2.add(new X1) // 不可以,不符合面向对象的多态特征
arr2.add(new X3) // 符合面向对象的多态特征
arr2.add(new X4) // 符合面向对象的多态特征
//val arr3= new MyArr[X3] //不能使用X2的子类型
//val arr4= new MyArr[X4] //不能使用X2的子类型
}
}
//自定义一个数组类型,同时指定泛型的下界到Person
class MyArr[T >: X2]{
var elements:List[T] = Nil
//添加元素
def add(t:T): Unit ={
elements = t::elements
}
//删除头元素元素,并返回头元素
def remove(): T ={
val head = elements.head
elements = elements.tail
head
}
def showInfo: Unit ={
for(e <- elements) print(e+"\t")
}
}
case class X1(){}
class X2 extends X1
class X3 extends X2
class X4 extends X2
协变的应用
package com.qf.scala.day05
/**
* 泛型的协变 : 如果类A是类B的父类 那么List[A] 就是List[B]的父类
* 语法:[+A]
*/
object Scala_27_generic_4 {
def main(args: Array[String]): Unit = {
// 因为集合List本身就是协变的 源码:type List[+A] = scala.collection.immutable.List[A]
val list:List[O] = List[O]() //正常使用
//下面使用协变的方式指定泛型 , 由于P是0的子类型 所以 List[P]就是List[0]的子类型
val list1:List[O] = List[P]()
// 由于O是P的父类型 因为协变 List[O]是List[P]的父类, 所以不能把父类List[O]赋值给List[P]
// val list2 :List[P] = List[O]()
}
}
abstract class O {
def name: String
}
case class P(name: String) extends O
case class Q(name: String) extends O
object CovarianceTest extends App {
//定义参数类型List[O]
def printAnimalNames(o: List[O]): Unit = {
o.foreach { o =>
println(o.name)
}
}
val ps: List[P] = List(P("Whiskers"), P("Tom"))
val qs: List[Q] = List(Q("Fido"), Q("Rex"))
// 将List[P] 赋值给了List[O]的参数
printAnimalNames(ps)
// Whiskers
// Tom
//将List[Q] 赋值给了List[O]的参数
printAnimalNames(qs)
// Fido
// Rex
}
逆变的应用
在指定泛型时, 比如 泛型A 是泛型B的 父类, 那么就会造成List[A] 是 List[B] 的子类
package com.qf.scala.day05
/**
* 逆变的应用: 如果类A是类B的父类 那么List[A] 就是List[B]的子类
* 语法: [-A]
*/
object Scala_28_generic_5 {
def main(args: Array[String]): Unit = {
/**
* O是P的父类
* 如果是逆变 则会造成 Printer[O] 是 Printer[P]的 子类型
*/
val a1: Printer[P] = new Printer[O] // 多态的特点: 子类型可以赋值给父类的变量
//val a2: Printer[O] = new Printer[P] //报错: 因为违反了多态的特征
}
}
// 定义一个类,规定使用逆变
class Printer[-A] {}
package com.qf.scala.day07.generic
/**
* -A 表示逆变
* 在指定泛型时, 比如 泛型A 是泛型B的 父类, 那么就会造成List[A] 是 List[B] 的父类
*/
object _05Test_ {
def main(args: Array[String]): Unit = {
//逆变 研究的是 下面这五行之间的赋值是否正确
val animalPrinter:Printer[Cat] = new AnimalPrinter()
val catPrinter:Printer[Cat] = new CatPrinter()
val a:Printer[Animal] = new Printer[Animal]
val b:Printer[Cat] = new Printer[Animal]// 规定了逆变 所以Printer[Cat] 是 Printer[Animal] 父类
val c:Printer[Dog] = new Printer[Animal]
val cat = new Cat("小花")
//调用相关的方法
animalPrinter.print(cat)
catPrinter.print(cat)
}
}
class Printer[-A] {
def print(value: A): Unit={
println(" printter")
}
}
class AnimalPrinter extends Printer[Animal] {
override def print(animal: Animal): Unit =
println("The animal's name is: " + animal.name)
}
class CatPrinter extends Printer[Cat] {
override def print(cat: Cat): Unit =
println("The cat's name is: " + cat.name)
}
15高阶函数
15.1 匿名函数
1)定义
def 方法名(参数列表):返回值类型={}
val|var 函数名=(参数列表)=>{函数体}
val|var 函数名=(参数列表)=>函数体
val|var 函数名=(_:类型)+(_:类型)
val|var 函数名:((参数类型列表)=> 返回值类型) =((参数名称列表) =>{函数体})
val|var 函数名:(参数类型列表)=> 返回值类型 =(参数名称列表) =>函数体
val|var 函数名:(参数类型列表)=> 返回值类型 = _+_
2)匿名函数的演变
(参数列表)=>{函数体} <=匿名函数
(参数列表)=>函数体 <=匿名函数
(_:类型)+(_:类型) <=匿名函数
package com.qf.scala.day07.function
object _02NickNameFunction {
def main(args: Array[String]): Unit = {
//定义一个基本的函数
def f1(name:String):Unit = {
println(name)
}
//简化1,去掉花括号
def f2(name:String):Unit = println(name)
//简化2, 去掉返回值类型,因为scala会自动推断
def f3(name:String) = println(name)
//简化3: 去掉def 函数名 就变成了匿名函数,也就是lambda表达式, 需要注意的是,等号变=>
(name:String) => println(name)
}
}
小贴士:在方法内定义的方法和函数,都可以被称之为函数
匿名函数的应用和简化
package com.qf.scala.day06
object Scala_01_NickNameFunction_1 {
def main(args: Array[String]): Unit = {
//定义一个匿名函数
//(name:String) => println(name)
// 如果想要使用匿名函数,方式1,就是用一个变量引用他
val f1 = (name:String) => println(name)
//调用
f1("michael")
//函数可以作为参数传入其他方法或者函数中, 参数是一个函数,实质上传入的是操作,也就是这个逻辑运算模板,不是函数的逻辑运算后的返回值
def f2(op:String => Unit):Unit={
op("lucy") //调用传进来的函数,由于该函数是一个String形参,那么在调用时,要传入一个具体的String类型
}
val func:String => Unit = name => println(name) //这是一个具体的函数
f2(func) //调用f2时,就要传入一个具体的函数参数
//将匿名函数传入方法2中
/*
*执行过程:调用f2时,传入了一个匿名函数的执行逻辑,赋值给了f2的形参op,
* 然后执行f2的方法体,方法体是 op("lucy") ,而op接受的是一个匿名函数,
* 则将lucy赋值了匿名函数的形参name, 匿名函数的逻辑是println(name),所以最后就是打印lucy
*/
f2( (name:String) => println(name))
//简化1: 由于定义f2时,传入的函数的形参确定是String类型,因此匿名时可以省略
f2(y=>println(y)) //当只有一个参数时,可以省略花括号
//简化2: 由于匿名函数中的形参在函数体中就用了一次,所以可以简化成_
f2(println(_))
//简化3: 如果函数体确定调用的是另外一个函数体,那么_也可以省略
f2(println)
// 练习: 定义一个方法,函数作为参数,函数是计算两个int的形参的结果,返回值int
val f10 = (a:Int,b:Int)=> a+b
def f3(op:(Int,Int)=>Int):Int= {
op(1, 2)
}
//调用f3时,要传入一个具体函数,或者是匿名函数
println("f10:" +f3(f10))
//传入匿名函数:
println(f3((x, y) => x + y))
println(f3((x, y) => x - y))
println(f3((x, y) => x * y))
println(f3(_ / _)) //可以简化成_
}
}
15.2高阶应用
函数除了被直接调用,执行具体的逻辑之外, 因为函数在scala中是一等公民,所以也可以由以下用途
1. 作为一个值,赋值给其他变量
2. 可以作为方法或者是函数的参数
3. 可以作为方法或者函数的返回值
案例:作为值进行传递
package com.qf.scala.day06
object Scala_02_functionAsValue_1 {
def main(args: Array[String]): Unit = {
//定义一个函数
val f1 = (a:Int,b:Int) => a+b
/**
* 将f1这个函数当成普通的值,赋值给其他变量a1
*
* 原理:因为函数本身是一等公民,则在内存中就是一个对象,则具有内存地址。
* 变量名其实存储的就是该对象的地址信息。
* 即: f1存储的就是匿名函数 (a:Int,b:Int) => a+b的地址
* val a1 = f1 这个操作,其实就是将f1的地址信信息再次赋值给了a1
*
* 此时:a1和f1指向的都是同一个匿名函数
*/
val a1 = f1
//调用a1
println(a1(10, 20))
}
}
函数作为参数传递
package com.qf.scala.day06
object Scala_03_functionAsParameter_1 {
def main(args: Array[String]): Unit = {
//定义一个函数:
val f1 = (a:Int,b:Int) => a-b
/**
* 定义一个参数是函数的方法, 设计两个参数列表,相对来说比较灵活,因为第一个参数列表
* 可以从外界获取任意值。op函数就可以对参数a和b做处理操作
*/
def m1(a:Int,b:Int)(op:(Int,Int)=>Int): Unit ={
var result = op(a,b)
println("result:"+result)
}
//调用m1, 给两个参数列表赋值,第一个参数列表赋值两个int的值,第二个参数列表赋值一个函数
m1(100,200)(f1)
//调用m1时,也可以传入一个匿名函数
m1(100,200)((a,b)=>a+1+b)
}
}
函数作为返回值
package com.qf.scala.day06
object Scala_04_functionAsReturn_1 {
def main(args: Array[String]): Unit = {
/**
* 函数作为返回值类型的应用 , 写法和使用冒号定义函数的第二部分一样
*
* 如: val f1: (Int,Int)=>Int = (a,b) =>a+b
*/
def m1(a:Int,b:Int):(Int,Int)=>Int={
val f1 = (x:Int,y:Int) => x + y
//f1(2,3) // 此时如果是最后一行,则返回的是f1(2,3)经过计算的结果
f1 // 此时如果是最后一行, 则返回的是f1这个函数的执行模板
}
/**
* 练习: m2返回的是一个函数: m3 _
* m3返回的也是一个函数 m4 _
*/
def m2(name:String):(Int=>((Int,Int)=>Boolean)) = {
def m3(num:Int):((Int,Int)=>Boolean)={
def m4(a:Int,b:Int):Boolean={
a>b
}
m4 _
}
m3 _
}
/**
* 调用:
* 类似于数学公式
* 公式1: f(z) = x + y + z
*
* 给x赋值 10 ,返回一个新的公式2
* 公式2: f(z) = 10 + y + z
*
* 给y赋值 100 ,返回一个新的公式3
* 公式3: f(z) = 10 + 100 + z
*
* 给z赋值1000 返回具体值
* f(z) = 10 + 100 + 1000
*
*
* 调用时:可以一个一个赋值,也可以一次性都赋值
* f(z) = x + y + z
* f(z) = 1 + 2 + 3
*
*
*/
val f10 = m2("michael") // 返回的是一个函数, 其实就是m3
println("f10的函数样子:"+f10)
val f11 = f10(20) //返回的是一个函数, 其实就是m4
println("f11的函数样子:"+f11)
val bool = f11(1, 3)
println(bool)
//一次性赋值
val bool1 = m2("tom")(100)(4, 3)
println(bool1)
}
}
练习题
package com.qf.scala.day07.function
object _07FunctionExercise {
def main(args: Array[String]): Unit = {
/**
* 定义f0函数,三个参数,分别为Int的x,String的y,Array[Int]的z,
* 逻辑体:当x为偶数,y为Helloworld, z的长度使5时,返回true,其他情况返回false
*/
def f0(x:Int,y:String,z:Array[Int]): Boolean ={
x%2==0&&y.equals("helloworld")&&z.length==5
}
println(f0(2, "helloworld", Array(1, 2, 3, 4, 5)))
/**
* 定义一个函数f1, 参数Int的x, 返回一个函数,暂定f2
* f2这个函数, 参数是String类型的y, 返回一个函数,暂定f3
* f3这个函数, 参数是Array[Int]的z, 返回boolean
* f3里的逻辑如下: 当x为偶数,y为Helloworld, z的长度使5时,返回true,其他情况返回false
*
*/
def f1(x:Int):String=>Array[Int]=>Boolean={
y => z => x%2==0&&y.equals("helloworld")&&z.length==5
}
println(f1(1)("helloworld")(Array(1, 2, 3, 4, 5)))
}
}
15.3闭包函数
当一个函数应用到外部的变量时,那么这个函数和所在的环境都会被打包成一个对象,存在堆空间里,这个函数就叫闭包函数。
package com.qf.scala.day06
/**
* 闭包函数的概念: 当函数体引入了一个或N个外部的变量是,该函数就是闭包函数
*
* 通常情况下:函数体内只有自己的形参和内部定义的局部变量
*/
object Scala_06_ClosePacketFunction {
def main(args: Array[String]): Unit = {
/**
* 针对于m1这个函数来说,函数体内的变量都是自己的,
* 有形参参数a 和 b 还有局部定义的x,y
*
* 因为没有用到外部的变量,所以不是闭包函数
*/
def m1(a:Int,b:Int):Int ={
val x = a + 1
val y = b + 1;
x + y
}
/**
* 针对于m2这个函数,除了用了自己的a,b,x,y之外,还用到了外部的m和n
* 所以,该函数一个是闭包函数。
*
* 为什么叫闭包函数:
* 就是因为在应用时,将外部的变量和本身的函数体打包到一起,存储在堆内存中
*/
val m = 10
val n = 20
def m2(a:Int,b:Int):Int={
val x = a + m
val y = b + n
x + y
}
}
}
def f1(x:Int):String=>Array[Int]=>Boolean={
y => z =>x%2==0&&y.equals("helloworld")&&z.length==5
}
f1是一个闭包函数,因为里层的匿名函数应用到了外部的变量
15.4柯里化函数
1. 概念:
参数列表有多个变量的函数,转成多个参数列表的函数,每个参数列表都使一个独立的变量,这样的函数就是柯里化函数
2. 运行原理:
-- 多个参数列表,对应多个函数,每个函数都是一个形参
-- 第一个函数,接收的是第一个参数的值,返回第二个函数
-- 第二个函数,接收的使第二个参数的值,返回第三个函数
..........
package com.qf.scala.day06
/**
* 柯里化函数:
* 多参数列表的方法或函数,就是柯里化函数
*
* 调用过程中:如果只给一个参数列表赋值,则返回的还是一个函数:
*
*/
object Scala_07_CurrieFunction {
def main(args: Array[String]): Unit = {
/**
* 定义一个普通的函数,只不是是多个参数
*/
def f1(a:Int,b:Int,c:Int,d:Int):Int={
a+b+c+d
}
/**
* 将上一个函数转成柯里化函数:
* 就是将多个参数,分别转到一个参数列表中
* 即: 将具有多个参数的一个参数列表,转成多个参数列表
*/
def f2(a:Int)(b:Int)(c:Int)(d:Int):Int={
a+b+c+d
}
//调用f1
println(f1(1, 2, 3, 4))
//调用f2,可以一次性传入参数
println(f2(1)(2)(3)(4))
/**
* 调用f2时,也可以一次赋值一个值
*
* 类似于: 数学公式
* f = x + y + m + n
* 第一次 给x赋值 依然返回一个函数
* f = 1 + y + m + n
* 第二次 给y赋值 依然返回一个函数
* f = 1 + 2 + m + n
* 第三次 给m赋值 依然返回一个函数
* f = 1 + 2 + 3 + n
* 第四次 给n赋值 返回的就是一个具体的值 10
* 10 = 1 + 2 + 3 + 4
*/
val intToIntToIntToInt:Int=>(Int=>(Int=>Int)) = f2(1) _
val intToIntToInt:Int=>(Int=>Int) = intToIntToIntToInt(2)
val intToInt:Int=>Int = intToIntToInt(3)
val i:Int = intToInt(4)
println(i)
}
}
16 隐式操作
简介
1. 隐式操作的强大功能
-- (1) 帮助两个类型进行转换
-- (2) 帮助给方法的形参自动赋值
2. 隐式操作的时机
scala的编译器,会在第一次编译失败时,寻找可以帮助代码再次编译成功的隐式信息
注意:java的编译器,如果第一次编译失败,就失败了。
3. 寻找的隐式信息的范围:
-- 先在该代码所在的作用域内寻找,
-- 如果找不到,会向外层寻找。如果最终没有找到,则编译失败
4. 隐式信息可以分为如下几种,关键字是implicit
-- 隐式类型
-- 隐式函数
-- 隐式参数
5. 同一个作用域里,不能有两个功能一样的隐式参数或者隐式类
隐式类型的应用
作用:就是帮助某一种类型转换成另外一种类型
package com.qf.scala.day07.implicit1
import java.io.File
import scala.io.Source
object _01ImplicitDemo{
def main(args: Array[String]): Unit = {
//定义隐式类,可以把File转换成定义的隐式类RichFile
implicit class RichFile(from:File){
def read:String = Source.fromFile(from.getPath).mkString
}
//使用隐式类做已有类的动能的扩展
val contents = new File("D:/input/dept.txt").read
println(contents)
}
}
package com.qf.scala.day06
import java.io.File
import scala.io.Source
/**
* Scala的编译器在寻址隐式信息时,除了临近的作用域外,只能在类体里找。
*/
class RichFile(val f: File) {
def read = Source.fromFile(f.getPath).mkString
}
object RichFile {
//隐式转换方法
implicit def file2RichFile(from: File) = new RichFile(from)
}
object Scala_09_implicitClass_2 {
// 需要引入到该作用域
import RichFile._
def main(args: Array[String]): Unit = {
println(new File("c://words.txt").read)
}
}
隐式函数的应用
package com.qf.scala.day06
object Scala_10_implicitFunction {
def main(args: Array[String]): Unit = {
val a:Long = 10
/**
* 第一次编译是:一定报错,因为Long不能转成Int
* 然后编译器就会在作用域内找相关的隐式信息,发现有一个隐式函数m1
* 正好形参是long类型,返回值是Int, 逻辑体正确,就使用该函数进行隐式操作
* 再次编译时,通过。
*
*/
val b:Int = a
println(b)
}
implicit def m1(x:Long):Int = x.toInt
}
隐式参数的应用
package com.qf.scala.day06
object Scala_11_implicitParameter {
implicit val a = 10
def main(args: Array[String]): Unit = {
def m1(a:Int)(implicit b:Int)={
a+b
}
/**
* 正常调用:应该给a和b同时赋值。
* 但是b是一个隐式参数,因此,如果不给b赋值时,scala编译器会报错
* 报错后,scala就会在作用域内寻找是否有一个隐式的Int类型的值,发现
* 有一个隐式的a,整好是Int类型,则将a的10赋值给b.
*
* 编译成功
*/
println(m1(1))
}
}
导包的操作
1. scala在使用类型时,有些类需要导包,有些类不需要导包
-- 导入具体类名
import scala.collection.mutable.ListBuffer
-- 导入包下的所有的类
import scala.collection.mutable._
-- 导入多个类
import scala.collection.mutable.{ListBuffer,ArrayBuffer}
2. 不需要导包的类,其实已经隐式导入进来了,比如
import java.lang._ // in JVM projects, or system namespace in .NET
import scala._ // everything in the scala package
import Predef._ // everything in the Predef object
3. 导包语句,可以在任何位置
16 通信机制
-- Actor: Scala的线程并发模型
-- AKKA: 是Scala低版本的基于Actor的通信机制
-- Netty: 是目前Scala版本的默认通信机制
将要学习的spark 现在也用的是netty通信机制。