Scala思维导图
2022-04-01 10:24:57 73 举报
AI智能生成
Scala思维导图
作者其他创作
大纲/内容
运行第一个Scala程序
Scala介绍
Scala介绍
Scala是一门以Java虚拟机 (JVM)为运行环境的并将面向对象和函数式编程的最佳特性结合在一起的静态类型编程语言
Scala特点
1.Scala是一门多范式的编程语言,支持面向对象和函数式编程
2.Scala源代码(.Scala)会编译成Java字节码(.class)然后运行在JVM之上,并可以调用现有的Java类库,实现两种语言的无缝对接
3.Scala但作为一门语言来看,非常的简洁高效,不支持三元运算,++,--
基本语法
object helloscala{
def main(arg:Array[String]):Unit={
printlin("hello,scala")
}
}
def main(arg:Array[String]):Unit={
printlin("hello,scala")
}
}
①object表示一个伴生对象,可以理解成一个对象
②helloscala是对象名,他底层对应的类名是helloscala$
③当编写object helloscala时,底层会生成两个.class文件,分别时helloscala.class和helloscala$.class
(Java程序编写时只会生成一个.class文件)
④def表示一个方法,是一个关键字
⑤main表示方法的名字,是程序的入口
⑥args:Array[String]表示形参,Scala特点是参数名在前,类型在后
⑦Array[String]表示字符串类型的数组
⑧Unit=表示该函数的返回值为空(void)
②helloscala是对象名,他底层对应的类名是helloscala$
③当编写object helloscala时,底层会生成两个.class文件,分别时helloscala.class和helloscala$.class
(Java程序编写时只会生成一个.class文件)
④def表示一个方法,是一个关键字
⑤main表示方法的名字,是程序的入口
⑥args:Array[String]表示形参,Scala特点是参数名在前,类型在后
⑦Array[String]表示字符串类型的数组
⑧Unit=表示该函数的返回值为空(void)
Scala plugin安装
Scala plugin下载地址:https://plugins.jetbrains.com/plugin/1347-scala/versions
1.打开idea
2.点击左上角file>settings
3.点击plugins
4.在搜索框中输入scala
5.如果没搜到则表示没有安装scala plugin
6.点击install plugin from disk
7.找到插件路径并点击ok
8.点击右下角apply
9.点击ok
10.重启idea
11.如果安装成功,则会在plugin中搜索到scala
2.点击左上角file>settings
3.点击plugins
4.在搜索框中输入scala
5.如果没搜到则表示没有安装scala plugin
6.点击install plugin from disk
7.找到插件路径并点击ok
8.点击右下角apply
9.点击ok
10.重启idea
11.如果安装成功,则会在plugin中搜索到scala
用idea运行Scala程序
1.点击file>new>project...
2.新建一个maven工程
3.到main下创建一个Scala文件夹
4.将Scala文件夹source成root(根目录)
①右键文件夹
②选择mark directory as
③选择source root
5.导入Scala framework
6.右键项目名(Scala)
7.点击add framework support...
8.从右边的选择栏中选择Scala
9.点击Use library 右侧的create
10.点击底侧的browse
11.找到Scala的安装主目录点击ok即可
12.创建Scala object
2.新建一个maven工程
3.到main下创建一个Scala文件夹
4.将Scala文件夹source成root(根目录)
①右键文件夹
②选择mark directory as
③选择source root
5.导入Scala framework
6.右键项目名(Scala)
7.点击add framework support...
8.从右边的选择栏中选择Scala
9.点击Use library 右侧的create
10.点击底侧的browse
11.找到Scala的安装主目录点击ok即可
12.创建Scala object
Scala语法基础
数据类型
数据类型一览
①Byte 8位有符号补码整数。数值区间为 -128 到 127
②Short 16位有符号补码整数。数值区间为 -32768 到 32767
③Int 32位有符号补码整数。数值区间为 -2147483648 到 2147483647
④Long 64位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807
⑤Float 32 位, IEEE 754 标准的单精度浮点数
⑥Double 64 位 IEEE 754 标准的双精度浮点数
⑦Char 16位无符号Unicode字符, 区间值为 U+0000 到 U+FFFF
⑧String 字符序列
⑨Boolean true或false
⑩Unit 表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。
11.Null null 或空引用
12.Nothing Nothing类型在Scala的类层级的最底端;它是任何其他类型的子类型。
13.Any Any是所有其他类的超类
14.AnyRef AnyRef类是Scala里所有引用类(reference class)的基类
②Short 16位有符号补码整数。数值区间为 -32768 到 32767
③Int 32位有符号补码整数。数值区间为 -2147483648 到 2147483647
④Long 64位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807
⑤Float 32 位, IEEE 754 标准的单精度浮点数
⑥Double 64 位 IEEE 754 标准的双精度浮点数
⑦Char 16位无符号Unicode字符, 区间值为 U+0000 到 U+FFFF
⑧String 字符序列
⑨Boolean true或false
⑩Unit 表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。
11.Null null 或空引用
12.Nothing Nothing类型在Scala的类层级的最底端;它是任何其他类型的子类型。
13.Any Any是所有其他类的超类
14.AnyRef AnyRef类是Scala里所有引用类(reference class)的基类
数据类型说明
1.Scala的数据类型与Java的基本一样
2.在Scala中有一个根类型Any,它是是所有类的父类
3.Scala中一切皆为对象,分为两大类AnyVal(值类型),AnyRef(引用类型)
3.Null类型是Scala的特别类型,它只有一个值null,它是bottom class,是所有AnyRef类型的子类
4.Nothing类型也是bottom class,它是所有类的子类,在开发过程中可以将Nothing类型的值返回给任意变量或者函数,这里抛出异常使用很多
5.在Scala中仍然遵守,低精度的值向高精度的值自动转换(implictit conversion)隐式转换
数据类型的强制转换
1.当进行数据的从大到小时,就需要使用强制转换
2.强转符号指针对最近的操作数有效,往往会使用小括号提升优先级
3.char类型可以保存int的常量值,但是不能保存int的变量值
4.byte和short类型在进行运算时,当做int类型处理
值类型和String类型的转换
二.基本数据类型转string类型
语法:在基本数据类型的值后+""即可
例子:String str1 = true+""
String str2 = 4.5+""
String str3 = 100+""
一.介绍
在程序开发中,经常需要将基本数据类型转换成string类型,或者将string类型转换成基本数据类型
在程序开发中,经常需要将基本数据类型转换成string类型,或者将string类型转换成基本数据类型
二.基本数据类型转string类型
语法:在基本数据类型的值后+""即可
例子:String str1 = true+""
String str2 = 4.5+""
String str3 = 100+""
三.string类型转换成基本数据类型
语法:通过基本数据类型的String的toxxx方法即可转换
例子:s1.toInt
s1.toFloat
s1.toDouble
s1.toByte
s1.toLong
s1.toShort
语法:通过基本数据类型的String的toxxx方法即可转换
例子:s1.toInt
s1.toFloat
s1.toDouble
s1.toByte
s1.toLong
s1.toShort
四。转换中可以存在的细节
1.在将string类型的转成基本数据类型时,要确保string类型能够转换成有效的数据。比如我们可以将“123”转换成一个整数,但是不能将“hello”转换成一个整数。
2.类似于将字符串“12.5”转成Int,此时会报错,因为在Scala中,不是将小数点后的数据进行截取,而是会抛出异常,所有“12.5”只能转换成Double或Float
1.在将string类型的转成基本数据类型时,要确保string类型能够转换成有效的数据。比如我们可以将“123”转换成一个整数,但是不能将“hello”转换成一个整数。
2.类似于将字符串“12.5”转成Int,此时会报错,因为在Scala中,不是将小数点后的数据进行截取,而是会抛出异常,所有“12.5”只能转换成Double或Float
标识符命名规则
一.标识符概念
1.Scala对各种变量,方法,函数等命名时使用的字符序列称为标识符
2.凡是自己可以取名字的地方都叫标识符
1.Scala对各种变量,方法,函数等命名时使用的字符序列称为标识符
2.凡是自己可以取名字的地方都叫标识符
二.标识符的命名规则
Scala中的标识符声明基本和Java一致,但是细节上会有些变化
1.首字母为字母,后续可以接字母数字,美元符号,也可以接下划线
2.数字不可作为开头
3.首字符为操作符(比如+-*/),后续字符也要跟操作符,至少一个
4.操作符(比如+-*/)不能在标识符的中间或最后
5.关键字也是可以作为标识符,但是需要加上反引号,比如(`true`)
Scala中的标识符声明基本和Java一致,但是细节上会有些变化
1.首字母为字母,后续可以接字母数字,美元符号,也可以接下划线
2.数字不可作为开头
3.首字符为操作符(比如+-*/),后续字符也要跟操作符,至少一个
4.操作符(比如+-*/)不能在标识符的中间或最后
5.关键字也是可以作为标识符,但是需要加上反引号,比如(`true`)
三.标识符命名的细节
1.在Scala中Int,Float不是关键字,而是预定义标识符。所以Int,Float可以作为标识符,但是不推荐
2.单独的下划线不能作为标识符(_)。因为_有很多其他的作用。
1.在Scala中Int,Float不是关键字,而是预定义标识符。所以Int,Float可以作为标识符,但是不推荐
2.单独的下划线不能作为标识符(_)。因为_有很多其他的作用。
四.标识符命名注意事项
1.包名:尽量采取有意义的包名,简短,有意义
2.变量名,函数名,方法名尽量采用驼峰法
1.包名:尽量采取有意义的包名,简短,有意义
2.变量名,函数名,方法名尽量采用驼峰法
运算符
二.算术运算符中的细节
1./除的运算规则:a/b的结果去整数部分,比如3/2=1
2.%取余的运算规则:a%b=a-a/b+b
一.运算符介绍
运算符是一种特殊的符号,用来表示数据的运算,赋值和比较等
1.算术运算符 + - * / %
2.赋值运算符 =
3.关系运算符 < > = 等
4.逻辑运算符 与(&&) 或(||) 非(!)
5.位运算符
运算符是一种特殊的符号,用来表示数据的运算,赋值和比较等
1.算术运算符 + - * / %
2.赋值运算符 =
3.关系运算符 < > = 等
4.逻辑运算符 与(&&) 或(||) 非(!)
5.位运算符
二.算术运算符中的细节
1./除的运算规则:a/b的结果去整数部分,比如3/2=1
2.%取余的运算规则:a%b=a-a/b+b
三.关系运算符中的细节
1.如果两个浮点数进行比较,应当保证数据类型一致
1.如果两个浮点数进行比较,应当保证数据类型一致
键盘输入语句
1.从控制台接受用户的信息
import scala.io.StdIn
println("请输入姓名")
var name = StdIn.readLine
import scala.io.StdIn
println("请输入姓名")
var name = StdIn.readLine
流程控制
流程控制有三种
1.顺序控制
2.条件(分支)控制
3.循环控制
1.顺序控制
2.条件(分支)控制
3.循环控制
条件控制
1.Scala条件控制和Java基本一样,Scala中没有三元表达式
2.注意:Scala用match替代了switch语句,而且更加强大
1.Scala条件控制和Java基本一样,Scala中没有三元表达式
2.注意:Scala用match替代了switch语句,而且更加强大
循环控制
for循环
1.循环守卫
①.基本案例
for(i <- 1 to 3 if i!=2){
println("")
}
②.循环守卫,即循环保护式(也称条件判断式),保护式为true则进入 循环体内部,为false则跳过,类似于continue
2.引入变量
①基本案例
for(i <- 1 to 3;j=4-1){
print(j+" ")
}
3.循环嵌套
①基本案例
for(i <- 1 to 3; j <- 1 to 3){
print(i,j)
}
②以上使用方式等价于
for(i <- 1 to 3){
for(j <- 1 to 3){
print(i,j)
}
}
4.循环返回值
①基本案例
val res=for(i <- 1 to 10) yield i
print(res)
②基本案例说明
将遍历过程中处理的结果返回到一个新的vector集合中,使用yield关键字,yield后可以接收一段代码块
5.循环控制步长
①基本案例
for(i <- range(1,10,2)){
print(i)
}
说明range(1,10,2) 1代表start值,10代表end值,2代表步长
6.循环控制步长,方法二
①基本案例
for(i <- 1 to 10 if i%2==1){
print(i)
}
说明:利用循环守卫控制步长
①.基本案例
for(i <- 1 to 3 if i!=2){
println("")
}
②.循环守卫,即循环保护式(也称条件判断式),保护式为true则进入 循环体内部,为false则跳过,类似于continue
2.引入变量
①基本案例
for(i <- 1 to 3;j=4-1){
print(j+" ")
}
3.循环嵌套
①基本案例
for(i <- 1 to 3; j <- 1 to 3){
print(i,j)
}
②以上使用方式等价于
for(i <- 1 to 3){
for(j <- 1 to 3){
print(i,j)
}
}
4.循环返回值
①基本案例
val res=for(i <- 1 to 10) yield i
print(res)
②基本案例说明
将遍历过程中处理的结果返回到一个新的vector集合中,使用yield关键字,yield后可以接收一段代码块
5.循环控制步长
①基本案例
for(i <- range(1,10,2)){
print(i)
}
说明range(1,10,2) 1代表start值,10代表end值,2代表步长
6.循环控制步长,方法二
①基本案例
for(i <- 1 to 10 if i%2==1){
print(i)
}
说明:利用循环守卫控制步长
while循环
二.while语句循环
var i = 0
while (i < 10) {
println("Go,China!" + i)
i += 1
}
var i = 0
while (i < 10) {
println("Go,China!" + i)
i += 1
}
do-while循环
三.do while循环
var j = 1
do {
println("中国牛逼!" + j)
j += 1
} while (j < 10)
}
var j = 1
do {
println("中国牛逼!" + j)
j += 1
} while (j < 10)
}
循环中断
break
1.基本案例
import util.control.Breaks._
var n=10
breakable(
while (n <=20){
n+= 1
if (n == 18){
break()
}
}
)
import util.control.Breaks._
var n=10
breakable(
while (n <=20){
n+= 1
if (n == 18){
break()
}
}
)
continue
Scala内置控制结构特地的去掉了break和continue,是为了更好的适应函数化编程,推荐使用函数的风格解决break和continue的功能,而不是一个关键字
二.continue
continue可以利用循环守卫实现
二.continue
continue可以利用循环守卫实现
scala常用包
1.scala.collection 该包和子包,包含了Scala集合架构
①scala.collection.immutable 不可变的顺序数据结构
②scala.collection.mutable 可变的顺序数据结构
③scala.collection.concurrent 可变的并发数据结构
④scala.collection.parallel.immutable 不可变的并行数据结构
⑤scala.collection.parallel.mutable 可变的并行数据结构
2.scala.concurrent 并行编程的基础类
3.scala.io 输出和输入操作
4.scala.math 基本数学函数和附加数字类型 bigInt等
5.scala.sys 以其他进程和操作系统进行交互
6.scala.util.matching 常规操作包
①scala.collection.immutable 不可变的顺序数据结构
②scala.collection.mutable 可变的顺序数据结构
③scala.collection.concurrent 可变的并发数据结构
④scala.collection.parallel.immutable 不可变的并行数据结构
⑤scala.collection.parallel.mutable 可变的并行数据结构
2.scala.concurrent 并行编程的基础类
3.scala.io 输出和输入操作
4.scala.math 基本数学函数和附加数字类型 bigInt等
5.scala.sys 以其他进程和操作系统进行交互
6.scala.util.matching 常规操作包
Scala函数式编程
学习过程
函数式编程基础
一.函数式编程基础
1.函数定义/声明
2.函数运行机制
3.递归
4.过程
5.惰性函数和异常
1.函数定义/声明
2.函数运行机制
3.递归
4.过程
5.惰性函数和异常
函数式编程高级
二.函数式编程高级
1.值函数(函数字面量)
2.高阶函数
3.闭包
4.应用函数
5.柯里化函数,抽象控制...
1.值函数(函数字面量)
2.高阶函数
3.闭包
4.应用函数
5.柯里化函数,抽象控制...
函数式编程介绍
三.函数的递归
1.在执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
2.函数的局部变量是独立的,不会相互影响
3.递归必须向退出递归条件逼近,否则就无限递归了
4.当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用就返回给谁
一.方法,函数,函数式编程和面向对象概念
1.在Scala中,方法和函数几乎可以等同(比如它们的定义,使用,运行机制都是一样的),只是函数的使用方法更加灵活多样 【方法转函数】
2.函数式编程是从编程方式(范式)的角度来谈的。可以理解为,函数式编程把函数当成一个公民,充分并灵活利用函数,支持函数的多种使用方式。函数的创建不在依赖类或者对象
3.面向对象编程是以对象为基础的编程方式。
4.在Scala中函数式编程和面向对象编程融合在一起
1.在Scala中,方法和函数几乎可以等同(比如它们的定义,使用,运行机制都是一样的),只是函数的使用方法更加灵活多样 【方法转函数】
2.函数式编程是从编程方式(范式)的角度来谈的。可以理解为,函数式编程把函数当成一个公民,充分并灵活利用函数,支持函数的多种使用方式。函数的创建不在依赖类或者对象
3.面向对象编程是以对象为基础的编程方式。
4.在Scala中函数式编程和面向对象编程融合在一起
二.函数的定义
1.基本语法
def Dog (n1:Int,n2:Int):Int={
//语句。。。。
return 返回值
}
①.函数声明关键字:def (definition)
②.返回值形式1::返回值的类型=
③.返回值形式2:只有一个等号=,表示返回值类型不确定
④.返回值形式3:什么都没有,表示没有返回值,等价于:Unit=
⑤.代码块中如果没有return,则默认以执行到最后一行的结果作为返回值
1.基本语法
def Dog (n1:Int,n2:Int):Int={
//语句。。。。
return 返回值
}
①.函数声明关键字:def (definition)
②.返回值形式1::返回值的类型=
③.返回值形式2:只有一个等号=,表示返回值类型不确定
④.返回值形式3:什么都没有,表示没有返回值,等价于:Unit=
⑤.代码块中如果没有return,则默认以执行到最后一行的结果作为返回值
三.函数的递归
1.在执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
2.函数的局部变量是独立的,不会相互影响
3.递归必须向退出递归条件逼近,否则就无限递归了
4.当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用就返回给谁
函数的注意事项和细节
1.函数的形参列表可以是多个,如果函数没有形参可以不带()
2.形参列表和返回值列表的数据类型可以是值类型和引用类型
3.Scala中的函数可以根据函数体最后一行代码自行推断出函数的返回类型,那么在这种情况下,return关键字可以省略
4.因为Scala可以自行推断,所以返回值的类型也可以省略
5.如果函数明确使用return关键字,那么函数的返回类型就不能自行推断了,这时就要写成:返回值类型=,如果不写,即使有return,返回值为()
6.如果函数声明无返回值(Unit),那么函数体中有return关键字,也不会有返回值
7.如果明确函数无返回值或者不确定返回值类型,那么返回值类型可以省略(或声明Any,因为Any是所有数据类型的父类)
8.Scala语法中任何语法结构都可以嵌套其他语法结构,即:函数中可以在声明定义函数,类中可再声明类,方法中可再声明方法
9.Scala函数的形参,在声明参数时,直接赋初始值(默认值),这是调用函数,如果没有指定实参,则会使用默认值。如果指定了实参,则实参覆盖默认值。指定默认值n:int=1
10.如果函数存在多个参数,每一参数都可以设定默认值,那么这个时候,参数到底是覆盖默认值,还是赋值给没有默认值的参数,这就不确定了(默认声明顺序【从左到右】),这种情况下,可以采用带名参数
def getSum(n1:Int=1){}
getSum(n1=2)
11.再Scala函数中形参默认是val,因此不能再函数中进行修改
12.递归函数未执行之前是无法推断出来结果类型,在使用时必须有明确的返回值类型
13.Scala函数支持可变参数,可变参数只能放在参数列表的最后
①//支持0到多个参数
def sum(args:Int*):Int={}
②支持1到多个参数
def sum(n:Int,args:Int*):Int={}
说明:args是集合,通过for循环可以访问到各个值
2.形参列表和返回值列表的数据类型可以是值类型和引用类型
3.Scala中的函数可以根据函数体最后一行代码自行推断出函数的返回类型,那么在这种情况下,return关键字可以省略
4.因为Scala可以自行推断,所以返回值的类型也可以省略
5.如果函数明确使用return关键字,那么函数的返回类型就不能自行推断了,这时就要写成:返回值类型=,如果不写,即使有return,返回值为()
6.如果函数声明无返回值(Unit),那么函数体中有return关键字,也不会有返回值
7.如果明确函数无返回值或者不确定返回值类型,那么返回值类型可以省略(或声明Any,因为Any是所有数据类型的父类)
8.Scala语法中任何语法结构都可以嵌套其他语法结构,即:函数中可以在声明定义函数,类中可再声明类,方法中可再声明方法
9.Scala函数的形参,在声明参数时,直接赋初始值(默认值),这是调用函数,如果没有指定实参,则会使用默认值。如果指定了实参,则实参覆盖默认值。指定默认值n:int=1
10.如果函数存在多个参数,每一参数都可以设定默认值,那么这个时候,参数到底是覆盖默认值,还是赋值给没有默认值的参数,这就不确定了(默认声明顺序【从左到右】),这种情况下,可以采用带名参数
def getSum(n1:Int=1){}
getSum(n1=2)
11.再Scala函数中形参默认是val,因此不能再函数中进行修改
12.递归函数未执行之前是无法推断出来结果类型,在使用时必须有明确的返回值类型
13.Scala函数支持可变参数,可变参数只能放在参数列表的最后
①//支持0到多个参数
def sum(args:Int*):Int={}
②支持1到多个参数
def sum(n:Int,args:Int*):Int={}
说明:args是集合,通过for循环可以访问到各个值
过程
1.过程的基本介绍
将函数的返回类型为Unit的函数称为过程,如果没有明确函数没有返回值,那么等号可以去掉。简单来说:没有返回值的函数称为过程。
将函数的返回类型为Unit的函数称为过程,如果没有明确函数没有返回值,那么等号可以去掉。简单来说:没有返回值的函数称为过程。
惰性函数
一.惰性函数使用的场景
惰性计算(尽可能延迟表达式计算)是许多函数式编程语言的特性。惰性集合在需要时提供其元素,无需预先计算它们,这带来一些好处,首先,可以将耗时计算推迟到绝对需要的时候。其次,可以创造无限个集合,只要它们继续收到请求,就会继续提供元素。函数的惰性使用可以得到更高效的代码。Java并没有为惰性提供原生支持,Scala提供了
二.惰性函数介绍
当函数返回值被声明为lazy时,函数执行将会被推迟,直到我没首次对此取值,该函数才会执行。这种函数称之为惰性函数
三.注意事项和细节
1.lazy不能修饰var类型的变量
2.不但是在调用函数时,加了lazy,会导致函数的执行被推迟,我们在声明一个变量时,如果声明了lazy,那么变量值的分配也会被推出 例如:lazy val i =10
惰性计算(尽可能延迟表达式计算)是许多函数式编程语言的特性。惰性集合在需要时提供其元素,无需预先计算它们,这带来一些好处,首先,可以将耗时计算推迟到绝对需要的时候。其次,可以创造无限个集合,只要它们继续收到请求,就会继续提供元素。函数的惰性使用可以得到更高效的代码。Java并没有为惰性提供原生支持,Scala提供了
二.惰性函数介绍
当函数返回值被声明为lazy时,函数执行将会被推迟,直到我没首次对此取值,该函数才会执行。这种函数称之为惰性函数
三.注意事项和细节
1.lazy不能修饰var类型的变量
2.不但是在调用函数时,加了lazy,会导致函数的执行被推迟,我们在声明一个变量时,如果声明了lazy,那么变量值的分配也会被推出 例如:lazy val i =10
异常
基本介绍
一.异常
1.Scala提供try和catch块来处理异常,try块用于包含可能出错的代码,catch块用于处理try块中发生的异常。可以根据需要在程序中设置任意数量的try...catch块
二.回顾:Java异常处理的注意点
1.Java语言按照try-catch-catch...-finally的方式来处理异常
2.不管有没有异常捕获,都会执行finally,因此通常可以在finally代码块中释放资源
3.可以有多个catch,分别捕获对应的异常,这时需要把范围小的异常写在前面,把范围大的异常写在后面,否则编译错误
三.Scala异常处理
1.Scala异常处理语法与Java基本的相同,但是Scala中没有checked(编译期)异常,即Scala中没有编译异常这个概念,异常都是在运行时处理
2.在Scala中只有一个catch,在这个catch中可以有多个case
3.用throw关键字,抛出一个异常对象。所有异常都是throwable的子类型,throw表达式是有类型的,就是nothing,因为nothing是所有类型的子类型,所有throw关键字可以用在需要类型的地方
def main(args:Array[String]):Unit={
val res=test()
println(res.roString)
}
def test():Nothing={
throw new Exception("异常")
}
1.Scala提供try和catch块来处理异常,try块用于包含可能出错的代码,catch块用于处理try块中发生的异常。可以根据需要在程序中设置任意数量的try...catch块
二.回顾:Java异常处理的注意点
1.Java语言按照try-catch-catch...-finally的方式来处理异常
2.不管有没有异常捕获,都会执行finally,因此通常可以在finally代码块中释放资源
3.可以有多个catch,分别捕获对应的异常,这时需要把范围小的异常写在前面,把范围大的异常写在后面,否则编译错误
三.Scala异常处理
1.Scala异常处理语法与Java基本的相同,但是Scala中没有checked(编译期)异常,即Scala中没有编译异常这个概念,异常都是在运行时处理
2.在Scala中只有一个catch,在这个catch中可以有多个case
3.用throw关键字,抛出一个异常对象。所有异常都是throwable的子类型,throw表达式是有类型的,就是nothing,因为nothing是所有类型的子类型,所有throw关键字可以用在需要类型的地方
def main(args:Array[String]):Unit={
val res=test()
println(res.roString)
}
def test():Nothing={
throw new Exception("异常")
}
使用基本案例
//判断输入的字符串内容是否为数字
def main(args: Array[String]): Unit = {
val s1="1231"
val r1=scala.util.Try(s1.toInt)
val result = r1 match {
case Success(_) => s1.toInt ;
case _ => s1
}
val result2 = result.getClass.getSimpleName
print(result2.asInstanceOf[Int])
}
def main(args: Array[String]): Unit = {
val s1="1231"
val r1=scala.util.Try(s1.toInt)
val result = r1 match {
case Success(_) => s1.toInt ;
case _ => s1
}
val result2 = result.getClass.getSimpleName
print(result2.asInstanceOf[Int])
}
Scala面向对象编程
面向对象基础
一.Scala面向对象介绍
1.在Scala中一切皆为对象
2.面向对象的三大特征:封装,继承,多态
1.在Scala中一切皆为对象
2.面向对象的三大特征:封装,继承,多态
二.类和对象的区别
1.类是抽象的,概念的,代表一类事物,比如人类,猫类
2.对象是具体的,实际的,代表一个具体事物
3.类是对象的一个模板,对象是类的一个个体,对应一个实例
4.Scala中类和对象的区别和联系是跟Java一样的
1.类是抽象的,概念的,代表一类事物,比如人类,猫类
2.对象是具体的,实际的,代表一个具体事物
3.类是对象的一个模板,对象是类的一个个体,对应一个实例
4.Scala中类和对象的区别和联系是跟Java一样的
三.属性(成员变量)
1.属性是类的一个组成部分,一般是值数据类型,也可以是引用类型。
2.属性的定义语法同变量,实例 【访问修饰符默认是私有的】var name:String=""
3.Scala中声明一个属性,必须给定默认值,然后根据默认值自动推断数据类型,数据类型可以省略
4.如果赋值为null,则一定要加类型,如果不加类型,那么该属性的类型就是null
5.属性的高级部分和构造器(构造方法/函数)相关。
1.属性是类的一个组成部分,一般是值数据类型,也可以是引用类型。
2.属性的定义语法同变量,实例 【访问修饰符默认是私有的】var name:String=""
3.Scala中声明一个属性,必须给定默认值,然后根据默认值自动推断数据类型,数据类型可以省略
4.如果赋值为null,则一定要加类型,如果不加类型,那么该属性的类型就是null
5.属性的高级部分和构造器(构造方法/函数)相关。
四.方法
1.方法的调用机制
①当Scala程序开始执行时,先会在栈区开辟一个main栈。main栈是最后被销毁的
②当Scala程序执行到一个方法时,会开辟一个新栈
③每个栈是独立的空间,变量(基本数据类型)是独立的,相互不受影响,引用类型除外
④当方法执行完毕后,该方法的栈会被jvm回收
1.方法的调用机制
①当Scala程序开始执行时,先会在栈区开辟一个main栈。main栈是最后被销毁的
②当Scala程序执行到一个方法时,会开辟一个新栈
③每个栈是独立的空间,变量(基本数据类型)是独立的,相互不受影响,引用类型除外
④当方法执行完毕后,该方法的栈会被jvm回收
五.创建对象
基本语法 val | var 对象名[:类型] = new 类型()
1.如果我们不希望改变对象的引用(即:内存地址),应该声明val,否则声明var。Scala设计者推荐使用val,因为一般来说,我们只改变对象属性值,而不是改变对象的引用
2.Scala在声明对象变量时,可以根据创建对象的类型自动判断,所以类型声明可以省略,但是当类型和后面new对象类型有继承和多态时,就必须写了
基本语法 val | var 对象名[:类型] = new 类型()
1.如果我们不希望改变对象的引用(即:内存地址),应该声明val,否则声明var。Scala设计者推荐使用val,因为一般来说,我们只改变对象属性值,而不是改变对象的引用
2.Scala在声明对象变量时,可以根据创建对象的类型自动判断,所以类型声明可以省略,但是当类型和后面new对象类型有继承和多态时,就必须写了
六.对象创建流程分析
先看一个案例
class Person{
var age:Short=90
var name:String=_
def this(n:String,a:Int){
this()
this.name=n
this.age=a
}
}
var p:Person=new Person("马云","20")
流程分析
①加载类的信息(属性信息,方法信息)
②在内存(堆)中开辟空间
③使用父类的构造器(主和辅助)进行初始化
④使用主构造器对属性进行初始化
⑤使用辅助构造器对属性进行初始化
⑥将开辟的对象地址赋给p这个引用
先看一个案例
class Person{
var age:Short=90
var name:String=_
def this(n:String,a:Int){
this()
this.name=n
this.age=a
}
}
var p:Person=new Person("马云","20")
流程分析
①加载类的信息(属性信息,方法信息)
②在内存(堆)中开辟空间
③使用父类的构造器(主和辅助)进行初始化
④使用主构造器对属性进行初始化
⑤使用辅助构造器对属性进行初始化
⑥将开辟的对象地址赋给p这个引用
构造器
基本说明
三.Scala构造器的介绍
和Java一样,Scala构造对象也需要调用构造方法,并且有任意多个构造方法(即Scala构造器也支持重载)
Scala类的构造器包括:主构造器,辅助构造器
一.使用构造器的需求
前面在创建Person对象时,是先把对象创建好后,再给这个对象的姓名和年龄赋值。现在有个需求,就是在创建人类这个对象时,就直接指定这个对象的年龄和姓名,这时就需要用到构造器或者构造方法
前面在创建Person对象时,是先把对象创建好后,再给这个对象的姓名和年龄赋值。现在有个需求,就是在创建人类这个对象时,就直接指定这个对象的年龄和姓名,这时就需要用到构造器或者构造方法
二.回顾:Java构造器特点
1.在Java中一个类可以定义多个不同的构造方法,构造方法重载
2.如果程序员没有定义构造方法,系统会自动给类生成一个默认的无参构造方法(也叫默认构造器)比如Person(){}
3.一旦顶定义了直接的构造方法(构造器),默认的就会被覆盖,除非显示定义以下
1.在Java中一个类可以定义多个不同的构造方法,构造方法重载
2.如果程序员没有定义构造方法,系统会自动给类生成一个默认的无参构造方法(也叫默认构造器)比如Person(){}
3.一旦顶定义了直接的构造方法(构造器),默认的就会被覆盖,除非显示定义以下
三.Scala构造器的介绍
和Java一样,Scala构造对象也需要调用构造方法,并且有任意多个构造方法(即Scala构造器也支持重载)
Scala类的构造器包括:主构造器,辅助构造器
四.Scala构造器的基本语法
class 类名(形参列表){//主构造器
//类体
def this(形参列表){//辅助构造器
}
def this(形参列表){//辅助构造器
}
说明:主构造器只有一个,辅助构造器函数名称为this,可以有多个。编译器通过不同参数来区分
class 类名(形参列表){//主构造器
//类体
def this(形参列表){//辅助构造器
}
def this(形参列表){//辅助构造器
}
说明:主构造器只有一个,辅助构造器函数名称为this,可以有多个。编译器通过不同参数来区分
构造器使用注意事项和细节
1.Scala构造器作用是完成对新对象的初始化,构造器没有返回值
2.主构造器的声明直接放置于类名之后
3.主构造器会执行类定义中的所有语句,这里可以体会到Scala的函数式编程和面向对象编程融合在一起,即:构造器也是方法(函数),传递方法以及使用方法和前面函数部分内容没有区别
4.如果主构造器无参数,小括号可以省略,构造对象时调用构造方法的小括号也可以省略
5.辅助构造器的名称为this(这个和Java是不一样的),多个辅助构造器通过不同参数列表进行区分,在底层就是构造器重载。
6.辅助构造器一定要先调用主构造器,执行主构造器中的逻辑,并且需要在第一行调用
7.Java里辅助构造器可以直接super去调用父类的主构造器,但是在Scala中只允许主构造器去调用父类的主构造器,使用辅助构造器先要调用主构造器,主构造器会默认先执行父类中的构造器
8.如果想让主构造器变成私有的,可以在()之前加上private,这样用户只能通过辅助构造器来构造对象
9.辅助构造器的声明和主构造器的声明一致,否则会发生错误
2.主构造器的声明直接放置于类名之后
3.主构造器会执行类定义中的所有语句,这里可以体会到Scala的函数式编程和面向对象编程融合在一起,即:构造器也是方法(函数),传递方法以及使用方法和前面函数部分内容没有区别
4.如果主构造器无参数,小括号可以省略,构造对象时调用构造方法的小括号也可以省略
5.辅助构造器的名称为this(这个和Java是不一样的),多个辅助构造器通过不同参数列表进行区分,在底层就是构造器重载。
6.辅助构造器一定要先调用主构造器,执行主构造器中的逻辑,并且需要在第一行调用
7.Java里辅助构造器可以直接super去调用父类的主构造器,但是在Scala中只允许主构造器去调用父类的主构造器,使用辅助构造器先要调用主构造器,主构造器会默认先执行父类中的构造器
8.如果想让主构造器变成私有的,可以在()之前加上private,这样用户只能通过辅助构造器来构造对象
9.辅助构造器的声明和主构造器的声明一致,否则会发生错误
属性高级(结合构造器)
1.Scala类主构造器的形参未用任何修饰符,那么这个参数就是局部变量
class B(name:String){}
2.如果参数使用val关键字声明,那么Scala会将参数作为类的私有的只读属性
class B(val name:String){}
3. 如果参数使用var关键字声明,那么Scala会将参数作为类的成员属性使用,
class A(var name:String){}
4.Bean属性,使用@BeanProperty注解 会在底层自动生成getXxx()和setXxx()方法
@BeanProperty var name ="张三"
class B(name:String){}
2.如果参数使用val关键字声明,那么Scala会将参数作为类的私有的只读属性
class B(val name:String){}
3. 如果参数使用var关键字声明,那么Scala会将参数作为类的成员属性使用,
class A(var name:String){}
4.Bean属性,使用@BeanProperty注解 会在底层自动生成getXxx()和setXxx()方法
@BeanProperty var name ="张三"
包
Java包介绍
1.回顾:Java包的作用
①区分相同名字的类
②当类很多时,可以更好的管理类
③控制访问范围
2.打包基本语法
package com.java
3.打包的实质
就是创建不同文件夹来保存类文件
①区分相同名字的类
②当类很多时,可以更好的管理类
③控制访问范围
2.打包基本语法
package com.java
3.打包的实质
就是创建不同文件夹来保存类文件
Scala包介绍
一.Scala包基本介绍
和Java一样,Scala中管理项目可以使用包,但Scala包中功能更强大,使用也相对复杂。
和Java一样,Scala中管理项目可以使用包,但Scala包中功能更强大,使用也相对复杂。
二.Scala包和Predef包可以直接使用
三.Scala包的引入
1.Scala引入包也是import,基本原理和机制和Java一样,但是Scala中的import功能更加强大,灵活
2.因为Scala语言源自于Java,所以Java.lang包中的类会自动引入到当前环境中,Scala中的Scala包和predef(预定义)包的类也会自动引入到当前环境中,即下面的类可以直接使用
3.如果想把其他包中的类引入到当前环境中,需要用import
1.Scala引入包也是import,基本原理和机制和Java一样,但是Scala中的import功能更加强大,灵活
2.因为Scala语言源自于Java,所以Java.lang包中的类会自动引入到当前环境中,Scala中的Scala包和predef(预定义)包的类也会自动引入到当前环境中,即下面的类可以直接使用
3.如果想把其他包中的类引入到当前环境中,需要用import
四.Scala引入包的细节和注意事项
1.在Scala中,import语句可以出现在任何地方,并不和Java一样限于文件顶部,import语句的作用一致延申到包含该语句的块末尾,这样做的好处是:在需要时引入包,缩小import包的作用范围,提高效率
2.Java中如果想要导入包的所有类,可以通过通配符*,Scala采用_
3.如果不想要某个包中的全部类,而是其中的几个类,可以采用选取器(大括号)
4.如果引入的多个包中含有相同的类,那么可以将不需要的类进行重命名进行区分,这个就是重命名
//将HashMap重命名成JavaHashMap}
import java.util.{HashMap => JavaHashMap}
5.如果某个冲突的类根本不会用到,那么这个类可以直接隐藏掉
//将HashMap这个包隐藏掉,其他包可以使用
import java.util.{HashMap => _,_}
1.在Scala中,import语句可以出现在任何地方,并不和Java一样限于文件顶部,import语句的作用一致延申到包含该语句的块末尾,这样做的好处是:在需要时引入包,缩小import包的作用范围,提高效率
2.Java中如果想要导入包的所有类,可以通过通配符*,Scala采用_
3.如果不想要某个包中的全部类,而是其中的几个类,可以采用选取器(大括号)
4.如果引入的多个包中含有相同的类,那么可以将不需要的类进行重命名进行区分,这个就是重命名
//将HashMap重命名成JavaHashMap}
import java.util.{HashMap => JavaHashMap}
5.如果某个冲突的类根本不会用到,那么这个类可以直接隐藏掉
//将HashMap这个包隐藏掉,其他包可以使用
import java.util.{HashMap => _,_}
Scala包对象
基本介绍
一.包对象介绍
包可以包含类,对象和特质trait,但不能包含函数/方法或变量的定义。这是Java虚拟机的局限,为了弥补这一点不足,Scala提供包对象来解决这个问题
包可以包含类,对象和特质trait,但不能包含函数/方法或变量的定义。这是Java虚拟机的局限,为了弥补这一点不足,Scala提供包对象来解决这个问题
包对象使用注意事项
二.包对象的注意事项
1.每个包中只能有一个包对象
2.包对象名称需要与包名一致,一般用来对包的功能补充
1.每个包中只能有一个包对象
2.包对象名称需要与包名一致,一般用来对包的功能补充
包的可见性
一.回顾:Java访问修饰符基本介绍
1.公开级别:public
2.受保护级别:protected,对子类和同一个包中的类公开
3.默认级别:没有修饰符,向同一个包的类公开
4.私有级别:private,只有类本身可以访问,不对外公开
1.公开级别:public
2.受保护级别:protected,对子类和同一个包中的类公开
3.默认级别:没有修饰符,向同一个包的类公开
4.私有级别:private,只有类本身可以访问,不对外公开
二.Scala包的可见性
1.当属性访问权限为默认时,从底层看属性时private,但是提供了xxx_$eq()【类似setter】/xxx()【类似getter】方法,因此从效果上看时任何地方都可以访问
2.当方法访问权限问默认时,默认为public访问权限
3.private时私有权限,只能在类的内部和伴生对象中使用
4.protected为受保护权限,在Scala中受保护权限比Java更严格,只能子类访问,同包无法访问
5.在Scala中没有public关键字,即不能用public显式的修饰属性和方法
6.包访问权限(表示属性有了限制,同时包也有了限制),这点和Java不一样,体现出Scala包使用的灵活性
1.当属性访问权限为默认时,从底层看属性时private,但是提供了xxx_$eq()【类似setter】/xxx()【类似getter】方法,因此从效果上看时任何地方都可以访问
2.当方法访问权限问默认时,默认为public访问权限
3.private时私有权限,只能在类的内部和伴生对象中使用
4.protected为受保护权限,在Scala中受保护权限比Java更严格,只能子类访问,同包无法访问
5.在Scala中没有public关键字,即不能用public显式的修饰属性和方法
6.包访问权限(表示属性有了限制,同时包也有了限制),这点和Java不一样,体现出Scala包使用的灵活性
面向对象的三大特性
封装
一.封装
1.封装就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序其他部分只有通过被授权的的操作(成员方法),才能对数据进行操作
2.封装的理解和好处
①隐藏实现的细节
②可以对数据进行验证,保证安全合理
③同时可以加入业务逻辑
3.如何体现封装
①对类的属性进行封装
②通过成员方法,包实现封装
4.封装的实现步骤
①将属性进行私有化
②提供一个公共的set方法,对属性判断并赋值
def setXxx(参数名:类型){
//加入数据验证的业务逻辑
//属性=参数名
}
③提供一个公共的get方法,用于获取属性 的值
def getXxx(){
return 属性
}
5.Scala的封装特性
①Scala中为了简化代码的开发,当声明属性时,本身就提供了对应的setting/getting方法,如果属性声明为private,那么生成setting/getting方法也是private,如果属性省略访问权限修饰符,那么生成setting/getting方法就是public
②如果我们对一个属性进行简单的set和get,只要声明一下该属性(属性使用默认访问修饰符)不用专门的getting和setting,默认会创建
③从形式上看dog.food直接访问属性,其实底层仍然是访问方法,
④有了上面的特性,目前很多新的框架,在进行反射时,也支持对属性的直接反射
1.封装就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序其他部分只有通过被授权的的操作(成员方法),才能对数据进行操作
2.封装的理解和好处
①隐藏实现的细节
②可以对数据进行验证,保证安全合理
③同时可以加入业务逻辑
3.如何体现封装
①对类的属性进行封装
②通过成员方法,包实现封装
4.封装的实现步骤
①将属性进行私有化
②提供一个公共的set方法,对属性判断并赋值
def setXxx(参数名:类型){
//加入数据验证的业务逻辑
//属性=参数名
}
③提供一个公共的get方法,用于获取属性 的值
def getXxx(){
return 属性
}
5.Scala的封装特性
①Scala中为了简化代码的开发,当声明属性时,本身就提供了对应的setting/getting方法,如果属性声明为private,那么生成setting/getting方法也是private,如果属性省略访问权限修饰符,那么生成setting/getting方法就是public
②如果我们对一个属性进行简单的set和get,只要声明一下该属性(属性使用默认访问修饰符)不用专门的getting和setting,默认会创建
③从形式上看dog.food直接访问属性,其实底层仍然是访问方法,
④有了上面的特性,目前很多新的框架,在进行反射时,也支持对属性的直接反射
继承
四.Scala类型检查和转换
1.要测试某个对象是否属于给定的类,可以用isInstanceOf方法。用asInstanceOf方法将引用转换为子类的引用。classOf获取对象的类名
①obj.isInstanceOf[T] 就如同Java中的obj isInstanceOf T 判断obj是不是T类型
②obj.asInstanceOf[T] 就如同Java的(T).obj 将obj强转成T类型
③classOf[String] 就如同Java的 String.class 输出方法的类名
2.类型的检查和转换最大的价值在于:可以判断传入对象的类型,然后转换成对应的子类对象,进行相关的操作,这里也体现出多态的特点
五.Java中超类的构造
1.超类:表示父类或者父类的父类
2.从代码上看,在Java中,创建子类对象时,子类的构造器总是去调用父类的构造器(显式或者隐式
一.回顾:Java继承
class 子类名 extends 父类{类体}
class 子类名 extends 父类{类体}
二.继承的基本介绍
1.继承可以解决代码复用,让编程更加靠近人类思维,当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的方法和属性,所有子类不需要重新定义这些方法和属性,只需要通过extends语句声明继承父类即可。和Java一样,Scala也支持类的单继承
2.子类继承了父类所有属性,只是私有的属性不能直接访问,需要通过公共的方法去访问
1.继承可以解决代码复用,让编程更加靠近人类思维,当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的方法和属性,所有子类不需要重新定义这些方法和属性,只需要通过extends语句声明继承父类即可。和Java一样,Scala也支持类的单继承
2.子类继承了父类所有属性,只是私有的属性不能直接访问,需要通过公共的方法去访问
三.重写方法
Scala明确规定,重写一个非抽象方法需要用override修饰符,调用超类(父类)方法使用super方法
Scala明确规定,重写一个非抽象方法需要用override修饰符,调用超类(父类)方法使用super方法
四.Scala类型检查和转换
1.要测试某个对象是否属于给定的类,可以用isInstanceOf方法。用asInstanceOf方法将引用转换为子类的引用。classOf获取对象的类名
①obj.isInstanceOf[T] 就如同Java中的obj isInstanceOf T 判断obj是不是T类型
②obj.asInstanceOf[T] 就如同Java的(T).obj 将obj强转成T类型
③classOf[String] 就如同Java的 String.class 输出方法的类名
2.类型的检查和转换最大的价值在于:可以判断传入对象的类型,然后转换成对应的子类对象,进行相关的操作,这里也体现出多态的特点
五.Java中超类的构造
1.超类:表示父类或者父类的父类
2.从代码上看,在Java中,创建子类对象时,子类的构造器总是去调用父类的构造器(显式或者隐式
六.Scala中超类的构造
1.类有一个主构造器和任意数量的辅助构造器,而每个辅助构造器都必须先调用主构造器(也可以是间接调用)。
2.只有主构造器可以调用父类的构造器。子类的辅助构造器不能直接调用父类的构造器。在Scala的构造器中,不能直接调用super(params)
1.类有一个主构造器和任意数量的辅助构造器,而每个辅助构造器都必须先调用主构造器(也可以是间接调用)。
2.只有主构造器可以调用父类的构造器。子类的辅助构造器不能直接调用父类的构造器。在Scala的构造器中,不能直接调用super(params)
七.回顾 :Java中的覆写字段
1.在Scala中,子类改写父类的字段,称之为覆写(重写)字段,覆写字段需要用到override修饰
2.回顾:在Java中只有方法的重写,没有属性(字段)的重写,准确的讲,是隐藏字段代替了重写
3.Java中有一个隐藏字段的概念,当父类和子类定义了一个同名的字段,不会报错。但对于同一个对象,用父类的引用去取值(字段),会取到父类字段的值,用子类的引用去取值(字段),就会取到子类字段的值。在实际开发中,要尽量避免父类和子类使用了同名的字段,否则会出现一些不容易发现的bug
4.Java中的动态绑定机制(类与类属性和方法都重名)
①如果调用的是方法,则jvm机会将方法和对象的内存地址动态的绑定。简单来说,方法会先在自己类中调用,如果自己类中没有该方法,则会到父类中调用
②如果调用的是属性,则没有动态绑定机制,在哪里调用就返回对应的值。简单来说,方法调用属性,会直接在本类中寻找该属性
1.在Scala中,子类改写父类的字段,称之为覆写(重写)字段,覆写字段需要用到override修饰
2.回顾:在Java中只有方法的重写,没有属性(字段)的重写,准确的讲,是隐藏字段代替了重写
3.Java中有一个隐藏字段的概念,当父类和子类定义了一个同名的字段,不会报错。但对于同一个对象,用父类的引用去取值(字段),会取到父类字段的值,用子类的引用去取值(字段),就会取到子类字段的值。在实际开发中,要尽量避免父类和子类使用了同名的字段,否则会出现一些不容易发现的bug
4.Java中的动态绑定机制(类与类属性和方法都重名)
①如果调用的是方法,则jvm机会将方法和对象的内存地址动态的绑定。简单来说,方法会先在自己类中调用,如果自己类中没有该方法,则会到父类中调用
②如果调用的是属性,则没有动态绑定机制,在哪里调用就返回对应的值。简单来说,方法调用属性,会直接在本类中寻找该属性
八.覆写字段的注意事项和细节
1.def只能重写另一个def(即方法只能重写另一个方法)
2.val只能重写另一个val属性 或不带参数的def
3.var只能重写另一个抽象的var属性
抽象属性:声明未初始化的变量就是抽象的属性,抽象属性在抽象类
var重写抽象的var属性小结
①一个属性没有初始化,那么这个属性就是抽象属性
②抽象属性在编译成字节码文件时,属性并不会声明,但是会自动生成抽象方法,所有类必须声明为抽象类
③如果时覆写一个父类的抽象属性,那么override关键字可以省略(原因:父类的抽象属性生成的时抽象方法,因此就不会涉及到方法重写的概念,因此override可以省略)
1.def只能重写另一个def(即方法只能重写另一个方法)
2.val只能重写另一个val属性 或不带参数的def
3.var只能重写另一个抽象的var属性
抽象属性:声明未初始化的变量就是抽象的属性,抽象属性在抽象类
var重写抽象的var属性小结
①一个属性没有初始化,那么这个属性就是抽象属性
②抽象属性在编译成字节码文件时,属性并不会声明,但是会自动生成抽象方法,所有类必须声明为抽象类
③如果时覆写一个父类的抽象属性,那么override关键字可以省略(原因:父类的抽象属性生成的时抽象方法,因此就不会涉及到方法重写的概念,因此override可以省略)
Scala继承关系层级图
子主题
小结
1.在Scala中,所有其他类都是AnyRef的子类,类似于Java的object
2.AnyVal和AnyRef都扩展自Any类。Any类时根节点
3.Any中定义了isInstanceOf,asInstanceOf方法,以及哈希方法等
4.Null类型的唯一实例就是null对象,可以将null赋值给任何引用,但不能赋值给值类型的变量
5.Nothing类型没有实例。它对于泛型结构是有用处的,举例:空列表nuil的类型时List[Nothing],它是List[T]的子类型,T可以时任何类
1.在Scala中,所有其他类都是AnyRef的子类,类似于Java的object
2.AnyVal和AnyRef都扩展自Any类。Any类时根节点
3.Any中定义了isInstanceOf,asInstanceOf方法,以及哈希方法等
4.Null类型的唯一实例就是null对象,可以将null赋值给任何引用,但不能赋值给值类型的变量
5.Nothing类型没有实例。它对于泛型结构是有用处的,举例:空列表nuil的类型时List[Nothing],它是List[T]的子类型,T可以时任何类
多态
什么是多态:目的是为了让代码更加,降低耦合
有继承或实现特质(接口)
父类引用指向子类对象或接口指向实现类
有继承或实现特质(接口)
父类引用指向子类对象或接口指向实现类
Scala面向对象高级
抽象
一.抽象类
在Scala中,通过abstract关键字标记并不能被实例化的类。方法不用标记abstract,只要省略方法体即可。抽象类可以拥有抽象字段,抽象字段就是没有初始化的字段
在Scala中,通过abstract关键字标记并不能被实例化的类。方法不用标记abstract,只要省略方法体即可。抽象类可以拥有抽象字段,抽象字段就是没有初始化的字段
二.抽象类的价值
抽象类的价值更多的是在于价值,是设计者设计好后,让子类实现并继承抽象类(即:实现抽象类的抽象方法)
抽象类的价值更多的是在于价值,是设计者设计好后,让子类实现并继承抽象类(即:实现抽象类的抽象方法)
三.抽象类的使用注意事项和细节
1.默认情况下抽象类不能被实例化,但是在实例化时动态的实现了抽象类中所有的抽象方法,就可以实例化
2.抽象类不一定要包含abstract方法。也就是说抽象类可以没有abstract方法
3.一旦类中包含抽象方法或者抽象属性,这个类必须要声明abstract
4.抽象方法不能有主体,不允许使用abstract修饰
5.如果一个类继承了抽象类,那么这个类必须实现这个抽象类的所有抽象方法和抽象属性,除非它自己声明abstract
6.抽象方法和抽象属性不能使用private和final关键字,因为这些关键字是和重写/实现相违背的
7.抽象类可以有实现的方法
8子类重写抽象方法不需要加上abstract,加了也不会错
1.默认情况下抽象类不能被实例化,但是在实例化时动态的实现了抽象类中所有的抽象方法,就可以实例化
2.抽象类不一定要包含abstract方法。也就是说抽象类可以没有abstract方法
3.一旦类中包含抽象方法或者抽象属性,这个类必须要声明abstract
4.抽象方法不能有主体,不允许使用abstract修饰
5.如果一个类继承了抽象类,那么这个类必须实现这个抽象类的所有抽象方法和抽象属性,除非它自己声明abstract
6.抽象方法和抽象属性不能使用private和final关键字,因为这些关键字是和重写/实现相违背的
7.抽象类可以有实现的方法
8子类重写抽象方法不需要加上abstract,加了也不会错
伴生对象和伴生类
静态介绍
一.Java静态概念
1.代码的基本使用
public static 返回值类型 方法名(参数列表){方法体}
说明:Java中的静态方法并不是通过对象调用的,而是通过类对象调用的,使用静态操作并不是面向对象的
1.代码的基本使用
public static 返回值类型 方法名(参数列表){方法体}
说明:Java中的静态方法并不是通过对象调用的,而是通过类对象调用的,使用静态操作并不是面向对象的
二.Scala的静态概念(伴生对象)
Scala语言完全是面向对象的(万物皆对象)的语言,所以并没静态的操作(即在Scala中没有静态的概念)。但是为了和Java语言交互(因为Java中友静态概念),就产生了一种特殊的对象来模拟类对象,我们称之为伴生对象。这个类的所有静态内容都可以放置在它的伴生对象中声明和调用
Scala语言完全是面向对象的(万物皆对象)的语言,所以并没静态的操作(即在Scala中没有静态的概念)。但是为了和Java语言交互(因为Java中友静态概念),就产生了一种特殊的对象来模拟类对象,我们称之为伴生对象。这个类的所有静态内容都可以放置在它的伴生对象中声明和调用
伴生对象小结
三.伴生对象小结
1.scala中伴生对象用object关键字声明,伴生对象中的内容全部是静态
2.伴生对象对应的是伴生类,伴生对象的名称和伴生类的名称要一致
3.伴生对象中的属性和方法可以直接通过伴生对象名称调用
4.从语法上来看,所谓的伴生对象就是静态方法和静态属性的集合
5.从技术上来看,Scala还是没有生成静态的内容,只不过是将伴生对象生成了一个新的类,实现属性和方法的调用
6.从底层原理上看,伴生对象静态特性是依赖于public static final MODULE$实现的
7.伴生对象和伴生类的声明必须在同一个源码文件中,但是没有伴生类,就没有所谓的伴生对象了,所有放哪里无所谓,但是底层还是生成了一个空的伴生类
8.如果class A独立存在,那么A就是一个类;如果object A独立存在,那么A就是一个“静态”性质的对象(类对象)
9.当一个文件中同时存在伴生对象和伴生类,那么文件的图标会发生变化
1.scala中伴生对象用object关键字声明,伴生对象中的内容全部是静态
2.伴生对象对应的是伴生类,伴生对象的名称和伴生类的名称要一致
3.伴生对象中的属性和方法可以直接通过伴生对象名称调用
4.从语法上来看,所谓的伴生对象就是静态方法和静态属性的集合
5.从技术上来看,Scala还是没有生成静态的内容,只不过是将伴生对象生成了一个新的类,实现属性和方法的调用
6.从底层原理上看,伴生对象静态特性是依赖于public static final MODULE$实现的
7.伴生对象和伴生类的声明必须在同一个源码文件中,但是没有伴生类,就没有所谓的伴生对象了,所有放哪里无所谓,但是底层还是生成了一个空的伴生类
8.如果class A独立存在,那么A就是一个类;如果object A独立存在,那么A就是一个“静态”性质的对象(类对象)
9.当一个文件中同时存在伴生对象和伴生类,那么文件的图标会发生变化
apply方法
四.伴生对象apply方法
1.在伴生对象中定义apply方法,可以实现类名()的方式创建对象实例
1.在伴生对象中定义apply方法,可以实现类名()的方式创建对象实例
特质
特质基本介绍与快速入门
一.特质的声明
trait 特质名{
trait体
}
trait的命名一般首字母大写
trait 特质名{
trait体
}
trait的命名一般首字母大写
二.Scala中特质的使用
一个类中具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所有在使用时也是用extends关键字,如果有多个特征存在父类,那么需要用with关键字连接
1.没有父类
class 类名 extends 特质1 with 特质2 with 特质3
2.有父类
class 类名 extends 父类 with 特质1 with 特质2
一个类中具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所有在使用时也是用extends关键字,如果有多个特征存在父类,那么需要用with关键字连接
1.没有父类
class 类名 extends 特质1 with 特质2 with 特质3
2.有父类
class 类名 extends 父类 with 特质1 with 特质2
三.快速入门
1.可以把特质看作是对继承的一种补充。Scala的继承是单继承,也就是一个类最多只能有一个父类,这就对子类功能的扩展有一定的影响。所以我们认为:Scala引入trait特质,第一可以替代Java的接口,第二也是对单继承的一种补充
1.可以把特质看作是对继承的一种补充。Scala的继承是单继承,也就是一个类最多只能有一个父类,这就对子类功能的扩展有一定的影响。所以我们认为:Scala引入trait特质,第一可以替代Java的接口,第二也是对单继承的一种补充
四.特质的再说明
1.trait可以同时拥有抽象方法和具体方法,一个类可以实现或者继承多个特质
2.Java中的接口可以直接当成Scala特质使用
1.trait可以同时拥有抽象方法和具体方法,一个类可以实现或者继承多个特质
2.Java中的接口可以直接当成Scala特质使用
Java接口
一.回顾Java接口
1.声明接口
interface 接口名
2.实现接口
class 实现接口类类名 implements 接口名1,接口名2
3.小结
①在Java中,一个类可以实现多个接口
②在Java中,接口之间支持多继承
③接口中属性都是常量
④方法都是抽象的也可以是具体的
1.声明接口
interface 接口名
2.实现接口
class 实现接口类类名 implements 接口名1,接口名2
3.小结
①在Java中,一个类可以实现多个接口
②在Java中,接口之间支持多继承
③接口中属性都是常量
④方法都是抽象的也可以是具体的
Scala用特质代替接口
二.Scala接口的介绍
1.从面向对象来看,接口并不属于面向对象的范畴,Scala是纯面向对象的语言,在Scala中没有接口
2.Scala语言中采用特质trait(特征)来代替接口的概念,多个类具有相同的特征时,就可以将这个特征独立出来,采用关键字trait声明。简单理解为 trait等价于 interface+abstract
二.Scala接口的介绍
1.从面向对象来看,接口并不属于面向对象的范畴,Scala是纯面向对象的语言,在Scala中没有接口
2.Scala语言中采用特质trait(特征)来代替接口的概念,多个类具有相同的特征时,就可以将这个特征独立出来,采用关键字trait声明。简单理解为 trait等价于 interface+abstract
叠加特质
一.叠加特质的基本介绍
构建对象的同时如果混入多个特质,称之为叠加特质
那么特质声明顺序从左到右,方法执行顺序从左到右
构建对象的同时如果混入多个特质,称之为叠加特质
那么特质声明顺序从左到右,方法执行顺序从左到右
二.叠加特质的注意事项和细节
1.特质声明顺序从左到右
2.Scala在执行叠加对象方法时,会首先从最右边的特质开始执行,类似于数据结构栈
3.Scala特质中如果调用super方法,并不是表示调用父特质中的方法,而是向左边继续查找特质,如果左边没有特质,此时就会调用父特质
4.如果想要调用具体特质的方法,可以指定super[特质名].方法,其中泛型必须是该类的直接超类类型
1.特质声明顺序从左到右
2.Scala在执行叠加对象方法时,会首先从最右边的特质开始执行,类似于数据结构栈
3.Scala特质中如果调用super方法,并不是表示调用父特质中的方法,而是向左边继续查找特质,如果左边没有特质,此时就会调用父特质
4.如果想要调用具体特质的方法,可以指定super[特质名].方法,其中泛型必须是该类的直接超类类型
三.在特质中重写抽象方法
trait A{
def insert() //抽象方法
}
trait B extends A{
override def insert(): Unit = {
println("将数据保存到文件中!")
super.insert() //思考这里super如何调用父特质中的抽象方法
}
}
以上代码会出错,原因:是没有完全实现insert方法,同时还要声明abstract override
解决方法:①去掉super.insert()
②调用父特质的抽象方法,那么在实际使用中,没有方法的具体实现,无法通过编译,为了避免这种情况的发生。可以重写抽象方法,这样在使用过程中必须考虑动态混入的顺序问题
trait A{
def insert() //抽象方法
}
trait B extends A{
override def insert(): Unit = {
println("将数据保存到文件中!")
super.insert() //思考这里super如何调用父特质中的抽象方法
}
}
以上代码会出错,原因:是没有完全实现insert方法,同时还要声明abstract override
解决方法:①去掉super.insert()
②调用父特质的抽象方法,那么在实际使用中,没有方法的具体实现,无法通过编译,为了避免这种情况的发生。可以重写抽象方法,这样在使用过程中必须考虑动态混入的顺序问题
特质扩展
一.富接口的概念
当一个特质中既有抽象方法,又有非抽象方法,这种特质我们称之为富接口
当一个特质中既有抽象方法,又有非抽象方法,这种特质我们称之为富接口
二.特质中的具体字段
特质中可以定义具体字段,如果初始化了就是具体字段,如果没有初始化就是抽象字段。混入该特质的类就具有了该字段,字段不是继承而是直接加入类,成为自己的字段
特质中可以定义具体字段,如果初始化了就是具体字段,如果没有初始化就是抽象字段。混入该特质的类就具有了该字段,字段不是继承而是直接加入类,成为自己的字段
三.特质构造顺序
1.特质也是有构造器的,构造器中的内容由“字段初始化”和一些其他语句构成。具体实现请参考“叠加特质”
第一种特质构造顺序(声明类的同时混入特质【静态混入】)
①调用当前类的超类构造器
②第一个特质的父类构造器
③第一个特质构造器
④第二个特质构造器的父特质构造器,如果已经执行,就不会在执行
⑤第二个特质构造器
⑥重复4,5步骤(如果有第3,4个特质)
⑦最后执行本类的构造
第二种特质构造顺序(构建类的同时混入特质【动态混入】)
①调用当前类的超类构造器,然后执行本类的构造
②第一个特质的父类构造器
③第一个特质构造器
④第二个特质构造器的父特质构造器,如果已经执行,就不会在执行
⑤第二个特质构造器
⑥重复4,5步骤(如果有第3,4个特质)
分析两种方式对构造顺序的影响
第1种方式实际是构建类对象,在混入特质时,该对象还没有创建
第2种方式实际是构造匿名子类,可以理解成在混入特质时,该对象已经创建
1.特质也是有构造器的,构造器中的内容由“字段初始化”和一些其他语句构成。具体实现请参考“叠加特质”
第一种特质构造顺序(声明类的同时混入特质【静态混入】)
①调用当前类的超类构造器
②第一个特质的父类构造器
③第一个特质构造器
④第二个特质构造器的父特质构造器,如果已经执行,就不会在执行
⑤第二个特质构造器
⑥重复4,5步骤(如果有第3,4个特质)
⑦最后执行本类的构造
第二种特质构造顺序(构建类的同时混入特质【动态混入】)
①调用当前类的超类构造器,然后执行本类的构造
②第一个特质的父类构造器
③第一个特质构造器
④第二个特质构造器的父特质构造器,如果已经执行,就不会在执行
⑤第二个特质构造器
⑥重复4,5步骤(如果有第3,4个特质)
分析两种方式对构造顺序的影响
第1种方式实际是构建类对象,在混入特质时,该对象还没有创建
第2种方式实际是构造匿名子类,可以理解成在混入特质时,该对象已经创建
四.扩展类的特质
1.特质可以继承类,以用来扩展该特质的一些功能
2.所有混入该特质的类,会自动成为那个特质所继承的超类的子类
3.如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类,否则就出现了多继承,就会报错。
五.自身类型
说明:自身类型主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型
1.特质可以继承类,以用来扩展该特质的一些功能
2.所有混入该特质的类,会自动成为那个特质所继承的超类的子类
3.如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类,否则就出现了多继承,就会报错。
五.自身类型
说明:自身类型主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型
嵌套类
四.Scala嵌套类的使用
1.内部类如果想要访问外部类中的属性,可以通过外部类对象访问。即访问方式:外部类名.this.属性名
2.内部类如果想要访问外部类的属性,也可以通过外部类别名访问(推荐)。即访问方式:外部类别名.this.属性名
五.类型投影
是指在方法声明上,如果使用 外部类#内部类 的方式,表示忽略内部类对象关系,等同于Java内部类的操作,我们将这种方式称为类型投影
一.嵌套类
在Scala种,你几乎可以在任何语法结构中嵌套任何语法结构。如果类中可以再定义一个类,这样的类就是嵌套类,其他语法结构也是一样的。嵌套类类似于Java种的内部类
面试题:Java种,类有哪五大成员?
1.属性
2.方法
3.内部类
4.构造器
5.代码块
在Scala种,你几乎可以在任何语法结构中嵌套任何语法结构。如果类中可以再定义一个类,这样的类就是嵌套类,其他语法结构也是一样的。嵌套类类似于Java种的内部类
面试题:Java种,类有哪五大成员?
1.属性
2.方法
3.内部类
4.构造器
5.代码块
二.回顾:Java内部类
在Java种,一个类的内部又完整的嵌套了另一个完整的类结构。被嵌套的类成为内部类(inner class),嵌套其他类的类称为外部类。内部类最大的特点就是可以直接访问私有属性,并且可以体现类于类的之间的包含关系
三.Java内部类的分类
1.从定义在外部类的成员位置上来看
①成员内部类(没有用static修饰)
②静态内部类(用static修饰)
2.定义在外部类局部位置上(比如方法内)来看
①分为局部内部类(有类名)
②匿名内部类(没有类名)
在Java种,一个类的内部又完整的嵌套了另一个完整的类结构。被嵌套的类成为内部类(inner class),嵌套其他类的类称为外部类。内部类最大的特点就是可以直接访问私有属性,并且可以体现类于类的之间的包含关系
三.Java内部类的分类
1.从定义在外部类的成员位置上来看
①成员内部类(没有用static修饰)
②静态内部类(用static修饰)
2.定义在外部类局部位置上(比如方法内)来看
①分为局部内部类(有类名)
②匿名内部类(没有类名)
四.Scala嵌套类的使用
1.内部类如果想要访问外部类中的属性,可以通过外部类对象访问。即访问方式:外部类名.this.属性名
2.内部类如果想要访问外部类的属性,也可以通过外部类别名访问(推荐)。即访问方式:外部类别名.this.属性名
五.类型投影
是指在方法声明上,如果使用 外部类#内部类 的方式,表示忽略内部类对象关系,等同于Java内部类的操作,我们将这种方式称为类型投影
隐式转换
隐式转换和隐式参数
一.隐式转换的实际需要
指定某些类型的相互转换 比如 Double <=> Int
指定某些类型的相互转换 比如 Double <=> Int
二.隐式转换基本介绍
隐式转换函数是以implicit关键字声明的带有单个参数的函数。这种函数将会自动应用,将值从一种类型转换成另一种类型
隐式转换函数是以implicit关键字声明的带有单个参数的函数。这种函数将会自动应用,将值从一种类型转换成另一种类型
三.快速入门
implicit def f1(d:Double):Int={ //将double类型转换成int类型
d.toInt
}
implicit def f1(d:Double):Int={ //将double类型转换成int类型
d.toInt
}
四.隐式转换注意事项和细节
1.隐式转换函数的函数名可以是任意的,隐式转换于函数名称无关,只与函数签名(函数参数类型和返回值类型)有关
2.隐式函数可以有多个(隐式函数列表),但是要保证当前环境下只有一个隐式函数能被识别
1.隐式转换函数的函数名可以是任意的,隐式转换于函数名称无关,只与函数签名(函数参数类型和返回值类型)有关
2.隐式函数可以有多个(隐式函数列表),但是要保证当前环境下只有一个隐式函数能被识别
五.隐式转换丰富类库的功能
1.如果需要为一个类增加一个方法,可以通隐式转换来实现。(动态增加功能),比如想为MySQL类增加一个delete方法
2.当前程序中,如果想要给MySQL类增加功能是非常简单的,但是在实际项目中,如果想要增加新的功能就要修改源代码,这是很难接受的。而且违背了软件开发的OCP开发原则(闭合原则:open close priceple),这种情况下,可以通过隐式转换函数给类添加功能
1.如果需要为一个类增加一个方法,可以通隐式转换来实现。(动态增加功能),比如想为MySQL类增加一个delete方法
2.当前程序中,如果想要给MySQL类增加功能是非常简单的,但是在实际项目中,如果想要增加新的功能就要修改源代码,这是很难接受的。而且违背了软件开发的OCP开发原则(闭合原则:open close priceple),这种情况下,可以通过隐式转换函数给类添加功能
六.隐式值
1.隐式值也叫隐式变量,将形参变量标记成implicit,所以编译器会在方法省略隐式参数的情况下,去搜索作用域中的隐式变量作为缺省参数(默认值)。
2.当同时有implicit值和默认值时,会调用implicit值,因为implicit值的优先级高
3.当同时有implicit值和默认值时,但是两则类型不匹配,则会调用默认值
4.当同时有implicit值和没有默认值时,但是implicit值类型与形参类型不匹配,此时会报错
1.隐式值也叫隐式变量,将形参变量标记成implicit,所以编译器会在方法省略隐式参数的情况下,去搜索作用域中的隐式变量作为缺省参数(默认值)。
2.当同时有implicit值和默认值时,会调用implicit值,因为implicit值的优先级高
3.当同时有implicit值和默认值时,但是两则类型不匹配,则会调用默认值
4.当同时有implicit值和没有默认值时,但是implicit值类型与形参类型不匹配,此时会报错
总结:
①在程序中,同时有 隐式值 默认值 传值 时
②优先级为 传值 > 隐式值 > 默认值
③在隐式值匹配时,不能有二义性
④如果隐式值 默认值 传值都不存在,则程序会报错
①在程序中,同时有 隐式值 默认值 传值 时
②优先级为 传值 > 隐式值 > 默认值
③在隐式值匹配时,不能有二义性
④如果隐式值 默认值 传值都不存在,则程序会报错
隐式类
二.隐式类的特点
1.其他带有构造参数有且只能有一个
2.隐式类必须定义在“类” “伴生对象”“包对象”中,既隐式类不能时顶级的(top-class objects)
3.隐式类不能是case class样例类(case class后面讲解)
4.作用域内不能有与之相同名称的标识符(名称)
一.隐式类的基本介绍
在Scala2.10后提供了隐式类,可以使用implicit声明类,隐式类非常强大,同时可以扩展类的功能,比前面使用的隐式转换(函数)丰富类库功能更加方便,在集合中隐式类会发挥重要作用
在Scala2.10后提供了隐式类,可以使用implicit声明类,隐式类非常强大,同时可以扩展类的功能,比前面使用的隐式转换(函数)丰富类库功能更加方便,在集合中隐式类会发挥重要作用
二.隐式类的特点
1.其他带有构造参数有且只能有一个
2.隐式类必须定义在“类” “伴生对象”“包对象”中,既隐式类不能时顶级的(top-class objects)
3.隐式类不能是case class样例类(case class后面讲解)
4.作用域内不能有与之相同名称的标识符(名称)
三.创建方式
implicit class BBB(i: Int){
def add(j: Int) = i + j
}
}
implicit class BBB(i: Int){
def add(j: Int) = i + j
}
}
隐式转换细节
一.隐式转换时机
1.当方法中的参数的类型与目标类型不一致时
2.当对象调用所在类中不存在的方法和成员时,编译器会自动将对象进行隐式转换
1.当方法中的参数的类型与目标类型不一致时
2.当对象调用所在类中不存在的方法和成员时,编译器会自动将对象进行隐式转换
二.隐式解析机制
即:编译器是如何查找到缺失信息的,解析具有以下两种规则
1.首先会在当前代码作用域下查找隐式实体(隐式方法,隐式类,隐式对象),这是一般情况
2.如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域查找。类型的作用域是指与该类型相关联的全部伴生模块,一个隐式实体的类型T它的查找范围如下(这种情况复杂并且使用范围广,应当避免出现)
即:编译器是如何查找到缺失信息的,解析具有以下两种规则
1.首先会在当前代码作用域下查找隐式实体(隐式方法,隐式类,隐式对象),这是一般情况
2.如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域查找。类型的作用域是指与该类型相关联的全部伴生模块,一个隐式实体的类型T它的查找范围如下(这种情况复杂并且使用范围广,应当避免出现)
三.隐式转换的规则
1.隐式转换不能有二义性
2.隐式操作不能嵌套使用
1.隐式转换不能有二义性
2.隐式操作不能嵌套使用
Scala数据结构
集合
集合的基本介绍
一.Scala集合基本介绍
1.Scala同时支持不可变集合和可变集合,不可变集合可以安全的并发访问
2.两个主要的包
不可变集合:scala.collection.immutable
可变集合:scala.collection.mutable
3.Scala默认采用不可变集合,对于几乎所有的集合类,Scala都同时提供了可变()和不可变(immutable)的版本,
4.Scala的集合有三大类:序列Seq,集Set,映射Map,所有集合都扩展自Iterable特质,在Scala中集合有可变(mutable)和不可变(immutable)两种类型
1.Scala同时支持不可变集合和可变集合,不可变集合可以安全的并发访问
2.两个主要的包
不可变集合:scala.collection.immutable
可变集合:scala.collection.mutable
3.Scala默认采用不可变集合,对于几乎所有的集合类,Scala都同时提供了可变()和不可变(immutable)的版本,
4.Scala的集合有三大类:序列Seq,集Set,映射Map,所有集合都扩展自Iterable特质,在Scala中集合有可变(mutable)和不可变(immutable)两种类型
二.可变集合和不可变集合
1.不可变集合:Scala不可变集合,就时这个集合本身不能动态变化。类似于Java的数组,时不可以动态增长的
2.可变集合:就是这个集合本身可以动态变化,是可以动态增长的
1.不可变集合:Scala不可变集合,就时这个集合本身不能动态变化。类似于Java的数组,时不可以动态增长的
2.可变集合:就是这个集合本身可以动态变化,是可以动态增长的
三.集合的种类
1.数组Array
2.元组Tuple
3.列表list
4.队列Queue
5.映射Map
6.集Set
1.数组Array
2.元组Tuple
3.列表list
4.队列Queue
5.映射Map
6.集Set
数组(Array)
定长数组
第一种方式定义数组(动态创建)
这里的数组等同于Java中的数组,中括号的类型就是数组的类型,小括号中的数字代表数组的长度
var arr1 = new Array[int](10)
这里的数组等同于Java中的数组,中括号的类型就是数组的类型,小括号中的数字代表数组的长度
var arr1 = new Array[int](10)
第二种方式定义数组(静态创建)
在定义数组时,直接赋值
//使用apply方法创建数组对象
val arr = Array(1,2,zhangsan)
在定义数组时,直接赋值
//使用apply方法创建数组对象
val arr = Array(1,2,zhangsan)
注意:如果赋值元素全部为Int,则该数组的泛型就是Int;如果有各种类型的元素,则该数组的泛型为Any
变长数组
一.变长数组的基本使用
val array = new ArrayBuffer[Int]()
arrar.append(7) //追加元素
array.remove(0) //删除元素 ,索引位置
array(0) = 7 //重新赋值
val array = new ArrayBuffer[Int]()
arrar.append(7) //追加元素
array.remove(0) //删除元素 ,索引位置
array(0) = 7 //重新赋值
二.变长数组分析小结
1.ArrayBuffer是变长数组,类似于Java的ArrayBuffer
2.val arr = ArrayBuffer[Int]()
3.def append(elems:A*) {append(elems)}接受的是可变参数
4.每append一次,arr在底层会重新分配空间,进行扩容,arr的内存地址会发生变化,也就成为新的arrayBuffer
1.ArrayBuffer是变长数组,类似于Java的ArrayBuffer
2.val arr = ArrayBuffer[Int]()
3.def append(elems:A*) {append(elems)}接受的是可变参数
4.每append一次,arr在底层会重新分配空间,进行扩容,arr的内存地址会发生变化,也就成为新的arrayBuffer
定长数组与变长数组相互转换
三.定长数组和变长数组的转换
arr1.toBuffer //定长数组转变长数组
arr2.toArray //变长数组转可变数组
①arr2.toArray 返回的结果才是一个定长数组,arr2本身没有发生变化
②arr1.toBuffer返回结果才是一个可变数组,arr1本身没有发生变化
arr1.toBuffer //定长数组转变长数组
arr2.toArray //变长数组转可变数组
①arr2.toArray 返回的结果才是一个定长数组,arr2本身没有发生变化
②arr1.toBuffer返回结果才是一个可变数组,arr1本身没有发生变化
多维数组
一.多维数组的基本操作
//说明:二维数组中有三个一维数组,每个一维数组中有四个元素
val arr = Array.ofDim[Double](3,4)
//赋值
arr(1)(1) = 11
//说明:二维数组中有三个一维数组,每个一维数组中有四个元素
val arr = Array.ofDim[Double](3,4)
//赋值
arr(1)(1) = 11
二.多维数组的遍历
for (item <- arr){
for (item2 <- item){
print(item)
}
}
for (item <- arr){
for (item2 <- item){
print(item)
}
}
元组(Tuple)
一.tuple介绍
元组可理解成一个容器,可以存放各种相同和不同的数据。简单来说,就是将多个无关的数据封装成一个整体,称为元组。注意:元组内最多只能有22个数据
元组可理解成一个容器,可以存放各种相同和不同的数据。简单来说,就是将多个无关的数据封装成一个整体,称为元组。注意:元组内最多只能有22个数据
二.元组的创建
val tuple = (1,2,3,"hello",2,2)
val tuple = (1,2,3,"hello",2,2)
三.小结
1.tuple的类型取决于tuple中有多少个元素,比如有 6个元素,则类型就是tuple6
2.元组中最大只能有22个元素
1.tuple的类型取决于tuple中有多少个元素,比如有 6个元素,则类型就是tuple6
2.元组中最大只能有22个元素
四.元组的访问
访问元组中的数据,可以采用顺序序号(_顺序号),也可以通过索引(productElement)访问
tuple1._1 //1代表顺序号
tuple1.productElement(0) //0代表索引
访问元组中的数据,可以采用顺序序号(_顺序号),也可以通过索引(productElement)访问
tuple1._1 //1代表顺序号
tuple1.productElement(0) //0代表索引
五.元组的遍历
1.元组的遍历需要用到迭代器
2.即 for(item <- tuple1.productIterator){
print(item)
}
1.元组的遍历需要用到迭代器
2.即 for(item <- tuple1.productIterator){
print(item)
}
列表(List)
一.列表的基本介绍
Scala中list和Java List本不一样,Java中的List是一个接口,真正存放数据的是ArrayList,而Scala的list可以直接存放数据,就是一个object,默认情况下,Scala的list是不可变的,list属于序列Seq
Scala中list和Java List本不一样,Java中的List是一个接口,真正存放数据的是ArrayList,而Scala的list可以直接存放数据,就是一个object,默认情况下,Scala的list是不可变的,list属于序列Seq
二.说明
1.在默认情况下list是Scala.collection.immutable.list,即list是不可变的
2.在Scala中,list就是不可变的,如果需要使用可变的list,则使用listBuffer
3.Nil代表空集合
1.在默认情况下list是Scala.collection.immutable.list,即list是不可变的
2.在Scala中,list就是不可变的,如果需要使用可变的list,则使用listBuffer
3.Nil代表空集合
三.小结
1.list默认为不可变的集合
2.list在Scala包对象声明的,因此不需要引入其他包也可以使用
3.val List = scala.collection.immutable.List
4.list中可以存放任何数据类型,比如arr1的类型为list[Any]
5.如果希望得到一个空列表,可以使用Nil对象,在Scala包对象声明的,因此不需要引入其他包也可以使用
val Nil = scala.collection.immuteable.Nuil
1.list默认为不可变的集合
2.list在Scala包对象声明的,因此不需要引入其他包也可以使用
3.val List = scala.collection.immutable.List
4.list中可以存放任何数据类型,比如arr1的类型为list[Any]
5.如果希望得到一个空列表,可以使用Nil对象,在Scala包对象声明的,因此不需要引入其他包也可以使用
val Nil = scala.collection.immuteable.Nuil
四.列表的元素追加
1.基本介绍
向列表中增加元素,会返回新的列表/集合对象,本身的集合并没有发生变化。注意Scala列表追加元素的形式非常独特,和Java不一样
val list01 = List(1,2,3)
var list02 = list01:+9 //创建一个新的列表并在这个列表的最后添加
var list03 = 4 +: list01 //(同上)在列表的最前面添加
2.添加数据::符号
①符号::表示向集合中新建集合添加元素
②运算时,集合对象一定要放置在最右边
③运算规则,从左向右
④:::运算符是将集合中的每一个元素加入到空集合中,并且:::符号左右两边都为集合,不然会报错
⑤ //将元素1 2 和 list列表存放到空列表Nil中
val list2 = 1 :: 2 :: list :: Nil
println(list2) //List(1, 2, List(1, 2, 3, 4))
1.基本介绍
向列表中增加元素,会返回新的列表/集合对象,本身的集合并没有发生变化。注意Scala列表追加元素的形式非常独特,和Java不一样
val list01 = List(1,2,3)
var list02 = list01:+9 //创建一个新的列表并在这个列表的最后添加
var list03 = 4 +: list01 //(同上)在列表的最前面添加
2.添加数据::符号
①符号::表示向集合中新建集合添加元素
②运算时,集合对象一定要放置在最右边
③运算规则,从左向右
④:::运算符是将集合中的每一个元素加入到空集合中,并且:::符号左右两边都为集合,不然会报错
⑤ //将元素1 2 和 list列表存放到空列表Nil中
val list2 = 1 :: 2 :: list :: Nil
println(list2) //List(1, 2, List(1, 2, 3, 4))
五.listBuffer可变列表
①列表创建 val list = new ListBuffer[Int]()
②访问列表 list(0)
③动态追加元素 list += 1 和 list.appent(1,2,3) 和 list :+ 5
④列表中添加列表 list ++= list01
⑤删除元素 list.remove(1) //索引值(下标值)
①列表创建 val list = new ListBuffer[Int]()
②访问列表 list(0)
③动态追加元素 list += 1 和 list.appent(1,2,3) 和 list :+ 5
④列表中添加列表 list ++= list01
⑤删除元素 list.remove(1) //索引值(下标值)
六.listBuffer的操作案例
//listBuffer列表添加元素或列表
listBuffer += (99,22) //可以添加一个或多个元素
listBuffer.append(29,100)
listBuffer ++= list //添加一个列表
println(listBuffer)
//listBuffer删除元素
listBuffer -= 2 //删除2这个元素
listBuffer -= 1000 //如果删除的这个元素不存在,则不执行,也不会报错
listBuffer.remove(1) //删除下标为 1 的元素
listBuffer.remove(1,3) //从下标为 1 的元素开始,往后删除 3 个元素
println(listBuffer)
//listBuffer列表添加元素或列表
listBuffer += (99,22) //可以添加一个或多个元素
listBuffer.append(29,100)
listBuffer ++= list //添加一个列表
println(listBuffer)
//listBuffer删除元素
listBuffer -= 2 //删除2这个元素
listBuffer -= 1000 //如果删除的这个元素不存在,则不执行,也不会报错
listBuffer.remove(1) //删除下标为 1 的元素
listBuffer.remove(1,3) //从下标为 1 的元素开始,往后删除 3 个元素
println(listBuffer)
七.列表的其他操作
list.head //返回列表的第一个元素
list.tail //返回列表除第一个元素外的元素
list.isEmpty //判断列表是否为空,空true,非空false
list.last //返回列表最后一个元素
list.init //返回列表除最后一个元素外的元素
list.head //返回列表的第一个元素
list.tail //返回列表除第一个元素外的元素
list.isEmpty //判断列表是否为空,空true,非空false
list.last //返回列表最后一个元素
list.init //返回列表除最后一个元素外的元素
队列(Queue)
一.队列的基本介绍
1.队列是一个有序列表,再底层可以用数组或链表来实现
2.其输出和输入要遵循先入先出的原则。即:先存入队列的数据先取出,后存入的数据后取出
3.在Scala中,由设计者直接给我们提供队列类型使用
4.在Scala中,由scala.collection.mutable.Queue 和 scala.collect.immutable.Queue 我们使用最多的是可变的队列
1.队列是一个有序列表,再底层可以用数组或链表来实现
2.其输出和输入要遵循先入先出的原则。即:先存入队列的数据先取出,后存入的数据后取出
3.在Scala中,由设计者直接给我们提供队列类型使用
4.在Scala中,由scala.collection.mutable.Queue 和 scala.collect.immutable.Queue 我们使用最多的是可变的队列
二.队列的创建
val queue = new mutable.Queue[Int] //Int为队列的泛型
val queue = new mutable.Queue[Int] //Int为队列的泛型
三.队列添加元素
1.queue += 20
2.queue ++= list(2,3,4) //将列表中每个元素取出后加入队列
3.queue += list(2,3,4) //将list直接加入队列,前提是queue的泛型是Any
1.queue += 20
2.queue ++= list(2,3,4) //将列表中每个元素取出后加入队列
3.queue += list(2,3,4) //将list直接加入队列,前提是queue的泛型是Any
三.出入队列
1.(出队列)删除第一个元素并返回 queue.dequeue()
2.(入队列)在队尾添加元素 queue.enqueue(100,2,100) //元素是有序的,并且可以重复
1.(出队列)删除第一个元素并返回 queue.dequeue()
2.(入队列)在队尾添加元素 queue.enqueue(100,2,100) //元素是有序的,并且可以重复
四.返回队列中的元素
1.返回队列第一个元素 queue.head
2.返回队列最后一个元素 queue.last
3.返回队列尾部元素,返回的是一个队列,所以可以级联使用该方法(除了第一个元素,其他元素都返回)
queue.tail
queue.tail.tail
1.返回队列第一个元素 queue.head
2.返回队列最后一个元素 queue.last
3.返回队列尾部元素,返回的是一个队列,所以可以级联使用该方法(除了第一个元素,其他元素都返回)
queue.tail
queue.tail.tail
映射(Map)
Map的创建
一.回顾:Java中的Map的基本介绍
HashMap 是一个散列表(数组+链表),它存储的内容是键值对映射(key-value),key不能重复,Java中的Hash是无序的
HashMap 是一个散列表(数组+链表),它存储的内容是键值对映射(key-value),key不能重复,Java中的Hash是无序的
二.Scala中的Map
1.Scala中的Map和Java类似,也是一个散列表,它存储的内容也是键值对(key-value)映射,Scala中不可变的Map是有序的,不可变的Map是无序的
2.Scala中,有可变Map:scala.collection.mutable.Map
不可变Map:scala.collection.immutable.Map
1.Scala中的Map和Java类似,也是一个散列表,它存储的内容也是键值对(key-value)映射,Scala中不可变的Map是有序的,不可变的Map是无序的
2.Scala中,有可变Map:scala.collection.mutable.Map
不可变Map:scala.collection.immutable.Map
三.构建不可变的Map
//Map默认是不可变的
val map1 = Map("zhangsan" -> 10,"lisi" -> 18) //key-value的类型不做限制
小结
①从输出结构看,输出顺序和声明顺序一致
②构建Map集合中,集合中元素其实是Tuple2类型
③默认情况下(即在没有引包),Map是不可变的
//Map默认是不可变的
val map1 = Map("zhangsan" -> 10,"lisi" -> 18) //key-value的类型不做限制
小结
①从输出结构看,输出顺序和声明顺序一致
②构建Map集合中,集合中元素其实是Tuple2类型
③默认情况下(即在没有引包),Map是不可变的
四.构建可变映射Map
val map2 = mutable.Map("name" -> "zhangsan")
五.创建空的映射
val map3 = new scala.collection.mutable.HashMap[Any,Any]
六.对偶元组创建
val map4 = mutable.Map(("A",12),("B",18),("C",20))
说明:即创建包含键值对的二元组,和第四种方式等价,只是形式上不同。对偶元组就是只包含两个数据的元组。
val map2 = mutable.Map("name" -> "zhangsan")
五.创建空的映射
val map3 = new scala.collection.mutable.HashMap[Any,Any]
六.对偶元组创建
val map4 = mutable.Map(("A",12),("B",18),("C",20))
说明:即创建包含键值对的二元组,和第四种方式等价,只是形式上不同。对偶元组就是只包含两个数据的元组。
Map的操作
val map = mutable.Map("name"<- "张三","age" <- 18)
一.Map的取值(方式一)
val value1 = map("name")
print(value1)
说明:①如果key存在,则返回对应的值
②如果key不存在,则会抛出异常【java.util.NoSuchElementException】。可以用map.contains("name")检查name是否存在
③在Java中,如果key不存在则返回null
二.Map取值(方式二)
map.get("name").get
说明:如果key存在,map.get("name")就会返回some,在用get方法就可以取出值;如果key不存在则会返回None
三.Map取值(方式三)
map.getOrElse("name","默认值") //如果key存在,则返回对应的值,不存在则返回默认值
四.Map取值方式建议
1.如果确定map中有这个key,则使用map(key),速度快
2.如果不确定map中是否有这个key,则使用map.contains(key),可以加入逻辑
3.如果只是简单的希望得到一个值,使用map.getOrElse("Ip","127.0.0.1")
一.Map的取值(方式一)
val value1 = map("name")
print(value1)
说明:①如果key存在,则返回对应的值
②如果key不存在,则会抛出异常【java.util.NoSuchElementException】。可以用map.contains("name")检查name是否存在
③在Java中,如果key不存在则返回null
二.Map取值(方式二)
map.get("name").get
说明:如果key存在,map.get("name")就会返回some,在用get方法就可以取出值;如果key不存在则会返回None
三.Map取值(方式三)
map.getOrElse("name","默认值") //如果key存在,则返回对应的值,不存在则返回默认值
四.Map取值方式建议
1.如果确定map中有这个key,则使用map(key),速度快
2.如果不确定map中是否有这个key,则使用map.contains(key),可以加入逻辑
3.如果只是简单的希望得到一个值,使用map.getOrElse("Ip","127.0.0.1")
五.更新map中的元素
val map2 = mutable.Map("name"-> "张三","age" -> 18)
map2("name")="王五"
说明:更新的前提是map是可变的,不然会报错;如果key存在则会修改,如果不存在则会添加一个key-value
val map2 = mutable.Map("name"-> "张三","age" -> 18)
map2("name")="王五"
说明:更新的前提是map是可变的,不然会报错;如果key存在则会修改,如果不存在则会添加一个key-value
六.增加map中的元素
map2 += ("D" -> 2) //添加单个元素,如果key已经存在,则会更新
map2 += ("C" -> 3,"E" -> 4) //添加多个元素
七.删除map中的元素
map2 -= ("D") //删除只需要写key
map2 -= ("D","E") //可以同时删除多个key-value
说明:删除时只需要写key,可以同时删除多个;如果key存在则删除,不存在也不会报错
map2 += ("D" -> 2) //添加单个元素,如果key已经存在,则会更新
map2 += ("C" -> 3,"E" -> 4) //添加多个元素
七.删除map中的元素
map2 -= ("D") //删除只需要写key
map2 -= ("D","E") //可以同时删除多个key-value
说明:删除时只需要写key,可以同时删除多个;如果key存在则删除,不存在也不会报错
八.map的遍历
1.for((k,v) <- map2) println(k,v) //遍历map的key和value
2.for(k <- map2.keys) println(k) //只遍历map的key
3.for(v <- map2.values) println(V) //只遍历map的value
4.for(t <- map2) println(t) //遍历map结果以二元组的方式返回key-value,返回结果时Tuple2
1.for((k,v) <- map2) println(k,v) //遍历map的key和value
2.for(k <- map2.keys) println(k) //只遍历map的key
3.for(v <- map2.values) println(V) //只遍历map的value
4.for(t <- map2) println(t) //遍历map结果以二元组的方式返回key-value,返回结果时Tuple2
集(Set)
一.集的基本介绍
集是不重复元素的集合。集不保留顺序,默认是以哈希集实现
二.回顾:Java中Set
Java中,Hash是实现Set<E>接口的一个实现类,数据是以哈希表的顺序存放的,里面不能包含重复数据。Set接口是一个不包含重复数据的collection,HashSet中的数据也是没有顺序的
三.Scala中的Set说明
1.默认情况下,Scala使用的是不可变的集合,如果想使用可变的集合,需要引入scala.collection.mutable.Set包。
2.set(1) 可以判断元素 1 在集合中是否存在,存在返回true,不存在返回false
集是不重复元素的集合。集不保留顺序,默认是以哈希集实现
二.回顾:Java中Set
Java中,Hash是实现Set<E>接口的一个实现类,数据是以哈希表的顺序存放的,里面不能包含重复数据。Set接口是一个不包含重复数据的collection,HashSet中的数据也是没有顺序的
三.Scala中的Set说明
1.默认情况下,Scala使用的是不可变的集合,如果想使用可变的集合,需要引入scala.collection.mutable.Set包。
2.set(1) 可以判断元素 1 在集合中是否存在,存在返回true,不存在返回false
四.集Set的创建
1.创建不可变的集
val set = Set(1,2,3) //不需要引包,默认就是不可变的
2.可变集合的创建
import scala.collection.mutable.Set
val mutable = mutable.Set(1,2,3)
1.创建不可变的集
val set = Set(1,2,3) //不需要引包,默认就是不可变的
2.可变集合的创建
import scala.collection.mutable.Set
val mutable = mutable.Set(1,2,3)
五.可变集合元素的添加
set.add(4) //只有可变集才能添加元素,如果添加的元素重复也不会报错,但是不会添加
set += 8
set.add(4) //只有可变集才能添加元素,如果添加的元素重复也不会报错,但是不会添加
set += 8
六.可变集合元素的删除
set.remove(4) //集Set中可以根据值来删除,因为Set中的元素不重复
set -= 8 //如果删除的元素不存在,则不会报错。
set.remove(4) //集Set中可以根据值来删除,因为Set中的元素不重复
set -= 8 //如果删除的元素不存在,则不会报错。
七.Set的遍历
for(x <- set){
print(x)
}
八.集合的其他操作
val set1 = Set(1,2,3,4)
val set2 = Set(3,4,5,6)
1.求两个集合的并集(两个集合相加)
val res1 = set1 ++ set2
2.求两个集合的交集(返回两个集合中都存在的元素)
val res2 = set1.&(set2) //方法1
val res3 = set1.intersect(set2) //方法2
for(x <- set){
print(x)
}
八.集合的其他操作
val set1 = Set(1,2,3,4)
val set2 = Set(3,4,5,6)
1.求两个集合的并集(两个集合相加)
val res1 = set1 ++ set2
2.求两个集合的交集(返回两个集合中都存在的元素)
val res2 = set1.&(set2) //方法1
val res3 = set1.intersect(set2) //方法2
常用高阶函数
map映射操作
先看一个实际需求
要求:请将List(3,5,7) 中的所有元素都 * 2 ,将其结果放到一个新的集合中返回,即返回一个新的List(6,10,14), 请编写程序实现
传统方法
val list1 = List(3, 5, 7)
var list2 = List[Int]()
for (item <- list1) { //遍历
list2 = list2 :+ item * 2
}
println(list2)
上面提出的问题,其实就是一个关于集合元素映射操作的问题。在Scala中可以通过map映射操作来解决:将集合中每一个元素通过指定功能(函数)映射(转换)成新的结果集合,这里其实就是所谓的将函数作为参数传递给另外一个函数,这就是函数式编程的特点
使用map完成上述操作:list2 = list1.map(_*2)
高阶函数:一个可以接受函数的函数
//创建一个高阶函数,即可以接收函数的函数
//f:Double => Double表示一个可以接收Double类型并返回Double类型的函数
//n1 代表test的形参
//= 表示返回的类型使用类型推导
//f(n1) 表示执行传入的函数
def test(f:Double => Double,n1 :Double)={
f(n1)
要求:请将List(3,5,7) 中的所有元素都 * 2 ,将其结果放到一个新的集合中返回,即返回一个新的List(6,10,14), 请编写程序实现
传统方法
val list1 = List(3, 5, 7)
var list2 = List[Int]()
for (item <- list1) { //遍历
list2 = list2 :+ item * 2
}
println(list2)
上面提出的问题,其实就是一个关于集合元素映射操作的问题。在Scala中可以通过map映射操作来解决:将集合中每一个元素通过指定功能(函数)映射(转换)成新的结果集合,这里其实就是所谓的将函数作为参数传递给另外一个函数,这就是函数式编程的特点
使用map完成上述操作:list2 = list1.map(_*2)
高阶函数:一个可以接受函数的函数
//创建一个高阶函数,即可以接收函数的函数
//f:Double => Double表示一个可以接收Double类型并返回Double类型的函数
//n1 代表test的形参
//= 表示返回的类型使用类型推导
//f(n1) 表示执行传入的函数
def test(f:Double => Double,n1 :Double)={
f(n1)
flatmap扁平化操作
flatmap映射:flat 即压扁
flatmap:就是集合中每个元素的子元素映射到某个函数并返回新的集合
val names = ListBuffer("Python","Scala","Java")
def upper(s:String):String={ //接收一个字符串,将字符串字母全部变成大写后返回
s.toUpperCase
}
println(names.flatMap(upper)) //flatMap操作就是将集合中元素继续扁平化操作,即遍历所有元素
flatmap:就是集合中每个元素的子元素映射到某个函数并返回新的集合
val names = ListBuffer("Python","Scala","Java")
def upper(s:String):String={ //接收一个字符串,将字符串字母全部变成大写后返回
s.toUpperCase
}
println(names.flatMap(upper)) //flatMap操作就是将集合中元素继续扁平化操作,即遍历所有元素
filter过滤
filter:将符合要求的数据(筛选)放置到新的集合中
应用案例:
/*
将 val names = List("Alice", "Bob", "Nick") 集合中首字母为'A'的筛选到新的集合
*/
val names = List("Alice", "Aob", "Nick")
val res = names.filter(filter)
println(res)
def filter(s:String):Boolean={
s.startsWith("A")
}
应用案例:
/*
将 val names = List("Alice", "Bob", "Nick") 集合中首字母为'A'的筛选到新的集合
*/
val names = List("Alice", "Aob", "Nick")
val res = names.filter(filter)
println(res)
def filter(s:String):Boolean={
s.startsWith("A")
}
reduce化简
需求:val list = List(1,20,30,4,5) 求出list中元素之和
化简:将二元函数(接收两个参数的函数)引用于集合中的函数,
一.左化简reduceLeft 等价于 reduce
val list = List(1, 20, 30, 4, 5)
def sum(n1: Int, n2: Int): Int = {
n1 + n2
}
val res = list.reduceLeft(sum)
println("res=" + res)
左化简说明:
1.reduceLeft(f) 接收的函数需要的形式为 op: (B, A) => B): B
2.reduceLeft(f) 的运行规则是从左边开始执行将得到的结果返回给第一个参数,然后和下一个元素运行,以此类推
3.化简流程(((1+20)+30)+4)+5 = 60
二.右化简reduceRight
val list = List(1,2,3,4,5)
def product(n1:Int,n2:Int): Int ={
println(n1-n2)
n1-n2
}
val res = list.reduceLeft(product)
println(res)
右化简说明:
1.reduceRight(f) 的运行规则是从右边开始执行将结果返回给第二个参数,然后和下一个元素运算,以此类推
2.化简流程1-(2-(3-(4-5))) = 13
化简:将二元函数(接收两个参数的函数)引用于集合中的函数,
一.左化简reduceLeft 等价于 reduce
val list = List(1, 20, 30, 4, 5)
def sum(n1: Int, n2: Int): Int = {
n1 + n2
}
val res = list.reduceLeft(sum)
println("res=" + res)
左化简说明:
1.reduceLeft(f) 接收的函数需要的形式为 op: (B, A) => B): B
2.reduceLeft(f) 的运行规则是从左边开始执行将得到的结果返回给第一个参数,然后和下一个元素运行,以此类推
3.化简流程(((1+20)+30)+4)+5 = 60
二.右化简reduceRight
val list = List(1,2,3,4,5)
def product(n1:Int,n2:Int): Int ={
println(n1-n2)
n1-n2
}
val res = list.reduceLeft(product)
println(res)
右化简说明:
1.reduceRight(f) 的运行规则是从右边开始执行将结果返回给第二个参数,然后和下一个元素运算,以此类推
2.化简流程1-(2-(3-(4-5))) = 13
fold折叠
一.折叠的基本介绍
fold函数将上一步返回的值作为函数的第一个参数继续传递参与运算,直到list中的元素被遍历
1.可以把reduceLeft看做成简化版的foldLeft
操作代码
val list = List(2, 6, 3, 5, 9)
//foldLeft()和reduceLeft()的运行机制几乎一样
//list.foldLeft(5)(function)可以理解为List(5 , 2, 6, 3, 5, 9)的reduceLeft操作
//就是将 5 放到list的第一个元素位置,再进行reduceLeft操作
//运行流程:
// ((((5-2)-6)-3)-5)-9
val res1 = list.foldLeft(5)(function) //更多的时候5这个位置传的是一个函数
println(res1) //-20
折叠fold的简写
正斜杠 / 在哪边,就是哪边折叠
val list = List(2, 6)
val res = (5 /: list) (function) //这种写法等价于 list.foldLeft(5)(function)
println(res) //-3
val res2 =(list :\ 5) (function) //等价于 list.foldRight(5)(function)
println(res2) //1
def function(n1: Int, n2: Int): Int = {
n1 - n2
}
fold函数将上一步返回的值作为函数的第一个参数继续传递参与运算,直到list中的元素被遍历
1.可以把reduceLeft看做成简化版的foldLeft
操作代码
val list = List(2, 6, 3, 5, 9)
//foldLeft()和reduceLeft()的运行机制几乎一样
//list.foldLeft(5)(function)可以理解为List(5 , 2, 6, 3, 5, 9)的reduceLeft操作
//就是将 5 放到list的第一个元素位置,再进行reduceLeft操作
//运行流程:
// ((((5-2)-6)-3)-5)-9
val res1 = list.foldLeft(5)(function) //更多的时候5这个位置传的是一个函数
println(res1) //-20
折叠fold的简写
正斜杠 / 在哪边,就是哪边折叠
val list = List(2, 6)
val res = (5 /: list) (function) //这种写法等价于 list.foldLeft(5)(function)
println(res) //-3
val res2 =(list :\ 5) (function) //等价于 list.foldRight(5)(function)
println(res2) //1
def function(n1: Int, n2: Int): Int = {
n1 - n2
}
scan扫描
一.扫描的基本方式
扫描,即对某个集合中所有的元素做fold操作,但是会把产生的的所有中间结果放置于一个集合中保存
操作代码
val list = 1 to 5
//scan 扫描就是将集合中的每一个元素进行折叠操作,并将每个操作的结果放置再一个集合中
val res = list.scanLeft(8)(minux)
println(res) //8,7,5,2,-2,-7
//scanRight跟折叠fold操作基本相同,只是返回的结果不一样
val res2 = list.scanRight(6)(minux)
println(res2) //-3,4,-2,5,-1,6
def minux(n1:Int,n2:Int): Int ={
n1-n2
}
扫描,即对某个集合中所有的元素做fold操作,但是会把产生的的所有中间结果放置于一个集合中保存
操作代码
val list = 1 to 5
//scan 扫描就是将集合中的每一个元素进行折叠操作,并将每个操作的结果放置再一个集合中
val res = list.scanLeft(8)(minux)
println(res) //8,7,5,2,-2,-7
//scanRight跟折叠fold操作基本相同,只是返回的结果不一样
val res2 = list.scanRight(6)(minux)
println(res2) //-3,4,-2,5,-1,6
def minux(n1:Int,n2:Int): Int ={
n1-n2
}
groupBy分组
一.基本介绍
groupBy会按照列表中的某个元素中某个字段进行分组。会返回一个Map映射,分组字段做为key,包含该字段的元素做为value
二.代码演示1
//将列表进行分组 按照元素是否为 2 的倍数分组
val list = List(12,3,4,4,5,65,6,6,7,87,23)
val res = list.groupBy(x => if(x%2==0) "2的倍数" else "不是2的倍数" )
println(res)
三.代码演示2
//将列表按照每个元素中的城市字段进行分组
val list = List[String]("70999,1371001,广东,广州,中国移动,020,510000",
"71000,1371002,广东,广州,中国移动,020,510000",
"71348,1371350,广东,深圳,中国移动,0755,518000",
"71349,1371351,广东,深圳,中国移动,0755,518000")
//将匿名函数传入到groupBy中
val res = list.groupBy(s => s.split(",")(3))
//当匿名函数的参数在 => 符号右边只出现一次时,可以将参数用下划线 _ 代替。如下:
val res1 = list.groupBy(_.split(",")(3))
println(res1)
//也可以将自己写的函数传入到groupBy中
val res2 = list.groupBy(MygroupBy)
println(res2)
def MygroupBy(string: String)={
//被分组的列表中有多少个元素,这个函数就会被调用多少次
//将列表中每个元素按 , 号分割成数组,然后取出该数组中下标为 3 的元素
string.split(",")(3)
}
groupBy会按照列表中的某个元素中某个字段进行分组。会返回一个Map映射,分组字段做为key,包含该字段的元素做为value
二.代码演示1
//将列表进行分组 按照元素是否为 2 的倍数分组
val list = List(12,3,4,4,5,65,6,6,7,87,23)
val res = list.groupBy(x => if(x%2==0) "2的倍数" else "不是2的倍数" )
println(res)
三.代码演示2
//将列表按照每个元素中的城市字段进行分组
val list = List[String]("70999,1371001,广东,广州,中国移动,020,510000",
"71000,1371002,广东,广州,中国移动,020,510000",
"71348,1371350,广东,深圳,中国移动,0755,518000",
"71349,1371351,广东,深圳,中国移动,0755,518000")
//将匿名函数传入到groupBy中
val res = list.groupBy(s => s.split(",")(3))
//当匿名函数的参数在 => 符号右边只出现一次时,可以将参数用下划线 _ 代替。如下:
val res1 = list.groupBy(_.split(",")(3))
println(res1)
//也可以将自己写的函数传入到groupBy中
val res2 = list.groupBy(MygroupBy)
println(res2)
def MygroupBy(string: String)={
//被分组的列表中有多少个元素,这个函数就会被调用多少次
//将列表中每个元素按 , 号分割成数组,然后取出该数组中下标为 3 的元素
string.split(",")(3)
}
zip拉链
一.zip拉链的基本介绍
在开发中,当我们需要将两个集合进行对偶元组合并,可以使用拉链
val list1 = List(1,2,3)
val list2 = List(4,5,6)
val res = list1.zip(list2) //(1,4), (2,5), (3,6)
println(res)
二.拉链的注意事项
1.拉链的本质就是两个集合的合并操作,合并后每一个元素是一个对偶元组
2.拉链操作就是将两个集合中的元素一一对应,再进行合并
3.如果两个集合元素的个数不对应,会造成数据丢失
4.集合不限于list,也可以是Array等
5.如果要取出合并后的各个对偶元组,可以进行遍历
在开发中,当我们需要将两个集合进行对偶元组合并,可以使用拉链
val list1 = List(1,2,3)
val list2 = List(4,5,6)
val res = list1.zip(list2) //(1,4), (2,5), (3,6)
println(res)
二.拉链的注意事项
1.拉链的本质就是两个集合的合并操作,合并后每一个元素是一个对偶元组
2.拉链操作就是将两个集合中的元素一一对应,再进行合并
3.如果两个集合元素的个数不对应,会造成数据丢失
4.集合不限于list,也可以是Array等
5.如果要取出合并后的各个对偶元组,可以进行遍历
集合基本操作
iterator迭代器
一.基本说明
通过iterator方法从集合获取一个迭代器,通过while或者for循环遍历
val array = Array(1,2,3,4,5).iterator
println("=============while================")
while (array.hasNext){
println(array.next())
}
println("=============for================")
for (item <- array){
println(item)
}
通过iterator方法从集合获取一个迭代器,通过while或者for循环遍历
val array = Array(1,2,3,4,5).iterator
println("=============while================")
while (array.hasNext){
println(array.next())
}
println("=============for================")
for (item <- array){
println(item)
}
stream流
一.基本说明
stream是一个集合。它可以存放无穷多个元素,但是这无穷多个元素并不会一次性生产出来,而是需要多大的区间,就会动态的产生,末尾元素遵循lazy规则(即:需要是,才计算)
//创建一个流
def numForm(n:BigInt):Stream[BigInt]={n #:: numForm(n+1)}
val stream = numForm(1)
println(stream)
//取出第一个元素
println(stream.head)
println(stream.tail)
说明:
1.stream集合中存放的数据类型是BigInt
2.numForm是一个函数名,由程序员指定
3.创建的集合的第一个元素是n,后续生成的规则是n+1
4.后续元素生成的规则可以自行指定
5.使用tail方法就可以产生一个新的元素
stream是一个集合。它可以存放无穷多个元素,但是这无穷多个元素并不会一次性生产出来,而是需要多大的区间,就会动态的产生,末尾元素遵循lazy规则(即:需要是,才计算)
//创建一个流
def numForm(n:BigInt):Stream[BigInt]={n #:: numForm(n+1)}
val stream = numForm(1)
println(stream)
//取出第一个元素
println(stream.head)
println(stream.tail)
说明:
1.stream集合中存放的数据类型是BigInt
2.numForm是一个函数名,由程序员指定
3.创建的集合的第一个元素是n,后续生成的规则是n+1
4.后续元素生成的规则可以自行指定
5.使用tail方法就可以产生一个新的元素
view视图
一.基本介绍
stream的懒加载特性,也可以对其他集合应用view方法来得到类似的效果,具有如下特点:
1.view方法产生出一个总是被懒加载执行的集合
2.view不会缓存数据,每次都要重新计算,比如遍历view时。
val view = 1 to 100
val res = view.filter(eq)
println(res)
def eq(n:Int):Boolean={
n.toString.equals(n.toString.reverse)
}
stream的懒加载特性,也可以对其他集合应用view方法来得到类似的效果,具有如下特点:
1.view方法产生出一个总是被懒加载执行的集合
2.view不会缓存数据,每次都要重新计算,比如遍历view时。
val view = 1 to 100
val res = view.filter(eq)
println(res)
def eq(n:Int):Boolean={
n.toString.equals(n.toString.reverse)
}
线程安全
一.所有线程安全的集合都是以synchronized开头的集合
SynchronizedBuffer
SynchronizedMap
SynchronizedPriorityQueue
SynchronizedQueue
SynchronizeSet
SynchronizeStack
二.并行集合
1.Scala为了充分使用多核CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算
2.主要用到的算法有:Divied and conquer(分治算法),Scala通过splitters,combiners等抽象层来实现,主要原理是将计算工作分解成很多任务,分发给一些处理器去完成,并将它们处理的结果合并返回
3.Work stealin算法【学数学】,主要用于任务调度负载均衡(load-balancing),通俗点完成自己的所有任务之后,发现其他人还有活没干完,主动(或被安排)帮他人一起干,这样达到尽早干完的目的
SynchronizedBuffer
SynchronizedMap
SynchronizedPriorityQueue
SynchronizedQueue
SynchronizeSet
SynchronizeStack
二.并行集合
1.Scala为了充分使用多核CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算
2.主要用到的算法有:Divied and conquer(分治算法),Scala通过splitters,combiners等抽象层来实现,主要原理是将计算工作分解成很多任务,分发给一些处理器去完成,并将它们处理的结果合并返回
3.Work stealin算法【学数学】,主要用于任务调度负载均衡(load-balancing),通俗点完成自己的所有任务之后,发现其他人还有活没干完,主动(或被安排)帮他人一起干,这样达到尽早干完的目的
三.使用并行模块
Scala 2.13之后,并行模块变成了外部库,和XML、Swing、parser-combinators等一样需要在maven项目的pom.xml中手动导入:
<dependency>
<groupId>org.scala-lang.modules</groupId>
<artifactId>scala-parallel-collections_2.13</artifactId>
<version>0.2.0</version>
</dependency>
然后在代码文件中导入如下的包就行了: import scala.collection.parallel.CollectionConverters._ 参考链接:https://stackoverflow.com/questions/57287607/answer/submit[code=html]
四.基本案例
1.输出1-5
(1 to 5).foreach(println(_)) //单行操作
println()
(1 to 5).par.foreach(println(_)) //并行操作
2.查看并行集合中元素访问的线程
val result1 = (0 to 100).map{case _ => Thread.currentThread.getName}
val result2 = (0 to 100).par.map{case _ => Thread.currentThread.getName}
println(result1)
println(result2)
Scala 2.13之后,并行模块变成了外部库,和XML、Swing、parser-combinators等一样需要在maven项目的pom.xml中手动导入:
<dependency>
<groupId>org.scala-lang.modules</groupId>
<artifactId>scala-parallel-collections_2.13</artifactId>
<version>0.2.0</version>
</dependency>
然后在代码文件中导入如下的包就行了: import scala.collection.parallel.CollectionConverters._ 参考链接:https://stackoverflow.com/questions/57287607/answer/submit[code=html]
四.基本案例
1.输出1-5
(1 to 5).foreach(println(_)) //单行操作
println()
(1 to 5).par.foreach(println(_)) //并行操作
2.查看并行集合中元素访问的线程
val result1 = (0 to 100).map{case _ => Thread.currentThread.getName}
val result2 = (0 to 100).par.map{case _ => Thread.currentThread.getName}
println(result1)
println(result2)
操作符
一.操作符扩展
1.如果想在变量名,类名等定义中使用语法关键字(保留字),可以配合反引号`` val`val`=42
2.中置操作符:A操作符B (等同于) A.操作符(B)
1.如果想在变量名,类名等定义中使用语法关键字(保留字),可以配合反引号`` val`val`=42
2.中置操作符:A操作符B (等同于) A.操作符(B)
Scala模式匹配(特色)
模式匹配初体验
一.基本介绍
Scala中的模式匹配类似于Java中中的switch语法,但是更加强大
模式匹配语法中,采用match关键字声明,每个分支采用case关键字进行声明,当需要匹配时,会从第一个case分支开始,如果执行成功,就会执行相应的逻辑代码,如果不匹配,则继续执行下一个分支case。如果所有的case都不匹配,那么会执行case_分支,类似于Java中default
Scala中的模式匹配类似于Java中中的switch语法,但是更加强大
模式匹配语法中,采用match关键字声明,每个分支采用case关键字进行声明,当需要匹配时,会从第一个case分支开始,如果执行成功,就会执行相应的逻辑代码,如果不匹配,则继续执行下一个分支case。如果所有的case都不匹配,那么会执行case_分支,类似于Java中default
二.代码案例
val oper = '#'
val n1 = 20
val n2 = 10
var res = 0
oper match {
/*
1.match类似于Java的switch,
2.如果匹配成功,则会执行=> 后面的代码块
3.匹配顺序是从上往下
4.=>后面的代码块不需要写break,因为match会自动退出
5.如果所有的case都不匹配,则会执行case_后的代码块
6.如果所有的case都不匹配,并且没有case_,则会抛出异常MatchError
4.可以再match中使用其他类型,不仅仅是Char
*/
case '+' => res = n1 + n2
case '-' => res = n1 - n2
case '*' => res = n1 * n2
case '/' => res = n1 / n2
case _ => println("oper error")
}
println("res=" + res)
val oper = '#'
val n1 = 20
val n2 = 10
var res = 0
oper match {
/*
1.match类似于Java的switch,
2.如果匹配成功,则会执行=> 后面的代码块
3.匹配顺序是从上往下
4.=>后面的代码块不需要写break,因为match会自动退出
5.如果所有的case都不匹配,则会执行case_后的代码块
6.如果所有的case都不匹配,并且没有case_,则会抛出异常MatchError
4.可以再match中使用其他类型,不仅仅是Char
*/
case '+' => res = n1 + n2
case '-' => res = n1 - n2
case '*' => res = n1 * n2
case '/' => res = n1 / n2
case _ => println("oper error")
}
println("res=" + res)
模式匹配扩展
条件守卫
一.基本介绍
如果想要表达匹配某个范围的数据,就需要再模式匹配中增加条件守卫
for (ch <- "+-3!") {
var sign = 0
var digit = 0
ch match {
case '+' => sign = 1
case '-' => sign = -1
// 说明case _ if表示条件守卫,这里的下划线 _ 表示不接收ch
case _ if ch.toString.equals("3") => digit = 3
case _ => sign = 2
}
println(ch + " " + sign + " " + digit)
}
如果想要表达匹配某个范围的数据,就需要再模式匹配中增加条件守卫
for (ch <- "+-3!") {
var sign = 0
var digit = 0
ch match {
case '+' => sign = 1
case '-' => sign = -1
// 说明case _ if表示条件守卫,这里的下划线 _ 表示不接收ch
case _ if ch.toString.equals("3") => digit = 3
case _ => sign = 2
}
println(ch + " " + sign + " " + digit)
}
模式匹配中的变量
二.模式中的变量
如果在case关键字后跟变量名,那么match前表达式的值会赋给那个变量
val ch = 'V'
ch match {
case '+' => println("ok~")
case mychar => println("ok~" + mychar)
case _ => println ("ok~~")
}
如果在case关键字后跟变量名,那么match前表达式的值会赋给那个变量
val ch = 'V'
ch match {
case '+' => println("ok~")
case mychar => println("ok~" + mychar)
case _ => println ("ok~~")
}
模式匹配扩展2
匹配数组
一.基本介绍
1.Array(0) 匹配只有一个元素且为0 的数组
2.Array(x,y) 匹配数组有两个元素,并将两个元素赋值给x y。当然可以以此类推,赋值给三个值x y z
3.Array(0._*) 匹配数组从0开始到最后一个元素
val arrs = Array(Array(0),Array(1,2),Array(0,2,3),Array(2,3,4,5,6))
for (item <- arrs){
val res = item match{
case Array(0) => '0'
case Array(x,y) => x+"+"+y
case Array(0,_*) => "一零开头的数组"
case _ => "什么都不是"
}
// 0
// 1+2
// "一零开头的数组"
// "什么都不是"
println(res)
1.Array(0) 匹配只有一个元素且为0 的数组
2.Array(x,y) 匹配数组有两个元素,并将两个元素赋值给x y。当然可以以此类推,赋值给三个值x y z
3.Array(0._*) 匹配数组从0开始到最后一个元素
val arrs = Array(Array(0),Array(1,2),Array(0,2,3),Array(2,3,4,5,6))
for (item <- arrs){
val res = item match{
case Array(0) => '0'
case Array(x,y) => x+"+"+y
case Array(0,_*) => "一零开头的数组"
case _ => "什么都不是"
}
// 0
// 1+2
// "一零开头的数组"
// "什么都不是"
println(res)
匹配列表
匹配列表和匹配数组基本类似
val lists = Array(List(1),List(1,2),List(1,2,3,4,3,34),List(0,2,3))
for (item <- lists){
val res = item match{
case 1::Nil => "1"
case x::y::Nil => x+"+"+y
case 1::tail => "1....."
case _ => "asdasdasd"
}
println(res)
val lists = Array(List(1),List(1,2),List(1,2,3,4,3,34),List(0,2,3))
for (item <- lists){
val res = item match{
case 1::Nil => "1"
case x::y::Nil => x+"+"+y
case 1::tail => "1....."
case _ => "asdasdasd"
}
println(res)
匹配元组
val tuple = Array((0,1),(1,0),(1,1),(1,0,2))
for (item <- tuple){
val res = item match{
case (0,_) => "0...."
case (x,0) => x
case _ => "asdasdsa"
}
println(res )
for (item <- tuple){
val res = item match{
case (0,_) => "0...."
case (x,0) => x
case _ => "asdasdsa"
}
println(res )
匹配对象
三.小结
1.构建对象时,apply方法方法会被调用,比如 val n = Square(6)
2.当Square(n)写再case后面时,即【case Square(n) => xxx】,会默认调用unapply方法(对象提取器)
3.number会被传递给def unapply(z:Double) 的形参 z
4.如果返回unapply方法返回的时Some,则会将结果传递给Square(n)中的 n
5.case中对象的unapply方法(提取器)返回的时Some则匹配成功,为None则匹配失败
一.基本介绍
1.case中对象的unapply方法(提取器)返回some集合则为匹配成功
2.返回none则为匹配失败
1.case中对象的unapply方法(提取器)返回some集合则为匹配成功
2.返回none则为匹配失败
二.基本案例
object Square{
//对象提取器,它会提取出创建对象时传入的参数
//简单来说,unapply就是逆向拆解对象
def unapply(z:Double): Option[Double] = {
println("unapply方法被调用,z= "+z)
Some(math.sqrt(z))
}
def apply(z: Double): Double = z*z
val number:Double=36
number match {
/*
Square(n)运行机制
1.当匹配到case Square(n)时,
2.会先调用Objeck Square中unapply(z:Double)方法,传入的 z 值就是number
3.如果对象提取器返回的值时Some(6)则表示提取成功,同时将 6 赋值给 n ,最后将 n 输出
*/
case Square(n) => println("匹配成功 "+n)
case _ => println("asdadsa")
}
object Square{
//对象提取器,它会提取出创建对象时传入的参数
//简单来说,unapply就是逆向拆解对象
def unapply(z:Double): Option[Double] = {
println("unapply方法被调用,z= "+z)
Some(math.sqrt(z))
}
def apply(z: Double): Double = z*z
val number:Double=36
number match {
/*
Square(n)运行机制
1.当匹配到case Square(n)时,
2.会先调用Objeck Square中unapply(z:Double)方法,传入的 z 值就是number
3.如果对象提取器返回的值时Some(6)则表示提取成功,同时将 6 赋值给 n ,最后将 n 输出
*/
case Square(n) => println("匹配成功 "+n)
case _ => println("asdadsa")
}
三.小结
1.构建对象时,apply方法方法会被调用,比如 val n = Square(6)
2.当Square(n)写再case后面时,即【case Square(n) => xxx】,会默认调用unapply方法(对象提取器)
3.number会被传递给def unapply(z:Double) 的形参 z
4.如果返回unapply方法返回的时Some,则会将结果传递给Square(n)中的 n
5.case中对象的unapply方法(提取器)返回的时Some则匹配成功,为None则匹配失败
模式匹配扩展3
声明变量中的匹配
一.基本介绍
match中每一个case都可以被单独提取出来,意思和case中的一样
二.应用案例
val (x,y) = (1,2)
val (q,r) = BigInt(10) /% 3 //将BigInt(10) / 3 赋值给 q ,将BigInt(10) % 3 赋值给r
val arr = Array(1,2,34,4,5)
val Array(first,second,_*) = arr //表示取出arr中的前两个元素,并且赋值给first,second
match中每一个case都可以被单独提取出来,意思和case中的一样
二.应用案例
val (x,y) = (1,2)
val (q,r) = BigInt(10) /% 3 //将BigInt(10) / 3 赋值给 q ,将BigInt(10) % 3 赋值给r
val arr = Array(1,2,34,4,5)
val Array(first,second,_*) = arr //表示取出arr中的前两个元素,并且赋值给first,second
for循环中的模式匹配
一.基本介绍
for循环中也可以进行模式匹配
val map = Map("A" -> 1,"B" -> 2,"C" -> 3)
for ((k,v) <- map){ //for表达式中也可以使用模式匹配
println(k,v)
}
for ((k,2) <- map){ //这里表示遍历出value为 2 的键值对,其他的过滤
println(k,2)
}
for ((k,v) <- map if v==3){ //这里使用的for循环守卫
println(k,v)
}
for循环中也可以进行模式匹配
val map = Map("A" -> 1,"B" -> 2,"C" -> 3)
for ((k,v) <- map){ //for表达式中也可以使用模式匹配
println(k,v)
}
for ((k,2) <- map){ //这里表示遍历出value为 2 的键值对,其他的过滤
println(k,2)
}
for ((k,v) <- map if v==3){ //这里使用的for循环守卫
println(k,v)
}
样例类
一.基本介绍
1.样例类仍然是类
2.样例类用case关键字进行声明
3.样例类是为模式匹配而优化的类
4.构造器中的每一参数都成为了val,除非显示的声明var (不推荐)
5.在样例类对应的伴生对象中存在apply方法,可以是在实例化时不使用new关键字,就可以直接构造出对象
6.也提供unapply方法,让模式匹配可以正常的工作
7.将自动生成toString,equal,hashCode和copy方法(有点类似模板类,直接生成使用)
8.除了上述外,样例类和其他类完全一样,可以添加字段和方法
1.样例类仍然是类
2.样例类用case关键字进行声明
3.样例类是为模式匹配而优化的类
4.构造器中的每一参数都成为了val,除非显示的声明var (不推荐)
5.在样例类对应的伴生对象中存在apply方法,可以是在实例化时不使用new关键字,就可以直接构造出对象
6.也提供unapply方法,让模式匹配可以正常的工作
7.将自动生成toString,equal,hashCode和copy方法(有点类似模板类,直接生成使用)
8.除了上述外,样例类和其他类完全一样,可以添加字段和方法
二.样例类的基本写法
abstract class Amount
case class Dollar(n:Int) extends Amount //这里的形参会在底层被声明成val
case class Currency(n:Int,s:Stirng) extends Amount
case Object NoAmount extends Amount
①样例类的使用案例一
val arr =Array(Dollar(1000),Currency(5000,"RMB"),noAmcount)
for (item <- arr){
val res = item match {
//样例类会自动创建unapply方法,会将创建对象传入的参数提取出来
case Dollar(n) => "$"+n
case Currency(k,v) => k+" "+v
case _ => "sdfasdf"
}
println(res)
}
②样例类的使用案例二
val amt = Currency(1500,"RMB")
val amt2 = amt.copy() //利用amt对象copy一个新的对象
println(amt.n,amt2.s) //样例类会将形参创建成一个可读不可写的属性,所以可以访问读取
val amt3 = amt.copy(n=1800) //copy时也可以指定参数
println(amt3) //这里可以直接数据对象的属性,因为底层有toString方法
abstract class Amount
case class Dollar(n:Int) extends Amount //这里的形参会在底层被声明成val
case class Currency(n:Int,s:Stirng) extends Amount
case Object NoAmount extends Amount
①样例类的使用案例一
val arr =Array(Dollar(1000),Currency(5000,"RMB"),noAmcount)
for (item <- arr){
val res = item match {
//样例类会自动创建unapply方法,会将创建对象传入的参数提取出来
case Dollar(n) => "$"+n
case Currency(k,v) => k+" "+v
case _ => "sdfasdf"
}
println(res)
}
②样例类的使用案例二
val amt = Currency(1500,"RMB")
val amt2 = amt.copy() //利用amt对象copy一个新的对象
println(amt.n,amt2.s) //样例类会将形参创建成一个可读不可写的属性,所以可以访问读取
val amt3 = amt.copy(n=1800) //copy时也可以指定参数
println(amt3) //这里可以直接数据对象的属性,因为底层有toString方法
匹配嵌套结构
一.基本介绍
操作原理类似于正则表达式
三.最佳实践案例-商品捆绑打折出售
现在有一些商品,请使用Scala设计相关的样例类,完成商品捆绑打折出售。要求商品捆绑可以是单个商品,也可以是多个商品。打折时按照折扣x元进行设计.能够统计出所有捆绑商品打折后的最终价格
操作原理类似于正则表达式
三.最佳实践案例-商品捆绑打折出售
现在有一些商品,请使用Scala设计相关的样例类,完成商品捆绑打折出售。要求商品捆绑可以是单个商品,也可以是多个商品。打折时按照折扣x元进行设计.能够统计出所有捆绑商品打折后的最终价格
①
//设计样例类
abstract class Item
case class Book(desc:String,price:Double) extends Item
//discount表示优惠的金额
case class Bundle(desc:String,discount:Double,item: Item*) extends Item
②三个知识点
val sale = Bundle("书籍",10,Book("漫画",40),Bundle("文学书籍",20,Book("阳关",80),Book("围城",30)))
//知识点一:取出漫画二字
val res = sale match {
case Bundle(_,_,Book(desc,_),_*) => desc
}
println(res)
//知识点二(@表示法):取出漫画二字,和后面所以的内容,并存放到元组中。_*表示匹配多个Bundle
val res2 = sale match {
case Bundle(_,_,Book(desc,_),b @ _*) => (desc,b)
}
println(res2)
//知识点三:取出漫画二字,和后面的Bundle对象。
//说明因为没有使用 _* 即明确说明没有多个Bundle,所以返回的rest,就不是ArraySeq了。
val res3 = sale match {
case Bundle(_,_,Book(desc,_),rest) => (desc,rest)
}
println(res3)
③实现项目
def price(it: Item): Double ={
it match {
case Book(_,p) => p
case Bundle(_,disc,its @ _*) => its.map(price).sum - disc
}
println(price(sale))
//设计样例类
abstract class Item
case class Book(desc:String,price:Double) extends Item
//discount表示优惠的金额
case class Bundle(desc:String,discount:Double,item: Item*) extends Item
②三个知识点
val sale = Bundle("书籍",10,Book("漫画",40),Bundle("文学书籍",20,Book("阳关",80),Book("围城",30)))
//知识点一:取出漫画二字
val res = sale match {
case Bundle(_,_,Book(desc,_),_*) => desc
}
println(res)
//知识点二(@表示法):取出漫画二字,和后面所以的内容,并存放到元组中。_*表示匹配多个Bundle
val res2 = sale match {
case Bundle(_,_,Book(desc,_),b @ _*) => (desc,b)
}
println(res2)
//知识点三:取出漫画二字,和后面的Bundle对象。
//说明因为没有使用 _* 即明确说明没有多个Bundle,所以返回的rest,就不是ArraySeq了。
val res3 = sale match {
case Bundle(_,_,Book(desc,_),rest) => (desc,rest)
}
println(res3)
③实现项目
def price(it: Item): Double ={
it match {
case Book(_,p) => p
case Bundle(_,disc,its @ _*) => its.map(price).sum - disc
}
println(price(sale))
密封类
一.基本介绍
1.如果想让case类的所有子类都必须在申明该类的相同源文件中定义,可以将样例类通过超类声明为sealed,这个超类就成为了密封类
2.密封类就是不能在其他文件中定义子类
1.如果想让case类的所有子类都必须在申明该类的相同源文件中定义,可以将样例类通过超类声明为sealed,这个超类就成为了密封类
2.密封类就是不能在其他文件中定义子类
Scala函数式编程高级
偏函数
偏函数的引出
一.先看一个需求
给你一个集合val list = List(1, 2, 3, 4, "abc") ,请完成如下要求:
将集合list中的所有数字+1,并返回一个新的集合
要求忽略掉 非数字 的元素,即返回的 新的集合 形式为 (2, 3, 4, 5)
解决方案
//思路一:使用filter-map
val list = List(1, 2, 3, 4, "12123")
val res = list.filter(fun1).map(fun3).map(fun2)
println(res)
//思路二:模式匹配
val res2 = list.map(fun4)
println(res2)
def fun1(n:Any): Boolean ={
n.isInstanceOf[Int]
}
def fun2(n:Int):Int={
n+1
}
def fun3(n:Any): Int ={
n.asInstanceOf[Int]
}
def fun4(n:Any):Any={
n match {
case n:Int => n+1
case _ =>
}
给你一个集合val list = List(1, 2, 3, 4, "abc") ,请完成如下要求:
将集合list中的所有数字+1,并返回一个新的集合
要求忽略掉 非数字 的元素,即返回的 新的集合 形式为 (2, 3, 4, 5)
解决方案
//思路一:使用filter-map
val list = List(1, 2, 3, 4, "12123")
val res = list.filter(fun1).map(fun3).map(fun2)
println(res)
//思路二:模式匹配
val res2 = list.map(fun4)
println(res2)
def fun1(n:Any): Boolean ={
n.isInstanceOf[Int]
}
def fun2(n:Int):Int={
n+1
}
def fun3(n:Any): Int ={
n.asInstanceOf[Int]
}
def fun4(n:Any):Any={
n match {
case n:Int => n+1
case _ =>
}
偏函数介绍
一.基本介绍
1.在对某个集合进行处理时,其中有些元素不符合处理条件,这时就可以使用偏函数
2.将函数中封装了一组case语句的函数,叫做偏函数。它只会对作用于指定类型的参数或者指定范围的参数实施计算,超出范围的值会另外处理
3.偏函数在Scala中是一个特质PartialFunction
1.在对某个集合进行处理时,其中有些元素不符合处理条件,这时就可以使用偏函数
2.将函数中封装了一组case语句的函数,叫做偏函数。它只会对作用于指定类型的参数或者指定范围的参数实施计算,超出范围的值会另外处理
3.偏函数在Scala中是一个特质PartialFunction
二.代码演示
val list = List(1, 2, 3, 4, "12123")
//创建一个偏函数
//1.PartialFunction[Any,Int]表示偏函数接收一个Any类型的值,返回一个Int类型的值
//2.isDefinedAt(x: Any)这个方法如果返回true,则会调用apply方法,否则不会调用apply方法
//3.apply方法相当于创建一个对象,对传入的值+1,然后返回
val unit = new PartialFunction[Any, Int] {
override def isDefinedAt(x: Any): Boolean = x.isInstanceOf[Int]
override def apply(x: Any): Int = x.asInstanceOf[Int] + 1
}
val res = list.collect(unit)
println(res)
val list = List(1, 2, 3, 4, "12123")
//创建一个偏函数
//1.PartialFunction[Any,Int]表示偏函数接收一个Any类型的值,返回一个Int类型的值
//2.isDefinedAt(x: Any)这个方法如果返回true,则会调用apply方法,否则不会调用apply方法
//3.apply方法相当于创建一个对象,对传入的值+1,然后返回
val unit = new PartialFunction[Any, Int] {
override def isDefinedAt(x: Any): Boolean = x.isInstanceOf[Int]
override def apply(x: Any): Int = x.asInstanceOf[Int] + 1
}
val res = list.collect(unit)
println(res)
三.偏函数小结
1.偏函数是使用构建特质的实现类(使用方式是PartialFunction的匿名子类)
2.PartialFunction是个特质
3.构建偏函数时,参数形式[Any,Int]是泛型,第一个表示输入参数的类型,第二个是返回参数的类型
4.当使用偏函数时,会遍历集合中的所有元素,编译器执行流程会先执行isDefinedAt(),如果返回true,则会调用apply方法,构建一个新的Int对象返回
5.执行isDefinedAt() 为false,则会过滤这个元素,即不会构建新的Int对象
6.map函数不支持偏函数,因为map底层的机制就是所有元素循环遍历,无法过滤原来集合的元素
7.collect函数支持偏函数,多用于偏函数的使用
1.偏函数是使用构建特质的实现类(使用方式是PartialFunction的匿名子类)
2.PartialFunction是个特质
3.构建偏函数时,参数形式[Any,Int]是泛型,第一个表示输入参数的类型,第二个是返回参数的类型
4.当使用偏函数时,会遍历集合中的所有元素,编译器执行流程会先执行isDefinedAt(),如果返回true,则会调用apply方法,构建一个新的Int对象返回
5.执行isDefinedAt() 为false,则会过滤这个元素,即不会构建新的Int对象
6.map函数不支持偏函数,因为map底层的机制就是所有元素循环遍历,无法过滤原来集合的元素
7.collect函数支持偏函数,多用于偏函数的使用
偏函数的化简形式
偏函数的简化形式一
声明偏函数,需要重写特质中的方法,有时候会很麻烦,而Scala提供了简单的方法
implicit def dataCon(n:Double):Int={ //创建一个隐式转换函数
n.toInt
}
//偏函数的简化形式
def parFun:PartialFunction[Any,Int]={
case n:Int => n+1 //利用模式匹配
case n:Double => n*2 //这里运用了隐式转换将Double转成了Int
}
val list = List(1,2,3,4,2,2,3,4,"hello")
val res = list.collect(parFun)
println(res)
}
声明偏函数,需要重写特质中的方法,有时候会很麻烦,而Scala提供了简单的方法
implicit def dataCon(n:Double):Int={ //创建一个隐式转换函数
n.toInt
}
//偏函数的简化形式
def parFun:PartialFunction[Any,Int]={
case n:Int => n+1 //利用模式匹配
case n:Double => n*2 //这里运用了隐式转换将Double转成了Int
}
val list = List(1,2,3,4,2,2,3,4,"hello")
val res = list.collect(parFun)
println(res)
}
偏函数的简化形式二
这种方式不能指定传入值的类型和返回值的类型,只能使用默认的
//偏函数的简化形式二
val res2 = list.collect{
case n:Int => n+1
case n:Double => n*2
}
println(res2)
这种方式不能指定传入值的类型和返回值的类型,只能使用默认的
//偏函数的简化形式二
val res2 = list.collect{
case n:Int => n+1
case n:Double => n*2
}
println(res2)
做为参数的函数
一.基本介绍
函数作为一个变量传入到了另一个函数中,那么这个作为参数的函数的类型是function1,即:(参数类型) => 返回类型
二.代码演示
def plus(n:Int): Int ={
n+3
}
val list = List(1,2,3,4)
val res = list.map(plus(_))
println(res.mkString(",")
三.小结
1.map(plus(_))中的plus(_)就是将plus这个函数当做一个参数传给map,_代表从集合中遍历出来的每一个元素
2.plus(_)这里也可以写成plus表示对List(1,2,3,4)的遍历,将每次遍历的元素传给puls的n
3.进行n+3的运算后,返回新的Int,并加入到新的集合中result1中
4.def map[B,That](f:A=>B) 的声明中的f:A=>B是一个函数
函数作为一个变量传入到了另一个函数中,那么这个作为参数的函数的类型是function1,即:(参数类型) => 返回类型
二.代码演示
def plus(n:Int): Int ={
n+3
}
val list = List(1,2,3,4)
val res = list.map(plus(_))
println(res.mkString(",")
三.小结
1.map(plus(_))中的plus(_)就是将plus这个函数当做一个参数传给map,_代表从集合中遍历出来的每一个元素
2.plus(_)这里也可以写成plus表示对List(1,2,3,4)的遍历,将每次遍历的元素传给puls的n
3.进行n+3的运算后,返回新的Int,并加入到新的集合中result1中
4.def map[B,That](f:A=>B) 的声明中的f:A=>B是一个函数
匿名函数
基本介绍
一.基本介绍
没有名字的函数就是匿名函数,可以通过函数表达式来设置匿名函数
二.代码演示
val res = (n:Int) => n+3
println(res(2))
三.小结
1.不需要写def函数名
2.不需要写返回值类型,因为默认使用类型推导
3.如果代码块有多行,就使用{}
没有名字的函数就是匿名函数,可以通过函数表达式来设置匿名函数
二.代码演示
val res = (n:Int) => n+3
println(res(2))
三.小结
1.不需要写def函数名
2.不需要写返回值类型,因为默认使用类型推导
3.如果代码块有多行,就使用{}
匿名函数的简写(参数类型推断)
一.基本介绍
参数推断省去类型信息(在某些情况下【需要有应用场景】),参数类型可以推断出来,如list=(1,2,3) list.map() map中函数参数类型是可以推断出来的,同时也可以进行参数的简写
二参数类型推断的写法说明
1.参数类型是可以推断出来的,可以省略参数类型
2.当传入的函数,只有单个参数时,可以省去括号
3.如果变量只在 => 右边只出现一次,可以用 _ 代替
参数推断省去类型信息(在某些情况下【需要有应用场景】),参数类型可以推断出来,如list=(1,2,3) list.map() map中函数参数类型是可以推断出来的,同时也可以进行参数的简写
二参数类型推断的写法说明
1.参数类型是可以推断出来的,可以省略参数类型
2.当传入的函数,只有单个参数时,可以省去括号
3.如果变量只在 => 右边只出现一次,可以用 _ 代替
三.匿名函数的简化写法一(map)
val list = List(1,2,3,4)
//原始写法,将list列表中每个元素加 1 之后返回到一个新列表中
val res1 = list.map((n:Int)=>n+1) //传入一个匿名函数到map函数中
println(res1)
//简化1 :因为list列表中的元素都是Int,使用匿名函数的形参的类型就可以使用类型推断
//这时就可以省略Int
val res2 = list.map(n=>n+1)
println(res2)
//简化2 :如果变量在 => 右边只出现了一次,这时可以将变量写成下划线 _
val res3 = list.map(_+1)
println(res3)
val list = List(1,2,3,4)
//原始写法,将list列表中每个元素加 1 之后返回到一个新列表中
val res1 = list.map((n:Int)=>n+1) //传入一个匿名函数到map函数中
println(res1)
//简化1 :因为list列表中的元素都是Int,使用匿名函数的形参的类型就可以使用类型推断
//这时就可以省略Int
val res2 = list.map(n=>n+1)
println(res2)
//简化2 :如果变量在 => 右边只出现了一次,这时可以将变量写成下划线 _
val res3 = list.map(_+1)
println(res3)
四.匿名函数的简化写法二(reduce)
//原始写法,将list列表中的每个元素相加,并返回结果
val reduceRes1 = list.reduce((x:Int,y:Int)=>x+y)
println(reduceRes1)
//简化1 :因为形参的类型可以根据list元素的类型进行推断,所以Int可以省略
val reduceRes2 = list.reduce((x,y)=>x+y)
println(reduceRes2)
//简化2 : 因为变量在 => 的右边只出现一次,所以变量x y可以用下划线 _ 表示
val reduceRes3 = list.reduce(_+_)
println(reduceRes3)
}
//原始写法,将list列表中的每个元素相加,并返回结果
val reduceRes1 = list.reduce((x:Int,y:Int)=>x+y)
println(reduceRes1)
//简化1 :因为形参的类型可以根据list元素的类型进行推断,所以Int可以省略
val reduceRes2 = list.reduce((x,y)=>x+y)
println(reduceRes2)
//简化2 : 因为变量在 => 的右边只出现一次,所以变量x y可以用下划线 _ 表示
val reduceRes3 = list.reduce(_+_)
println(reduceRes3)
}
子主题
高级函数
一.基本介绍
可以传入函数的函数就作高阶函数,在某些情况下也可以返回一个函数
可以传入函数的函数就作高阶函数,在某些情况下也可以返回一个函数
二.代码演示
//定义一个高阶函数
def test(f:Double => Double,n:Double): Double ={
f(n)
}
val res = test((n:Double) => n+n,6)
println(res)
//定义一个高阶函数
def test(f:Double => Double,n:Double): Double ={
f(n)
}
val res = test((n:Double) => n+n,6)
println(res)
三.高阶函数返回函数类型
//创建一个高阶函数,它可以返回一个匿名函数
def test(n:Int) ={
(m:Int) => n-m
}
//调用这个高阶函数
//分步骤操作
val f1 = test(2) //这里会将2传递给 n ,即返回的结果是 (m:Int) =>2-m 是一个匿名函数
println(f1(4)) //这里会将4 传递给 m ,即会返回 2-4 = -2
//可以将上面的分步操作一次性完成
val res = test(3)(7)
println(res)
}
//创建一个高阶函数,它可以返回一个匿名函数
def test(n:Int) ={
(m:Int) => n-m
}
//调用这个高阶函数
//分步骤操作
val f1 = test(2) //这里会将2传递给 n ,即返回的结果是 (m:Int) =>2-m 是一个匿名函数
println(f1(4)) //这里会将4 传递给 m ,即会返回 2-4 = -2
//可以将上面的分步操作一次性完成
val res = test(3)(7)
println(res)
}
闭包
一.基本介绍
闭包(closure)就是一个函数和与其相关的引用环境组合的一个整体(实体)。
闭包(closure)就是一个函数和与其相关的引用环境组合的一个整体(实体)。
二.代码演示
def test(n:Int) ={
(m:Int) => n-m
}
//调用这个高阶函数
//分步骤操作
val f1 = test(2) //这里会将2传递给 n ,即返回的结果是 (m:Int) =>2-m 是一个匿名函数
println(f1(4)) //这里会将4 传递给 m ,即会返回 2-4 = -2
//可以将上面的分步操作一次性完成
val res = test(3)(7)
println(res)
def test(n:Int) ={
(m:Int) => n-m
}
//调用这个高阶函数
//分步骤操作
val f1 = test(2) //这里会将2传递给 n ,即返回的结果是 (m:Int) =>2-m 是一个匿名函数
println(f1(4)) //这里会将4 传递给 m ,即会返回 2-4 = -2
//可以将上面的分步操作一次性完成
val res = test(3)(7)
println(res)
三.代码小结
1.test函数返回的是 (m:Int) => n-m 匿名函数,因为该函数引用到了函数外的变量 n ,那么该函数和 n 整体形成了一个闭包。val f1 = test(2)这里的f1就是一个闭包
2.可以这样理解,返回函数是一个对象,而n就是该对象的一个字段,它们共同形成了一个闭包
3.当多次调用f1时(可以理解多次调用闭包),发现使用的是同一个n ,所以 n 不变
4.当使用闭包时,主要搞清楚返回函数引用了函数外的哪些变量,因为它们组合成了一个整体(实体),形成了一个闭包
1.test函数返回的是 (m:Int) => n-m 匿名函数,因为该函数引用到了函数外的变量 n ,那么该函数和 n 整体形成了一个闭包。val f1 = test(2)这里的f1就是一个闭包
2.可以这样理解,返回函数是一个对象,而n就是该对象的一个字段,它们共同形成了一个闭包
3.当多次调用f1时(可以理解多次调用闭包),发现使用的是同一个n ,所以 n 不变
4.当使用闭包时,主要搞清楚返回函数引用了函数外的哪些变量,因为它们组合成了一个整体(实体),形成了一个闭包
四.添加后缀名的案例
object clusureDemon {
def main(args: Array[String]): Unit = {
val f = makeSuffix(".jpg")
println(f("scala")) //scala.jpg
println(f("python.jpg")) //python.jpg
}
/*
传入一个文件名,如果这个文件名带有后缀名,那么直接返回;
如果这个文件名没有后缀名,则为它添加一个后缀名并返回
*/
def makeSuffix(suffix:String) ={
(fileName:String) => {
if (fileName.endsWith(suffix)) fileName
else fileName+suffix
}
}
}
五.闭包的好处
1.返回的匿名函数和 test(n:Int) 的 n 变量组合成了一个闭包,因为返回的函数要引用 n 这个变量
2.可以反复引用上一次传入的值
object clusureDemon {
def main(args: Array[String]): Unit = {
val f = makeSuffix(".jpg")
println(f("scala")) //scala.jpg
println(f("python.jpg")) //python.jpg
}
/*
传入一个文件名,如果这个文件名带有后缀名,那么直接返回;
如果这个文件名没有后缀名,则为它添加一个后缀名并返回
*/
def makeSuffix(suffix:String) ={
(fileName:String) => {
if (fileName.endsWith(suffix)) fileName
else fileName+suffix
}
}
}
五.闭包的好处
1.返回的匿名函数和 test(n:Int) 的 n 变量组合成了一个闭包,因为返回的函数要引用 n 这个变量
2.可以反复引用上一次传入的值
函数柯里化
一.基本介绍(curry)
1.函数编程中,接收多个参数的函数都可转换成接收一个参数的函数,这个转换过程就是函数柯里化
2.柯里化就是证明了函数只需要一个参数而已。在前面的学习中也用到了函数柯里化
3.柯里化是面向函数思想的必要产生结果
1.函数编程中,接收多个参数的函数都可转换成接收一个参数的函数,这个转换过程就是函数柯里化
2.柯里化就是证明了函数只需要一个参数而已。在前面的学习中也用到了函数柯里化
3.柯里化是面向函数思想的必要产生结果
二.代码演示
编写一个函数,接收两个整数,可以返回两个数的乘积,要求:
1.使用常规的方式完成
2.使用闭包的方式完成
3.使用函数柯里化完成
//常规方式
def fun1(x:Int,y:Int): Int ={
x*y
}
//闭包方式(使用匿名函数)
def fun2(x:Int) ={
(y:Int) => x*y
}
//函数柯里化方式
def fun3(x:Int)(y:Int): Int ={
x*y
println(fun1(2,3)) //调用普通函数
println(fun2(2)(5)) //调用闭包函数
println(fun3(2)(7)) //调用柯里化函数
编写一个函数,接收两个整数,可以返回两个数的乘积,要求:
1.使用常规的方式完成
2.使用闭包的方式完成
3.使用函数柯里化完成
//常规方式
def fun1(x:Int,y:Int): Int ={
x*y
}
//闭包方式(使用匿名函数)
def fun2(x:Int) ={
(y:Int) => x*y
}
//函数柯里化方式
def fun3(x:Int)(y:Int): Int ={
x*y
println(fun1(2,3)) //调用普通函数
println(fun2(2)(5)) //调用闭包函数
println(fun3(2)(7)) //调用柯里化函数
三.案例
/*
比较两个字符串在忽略大小写的情况下是否相等,注意,这里是两个任务:
全部转大写(或小写)
比较是否相等
针对这两个操作,我们用一个函数去处理的思想,其实也变成了两个函数处理的思想(柯里化)
*/
def eq(s1:String,s2:String): Boolean ={
s1.equals(s2)
}
//创建一个隐式类,用来增加集合的操作方法
implicit class TestEq(s:String){
def checkEq(ss:String)(f:(String,String) => Boolean): Boolean ={ //函数柯里化的方式
f(s.toLowerCase(),ss.toLowerCase())
}
}
val str = "hello"
val res = str.checkEq("HELLO")(eq)
println(res)
//方式二,直接传入一个匿名函数,并利用类型推断简写这个匿名函数
val res2 = str.checkEq("Hello")(_.equals(_))
println(res2)
/*
比较两个字符串在忽略大小写的情况下是否相等,注意,这里是两个任务:
全部转大写(或小写)
比较是否相等
针对这两个操作,我们用一个函数去处理的思想,其实也变成了两个函数处理的思想(柯里化)
*/
def eq(s1:String,s2:String): Boolean ={
s1.equals(s2)
}
//创建一个隐式类,用来增加集合的操作方法
implicit class TestEq(s:String){
def checkEq(ss:String)(f:(String,String) => Boolean): Boolean ={ //函数柯里化的方式
f(s.toLowerCase(),ss.toLowerCase())
}
}
val str = "hello"
val res = str.checkEq("HELLO")(eq)
println(res)
//方式二,直接传入一个匿名函数,并利用类型推断简写这个匿名函数
val res2 = str.checkEq("Hello")(_.equals(_))
println(res2)
控制抽象
一.基本介绍
控制抽象是这样的函数
1.参数是函数
2.函数参数没有输入值也没有返回值
控制抽象是这样的函数
1.参数是函数
2.函数参数没有输入值也没有返回值
二.代码演示
//像这样,参数是函数,函数没有输入值也没有返回值的函数,就是控制抽象
def myRun(f:()=>Unit): Unit ={
new Thread{
override def run(): Unit = {
f()
}
}.start()
}
myRun { //这里就是调用myRun这个函数
() => //这里表示没有传入参数也没有返回值
println("开始干活!")
Thread.sleep(5000)
println("干完了")
}
三.案例
/*
利用控制抽象写出until函数,实现一下代码
var n = 10
while (n > 0){
n -= 1
println(n)
}
*/
var x = 10
until(x > 0){
x -= 1
println(x)
}
//创建一个控制抽象函数,这里也用到了函数柯里化
def until(f1: => Boolean)(f2: => Unit): Unit = {
if (f1){
f2
until(f1)(f2)
}
//像这样,参数是函数,函数没有输入值也没有返回值的函数,就是控制抽象
def myRun(f:()=>Unit): Unit ={
new Thread{
override def run(): Unit = {
f()
}
}.start()
}
myRun { //这里就是调用myRun这个函数
() => //这里表示没有传入参数也没有返回值
println("开始干活!")
Thread.sleep(5000)
println("干完了")
}
三.案例
/*
利用控制抽象写出until函数,实现一下代码
var n = 10
while (n > 0){
n -= 1
println(n)
}
*/
var x = 10
until(x > 0){
x -= 1
println(x)
}
//创建一个控制抽象函数,这里也用到了函数柯里化
def until(f1: => Boolean)(f2: => Unit): Unit = {
if (f1){
f2
until(f1)(f2)
}
子主题
子主题
Akka网络编程
Akka基本介绍
基本介绍
一.基本介绍
1.Akka是Java虚拟机JVM平台上构建高并发,分布式和容错应用的工具包和运行时。可以理解成Akka是编写并发程序的框架。
2.Akka用Scala语言编写,同时提供了Scala和Java的开发接口。
3.Akka主要解决的问题是:可以轻松的写出高效稳定的并发程序,程序员不再过多的考虑线程,锁和资源竞争等细节
1.Akka是Java虚拟机JVM平台上构建高并发,分布式和容错应用的工具包和运行时。可以理解成Akka是编写并发程序的框架。
2.Akka用Scala语言编写,同时提供了Scala和Java的开发接口。
3.Akka主要解决的问题是:可以轻松的写出高效稳定的并发程序,程序员不再过多的考虑线程,锁和资源竞争等细节
Actor模型
二.Actor模型
1.处理并发问题关键是保证共享数据的一致性和正确性,因为程序是多线程的,多个线程对同一个数据修改,若不加同步条件,势必会造成数据污染。但是我们对关键代码加同步条件synchronized后,实际上开发就会堵塞在这段代码,对程序的效率有很大影响
2.若是用单线程处理,不会有数据一致性的问题,但是系统的性能又不能保证。
3.Actor模型的出现解决了这个问题,简化并发编程,提升程序性能。可以这样理解:Actor模型是一种处理并发问题的解决方案
1.处理并发问题关键是保证共享数据的一致性和正确性,因为程序是多线程的,多个线程对同一个数据修改,若不加同步条件,势必会造成数据污染。但是我们对关键代码加同步条件synchronized后,实际上开发就会堵塞在这段代码,对程序的效率有很大影响
2.若是用单线程处理,不会有数据一致性的问题,但是系统的性能又不能保证。
3.Actor模型的出现解决了这个问题,简化并发编程,提升程序性能。可以这样理解:Actor模型是一种处理并发问题的解决方案
Actor模型说明
三.Actor模型说明
1.Akka处理并发的方法基于Actor模型
2.在就基于Actor的系统里,所有事物都是Actor,就好像在面向对象设计里面所有的事物都是对象一样
3.Actor模型是作为一个并发编程设计和架构的。Actor与Actor之间只能通过消息通信
4.Actor与Actor之间只能用消息进行通信,当一个Actor给另一个Actor发消息,消息是有顺序的,只需要将消息投寄到相应的邮箱即可。
5.怎么处理消息是由接受消息的Actor决定的,发送消息的Actor可以等待回复,也可异步处理
6.ActorSystem的职责是负责创建并管理其创建的Actor,ActorSystem是单例的,一个jvm进程中有一个即可,而Actor可以有多个
7.Actor模型是对并发编程更高的抽象
8.Actor模型是异步的 非阻塞 高性能的事件驱动编程模型(最经典的案例就是Ajax异步请求)
9.Actor模型是轻量级事件处理(1GB内存可以容纳百万级别的Actor),因此处理大并发性能高
1.Akka处理并发的方法基于Actor模型
2.在就基于Actor的系统里,所有事物都是Actor,就好像在面向对象设计里面所有的事物都是对象一样
3.Actor模型是作为一个并发编程设计和架构的。Actor与Actor之间只能通过消息通信
4.Actor与Actor之间只能用消息进行通信,当一个Actor给另一个Actor发消息,消息是有顺序的,只需要将消息投寄到相应的邮箱即可。
5.怎么处理消息是由接受消息的Actor决定的,发送消息的Actor可以等待回复,也可异步处理
6.ActorSystem的职责是负责创建并管理其创建的Actor,ActorSystem是单例的,一个jvm进程中有一个即可,而Actor可以有多个
7.Actor模型是对并发编程更高的抽象
8.Actor模型是异步的 非阻塞 高性能的事件驱动编程模型(最经典的案例就是Ajax异步请求)
9.Actor模型是轻量级事件处理(1GB内存可以容纳百万级别的Actor),因此处理大并发性能高
Actor工作机制
四.Actor模型工作机制
1.ActorSystem创建Actor
2.ActorRef:可以理解成是Actor的代理或者引用。消息是通过ActorRef发送,而不能通过Actor发送消息,通过哪个ActorRef发消息,就表示把消息发给哪个Actor
3.消息发送到Dispatcher Message(消息发送器),他得到消息后,会将消息分发到对应的mailBox。Dispatcher Message可以理解成一个线程池,MailBox可以理解成消息队列,可以缓冲多个消息,遵守FIFO
4.Actor可以通过receive方法来获取消息,然后进行处理
1.ActorSystem创建Actor
2.ActorRef:可以理解成是Actor的代理或者引用。消息是通过ActorRef发送,而不能通过Actor发送消息,通过哪个ActorRef发消息,就表示把消息发给哪个Actor
3.消息发送到Dispatcher Message(消息发送器),他得到消息后,会将消息分发到对应的mailBox。Dispatcher Message可以理解成一个线程池,MailBox可以理解成消息队列,可以缓冲多个消息,遵守FIFO
4.Actor可以通过receive方法来获取消息,然后进行处理
Actor间传递消息机制
1.每个消息就是一个Message对象。Message继承Runable,因为Message就是线程类
2.从Actor模型工作机制看上去很麻烦,但是程序员编程时只需要编写Actor就可以了,其他工作交给Actor模型完成
3.A Actor要给B Actor发消息,先要拿到B Actor的代理对象ActorRef才能发消息
2.从Actor模型工作机制看上去很麻烦,但是程序员编程时只需要编写Actor就可以了,其他工作交给Actor模型完成
3.A Actor要给B Actor发消息,先要拿到B Actor的代理对象ActorRef才能发消息
Actor自我通讯
一.代码演示
//当我们继承Actor之后,就是一个Actor了,核心方法receive(抽象方法)需要重写
class actorToActor extends Actor{
//1.receive 方法会被该 Actor 的 mailbox(实现了Runable接口) 调用
//2.当mailbox 接收到消息后,就会调用receive方法
//3.Receive是一个偏函数
//4.type Receive = PartialFunction[Any, Unit] ---> 可以接收任意类型的值,没有返回值
override def receive: Receive = {
case "hello" => println("hello too")
case "ok" => println("ok too")
case "exit" => {
println("接受到exit,退出指令~~~")
context.stop(self) //停止了当前的ActorRef,即停掉了mailbox
context.system.terminate() //停止了ActorSystem
}
case _ => println("没有接受到值")
}
}
//当我们继承Actor之后,就是一个Actor了,核心方法receive(抽象方法)需要重写
class actorToActor extends Actor{
//1.receive 方法会被该 Actor 的 mailbox(实现了Runable接口) 调用
//2.当mailbox 接收到消息后,就会调用receive方法
//3.Receive是一个偏函数
//4.type Receive = PartialFunction[Any, Unit] ---> 可以接收任意类型的值,没有返回值
override def receive: Receive = {
case "hello" => println("hello too")
case "ok" => println("ok too")
case "exit" => {
println("接受到exit,退出指令~~~")
context.stop(self) //停止了当前的ActorRef,即停掉了mailbox
context.system.terminate() //停止了ActorSystem
}
case _ => println("没有接受到值")
}
}
object actorToActorDemo{
//先创建一个ActorSystem,专门用于创建创建actor
private val actorSystem = ActorSystem("actorSystem") //括号里面的是ActorSystem的名字
//利用ActorSystem创建一个actor的引用,即实例化actorToActor类 "A_actor"这个是actor引用(ActorRef)的名字
//Props[actorToActor]这里运用了反射机制
private val a_actorRef: ActorRef = actorSystem.actorOf(Props[actorToActor],"A_actor")
def main(args: Array[String]): Unit = {
//通过a_actorRef给A_actor发消息,实际上 是发送到A_actor的mailbox,然后通过mailbox提交给A_actor
//这里是有序发送
//mailbox是Runable机制,所以程序需要手动结束
a_actorRef ! "ok"
a_actorRef ! "hello"
a_actorRef ! "exit"
}
}
//先创建一个ActorSystem,专门用于创建创建actor
private val actorSystem = ActorSystem("actorSystem") //括号里面的是ActorSystem的名字
//利用ActorSystem创建一个actor的引用,即实例化actorToActor类 "A_actor"这个是actor引用(ActorRef)的名字
//Props[actorToActor]这里运用了反射机制
private val a_actorRef: ActorRef = actorSystem.actorOf(Props[actorToActor],"A_actor")
def main(args: Array[String]): Unit = {
//通过a_actorRef给A_actor发消息,实际上 是发送到A_actor的mailbox,然后通过mailbox提交给A_actor
//这里是有序发送
//mailbox是Runable机制,所以程序需要手动结束
a_actorRef ! "ok"
a_actorRef ! "hello"
a_actorRef ! "exit"
}
}
二.Actor自我通讯机制小结和说明
当程序执行 val a_actorRef: ActorRef = actorSystem.actorOf(Props[actorToActor],"A_actor"),就会完成如下任务
1.actorSystem是ActorSystem("actorSystem")这样创建的
2.这里的Prop[actorToActor]会使用反射机制,创建一个actorToActor对象,如果是actorSystem.actorOf(Prop(new actorToActor),"a_actorRef") 就是使用new的方式创建一个a_actorRef对象,这里Porp要用小括号
3.先创建一个actorToActor对象的代理对象a_actorRef,使用a_actorRef才能发送消息。
4.会在底层创建Dispatcher Message,这是一个线程池,用于纷分发消息,消息会发送到对应的Actor的mailbox
5.会在底层创建actorToActor的mailbox对象,该对象是一个队列,可以接受dispatcher Message发送的消息
6.mailbox实现了Runnable接口,是一个线程,一直运行并调用actor的receive方法,因此dispatcher Message向mailbox发消息时,actor的receive就可以得到消息
7.a_actorRef ! "ok",表示把ok消息发送到actorToActor的mailbox(通过dispatcher Message转发)
当程序执行 val a_actorRef: ActorRef = actorSystem.actorOf(Props[actorToActor],"A_actor"),就会完成如下任务
1.actorSystem是ActorSystem("actorSystem")这样创建的
2.这里的Prop[actorToActor]会使用反射机制,创建一个actorToActor对象,如果是actorSystem.actorOf(Prop(new actorToActor),"a_actorRef") 就是使用new的方式创建一个a_actorRef对象,这里Porp要用小括号
3.先创建一个actorToActor对象的代理对象a_actorRef,使用a_actorRef才能发送消息。
4.会在底层创建Dispatcher Message,这是一个线程池,用于纷分发消息,消息会发送到对应的Actor的mailbox
5.会在底层创建actorToActor的mailbox对象,该对象是一个队列,可以接受dispatcher Message发送的消息
6.mailbox实现了Runnable接口,是一个线程,一直运行并调用actor的receive方法,因此dispatcher Message向mailbox发消息时,actor的receive就可以得到消息
7.a_actorRef ! "ok",表示把ok消息发送到actorToActor的mailbox(通过dispatcher Message转发)
Actor之间通信
一.代码演示
class AActor(actorRef: ActorRef) extends Actor {
val bActorRef :ActorRef = actorRef
var count = 0
override def receive: Receive = {
case "start" => {
println("游戏开始!!")
self ! "我打!"
}
case "我打!" => {
println("我是A aaaaaaa")
count += 1
Thread.sleep(1000)
bActorRef ! "我打!" //向B Actor发送 “我打”
if (count == 10) { //当count等于10时,关闭AActor和BActor和ActorSystem
context.stop(self)
context.stop(sender())
context.system.terminate()
}
}
}
}
class BActocr extends Actor {
override def receive: Receive = {
case "我打!" => {
println("我是B bbbbbbbbbb")
Thread.sleep(1000)
sender() ! "我打!" //向发件人发送 “我打”
}
}
}
object ActorGame extends App {
//创建ActorSystem,名字叫做actorSystem
val actorSystem = ActorSystem("actorSystem")
val BRef: ActorRef = actorSystem.actorOf(Props[BActocr], "B")
val ARef: ActorRef = actorSystem.actorOf(Props(new AActor(BRef)), "A")
ARef ! "start"
}
class AActor(actorRef: ActorRef) extends Actor {
val bActorRef :ActorRef = actorRef
var count = 0
override def receive: Receive = {
case "start" => {
println("游戏开始!!")
self ! "我打!"
}
case "我打!" => {
println("我是A aaaaaaa")
count += 1
Thread.sleep(1000)
bActorRef ! "我打!" //向B Actor发送 “我打”
if (count == 10) { //当count等于10时,关闭AActor和BActor和ActorSystem
context.stop(self)
context.stop(sender())
context.system.terminate()
}
}
}
}
class BActocr extends Actor {
override def receive: Receive = {
case "我打!" => {
println("我是B bbbbbbbbbb")
Thread.sleep(1000)
sender() ! "我打!" //向发件人发送 “我打”
}
}
}
object ActorGame extends App {
//创建ActorSystem,名字叫做actorSystem
val actorSystem = ActorSystem("actorSystem")
val BRef: ActorRef = actorSystem.actorOf(Props[BActocr], "B")
val ARef: ActorRef = actorSystem.actorOf(Props(new AActor(BRef)), "A")
ARef ! "start"
}
二.Actor的receive方法小结
1.每个Actor都有对应的mailbox
2.mailbox实现了Runnable接口,一直处于运行状态
3.当消息到达mailbox时,mailbox就会调用receive方法,将消息推送给对应的Actor
1.每个Actor都有对应的mailbox
2.mailbox实现了Runnable接口,一直处于运行状态
3.当消息到达mailbox时,mailbox就会调用receive方法,将消息推送给对应的Actor
Akka网络编程基础
Akka支持面向大并发后端服务程序
1.网络编程有两种
1.TCP socket编程,是网络编程的主流。之所以叫TCP socket编程,是因为底层是基于ICP/IP协议的。比如:QQ聊天
2.b/s结构的http编程,我们使用浏览器去访问服务器时,使用的就是http协议,而http底层依旧是用tcp socket实现的。比如:京东
2.TCP/IP协议
TCP/IP协议,中文译名叫做传输控制协议/因特网互联协议,又叫网络通讯协议,这个协议时internet最基本的协议,Internet国际互联网的基础,简单的说,就是由网络层的ip协议和传输层的icp协议组成的
3.端口使用注意
1.在计算机(尤其是服务器)要尽量的少开端口
2.一个端口只能被一个程序监听
3.如果使用netstat -an 可以查看本机有哪些端口在监听
4.可以使用netstat -anb 来查看监听端口pid,在结合任务管理器关闭不安全的端口
1.网络编程有两种
1.TCP socket编程,是网络编程的主流。之所以叫TCP socket编程,是因为底层是基于ICP/IP协议的。比如:QQ聊天
2.b/s结构的http编程,我们使用浏览器去访问服务器时,使用的就是http协议,而http底层依旧是用tcp socket实现的。比如:京东
2.TCP/IP协议
TCP/IP协议,中文译名叫做传输控制协议/因特网互联协议,又叫网络通讯协议,这个协议时internet最基本的协议,Internet国际互联网的基础,简单的说,就是由网络层的ip协议和传输层的icp协议组成的
3.端口使用注意
1.在计算机(尤其是服务器)要尽量的少开端口
2.一个端口只能被一个程序监听
3.如果使用netstat -an 可以查看本机有哪些端口在监听
4.可以使用netstat -anb 来查看监听端口pid,在结合任务管理器关闭不安全的端口
设计模式介绍
学习设计模式必要性
一.学习设计者模式的必要性
1.面试需要
2.读源码时可以用到,尤其是一些大型的框架使用设计模式
3.设计模式能让专业人之间交流方便
4.提高代码的易维护性
5.设计模式是编程经验的总结,简单理解:即通用的编程应用场景的模式化,套路化(站在软件设计层面思考)
1.面试需要
2.读源码时可以用到,尤其是一些大型的框架使用设计模式
3.设计模式能让专业人之间交流方便
4.提高代码的易维护性
5.设计模式是编程经验的总结,简单理解:即通用的编程应用场景的模式化,套路化(站在软件设计层面思考)
设计模式介绍
二.设计模式的介绍
1.设计模式是程序员在面对同类软件工程设计问题上所总结出来的有用的经验,它不是代码,是一种思想
2.设计模式的本质提高了软件的维护性,通用性和扩展性,并降低了软件的复杂度
3.设计模式不限于某种语言
1.设计模式是程序员在面对同类软件工程设计问题上所总结出来的有用的经验,它不是代码,是一种思想
2.设计模式的本质提高了软件的维护性,通用性和扩展性,并降低了软件的复杂度
3.设计模式不限于某种语言
设计模式分类
三.设计模式的分类(23种)
1.创建型模式:单例模式,抽象工厂模式,建造者模式,工厂模式,原型模式
2.结构性模式:适配器模式,桥接模式,装饰模式,组合模式,外观模式,享元模式,代理模式
3.行为者模式:模板方法模式,命令模式,迭代器模式,观察者模式,中介者模式,备忘录模式,解释器模式,状态模式,策略模式,职责链模式,访问者模式
1.创建型模式:单例模式,抽象工厂模式,建造者模式,工厂模式,原型模式
2.结构性模式:适配器模式,桥接模式,装饰模式,组合模式,外观模式,享元模式,代理模式
3.行为者模式:模板方法模式,命令模式,迭代器模式,观察者模式,中介者模式,备忘录模式,解释器模式,状态模式,策略模式,职责链模式,访问者模式
泛型,上下界,逆变不变协变
泛型
一.基本介绍
1.如果我们要求函数的参数可以接受任意泛型。可以使用泛型,这个类型可以代表任意的数据类型。
2.例如List,在创建List时,可以传入整型,字符串,浮点数等等任意类型。那是因为List在类定义时引用了泛型。
二.泛型应用案例1
1.编写一个Message类
2.可以构建Int类型的Message,String类型的Message.
3.要求使用泛型来完成设计,(说明:不能使用Any)
1.如果我们要求函数的参数可以接受任意泛型。可以使用泛型,这个类型可以代表任意的数据类型。
2.例如List,在创建List时,可以传入整型,字符串,浮点数等等任意类型。那是因为List在类定义时引用了泛型。
二.泛型应用案例1
1.编写一个Message类
2.可以构建Int类型的Message,String类型的Message.
3.要求使用泛型来完成设计,(说明:不能使用Any)
类型约束 上界和下界
Java下界 下界
Java上界
在Java泛型里表示某个类型是A类型的子类型,使用extend关键字,这种形式叫upper bounds(上线或上界)语法如下:
<T extend A>
//使用通配符的形式
<? extends A>
Java下界
在Java中泛型里表示某个类型是A类型的父类型,使用super关键字
<T super A>
//使用通配符
<? super A>
在Java泛型里表示某个类型是A类型的子类型,使用extend关键字,这种形式叫upper bounds(上线或上界)语法如下:
<T extend A>
//使用通配符的形式
<? extends A>
Java下界
在Java中泛型里表示某个类型是A类型的父类型,使用super关键字
<T super A>
//使用通配符
<? super A>
Scala上界
在Scala里表示某个类型是A类型的子类型,也称上限或者上界。使用<:关键字。语法如下
[T <: A]
//使用通配符
[_ <: A]
Scala下界
在Scala中下界,使用 >: 关键字
[T >: A] //表示T的下界是A
//使用通配符
[_ >: A]
在Scala里表示某个类型是A类型的子类型,也称上限或者上界。使用<:关键字。语法如下
[T <: A]
//使用通配符
[_ <: A]
Scala下界
在Scala中下界,使用 >: 关键字
[T >: A] //表示T的下界是A
//使用通配符
[_ >: A]
Scala下界使用小结
①对于下界,可以传入任意类型
②传入和A直系的,是A父类就按照A父类处理,如果是A的子类,则按照A处理
③如果传入的对象与A无关,一律按照Object处理
④也就是说下界可以随便传,只是处理方式不一样
⑤不能使用上界的思路来类推下界的含义
①对于下界,可以传入任意类型
②传入和A直系的,是A父类就按照A父类处理,如果是A的子类,则按照A处理
③如果传入的对象与A无关,一律按照Object处理
④也就是说下界可以随便传,只是处理方式不一样
⑤不能使用上界的思路来类推下界的含义
逆变不变协变
一.基本介绍
1.Scala的协变(+),逆变(-),协变covariant、逆变contravariant、不可变invariant
2.对于一个带类型参数的类型,比如 List[T],如果对A及其子类型B,满足 List[B]也符合List[A]的子类型,那么就称为covariance(协变) ,如果 List[A]是 List[B]的子类型,即与原来的父子关系正相反,则称为contravariance(逆变)。如果一个类型支持协变或逆变,则称这个类型为variance(翻译为可变的或变型),否则称为invariance(不可变的)
3在Java里,泛型类型都是invariant,比如 List<String> 并不是 List<Object> 的子类型。而scala支持,可以在定义类型时声明(用加号表示为协变,减号表示逆变),如: trait List[+T] // 在类型定义时声明为协变这样会把List[String]作为List[Any]的子类型。
1.Scala的协变(+),逆变(-),协变covariant、逆变contravariant、不可变invariant
2.对于一个带类型参数的类型,比如 List[T],如果对A及其子类型B,满足 List[B]也符合List[A]的子类型,那么就称为covariance(协变) ,如果 List[A]是 List[B]的子类型,即与原来的父子关系正相反,则称为contravariance(逆变)。如果一个类型支持协变或逆变,则称这个类型为variance(翻译为可变的或变型),否则称为invariance(不可变的)
3在Java里,泛型类型都是invariant,比如 List<String> 并不是 List<Object> 的子类型。而scala支持,可以在定义类型时声明(用加号表示为协变,减号表示逆变),如: trait List[+T] // 在类型定义时声明为协变这样会把List[String]作为List[Any]的子类型。
二.应用实例
在这里引入关于这个符号的说明,在声明Scala的泛型类型时,“+”表示协变,而“-”表示逆变
C[+T]:如果A是B的子类,那么C[A]是C[B]的子类,称为协变
C[-T]:如果A是B的子类,那么C[B]是C[A]的子类,称为逆变
C[T]:无论A和B是什么关系,C[A]和C[B]没有从属关系。称为不变.
val t: Temp[Super] = new Temp[Sub]("hello world1")
class Temp3[A](title: String) { //Temp3[+A] //Temp[-A]
override def toString: String = {
title
}}
//支持协变
class Super
class Sub extends Super
在这里引入关于这个符号的说明,在声明Scala的泛型类型时,“+”表示协变,而“-”表示逆变
C[+T]:如果A是B的子类,那么C[A]是C[B]的子类,称为协变
C[-T]:如果A是B的子类,那么C[B]是C[A]的子类,称为逆变
C[T]:无论A和B是什么关系,C[A]和C[B]没有从属关系。称为不变.
val t: Temp[Super] = new Temp[Sub]("hello world1")
class Temp3[A](title: String) { //Temp3[+A] //Temp[-A]
override def toString: String = {
title
}}
//支持协变
class Super
class Sub extends Super
0 条评论
下一页