Scala学习笔记
2022-04-03 21:14:50 34 举报
AI智能生成
Scala是一种功能强大的编程语言,它集成了面向对象编程和函数式编程的特性。Scala的设计目标是提高程序员的生产力,让代码更简洁、更易读。学习Scala需要掌握一些基本概念,如变量、数据类型、函数、类和对象等。此外,还需要了解Scala的一些特性,如模式匹配、高阶函数、闭包和隐式转换等。在学习过程中,可以通过阅读官方文档、参加在线课程或参考相关书籍来提高自己的技能。总之,Scala是一门值得学习的编程语言,它可以帮助你更好地解决实际问题并提高编程效率。
作者其他创作
大纲/内容
Scala语法基础(重要)
变量声明
变量声明的格式:var | val 变量名 [: 变量类型] = 变量值
var和val
var表示变量是可变的,可以重新赋值
val表示变量不可变
推荐使用val,没有线程安全问题,效率更高<br>
val类型的变量底层是使用了final修饰
同时在idea中,如果使用var修饰变量,但是后面没有重新给变量赋值,var会变成灰色
idea代码示例
和Java类似,Scala也是值传递,对于引用数据类型,例如对象,只是地址不变,但是对象的属性是可以改变的<br>
变量类型可以省略,由编译器自行推导,推荐省略,代码更简洁
在idea中配置Scala数据类型灰色提示
Settings -> Editor -> Inlay Hints -> Scala -> Type hints -> Show type hints for
数据类型
在Scala中没有基本数据类型,都是引用数据类型。这点和Java不一样
Scala数据类型分为两大类AnyVal(值类型)和AnyRef(引用类型)。不管是AnyVal和AnyRef都是对象
数据类型一览
①Byte 8位有符号补码整数。数值区间为 -128 到 127<br><br>②Short 16位有符号补码整数。数值区间为 -32768 到 32767<br><br>③Int 32位有符号补码整数。数值区间为 -2147483648 到 2147483647。如果是整数且不加L,默认为Int,和Java一样。<br><br>④Long 64位有符号补码整数。例如123L。数值区间为 -9223372036854775808 到 9223372036854775807<br><br>⑤Float 32 位。IEEE 754 标准的单精度浮点数。例如0.1F。如果是小数且不加F或者D,默认为Float类型,和Java一样。<br><br>⑥Double 64 位 IEEE 754 标准的双精度浮点数。例如0.1D。<br><br>⑦Char 16位无符号Unicode字符, 区间值为 U+0000 到 U+FFFF<br><br>⑧String 字符序列<br><br>⑨Boolean true或false<br><br>⑩Unit 表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。<br><br>11.Null null 或空引用<br><br>12.Nothing Nothing类型在Scala的类层级的最底端;它是任何其他类型的子类型。<br><br>13.Any Any是所有其他类的超类<br><br>14.AnyRef AnyRef类是Scala里所有引用类(reference class)的基类
Symbol
属于基本类型,被映射成scala.Symbol<br><br>2.当两个Symbol值相等时,指向同一个实例<br><br>3.Symbol类型存在的意义:作为不可变的字符串,同时不必重复地为相同对象创建实例,节省资源<br>4.定义变量val s=‘my_symbol时,s值即为<br><br> s:Symbol = ’my_symbol。<br><br> 而s.name为Sting类型,值为my_symbol
数据类型说明
1.Scala的数据类型与Java的基本一样
2.在Scala中有一个根类型Any,它是是所有类的父类
3.Scala中一切皆为对象,分为两大类AnyVal(值类型)和AnyRef(引用类型)
4.Null类型是Scala的特别类型,它只有一个值null,它是bottom class,是所有AnyRef类型的子类
5.Nothing类型也是bottom class,它是所有类的子类,在开发过程中可以将Nothing类型的值返回给任意变量或者函数,这里抛出异常使用很多
6.在Scala中仍然遵守,低精度的值向高精度的值自动转换(implictit conversion)隐式转换
小结<br><br>1.在Scala中,所有引用类型的类都是AnyRef的子类,类似于Java的object<br><br>2.AnyVal和AnyRef都扩展自Any类。Any类是根节点<br><br>3.Any中定义了isInstanceOf、asInstanceOf方法,以及哈希方法等<br><br>4.Null类型的唯一实例就是null对象,可以将null赋值给任何引用,但不能赋值给值类型的变量<br><br>5.Nothing类型没有实例。它对于泛型结构是有用处的,举例:空列表nuil的类型时List[Nothing],它是List[T]的子类型,T可以时任何类<br>
标识符(变量名)
和Java一样
运算符
和Java基本一样
Scala不存在i++或者++i这样的写法,只能使用i = i + 1
Scala不支持三目运算符,在Scala中使用if - else的方式实现<br>
var num2 = if (5 > 6) "大于" else "小于"
分支控制<br>
和Java完全一样
if() ... else if(...) else
循环控制
for循环
1. 基本案例<br>for (i <- 1 to 3) {<br> println(i)<br>}<br>1 to 3左闭右闭<br>i表示循环中的变量,并且i是val修饰的,不能改变值<br>1 until 3表示左闭右开
2. 循环守卫<br> for(i <- 1 to 3 if i!=2){<br> println(i)<br> }<br>循环守卫,即循环保护式(也称条件判断式),保护式为true则进入循环体内部,为false则跳过,类似于continue<br>
3. 引入变量<br>for (i <- 1 to 10; j = 1 + i) {<br> println("i = " + i + ", j = " + j)<br> }<br>
4. 循环嵌套<br>for (i <- 1 to 3; j <- 1 to 3) {<br> println("i = " + i + ", j = " + j)<br> }<br><br>等价于<br>for (i <- 1 to 3) {<br> for (j <- 1 to 3) {<br> println("i = " + i + ", j = " + j)<br> }<br> }<br>
5. 循环返回值<br>val rest = for (i <- 1 to 10 ) yield {<br> if (i % 2 == 0) {<br> i<br> } else {<br> "不是偶数"<br> }<br> }<br>案例说明<br> 将遍历过程中处理的结果返回到一个新的Vector集合中,使用yield关键字,yield后可以接收一段代码块<br>
6. 循环控制步长<br>方法一<br>for (i <- Range(1, 10, 2)) {<br> println(i)<br> }<br> 说明Range(1, 10, 2) <br>1代表start值,10代表end值,2代表步长<br><br>for(i <- 1 to 10 if i%2==1){<br> print(i) <br> }<br> 说明:利用循环守卫控制步长<br>
while循环
和Java完全一致
//1. 计算1-100的和<br> var i = 1<br> var sum = 0<br> while (i < 101) {<br> sum += i<br> i += 1<br> }<br> println(sum)
do while循环
和Java完全一致
//使用do while循环, 输出10次"hello, 尚硅谷"<br> var i = 0<br> do {<br> println("Hello, 尚硅谷")<br> i += 1<br> } while (i < 10)
<font color="#ff0000">高版本已经废弃</font>
实现Java的continue和break关键字的效果
for循环
continue
//continue。通过循环守卫实现<br> for (i <- 1 to 10 if i % 2 == 0) {<br> println(i)<br> }<br> //直接在代码中进行if判断<br> for (i <- 1 to 10) {<br> if (i % 2 == 0) {<br> println(i)<br> }<br> }
break
//2. 使用循环守卫实现break<br> println("*******************")<br> var loop = true<br> var sum = 0<br> for (i <- 1 to 100 if loop) {<br> sum += i<br> if (sum > 20) {<br> loop = false<br> }<br> println("i = " + i)<br> }<br> println("sum = " + sum)
while和do while循环
continue
类似于for循环的循环守卫,定义一个变量
break
//breakable()函数<br> //说明<br> //1. breakable是一个高阶函数: 可以接收函数的函数就是高阶函数<br> import scala.util.control.Breaks._<br> var n = 10<br> breakable {<br> while (n <= 20) {<br> n += 1<br> println(n)<br> if (n == 18) {<br> break()<br> }<br> }<br> }
推荐使用for循环,while循环和do while循环不能返回值,变量必须定义在外部,且必须定义成var<br>
字符串插值
Scala提供了一种新的机制来根据数据生成字符串:字符串插值。字符串插值允许使用者将变量引用直接插入处理过的字面字符中
val name="James"<br>println(s"Hello,$name")//Hello,James
Scala函数式编程(重要)
函数式编程介绍
函数定义的基本格式<br>def 函数名([参数名: 参数类型], ...)[[: 返回值类型] = ] {<br> 函数体<br> [return ]返回值<br>}
返回值形式1::返回值的类型 =
返回值形式2:只有一个等号=,表示返回值类型不确定,由编译器自行推导
返回值形式3:什么都没有,表示没有返回值,等价于:Unit=
函数的注意事项和细节
如果函数没有形参,在进行函数调用的时候可以不写(),<font color="#ff0000"><b>建议不要省略</b></font>
Scala中的函数可以根据函数体最后一行代码自行推断出函数的返回类型,那么在这种情况下,return关键字可以省略<br>因为Scala可以自行推断,所以函数声明处的返回值的类型也可以省略
如果函数明确使用return关键字,那么函数的返回类型就不能自行推断了,这时就要写成:返回值类型 =,如果不写,即使有return,返回值为()
如果函数需要声明无返回值(Unit),那么函数体中有return关键字,也不会有返回值
Scala语法中任何语法结构都可以嵌套其他语法结构,即:函数中可以在声明定义函数,类中可再声明类,方法中可再声明方法
<font color="#ff0000"><b>在Scala函数中形参默认是val,因此不能在函数中进行修改</b></font>
Scala函数支持可变参数,可变参数只能放在参数列表的最后<br> ①//支持0到多个参数<br> def sum(args:Int*):Int={}<br><br> ②支持1到多个参数<br> def sum(n:Int,args:Int*):Int={}<br><br> 说明:args是集合,通过for循环可以访问到各个值
默认形参和命名参数<br>
和Java不同的是,Scala可以给函数的形参设定默认值
Scala函数的形参,在声明参数时,直接赋初始值(默认值),这时调用函数,如果没有指定实参,则会使用默认值。如果指定了实参,则实参覆盖默认值。<br>
如果函数存在多个参数,每一参数都可以设定默认值。<br>在进行函数调用的时候,参数到底是覆盖默认值,还是赋值给没有默认值的参数,这就不确定了<br>
这种情况下,可以采用命名参数<br> def getSum(n1:Int=1){}<br> getSum(n1=2)
过程
将函数的返回类型为Unit的函数称为过程,如果没有明确函数没有返回值,那么等号可以去掉。
简单来说:没有返回值的函数称为过程。
惰性函数、惰性变量(lazy关键字)
一.惰性函数使用的场景<br> 惰性计算(尽可能延迟表达式计算)是许多函数式编程语言的特性。惰性集合在需要时提供其元素,无需预先计算它们,这带来一些好处。<br>首先,可以将耗时计算推迟到绝对需要的时候。其次,可以创造无限个集合,只要它们继续收到请求,就会继续提供元素。<br>函数的惰性使用可以得到更高效的代码。Java并没有为惰性提供原生支持,Scala提供了lazy关键字来支持惰性函数<br>
二.惰性函数介绍<br> 当函数返回值被声明为lazy时,函数执行将会被推迟,直到我没首次对此取值,该函数才会执行。这种函数称之为惰性函数<br>
三.注意事项和细节<br> 1.lazy不能修饰var类型的变量,只能修饰val类型的变量<br> 2.不但是在调用函数时,加了lazy,会导致函数的执行被推迟,我们在声明一个变量时,如果声明了lazy,那么变量值的分配也会被推出 例如:lazy val i =10
代码示例
//main方法开始执行<br>//sum方法之后<br>//sum函数执行了。。。。。。。。<br>//3<br>object LazyDemo01 {<br><br> def main(args: Array[String]): Unit = {<br> println("main方法开始执行")<br> //当函数返回值被声明为lazy时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。<br> //这种函数我们称之为惰性函数,在Java的某些框架代码中称之为懒加载(延迟加载)。<br> //val result = sum(1, 2)<br> lazy val result = sum(1, 2)<br> println("sum方法之后")<br> println(result)<br> }<br><br> def sum(arg1 : Int, arg2 : Int): Int = {<br> println("sum函数执行了。。。。。。。。")<br> arg1 + arg2<br> }<br><br>}
exception
Scala的异常处理机制和Java基本一致。使用try...catch...finally的形式。<br>catch的时候使用模式匹配,而不是多个从小到大的catch代码块。<br>Scala没有编译期异常的概念,都是运行时异常。<br>如果在代码中想要抛出异常,直接使用throw new XXXException即可和Java一样。
同时如果想要和Java一样,提醒代码可能会报错,可以使用throws关键字,但是方法调用处也不需要显示处理
代码示例
try {<br> var i = 1 / 0<br> } catch {<br> case ex: Exception =><br> ex.printStackTrace()<br> throw new RuntimeException("测试手动抛出运行时异常")<br> } finally {<br> println("finally最终执行了")<br> }
object ExceptionDemo02 {<br><br> def main(args: Array[String]): Unit = {<br> val unit: Unit = f1()<br> println(unit)<br> }<br><br> //scala提供了throws关键字来声明异常。可以使用方法定义声明异常。 它向调用者函数提供了此方法可能引发此异常的信息<br> //方法的调用处也不需要显示处理<br> //和java不同的是, scala不需要在方法上声明类型, 没有编译时异常, 都是运行时异常<br> @throws(classOf[NumberFormatException])<br> def f1(): Unit = {<br> "abc".toInt<br> }<br><br>}
Scala面向对象初级
面向对象基础
创建对象<br><br> 基本语法 val | var 对象名[:类型] = new 类型()<br> 1.如果我们不希望改变对象的引用(即:内存地址),应该声明val,否则声明var。Scala设计者推荐使用val,因为一般来说,我们只改变对象属性值,而不是改变对象的引用<br> 2.Scala在声明对象变量时,可以根据创建对象的类型自动判断,所以类型声明可以省略,但是当类型和后面new对象类型有继承和多态时,就必须写了
class Person{<br> var age:Short=90<br> var name:String=_<br> def this(n:String,a:Int){<br> this()<br> this.name=n<br> this.age=a<br> }<br>}<br><br>var p:Person=new Person("马云","20")<br><br>流程分析<br>①加载类的信息(属性信息、方法信息)<br>②在内存(堆)中开辟空间<br>③使用父类的构造器(主和辅助)进行初始化<br>④使用主构造器对属性进行初始化<br>⑤使用辅助构造器对属性进行初始化<br>⑥将开辟的对象地址赋给p这个引用<br>以上流程基本上和Java一致<br>
成员变量|属性
成员变量定义格式:var | val 属性名[: 数据类型] = 属性值<br>定义在类的内部
和Java类似成员变量可以是任意数据类型值类型和引用类型
Scala中没有静态成员变量,这点和Java不同
成员变量必须显示初始化, 可以使用_代替对应的值(需要指定数据类型)<br>不能使用var name = _ 因为编译器无法推断参数类型
属性默认都是private的,但是提供了对应public的XXX()和XXX_eq()方法<br>
Bean属性,使用@BeanProperty注解会在底层自动生成getXxx()和setXxx()方法<br> @BeanProperty var name ="张三"
成员方法
和函数使用类似
调用对象的方法为对象名.方法名(形参列表)
成员方法默认都是public的
成员方法可以访问成员变量,使用this.成员变量<br>也可以给成员变量重新赋值
成员方法使用和Java类似
Scala没有静态方法,这点和Java不一样
构造器
基本说明
Scala构造器的基本语法<br><br> class 类名(形参列表){//<font color="#ff0000">主构造器</font><br> //类体<br> def this(形参列表){//辅助构造器<br> }<br><br> def this(形参列表){//辅助构造器<br><br> }<br>说明:主构造器只有一个,辅助构造器函数名称为this,可以有多个。<br>编译器通过不同参数来区分多个构造器<br>
构造器使用注意事项和细节<br>
1.Scala构造器作用是完成对对象的初始化,构造器没有返回值,和Java一样<br><br>2.主构造器的声明直接放置于类名之后<br><br>3.主构造器会执行类定义中的所有语句,这里可以体会到Scala的函数式编程和面向对象编程融合在一起。<br>即:构造器也是方法(函数),传递方法以及使用方法和前面函数部分内容没有区别<br><br>4.如果主构造器无参数,小括号可以省略,构造对象时调用构造方法的小括号也可以省略,建议不要省略<br><br>5.辅助构造器的名称为this(<font color="#ff0000">这个和Java是不一样的</font>),多个辅助构造器通过不同参数列表进行区分,在底层就是构造器重载。<br><br>6.辅助构造器一定要先调用主构造器,执行主构造器中的逻辑,并且需要在第一行调用<br><br>7.Java里辅助构造器可以直接super去调用父类的主构造器,但是在Scala中只允许主构造器去调用父类的主构造器,使用辅助构造器先要调用主构造器,主构造器会默认先执行父类中的构造器<br><br>8.如果想让主构造器变成私有的,可以在()之前加上private,这样用户只能通过辅助构造器来创建对象<br><br>9.辅助构造器的声明和主构造器的声明一致,否则会发生错误
属性高级(结合构造器)
1.Scala类主构造器的形参未用任何修饰符,那么这个参数就是局部变量。可以理解主构造方法就是一个普通方法<br> class B(name:String){},在类中的任意地方都可以使用,<font color="#ff0000">且不允许重新赋值<br></font><br>2.如果参数使用val关键字声明,那么Scala会将参数作为类的私有的只读属性<br> class A(val name:String){}<br><br>3. 如果参数使用var关键字声明,那么Scala会将参数作为类的成员属性使用,<br> class A(var name:String){}<br>
包<br>
Scala包介绍
作用
区分同名类
将类按照文件夹进行管理
控制访问范围
对类的功能进行扩展
命名规则
只能包含数字、字母、下划线,不能使用数字开头,也不能使用关键字
命名规范
小写字母+小圆点
常见如com.公司名.项目名.业务模块
Scala自动导入的包<br>
java.lang.*
scala包
Predef包
import详解
1.在Scala中,import语句可以出现在任何地方,并不和Java一样限于文件顶部,import语句的作用一致延申到包含该语句的块末尾,这样做的好处是:在需要时引入包,缩小import包的作用范围,提高效率<br><br> 2.Java中如果想要导入包的所有类,可以通过通配符*,Scala采用_<br><br> 3.如果不想要某个包中的全部类,而是其中的几个类,可以采用选取器(大括号)<br><br> 4.<font color="#ff0000">如果引入的多个包中含有相同的类,那么可以将不需要的类进行重命名进行区分,这个就是重命名</font><br><br> //将Java的HashMap重命名成JavaHashMap}<br><br> import java.util.{HashMap => JavaHashMap} <br><br> 5.如果某个冲突的类根本不会用到,那么这个类可以直接隐藏掉<br><br> //将Java的HashMap这个包隐藏掉,其他包可以使用<br><br> import java.util.{HashMap => _,_}<br><br>6.Scala在定义包时,源码文件和定义的包可以不再同一个文件夹下。<br>但是编译后,会在同一个文件夹下(由编译器完成)<br>
代码实现
import 导入包
object ScalaPackageDemo03 {<br><br> def main(args: Array[String]): Unit = {<br> //1. scala中import可以出现在任何地方。可以缩小类的使用范围。其实感觉没啥用<br> //2. scala中采用下 _ 代替java中的 *<br> //3. 如果不想要某个包中全部的类,而是其中的几个类,可以采用选取器(大括号)import scala.collection.mutable.{HashMap, HashSet}<br> //4. scala可以对包进行重命名和隐藏<br> test2()<br> }<br><br> def test(): Unit = {<br> import scala.collection.mutable.{HashMap, HashSet}<br> var map = new HashMap()<br> var set = new HashSet()<br> }<br><br> def test2(): Unit = {<br> //重命名<br> //这个对于多个包有同名类非常有用<br> import java.util.{HashMap => JavaHashMap}<br> //隐藏<br> import scala.collection.mutable.{HashMap => _}<br> val intToString = new JavaHashMap[Int, String]()<br> println(intToString)<br> // new ScalaHashMap[]() 直接报错<br> }<br><br>}
Scala包对象
基本介绍
Java的package本质上就是文件夹,不能定义类、成员变量、方法<br>这是Java虚拟机的局限,为了弥补这一点不足,Scala提供包对象来解决这个问题
包对象使用注意事项<br>
1.每个包中只能有一个包对象
2.包对象名称需要与包名一致,一般用来对包的功能补充
代码示例
// 有一个 Fruit 类和三个 Fruit 对象在包 gardening.fruits 中<br> package gardening.fruits<br> <br> case class Fruit(name: String, color: String)<br> object Apple extends Fruit("Apple", "green")<br> object Plum extends Fruit("Plum", "blue")<br> object Banana extends Fruit("Banana", "yellow")<br> <br> // 在包对象 fruits 中定义变量 planted 和方法 showFruit<br> package gardening<br> package object fruits {<br> val planted = List(Apple, Plum, Banana)<br> def showFruit(fruit: Fruit): Unit = {<br> println(s"${fruit.name}s are ${fruit.color}")<br> }<br> }<br><br> // 在同一个包下使用包对象中的成员,可以直接使用<br> // 在不同包中使用包对象中定义的变量和方法:import gardening.fruits._,和导入类的方式相同。<br> import gardening.fruits._<br> object PrintPlanted {<br> def main(args: Array[String]): Unit = {<br> for (fruit <- fruits.planted) {<br> showFruit(fruit)<br> }<br> }<br> }<br>
因为包对象中的所有方法和属性,包下面的所有类都能访问,类似于抽取的作用。之前使用工具类,现在可以使用包对象<br>
因此在包对象中一般定义隐式值、隐式转换函数、隐式类和通用方法等<br>详情可以见org.apache.flink.streaming.api.scala包对象,里面定义了隐式转换函数
<font color="#ff0000">高版本已经废弃</font>
访问权限
1.成员变量访问权限为默认时,从底层看属性时private,但是提供了xxx_$eq()【类似setter】/xxx()【类似getter】方法,因此从效果上看时任何地方都可以访问<br><br> 2.当方法访问权限问默认时,默认为public访问权限<br><br> 3.private时私有权限,只能在类的内部和<font color="#ff0000">伴生对象</font>中使用<br><br> 4.protected为受保护权限,在Scala中受保护权限比Java更严格,只能子类访问,同包无法访问<br><br> 5.在Scala中没有public关键字,即不能用public显式的修饰属性和方法<br>
代码示例
object VisitTest {<br><br> def main(args: Array[String]): Unit = {<br> val demo0 = new VisitDemo01<br> //报错, private修饰的字段只能在本类和伴生对象中使用<br> //demo0.sal<br><br> println(demo0.name)<br><br> demo0.showInfo()<br><br> //protected只能在子类中使用, 同包无法访问<br> //demo0.age<br> }<br><br>}<br><br>object VisitDemo01 {<br><br> def test1(visitDemo01: VisitDemo01): Unit = {<br> //private为私有权限, 只能在类的内部和伴生对象中使用<br> println(visitDemo01.sal)<br> }<br><br>}<br><br>/**<br> * scala中字段、方法等访问权限和java不一样,有四种public、protected、private<br> * scala中没有public关键字, 默认就是public<br> * protected为受保护的权限, 只能子类访问, 同包不能访问<br> * private为私有权限, 只能在类的内部和伴生对象中使用<br> */<br>class VisitDemo01 {<br><br> //可以理解为public, 可以在任意地方使用<br> var name = "jack"<br><br> //私有属性, 只能在类的内部和伴生对象中使用<br> private val sal = 9999.9D<br><br> //protected只能在子类中使用, 同包无法访问<br> protected var age = 10<br><br> def showInfo(): Unit = {<br> println(s"薪资: $sal")<br> }<br><br><br>}<br>
Scala面向对象高级
继承
和Java一样,Scala也支持继承,也只支持单继承。
基本语法格式:class 子类名 extends 父类名 { 类体 }
子类继承了父类的所有属性和方法,但是父类中私有的属性和方法不能访问
访问父类的成员变量和方法使用super.的方式
重写父类方法
子类重写父类的非抽象方法需要使用override关键字修饰
class Person {<br><br> var name : String = "jack"<br><br> private val sal : Double = 9999.9<br><br> protected var age = 10<br><br> var job : String = "大数据工程师"<br><br> def showInfo(): Unit = {<br> println("name = " + name + ", sal = " + sal + ", job = " + job)<br> }<br><br> def printName(): Unit = {<br> println(name)<br> }<br><br>}<br><br>class Student extends Person {<br><br> def study(): Unit = {<br> println("name = " + name + ", job = " + job)<br>// 子类无法访问父类的私有属性,因为sal()方法被私有了<br>// println(sal)<br> }<br><br> override def printName(): Unit = {<br> println("子类重写父类的方法")<br> println(name)<br> }<br>}<br>
显示调用父类构造器
如果类之间存在继承关系那么在声明子类时必须要给父类的主构造器传值<br>可以简单地理解为子类主构造器必须显示调用父类的主构造器<br>
<font color="#ff0000">不存在super(...)调用父类的构造器的方式</font>
代码示例
class Student100(studentName: String) extends Person100(studentName) {<br><br> println(s"Student100住构造器被调用了。。。。传入的参数$studentName")<br><br> def this(age: Int, className: String, studentName: String) {<br> //不存在super(...)调用父类的构造器<br> //super(studentName)<br> this(studentName)<br> println(s"辅助构造器被调用了, age: $age, 班级名称: $className, 学生姓名: $studentName")<br> }<br><br> override def toString: String = {<br> s"学生姓名是: $studentName"<br> }<br><br>}<br><br>class Person100(name: String) {<br><br> println(s"Person100主构造被调用了。。。。传入的参数: $name")<br><br>}<br>
重写父类字段
在Scala中支持重写子类重写父类的字段,复写的字段需要使用override关键字修饰<br>
class AAA {<br><br> val age : Int = 20<br><br>}<br><br>class BBB extends AAA{<br><br> override val age : Int = 20<br><br>}
覆写字段的注意事项和细节<br><br> 1.def只能重写另一个def(即方法只能重写另一个方法)<br><br> 2.val只能重写另一个val属性 或不带参数的def<br><br> 3.var只能重写另一个抽象的var属性
class AAAA {<br><br> //这里运行报错,编译不报错<br>// var age : Int = 10<br><br> val age : Int = 20<br>}<br><br>class BBBB extends AAAA {<br><br> override val age: Int = 20<br><br>}
抽象类
Scala中可以使用abstract关键字用来标识不能实例化的类<br>抽象类中的可以有抽象字段、实例字段、抽象方法以及普通方法<br>
抽象字段就是没有初始化的字段<br>
抽象方法就是没有方法体的方法<br>
抽象属性<br>
声明未初始化的变量就是抽象的属性,抽象属性在抽象类<br>
var重写抽象的var属性小结<br>
①一个属性没有初始化,那么这个属性就是抽象属性<br><br> ②抽象属性在编译成字节码文件时,属性并不会声明,但是会自动生成抽象方法,所有类必须声明为抽象类<br><br> ③如果时覆写一个父类的抽象属性,那么override关键字可以省略<br>(原因:父类的抽象属性生成的时抽象方法,因此就不会涉及到方法重写的概念,因此override可以省略)
代码示例
abstract class Animal {<br><br>// 抽象字段,只会生成抽象的name()方法<br> val name : String<br><br>// 抽象字段,会生成抽象的age()和age_$eq()方法<br> var age : Int<br><br>// 非抽象字段,普通属性,生成color()和color_$eq()方法<br> var color : String = "黄色"<br><br>// 非抽象字段,不可变属性,生成sex()方法<br> val sex : String = "公"<br><br>// 抽象方法不需要生命abstract,只需要省略方法体即可<br> def eat(): Unit<br><br> //普通方法, 有方法体<br> def eat2(): Unit = {<br> println("eat2普通方法被调用了。。。。")<br> }<br><br>}
想要实例化一个抽象类,必须要要重写所有的抽象字段和抽象方法
val animal: Animal = new Animal {<br> override val name: String = ""<br> override var age: Int = _<br><br> override def eat(): Unit = {}<br> }
子类继承自抽象类,必须要重写所有的抽象字段和抽象方法,否则自己也声明成抽象类
抽象类中可以没有抽象方法或者抽象字段, 这点和Java类似
伴生对象和伴生类
静态概念介绍
Scala语言完全是面向对象的(万物皆对象)的语言,所以并没静态的操作(即在Scala中没有静态的概念)。<br>
但是为了和Java语言交互(因为Java中有静态概念),就产生了一种特殊的对象来模拟类对象,我们称之为伴生对象。
这个类的所有静态内容都可以放置在它的伴生对象中声明和调用
伴生对象小结
1.scala中伴生对象用object关键字声明,伴生对象中的内容全部是静态<br><br> 2.伴生对象对应的是伴生类,伴生对象的名称和伴生类的名称要一致<br><br> 3.伴生对象中的属性和方法可以直接通过伴生对象名称调用<br><br> 4.从语法上来看,所谓的伴生对象就是静态方法和静态属性的集合<br><br> 5.从技术上来看,Scala还是没有生成静态的内容,只不过是将伴生对象生成了一个新的类,实现属性和方法的调用<br><br> 6.从底层原理上看,伴生对象静态特性是依赖于public static final MODULE$实现的<br><br> 7.伴生对象和伴生类的声明必须在同一个源码文件中,但是没有伴生类,就没有所谓的伴生对象了,所有放哪里无所谓,但是底层还是生成了一个空的伴生类<br><br> 8.如果class A独立存在,那么A就是一个类;如果object A独立存在,那么A就是一个“静态”性质的对象(类对象)<br><br> 9.当一个文件中同时存在伴生对象和伴生类,那么文件的图标会发生变化<br><br>10.伴生对象是一种特殊的类,可以继承类、也可以实现trait等,但是只有一个实例
apply方法
伴生对象apply方法<br><br> 1.在伴生对象中定义apply方法,可以实现类名()的方式创建对象实例
代码示例
伴生类和伴生对象简单使用
object VisitDemo01 {<br><br> def main(args: Array[String]): Unit = {<br> //如果new对象没有形参, 那么可以省略()<br> val person = new Person<br> person.showInfo()<br> //对于伴生对象而言, 调用方法或者字段的方式就是伴生对象名.方法名或者字段名。和Java类似<br> //伴生对象名.方法名<br> Person.test(person)<br> //伴生对象.字段名, 获取字段的值<br> println(Person.xxx)<br> }<br><br>}<br><br>//当一个文件中出现了 class Person 和 object Person时<br>//伴生对象和伴生类必须写在同一个文件中<br>//1. class Person 称之为伴生类<br>//2. object Person 称之为伴生对象<br>//3. scala设计者将static拿掉,因为static并不是oop的内容,于是设计了伴生类和伴生对象的概念<br>//4. 在伴生类中写非静态的内容,在伴生对象写静态内容<br>//5. 如何理解呢?把伴生对象当成普通的类即可, 只不过只有一个对象, 而且是已经创建好的对象, 可以直接使用。<br>// 可以声明方法、字段、继承类等<br>class Person {<br><br> var name : String = "jack"<br><br> private val sal : Double = 9999.9<br><br> protected var age = 10<br><br> var job : String = "大数据工程师"<br><br> def showInfo(): Unit = {<br> println("name = " + name + ", sal = " + sal + ", job = " + job)<br> }<br><br>}<br><br>object Person {<br><br> val xxx = 20<br><br> def test(p : Person): Unit = {<br> p.showInfo()<br> }<br><br> def testVisit(person: Person): Unit = {<br> println(s"测试字段的访问权限, sal: ${person.sal}")<br> }<br><br>}
伴生对象apply方法
object Demo02 {<br><br> def main(args: Array[String]): Unit = {<br> //这里可以使用伴生对象的名称例如Pig(参数1, 参数2)。直接得到实例化对象<br> //会自动调用伴生对象的apply方法构建实例对象<br> println(Pig("李四"))<br> }<br><br>}<br><br>class Pig(var name: String = "王五") {<br><br> Pig.test1()<br><br> println(s"伴生对象的编号字段的值: ${Pig.number}")<br><br> override def toString: String = s"小猪的名字是$name"<br><br>}<br><br>object Pig {<br><br> val number = 0L<br><br> def apply(name: String): Pig = new Pig(name)<br><br> def apply(): Pig = new Pig()<br><br> def test1(): Unit = {<br> println("伴生对象Pig中的方法test1()被调用了。。。。")<br> }<br><br>}
特质(trait)
特质基本介绍与快速入门
一.特质的声明<br><br> trait 特质名{<br><br> trait体<br><br> }<br><br> trait的命名一般首字母大写
二.Scala中特质的使用<br><br> 一个类中具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所有在使用时也是用extends关键字,如果有多个特征存在父类,那么需要用with关键字连接<br><br> 1.没有父类<br><br> class 类名 extends 特质1 with 特质2 with 特质3<br><br> 2.有父类<br><br> class 类名 extends 父类 with 特质1 with 特质2
三.快速入门<br><br> 1.可以把特质看作是对继承的一种补充。Scala的继承是单继承,也就是一个类最多只能有一个父类,这就对子类功能的扩展有一定的影响。所以我们认为:Scala引入trait特质,第一可以替代Java的接口,第二也是对单继承的一种补充
四.特质的再说明<br><br> 1.trait可以同时拥有抽象方法和具体方法,一个类可以实现或者继承多个特质<br><br> 2.Java中的接口可以直接当成Scala特质使用
代码示例
object TraitDemo01 {<br><br> def main(args: Array[String]): Unit = {<br> new Demo01().sayHello()<br> new Demo01().sayHi("张三")<br> Demo01.sayHello()<br> }<br><br>}<br><br>trait Trait01 {<br><br>// var age : Int<br><br> def sayHi(name: String): Unit = {<br> println("Hi " + name)<br> }<br><br> def sayHello()<br><br>}<br><br>class Demo01 extends Trait01 {<br> override def sayHello(): Unit = {<br> println("Hello")<br> }<br>}<br><br>object Demo01 extends Trait01 {<br> override def sayHello(): Unit = {<br> println("Hello")<br> }<br>}
Scala用特质代替接口
1.从面向对象来看,接口并不属于面向对象的范畴,Scala是纯面向对象的语言,在Scala中没有接口<br><br> 2.Scala语言中采用特质trait(特征)来代替接口的概念,多个类具有相同的特征时,就可以将这个特征独立出来,采用关键字trait声明。<br>简单理解为 trait等价于 interface+abstract class
动态混入
动态混入是Scala特有的方式(Java没有动态混入),可在不修改类声明/定义的情况下,扩展类的功能,非常的灵活,耦合性低 。<br>动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能。
在不修改类的定义基础,让他们可以使用trait中的方法<br>除了可以在类声明时继承特质以外,还可以在构建对象时混入特质,扩展目标类的功能<br>此种方式也可以应用于对抽象类功能进行扩展<br>
代码示例
object MixInDemo01 {<br> def main(args: Array[String]): Unit = {<br> val oracleDB = new OracleDB with Operate3 //OCP原则。开闭原则<br> oracleDB.insert(100) //<br><br> val mySQL = new MySQL3 with Operate3<br> mySQL.insert(200)<br><br> //如果一个抽象类有抽象方法,如何动态混入特质<br> val mySql_ = new MySQL3_ with Operate3 {<br> override def say(): Unit = {<br> println("say")<br> }<br> }<br> mySql_.insert(999)<br> mySql_.say()<br> }<br>}<br><br>trait Operate3 { //特质<br> def insert(id: Int): Unit = { //方法(实现)<br> println("插入数据 = " + id)<br> }<br>}<br><br>class OracleDB { //空<br>}<br>abstract class MySQL3 { //空<br>}<br><br>abstract class MySQL3_ { //空<br> def say()<br>}<br><br><br>
叠加特质
一.叠加特质的基本介绍<br><br> 构建对象的同时如果混入多个特质,称之为叠加特质<br> 那么特质声明顺序从左到右,方法执行顺序从左到右
二.叠加特质的注意事项和细节<br><br> 1.特质声明顺序从左到右<br><br> 2.Scala在执行叠加对象方法时,会首先从最右边的特质开始执行,类似于数据结构栈<br><br> 3.Scala特质中如果调用super方法,并不是表示调用父特质中的方法,而是向左边继续查找特质,如果左边没有特质,此时就会调用父特质<br><br> 4.如果想要调用具体特质的方法,可以指定super[特质名].方法,其中泛型必须是该类的直接超类类型
三.在特质中重写抽象方法<br><br> trait A{<br> def insert() //抽象方法<br>}<br><br>trait B extends A{<br> override def insert(): Unit = {<br> println("将数据保存到文件中!")<br> super.insert() //思考这里super如何调用父特质中的抽象方法<br> }<br>}<br><br> 以上代码会出错,原因:是没有完全实现insert方法,同时还要声明abstract override<br> 解决方法:①去掉super.insert()<br> ②调用父特质的抽象方法,那么在实际使用中,没有方法的具体实现,无法通过编译,为了避免这种情况的发生。可以重写抽象方法,这样在使用过程中必须考虑动态混入的顺序问题
一.富接口的概念<br><br> 当一个特质中既有抽象方法,又有非抽象方法,这种特质我们称之为富接口 <br>
二.特质中的具体字段<br><br> 特质中可以定义具体字段,如果初始化了就是具体字段,如果没有初始化就是抽象字段。混入该特质的类就具有了该字段,字段不是继承而是直接加入类,成为自己的字段
特质扩展
三.特质构造顺序<br><br> 1.特质也是有构造器的,构造器中的内容由“字段初始化”和一些其他语句构成。具体实现请参考“叠加特质”<br>第一种特质构造顺序(声明类的同时混入特质【静态混入】)<br><br> ①调用当前类的超类构造器<br> ②第一个特质的父类构造器<br> ③第一个特质构造器<br> ④第二个特质构造器的父特质构造器,如果已经执行,就不会在执行<br> ⑤第二个特质构造器<br> ⑥重复4,5步骤(如果有第3,4个特质)<br> ⑦最后执行本类的构造<br><br> 第二种特质构造顺序(构建类的同时混入特质【动态混入】)<br> ①调用当前类的超类构造器,然后执行本类的构造<br> ②第一个特质的父类构造器<br> ③第一个特质构造器<br> ④第二个特质构造器的父特质构造器,如果已经执行,就不会在执行<br> ⑤第二个特质构造器<br> ⑥重复4,5步骤(如果有第3,4个特质)<br><br> 分析两种方式对构造顺序的影响<br> 第1种方式实际是构建类对象,在混入特质时,该对象还没有创建<br> 第2种方式实际是构造匿名子类,可以理解成在混入特质时,该对象已经创建<br>
四.扩展类的特质<br><br> 1.特质可以继承类,以用来扩展该特质的一些功能<br> 2.所有混入该特质的类,会自动成为那个特质所继承的超类的子类<br> 3.如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类,否则就出现了多继承,就会报错。<br>
五.自身类型<br> 说明:自身类型主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型
嵌套类
Scala类型检查和转换
①obj.isInstanceOf[T] 就如同Java中的obj isInstanceOf T 判断obj是不是T类型<br><br> ②obj.asInstanceOf[T] 就如同Java的(T).obj 将obj强转成T类型<br><br> ③classOf[String] 就如同Java的 String.class 输出方法的类名<br> <font color="#ff0000"></font>
<font color="#ff0000">Scala中推荐使用匹配代替强制转换</font>
隐式转换(重要)
隐式转换函数(隐式方法)<br>
一.隐式转换的实际需要<br><br> 指定某些类型的相互转换 比如 Double <=> Int <br>
二.隐式转换基本介绍<br><br> 隐式转换函数是以<font color="#ff0000">implicit</font>关键字声明的带有<font color="#ff0000">单个参数</font>的函数。这种函数将会自动应用,将值从一种类型转换成另一种类型 <br>
三.快速入门<br><br> implicit def f1(d:Double):Int={ //将double类型转换成int类型<br> d.toInt<br> }
四.隐式转换注意事项和细节<br><br> 1.隐式转换函数的函数名可以是任意的,隐式转换于函数名称无关,只与<font color="#ff0000">函数签名</font>(函数参数类型和返回值类型)有关<br><br> 2.隐式函数可以有多个(隐式函数列表),但是要保证当前环境下只有一个隐式函数能被识别
五.隐式转换丰富类库的功能<br><br> 1.如果需要为一个类增加一个方法,可以通隐式转换来实现。(动态增加功能),比如想为MySQL类增加一个delete方法<br><br> 2.当前程序中,如果想要给MySQL类增加功能是非常简单的,但是在实际项目中,如果想要增加新的功能就要修改源代码,这是很难接受的。而且违背了软件开发的OCP开发原则(闭合原则:open close priceple),这种情况下,可以通过隐式转换函数给类添加功能
代码示例
//导入对应的规则类,以免出现警告<br>import scala.language.implicitConversions<br><br>//指定某些数据类型的相互转化<br>//隐式转换函数的函数名可以是任意的, 与函数名无关, 只与函数的签名有关, 即函数的形参和返回值<br>object ImplicitFunDemo01 {<br><br> def main(args: Array[String]): Unit = {<br><br> //这里定义了隐式转换函数, 使用implicit关键字修饰只有单个参数的方法, 并且有返回值<br> //隐式转换函数只在当前作用于生效<br> //可以使用import关键字导入相应的隐式函数<br> implicit def f1(num: Double): Int = num.toInt<br><br> implicit def f2(num: Float): Int = num.toInt<br><br> //隐式函数可以有多个, 当时需要保证在当前环境下, 只能有一个函数被识别, 否则会发生编译错误<br> //implicit def f3(num: Float): Int = num.toInt<br><br> //scala中有下划线的就是有隐式转换, 编译器识别了隐式转换函数<br> val num: Int = <u>3.5D</u><br> println(num)<br><br> val num2: Int = <u>3.5f</u><br> println(num2)<br> }<br><br>}
隐式类
一.隐式类的基本介绍<br><br> 在Scala2.10后提供了隐式类,可以使用implicit声明类,隐式类非常强大,同时可以扩展类的功能,比前面使用的隐式转换(函数)丰富类库功能更加方便,在集合中隐式类会发挥重要作用
二.隐式类的特点<br><br> 1.其他带有构造参数有且只能有一个<br><br> 2.隐式类必须定义在“类“、”伴生对象”、“包对象”中,既即隐式类不能是顶级的(top-class objects)<br><br> 3.隐式类不能是case class样例类<br><br> 4.作用域内不能有与之相同名称的标识符(名称)
三.创建方式<br><br>implicit class BBB(i: Int){<br> def add(j: Int) = i + j<br> }<br>} <br>
代码示例
//在scala2.10后提供了隐式类, 可以使用implicit声明类, 隐式类非常强大, 同样可以拓展类的功能<br>//比前面使用隐式转换函数使用上更加方便, 在集合中隐式类会发挥重要的作用<br>object ImplicitClassDemo {<br><br> def main(args: Array[String]): Unit = {<br> //使用implicit关键字修饰类<br> //隐式类不能是顶级的class, 必须定义在其他顶级类中<br> //类的主构造方法就是要增强的类, 且只能是一个参数, 一般也是转换前的类型<br> //隐式类不能是case class<br> implicit class DB1(var mySQL1: MySQL1) {<br> def addSuffix(): String = {<br> mySQL1.hashCode() + " scala"<br> }<br> }<br><br> val mySQL = new MySQL1<br> mySQL.sayOk()<br> println(mySQL.<u>addSuffix</u>())<br> }<br><br>}<br><br>class MySQL1 {<br><br> def sayOk(): Unit = {<br> println("sayOk")<br> }<br>}
隐式参数(隐式形参)、隐式值(隐式变量)
隐式值(隐式变量)
隐式值也叫隐式变量,将变量标记使用implicit修饰。隐式值只要配合隐式参数
隐式参数(隐式形参)
指方法或者函数的形参使用了implict关键字修饰
隐式值和隐式参数的关系
1.编译器会在方法省略隐式参数的情况下,去搜索作用域中的隐式变量作为缺省参数(默认值)<br><br> 2.当同时有implicit值和默认值时,会调用implicit值,因为implicit值的优先级高<br><br> 3.当同时有implicit值和默认值时,但是两则类型不匹配,则会调用默认值<br><br> 4.当同时有implicit值和没有默认值时,但是implicit值类型与形参类型不匹配,此时会报错
隐式值和隐式参数的作用
在于可以给方法的形参设置默认值,但是优先级高于参数的默认值
在方法调用时,省略形参的赋值,让代码看上去更加简洁
一般隐式参数使用科里化的方式,而且也是最后一个形参
隐式参数和隐式值的代码示例
代码示例1
object ImplicitValDemo01 {<br><br> def main(args: Array[String]): Unit = {<br><br> //隐式值或者隐式变量<br> //可以使用import关键字导入相应的隐式值<br> implicit val string: String = "Jack"<br><br> //隐式值和隐式转换函数类似, 在作用域内只能有一个隐式值匹配, 不能有多个, 不然编译器无法确定选择哪个隐式值<br> //implicit val string2: String = "Jack"<br><br> //name就是隐式参数<br> def hello(implicit name: String): Unit = {<br> println(s"hello $name")<br> }<br><br> //这里不用传递name参数, 编译器会自动在作用域内寻找隐式值, 然后传入改方法<br> hello<br><br> //当然也可以传递参数覆盖隐式值<br> hello(name = "张三")<br><br> }<br><br>}
代码示例2
object ImplicitValDemo02 {<br><br> def main(args: Array[String]): Unit = {<br> // 隐式变量(值)<br> //implicit val name2: String = "Scala"<br> implicit val name1: String = "World"<br><br> //隐式参数<br> def hello(implicit content: String): Unit = {<br> println("Hello " + content)<br> }<br><br> //调用hello, 打印Hello World<br> hello<br><br> //当同时有隐式值和默认值,隐式优先级高<br> def hello2(implicit content: String = "jack"): Unit = {<br> println("Hello2 " + content)<br> }<br><br> //调用hello2, 打印Hello2 World<br> hello2<br><br> //当一个隐式参数匹配不到隐式值,仍然会使用默认值<br> def hello3(implicit content: Int = 123): Unit = {<br> println("Hello3 " + content)<br> }<br><br> //调用hello3, 打印Hello3 123<br> hello3<br><br> //当没有隐式值,没有默认值,又没有传值,就会报错<br> def hello4(implicit content: Double): Unit = {<br> println("Hello4 " + content)<br> }<br><br> //编译器直接报错, No implicits found for parameter content<br> //hello4<br> }<br><br>}
总结:<br><br> ①在程序中,同时有 隐式值 默认值 传值 时<br><br> ②优先级为 传值 > 隐式值 > 默认值<br><br> ③在隐式值匹配时,不能有二义性<br><br> ④如果隐式值 默认值 传值都不存在,则程序会报错
使用隐式参数常见问题
1)当函数没有柯里化时,implicit关键字会作用于函数列表中的的所有参数。<br>2)隐式参数使用时要么全部不指定,要么全不指定,不能只指定部分。<br>3)同类型的隐式值只能在作用域内出现一次,即不能在同一个作用域中定义多个相同类型的隐式值。<br>4)在指定隐式参数时,implicit 关键字只能出现在参数开头。<br>5)如果想要实现参数的部分隐式参数,只能使用函数的柯里化,<br>如要实现这种形式的函数,def test(x:Int, implicit y: Double)的形式,必须使用柯里化实现:def test(x: Int)(implicit y: Double).<br>6) 柯里化的函数, implicit 关键字只能作用于最后一个参数。否则,不合法。<br>7)implicit 关键字在隐式参数中只能出现一次,柯里化的函数也不例外!<br>
隐式对象
隐式对象和隐式参数、隐式值类似, 相当于一个默认值
使用implicit关键字修饰object<br>
隐式对象不能是顶级对象, 必须要在其他类或者对象中
代码示例
object ImplicitObjectDemo01 {<br><br> def main(args: Array[String]): Unit = {<br> trait S1 {<br> def fun(): Unit<br> }<br><br> //隐式对象不能是顶级对象, 必须要在其他类或者对象中<br> implicit object S2 extends S1 {<br> override def fun(): Unit = println("Spark")<br> }<br><br> def g(arg1: String)(implicit arg2: S1): Unit = {<br> println(s"隐式对象的解析!!!, 参数: $arg1")<br> arg2.fun()<br> }<br><br> g("Scala")(() => println("不是隐式对象"))<br><br> //这里隐式对象S2在这里当做隐式值使用<br> g("Scala")<br> }<br><br>}
上面的代码中的S2隐式对象,可以看成特质S1的一个默认实现。在调用时,就能够省略这个默认实现<br>
隐式转换细节<br>
一.隐式转换时机<br><br> 1.当方法中的参数的类型与目标类型不一致时<br><br> 2.当对象调用所在类中不存在的方法和成员时,编译器会自动将对象进行隐式转换<br>
二.隐式解析机制<br><br> 即:编译器是如何查找到缺失信息的,解析具有以下两种规则<br><br> 1.首先会在当前代码作用域下查找隐式实体(隐式方法,隐式类,隐式对象),这是一般情况<br><br> 2.如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域查找。<br>类型的作用域是指与该类型相关联的全部伴生模块,一个隐式实体的类型T它的查找范围如下(这种情况复杂并且使用范围广,应当避免出现)
三.隐式转换的规则<br><br> 1.隐式转换不能有二义性<br><br> 2.隐式操作不能嵌套使用
idea配置和显示下划线显示隐式转换<br>
Setting -> Languages & Frameworks -> Scala -> Hignligting<br>
效果1
效果2
Scala数据结构
集合
集合的基本介绍
一.Scala集合基本介绍<br><br> 1.Scala同时支持不可变集合和可变集合,不可变集合可以安全的并发访问<br><br> 2.两个主要的包<br><br> 不可变集合:scala.collection.immutable<br><br> 可变集合:scala.collection.mutable<br><br> 3.Scala默认采用不可变集合,对于几乎所有的集合类,Scala都同时提供了可变(mutable)和不可变(immutable)的版本,<br><br> 4.Scala的集合有三大类:序列Seq、集Set、映射Map,所有集合都扩展自Iterable特质,在Scala中集合有可变(mutable)和不可变(immutable)两种类型 <br>
二.可变集合和不可变集合<br><br> 1.不可变集合:Scala不可变集合,就时这个集合本身不能动态变化。类似于Java的数组,是不可以动态增长的<br><br> 2.可变集合:就是这个集合本身可以动态变化,是可以动态增长的
三.集合的种类<br><br> 1.数组Array<br><br> 2.元组Tuple<br><br> 3.列表List<br><br> 4.队列Queue<br><br> 5.映射Map<br><br> 6.集Set
数组(Array)
定长数组
第一种方式定义数组(动态创建)<br><br> 这里的数组等同于Java中的数组,中括号的类型就是数组的类型,小括号中的数字代表数组的长度<br><br> var arr1 = new Array[int](10)
第二种方式定义数组(静态创建)<br><br> 在定义数组时,直接赋值<br><br> //使用apply方法创建数组对象<br><br> val arr = Array(1,2,zhangsan)
注意:如果赋值元素全部为Int,则该数组的泛型就是Int;如果有各种类型的元素,则该数组的泛型为Any
变长数组
一.变长数组的基本使用<br><br> val array = new ArrayBuffer[Int]()<br><br> arrar.append(7) //追加元素<br><br> array.remove(0) //删除元素 ,索引位置<br><br> array(0) = 7 //重新赋值
二.变长数组分析小结<br><br> 1.ArrayBuffer是变长数组,类似于Java的ArrayList<br><br> 2.val arr = ArrayBuffer[Int]()<br><br> 3.def append(elems:A*) {append(elems)}接受的是可变参数<br><br> 4.每append一次,arr在底层会重新分配空间,进行扩容,arr的内存地址会发生变化,也就成为新的ArrayBuffer
定长数组与变长数组相互转换
三.定长数组和变长数组的转换<br><br> arr1.toBuffer //定长数组转变长数组<br><br> arr2.toArray //变长数组转可变数组<br><br> ①arr2.toArray 返回的结果才是一个定长数组,arr2本身没有发生变化<br><br> ②arr1.toBuffer返回结果才是一个可变数组,arr1本身没有发生变化
多维数组
一.多维数组的基本操作<br><br> //说明:二维数组中有三个一维数组,每个一维数组中有四个元素<br> val arr = Array.ofDim[Double](3,4)<br><br> //赋值<br> arr(1)(1) = 11
二.多维数组的遍历<br><br> for (item <- arr){<br> for (item2 <- item){<br> print(item)<br> }<br> } <br>
元组(Tuple)
一.tuple介绍<br><br> 元组可理解成一个容器,可以存放各种相同和不同的数据。简单来说,就是将多个无关的数据封装成一个整体,称为元组。注意:元组内最多只能有22个数据
二.元组的创建<br><br> val tuple = (1,2,3,"hello",2,2)
三.小结<br><br> 1.tuple的类型取决于tuple中有多少个元素,比如有 6个元素,则类型就是tuple6<br><br> 2.元组中最大只能有22个元素
四.元组的访问<br><br> 访问元组中的数据,可以采用顺序序号(_顺序号),也可以通过索引(productElement)访问<br><br> tuple1._1 //1代表顺序号<br><br> tuple1.productElement(0) //0代表索引
五.元组的遍历<br><br> 1.元组的遍历需要用到迭代器<br><br> 2.即 for(item <- tuple1.productIterator){<br><br> print(item)<br><br> }
列表(List)
一.列表的基本介绍<br><br> Scala中list和Java List本不一样,Java中的List是一个接口,真正存放数据的是ArrayList,而Scala的list可以直接存放数据,就是一个object,默认情况下,Scala的list是不可变的,list属于序列Seq
二.说明<br><br> 1.在默认情况下list是Scala.collection.immutable.list,即list是不可变的<br><br> 2.在Scala中,list就是不可变的,如果需要使用可变的list,则使用listBuffer<br><br> 3.Nil代表空集合
三.小结<br><br> 1.list默认为不可变的集合<br><br> 2.list在Scala包对象声明的,因此不需要引入其他包也可以使用<br><br> 3.val List = scala.collection.immutable.List<br><br> 4.list中可以存放任何数据类型,比如arr1的类型为list[Any]<br><br> 5.如果希望得到一个空列表,可以使用Nil对象,在Scala包对象声明的,因此不需要引入其他包也可以使用<br><br> val Nil = scala.collection.immuteable.Nil
四.列表的元素追加<br><br> 1.基本介绍<br> 向列表中增加元素,会返回新的列表/集合对象,本身的集合并没有发生变化。注意Scala列表追加元素的形式非常独特,和Java不一样<br> val list01 = List(1,2,3)<br> var list02 = list01:+9 //创建一个新的列表并在这个列表的最后添加<br> var list03 = 4 +: list01 //(同上)在列表的最前面添加<br><br> 2.添加数据::符号<br> ①符号::表示向集合中新建集合添加元素<br> ②运算时,集合对象一定要放置在最右边<br> ③运算规则,从左向右<br> ④:::运算符是将集合中的每一个元素加入到空集合中,并且:::符号左右两边都为集合,不然会报错<br> ⑤ //将元素1 2 和 list列表存放到空列表Nil中<br><br> val list2 = 1 :: 2 :: list :: Nil <br> println(list2) //List(1, 2, List(1, 2, 3, 4))
五.listBuffer可变列表<br><br> ①列表创建 val list = new ListBuffer[Int]()<br> ②访问列表 list(0)<br> ③动态追加元素 list += 1 和 list.appent(1,2,3) 和 list :+ 5 <br> ④列表中添加列表 list ++= list01<br> ⑤删除元素 list.remove(1) //索引值(下标值)
六.listBuffer的操作案例<br><br> //listBuffer列表添加元素或列表<br> listBuffer += (99,22) //可以添加一个或多个元素 <br> listBuffer.append(29,100)<br> listBuffer ++= list //添加一个列表<br> println(listBuffer)<br><br> //listBuffer删除元素<br> listBuffer -= 2 //删除2这个元素<br> listBuffer -= 1000 //如果删除的这个元素不存在,则不执行,也不会报错<br> listBuffer.remove(1) //删除下标为 1 的元素<br> listBuffer.remove(1,3) //从下标为 1 的元素开始,往后删除 3 个元素<br> println(listBuffer)
七.列表的其他操作<br><br> list.head //返回列表的第一个元素<br> list.tail //返回列表除第一个元素外的元素<br> list.isEmpty //判断列表是否为空,空true,非空false<br> list.last //返回列表最后一个元素<br> list.init //返回列表除最后一个元素外的元素
队列(Queue)
一.队列的基本介绍<br><br> 1.队列是一个有序列表,再底层可以用数组或链表来实现<br> 2.其输出和输入要遵循先入先出的原则。即:先存入队列的数据先取出,后存入的数据后取出<br> 3.在Scala中,由设计者直接给我们提供队列类型使用<br> 4.在Scala中,由scala.collection.mutable.Queue 和 scala.collect.immutable.Queue 我们使用最多的是可变的队列
二.队列的创建<br><br> val queue = new mutable.Queue[Int] //Int为队列的泛型
三.队列添加元素<br><br> 1.queue += 20 <br> 2.queue ++= list(2,3,4) //将列表中每个元素取出后加入队列<br> 3.queue += list(2,3,4) //将list直接加入队列,前提是queue的泛型是Any
三.出入队列<br><br> 1.(出队列)删除第一个元素并返回 queue.dequeue()<br>2.(入队列)在队尾添加元素 queue.enqueue(100,2,100) //元素是有序的,并且可以重复
四.返回队列中的元素<br><br> 1.返回队列第一个元素 queue.head<br> 2.返回队列最后一个元素 queue.last<br> 3.返回队列尾部元素,返回的是一个队列,所以可以级联使用该方法(除了第一个元素,其他元素都返回)<br><br> queue.tail<br> queue.tail.tail
映射(Map)
Map的创建
一.回顾:Java中的Map的基本介绍<br><br> HashMap 是一个散列表(数组+链表),它存储的内容是键值对映射(key-value),key不能重复,Java中的Hash是无序的
二.Scala中的Map<br><br> 1.Scala中的Map和Java类似,也是一个散列表,它存储的内容也是键值对(key-value)映射,Scala中不可变的Map是有序的,可变的Map是无序的<br> 2.Scala中,有可变Map:scala.collection.mutable.Map<br> 不可变Map:scala.collection.immutable.Map
三.构建不可变的Map<br><br> //Map默认是不可变的<br>val map1 = Map("zhangsan" -> 10,"lisi" -> 18) //key-value的类型不做限制<br><br> 小结<br> ①从输出结构看,输出顺序和声明顺序一致<br> ②构建Map集合中,集合中元素其实是Tuple2类型<br> ③默认情况下(即在没有引包),Map是不可变的
四.构建可变映射Map<br> val map2 = mutable.Map("name" -> "zhangsan")<br><br>五.创建空的映射<br> val map3 = new scala.collection.mutable.HashMap[Any,Any]<br><br>六.对偶元组创建<br> val map4 = mutable.Map(("A",12),("B",18),("C",20))<br><br> 说明:即创建包含键值对的二元组,和第四种方式等价,只是形式上不同。对偶元组就是只包含两个数据的元组。
Map的操作
val map = mutable.Map("name"<- "张三","age" <- 18)<br>一.Map的取值(方式一)<br> val value1 = map("name")<br> print(value1)<br> 说明:①如果key存在,则返回对应的值<br> ②如果key不存在,则会抛出异常【java.util.NoSuchElementException】。可以用map.contains("name")检查name是否存在<br> ③在Java中,如果key不存在则返回null<br><br>二.Map取值(方式二)<br> map.get("name").get<br> 说明:如果key存在,map.get("name")就会返回some,在用get方法就可以取出值;如果key不存在则会返回None<br><br>三.Map取值(方式三)<br> map.getOrElse("name","默认值") //如果key存在,则返回对应的值,不存在则返回默认值<br><br>四.Map取值方式建议<br> 1.如果确定map中有这个key,则使用map(key),速度快<br> 2.如果不确定map中是否有这个key,则使用map.contains(key),可以加入逻辑<br> 3.如果只是简单的希望得到一个值,使用map.getOrElse("Ip","127.0.0.1")<br>
五.更新map中的元素<br><br> val map2 = mutable.Map("name"-> "张三","age" -> 18)<br>map2("name")="王五"<br> 说明:更新的前提是map是可变的,不然会报错;如果key存在则会修改,如果不存在则会添加一个key-value
六.增加map中的元素<br> map2 += ("D" -> 2) //添加单个元素,如果key已经存在,则会更新<br> map2 += ("C" -> 3,"E" -> 4) //添加多个元素 <br><br>七.删除map中的元素<br> map2 -= ("D") //删除只需要写key<br> map2 -= ("D","E") //可以同时删除多个key-value<br> 说明:删除时只需要写key,可以同时删除多个;如果key存在则删除,不存在也不会报错
八.map的遍历<br><br> 1.for((k,v) <- map2) println(k,v) //遍历map的key和value<br> 2.for(k <- map2.keys) println(k) //只遍历map的key<br> 3.for(v <- map2.values) println(V) //只遍历map的value<br> 4.for(t <- map2) println(t) //遍历map结果以二元组的方式返回key-value,返回结果时Tuple2
集(Set)
一.集的基本介绍<br> 集是不重复元素的集合。集不保留顺序,默认是以哈希集实现<br><br>二.回顾:Java中Set<br> Java中,Hash是实现Set<E>接口的一个实现类,数据是以哈希表的顺序存放的,里面不能包含重复数据。Set接口是一个不包含重复数据的collection,HashSet中的数据也是没有顺序的<br><br>三.Scala中的Set说明<br> 1.默认情况下,Scala使用的是不可变的集合,如果想使用可变的集合,需要引入scala.collection.mutable.Set包。<br> 2.set(1) 可以判断元素 1 在集合中是否存在,存在返回true,不存在返回false
四.集Set的创建<br> 1.创建不可变的集<br> val set = Set(1,2,3) //不需要引包,默认就是不可变的<br><br> 2.可变集合的创建<br> import scala.collection.mutable.Set<br> val mutable = mutable.Set(1,2,3)
五.可变集合元素的添加<br><br> set.add(4) //只有可变集才能添加元素,如果添加的元素重复也不会报错,但是不会添加<br> set += 8
六.可变集合元素的删除<br><br> set.remove(4) //集Set中可以根据值来删除,因为Set中的元素不重复<br> set -= 8 //如果删除的元素不存在,则不会报错。
七.Set的遍历<br> for(x <- set){<br> print(x)<br> }<br><br>八.集合的其他操作<br> val set1 = Set(1,2,3,4)<br> val set2 = Set(3,4,5,6)<br><br> 1.求两个集合的并集(两个集合相加)<br> val res1 = set1 ++ set2<br><br> 2.求两个集合的交集(返回两个集合中都存在的元素)<br> val res2 = set1.&(set2) //方法1<br> val res3 = set1.intersect(set2) //方法2
常用高阶函数
map映射操作
先看一个实际需求<br> 要求:请将List(3,5,7) 中的所有元素都 * 2 ,将其结果放到一个新的集合中返回,即返回一个新的List(6,10,14), 请编写程序实现<br><br>传统方法<br> val list1 = List(3, 5, 7)<br> var list2 = List[Int]()<br> for (item <- list1) { //遍历<br> list2 = list2 :+ item * 2<br> }<br> println(list2)<br><br>上面提出的问题,其实就是一个关于集合元素映射操作的问题。在Scala中可以通过map映射操作来解决:将集合中每一个元素通过指定功能(函数)映射(转换)成新的结果集合,这里其实就是所谓的将函数作为参数传递给另外一个函数,这就是函数式编程的特点<br><br>使用map完成上述操作:list2 = list1.map(_*2)<br><br>高阶函数:一个可以接受函数的函数<br> //创建一个高阶函数,即可以接收函数的函数<br> //f:Double => Double表示一个可以接收Double类型并返回Double类型的函数<br> //n1 代表test的形参<br> //= 表示返回的类型使用类型推导<br> //f(n1) 表示执行传入的函数<br> def test(f:Double => Double,n1 :Double)={<br> f(n1)
flatmap扁平化操作<br>
flatmap映射:flat 即压扁<br><br>flatmap:就是集合中每个元素的子元素映射到某个函数并返回新的集合<br><br> val names = ListBuffer("Python","Scala","Java")<br> def upper(s:String):String={ //接收一个字符串,将字符串字母全部变成大写后返回<br> s.toUpperCase<br> } <br> println(names.flatMap(upper)) //flatMap操作就是将集合中元素继续扁平化操作,即遍历所有元素
filter过滤
filter:将符合要求的数据(筛选)放置到新的集合中<br><br>应用案例:<br> /*<br> 将 val names = List("Alice", "Bob", "Nick") 集合中首字母为'A'的筛选到新的集合<br> */<br> val names = List("Alice", "Aob", "Nick")<br> val res = names.filter(filter)<br> println(res)<br><br> def filter(s:String):Boolean={<br> s.startsWith("A")<br> }
reduce化简
需求:val list = List(1,20,30,4,5) 求出list中元素之和<br><br>化简:将二元函数(接收两个参数的函数)引用于集合中的函数,<br>一.左化简reduceLeft 等价于 reduce<br> val list = List(1, 20, 30, 4, 5)<br> def sum(n1: Int, n2: Int): Int = {<br> n1 + n2<br> }<br> val res = list.reduceLeft(sum)<br> println("res=" + res)<br><br>左化简说明:<br>1.reduceLeft(f) 接收的函数需要的形式为 op: (B, A) => B): B<br>2.reduceLeft(f) 的运行规则是从左边开始执行将得到的结果返回给第一个参数,然后和下一个元素运行,以此类推<br>3.化简流程(((1+20)+30)+4)+5 = 60<br><br>二.右化简reduceRight<br> val list = List(1,2,3,4,5)<br> def product(n1:Int,n2:Int): Int ={<br> println(n1-n2)<br> n1-n2<br> }<br> val res = list.reduceLeft(product)<br> println(res)<br><br>右化简说明:<br>1.reduceRight(f) 的运行规则是从右边开始执行将结果返回给第二个参数,然后和下一个元素运算,以此类推<br>2.化简流程1-(2-(3-(4-5))) = 13
fold折叠
一.折叠的基本介绍<br> fold函数将上一步返回的值作为函数的第一个参数继续传递参与运算,直到list中的元素被遍历<br><br>1.可以把reduceLeft看做成简化版的foldLeft<br><br>操作代码<br> val list = List(2, 6, 3, 5, 9)<br> //foldLeft()和reduceLeft()的运行机制几乎一样<br> //list.foldLeft(5)(function)可以理解为List(5 , 2, 6, 3, 5, 9)的reduceLeft操作<br> //就是将 5 放到list的第一个元素位置,再进行reduceLeft操作<br> //运行流程:<br> // ((((5-2)-6)-3)-5)-9<br><br> val res1 = list.foldLeft(5)(function) //更多的时候5这个位置传的是一个函数<br> println(res1) //-20<br><br>折叠fold的简写<br>正斜杠 / 在哪边,就是哪边折叠<br> val list = List(2, 6)<br> val res = (5 /: list) (function) //这种写法等价于 list.foldLeft(5)(function)<br> println(res) //-3<br><br> val res2 =(list :\ 5) (function) //等价于 list.foldRight(5)(function)<br> println(res2) //1<br><br> def function(n1: Int, n2: Int): Int = {<br> n1 - n2<br> }
scan扫描
一.扫描的基本方式<br> 扫描,即对某个集合中所有的元素做fold操作,但是会把产生的的所有中间结果放置于一个集合中保存<br><br>操作代码<br> val list = 1 to 5<br> //scan 扫描就是将集合中的每一个元素进行折叠操作,并将每个操作的结果放置再一个集合中<br> val res = list.scanLeft(8)(minux)<br> println(res) //8,7,5,2,-2,-7<br><br> //scanRight跟折叠fold操作基本相同,只是返回的结果不一样<br> val res2 = list.scanRight(6)(minux)<br> println(res2) //-3,4,-2,5,-1,6<br><br> def minux(n1:Int,n2:Int): Int ={<br> n1-n2<br> }
groupBy分组
一.基本介绍<br> groupBy会按照列表中的某个元素中某个字段进行分组。会返回一个Map映射,分组字段做为key,包含该字段的元素做为value<br><br>二.代码演示1<br> //将列表进行分组 按照元素是否为 2 的倍数分组<br> val list = List(12,3,4,4,5,65,6,6,7,87,23)<br> val res = list.groupBy(x => if(x%2==0) "2的倍数" else "不是2的倍数" )<br> println(res)<br><br>三.代码演示2<br> //将列表按照每个元素中的城市字段进行分组<br> val list = List[String]("70999,1371001,广东,广州,中国移动,020,510000",<br> "71000,1371002,广东,广州,中国移动,020,510000",<br> "71348,1371350,广东,深圳,中国移动,0755,518000",<br> "71349,1371351,广东,深圳,中国移动,0755,518000")<br><br> //将匿名函数传入到groupBy中<br> val res = list.groupBy(s => s.split(",")(3))<br> //当匿名函数的参数在 => 符号右边只出现一次时,可以将参数用下划线 _ 代替。如下:<br> val res1 = list.groupBy(_.split(",")(3))<br> println(res1)<br> //也可以将自己写的函数传入到groupBy中<br> val res2 = list.groupBy(MygroupBy)<br> println(res2)<br><br> def MygroupBy(string: String)={<br> //被分组的列表中有多少个元素,这个函数就会被调用多少次<br> //将列表中每个元素按 , 号分割成数组,然后取出该数组中下标为 3 的元素<br> string.split(",")(3)<br> }
zip拉链
一.zip拉链的基本介绍<br> 在开发中,当我们需要将两个集合进行对偶元组合并,可以使用拉链<br><br> val list1 = List(1,2,3)<br> val list2 = List(4,5,6)<br> val res = list1.zip(list2) //(1,4), (2,5), (3,6)<br> println(res)<br><br>二.拉链的注意事项<br> 1.拉链的本质就是两个集合的合并操作,合并后每一个元素是一个对偶元组<br> 2.拉链操作就是将两个集合中的元素一一对应,再进行合并<br> 3.如果两个集合元素的个数不对应,会造成数据丢失<br> 4.集合不限于list,也可以是Array等<br> 5.如果要取出合并后的各个对偶元组,可以进行遍历
集合基本操作
iterator迭代器
一.基本说明<br><br> 通过iterator方法从集合获取一个迭代器,通过while或者for循环遍历<br><br> val array = Array(1,2,3,4,5).iterator<br> println("=============while================")<br> while (array.hasNext){<br> println(array.next())<br> }<br><br> println("=============for================")<br> for (item <- array){<br> println(item)<br> }
stream流
一.基本说明<br> stream是一个集合。它可以存放无穷多个元素,但是这无穷多个元素并不会一次性生产出来,而是需要多大的区间,就会动态的产生,末尾元素遵循lazy规则(即:需要是,才计算)<br><br> //创建一个流<br> def numForm(n:BigInt):Stream[BigInt]={n #:: numForm(n+1)}<br> val stream = numForm(1)<br> println(stream)<br> //取出第一个元素<br> println(stream.head)<br> println(stream.tail)<br><br>说明:<br>1.stream集合中存放的数据类型是BigInt<br>2.numForm是一个函数名,由程序员指定<br>3.创建的集合的第一个元素是n,后续生成的规则是n+1<br>4.后续元素生成的规则可以自行指定<br>5.使用tail方法就可以产生一个新的元素
view视图
一.基本介绍<br> stream的懒加载特性,也可以对其他集合应用view方法来得到类似的效果,具有如下特点:<br> 1.view方法产生出一个总是被懒加载执行的集合<br> 2.view不会缓存数据,每次都要重新计算,比如遍历view时。<br><br> val view = 1 to 100<br> val res = view.filter(eq)<br> println(res)<br><br> def eq(n:Int):Boolean={<br> n.toString.equals(n.toString.reverse)<br> }
线程安全
一.所有线程安全的集合都是以synchronized开头的集合<br> SynchronizedBuffer<br> SynchronizedMap<br> SynchronizedPriorityQueue<br> SynchronizedQueue<br> SynchronizeSet<br> SynchronizeStack<br><br>二.并行集合<br> 1.Scala为了充分使用多核CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算<br> 2.主要用到的算法有:Divied and conquer(分治算法),Scala通过splitters,combiners等抽象层来实现,主要原理是将计算工作分解成很多任务,分发给一些处理器去完成,并将它们处理的结果合并返回<br> 3.Work stealin算法【学数学】,主要用于任务调度负载均衡(load-balancing),通俗点完成自己的所有任务之后,发现其他人还有活没干完,主动(或被安排)帮他人一起干,这样达到尽早干完的目的
三.使用并行模块<br><br> Scala 2.13之后,并行模块变成了外部库,和XML、Swing、parser-combinators等一样需要在maven项目的pom.xml中手动导入:<br><dependency><br> <groupId>org.scala-lang.modules</groupId><br> <artifactId>scala-parallel-collections_2.13</artifactId><br> <version>0.2.0</version><br> </dependency><br><br>然后在代码文件中导入如下的包就行了: import scala.collection.parallel.CollectionConverters._ 参考链接:https://stackoverflow.com/questions/57287607/answer/submit[code=html]<br><br>四.基本案例<br><br> 1.输出1-5<br> (1 to 5).foreach(println(_)) //单行操作<br> println()<br> (1 to 5).par.foreach(println(_)) //并行操作<br><br> 2.查看并行集合中元素访问的线程<br> val result1 = (0 to 100).map{case _ => Thread.currentThread.getName}<br> val result2 = (0 to 100).par.map{case _ => Thread.currentThread.getName}<br> println(result1)<br> println(result2)
操作符
一.操作符扩展<br><br> 1.如果想在变量名,类名等定义中使用语法关键字(保留字),可以配合反引号`` val`val`=42<br><br> 2.中置操作符:A操作符B (等同于) A.操作符(B)
Scala模式匹配(重要)
模式匹配初体验
Scala中的模式匹配类似于Java中的switch语法,但是更加强大<br>
模式匹配语法中,采用match关键字声明,每个分支采用case关键字进行声明,<br>当需要匹配时,会从第一个case分支开始,如果执行成功,就会执行相应的逻辑代码,同时终止;<br>如果不匹配,则继续执行下一个分支case。<br>如果所有的case都不匹配,那么会执行case_分支,类似于Java中default
二.代码案例<br><br>val oper = '#'<br>val n1 = 20<br>val n2 = 10<br>var res = 0<br>oper match {<br>/*<br>1.match类似于Java的switch,<br>2.如果匹配成功,则会执行=> 后面的代码块<br>3.匹配顺序是从上往下<br>4.=>后面的代码块不需要写break,因为match会自动退出<br>5.如果所有的case都不匹配,则会执行case_后的代码块<br>6.如果所有的case都不匹配,并且没有case_,则会抛出异常MatchError<br>4.可以再match中使用其他类型,不仅仅是Char<br>*/<br>case '+' => res = n1 + n2<br>case '-' => res = n1 - n2<br>case '*' => res = n1 * n2<br>case '/' => res = n1 / n2<br>case _ => println("oper error")<br>}<br><br>println("res=" + res)
模式匹配扩展
条件守卫
一.基本介绍<br><br> 如果想要表达匹配某个范围的数据,就需要再模式匹配中增加条件守卫<br><br> for (ch <- "+-3!") {<br> var sign = 0<br> var digit = 0<br> ch match {<br> case '+' => sign = 1<br> case '-' => sign = -1<br> // 说明case _ if表示条件守卫,这里的下划线 _ 表示不接收ch<br> case _ if ch.toString.equals("3") => digit = 3<br> case _ => sign = 2<br> }<br> println(ch + " " + sign + " " + digit)<br> }
模式匹配中的变量
二.模式中的变量<br><br> 如果在case关键字后跟变量名,那么match前表达式的值会赋给那个变量<br><br> val ch = 'V'<br>ch match {<br>case '+' => println("ok~")<br>case mychar => println("ok~" + mychar)<br>case _ => println ("ok~~")<br> }
声明变量中的匹配
一.基本介绍<br><br> match中每一个case都可以被单独提取出来,意思和case中的一样<br><br>二.应用案例<br> val (x,y) = (1,2)<br> val (q,r) = BigInt(10) /% 3 //将BigInt(10) / 3 赋值给 q ,将BigInt(10) % 3 赋值给r<br> val arr = Array(1,2,34,4,5)<br> val Array(first,second,_*) = arr //表示取出arr中的前两个元素,并且赋值给first,second
for循环中的模式匹配
一.基本介绍<br><br> for循环中也可以进行模式匹配<br><br> val map = Map("A" -> 1,"B" -> 2,"C" -> 3)<br><br> for ((k,v) <- map){ //for表达式中也可以使用模式匹配<br> println(k,v)<br> }<br><br> for ((k,2) <- map){ //这里表示遍历出value为 2 的键值对,其他的过滤<br> println(k,2)<br> }<br><br> for ((k,v) <- map if v==3){ //这里使用的for循环守卫<br> println(k,v)<br> }
匹配数组
一.基本介绍<br><br> 1.Array(0) 匹配只有一个元素且为0 的数组<br> 2.Array(x,y) 匹配数组有两个元素,并将两个元素赋值给x y。当然可以以此类推,赋值给三个值x y z <br> 3.Array(0._*) 匹配数组从0开始到最后一个元素<br><br> val arrs = Array(Array(0),Array(1,2),Array(0,2,3),Array(2,3,4,5,6))<br> for (item <- arrs){<br> val res = item match{<br> case Array(0) => '0'<br> case Array(x,y) => x+"+"+y<br> case Array(0,_*) => "一零开头的数组"<br> case _ => "什么都不是"<br> }<br> // 0<br> // 1+2<br> // "一零开头的数组"<br> // "什么都不是"<br> println(res)
匹配列表
匹配列表和匹配数组基本类似<br><br> val lists = Array(List(1),List(1,2),List(1,2,3,4,3,34),List(0,2,3))<br> for (item <- lists){<br> val res = item match{<br> case 1::Nil => "1"<br> case x::y::Nil => x+"+"+y<br> case 1::tail => "1....."<br> case _ => "asdasdasd"<br> }<br> println(res)<br>
匹配元组
val tuple = Array((0,1),(1,0),(1,1),(1,0,2))<br> for (item <- tuple){<br> val res = item match{<br> case (0,_) => "0...."<br> case (x,0) => x<br> case _ => "asdasdsa"<br> }<br> println(res )
样例类
一.基本介绍<br><br> 1.样例类仍然是类<br><br> 2.样例类用case关键字进行声明<br><br> 3.样例类是为模式匹配而优化的类<br><br> 4.构造器中的每一参数都成为了val,除非显示的声明var (不推荐)<br><br> 5.在样例类对应的伴生对象中存在apply方法,可以是在实例化时不使用new关键字,就可以直接构造出对象<br><br> 6.也提供unapply方法,让模式匹配可以正常的工作<br><br> 7.将自动生成toString,equal,hashCode和copy方法(有点类似模板类,直接生成使用)<br><br> 8.除了上述外,样例类和其他类完全一样,可以添加字段和方法
二.样例类的基本写法<br><br> abstract class Amount<br> case class Dollar(n:Int) extends Amount //这里的形参会在底层被声明成val<br> case class Currency(n:Int,s:Stirng) extends Amount<br> case Object NoAmount extends Amount<br><br> ①样例类的使用案例一<br> val arr =Array(Dollar(1000),Currency(5000,"RMB"),noAmcount)<br> for (item <- arr){<br> val res = item match {<br> //样例类会自动创建unapply方法,会将创建对象传入的参数提取出来<br> case Dollar(n) => "$"+n<br> case Currency(k,v) => k+" "+v<br> case _ => "sdfasdf"<br> }<br> println(res)<br> }<br><br><br> ②样例类的使用案例二<br> val amt = Currency(1500,"RMB")<br> val amt2 = amt.copy() //利用amt对象copy一个新的对象<br> println(amt.n,amt2.s) //样例类会将形参创建成一个可读不可写的属性,所以可以访问读取<br> val amt3 = amt.copy(n=1800) //copy时也可以指定参数<br> println(amt3) //这里可以直接数据对象的属性,因为底层有toString方法
匹配样例类
一.基本介绍<br><br> 1.case中对象的unapply方法(提取器)返回Some集合则为匹配成功<br> 2.返回None则为匹配失败
二.基本案例<br><br>object Square{<br> //对象提取器,它会提取出创建对象时传入的参数<br> //简单来说,unapply就是逆向拆解对象<br> def unapply(z:Double): Option[Double] = {<br> println("unapply方法被调用,z= "+z)<br> Some(math.sqrt(z))<br> }<br><br> def apply(z: Double): Double = z*z<br> val number:Double=36<br> number match {<br> /*<br> Square(n)运行机制<br> 1.当匹配到case Square(n)时,<br> 2.会先调用Objeck Square中unapply(z:Double)方法,传入的 z 值就是number<br> 3.如果对象提取器返回的值时Some(6)则表示提取成功,同时将 6 赋值给 n ,最后将 n 输出<br> */<br><br> case Square(n) => println("匹配成功 "+n)<br> case _ => println("asdadsa")<br> }
<br>三.小结<br><br> 1.构建对象时,apply方法方法会被调用,比如 val n = Square(6)<br><br> 2.当Square(n)写再case后面时,即【case Square(n) => xxx】,会默认调用unapply方法(对象提取器)<br><br> 3.number会被传递给def unapply(z:Double) 的形参 z<br><br> 4.如果返回unapply方法返回的时Some,则会将结果传递给Square(n)中的 n<br><br> 5.case中对象的unapply方法(提取器)返回的时Some则匹配成功,为None则匹配失败
匹配嵌套结构
一.基本介绍<br> 操作原理类似于正则表达式<br><br>三.最佳实践案例-商品捆绑打折出售<br> 现在有一些商品,请使用Scala设计相关的样例类,完成商品捆绑打折出售。要求商品捆绑可以是单个商品,也可以是多个商品。打折时按照折扣x元进行设计.能够统计出所有捆绑商品打折后的最终价格
①<br>//设计样例类<br>abstract class Item<br>case class Book(desc:String,price:Double) extends Item<br>//discount表示优惠的金额<br>case class Bundle(desc:String,discount:Double,item: Item*) extends Item<br><br>②三个知识点<br> val sale = Bundle("书籍",10,Book("漫画",40),Bundle("文学书籍",20,Book("阳关",80),Book("围城",30)))<br> //知识点一:取出漫画二字<br> val res = sale match {<br> case Bundle(_,_,Book(desc,_),_*) => desc<br> }<br> println(res)<br><br> //知识点二(@表示法):取出漫画二字,和后面所以的内容,并存放到元组中。_*表示匹配多个Bundle<br> val res2 = sale match {<br> case Bundle(_,_,Book(desc,_),b @ _*) => (desc,b)<br> }<br> println(res2)<br><br> //知识点三:取出漫画二字,和后面的Bundle对象。<br> //说明因为没有使用 _* 即明确说明没有多个Bundle,所以返回的rest,就不是ArraySeq了。<br> val res3 = sale match {<br> case Bundle(_,_,Book(desc,_),rest) => (desc,rest)<br> }<br> println(res3)<br><br>③实现项目<br> def price(it: Item): Double ={<br> it match {<br> case Book(_,p) => p<br> case Bundle(_,disc,its @ _*) => its.map(price).sum - disc<br> }<br> println(price(sale))
密封类
一.基本介绍<br><br> 1.如果想让case类的所有子类都必须在申明该类的相同源文件中定义,可以将样例类通过超类声明为sealed,这个超类就成为了密封类<br><br> 2.密封类就是不能在其他文件中定义子类
Scala函数式编程高级
偏函数
偏函数的引出
一.先看一个需求<br> 给你一个集合val list = List(1, 2, 3, 4, "abc") ,请完成如下要求:<br> 将集合list中的所有数字+1,并返回一个新的集合<br> 要求忽略掉 非数字 的元素,即返回的 新的集合 形式为 (2, 3, 4, 5)<br><br>解决方案<br> //思路一:使用filter-map<br> val list = List(1, 2, 3, 4, "12123")<br> val res = list.filter(fun1).map(fun3).map(fun2)<br> println(res)<br><br> //思路二:模式匹配<br> val res2 = list.map(fun4)<br> println(res2)<br><br> def fun1(n:Any): Boolean ={<br> n.isInstanceOf[Int]<br> }<br><br> def fun2(n:Int):Int={<br> n+1<br> }<br><br> def fun3(n:Any): Int ={<br> n.asInstanceOf[Int]<br> }<br><br> def fun4(n:Any):Any={<br> n match {<br> case n:Int => n+1<br> case _ =><br> }
偏函数介绍
一.基本介绍<br><br> 1.在对某个集合进行处理时,其中有些元素不符合处理条件,这时就可以使用偏函数<br> 2.将函数中封装了一组case语句的函数,叫做偏函数。它只会对作用于指定类型的参数或者指定范围的参数实施计算,超出范围的值会另外处理<br> 3.偏函数在Scala中是一个特质PartialFunction
二.代码演示<br><br> val list = List(1, 2, 3, 4, "12123")<br> //创建一个偏函数<br> //1.PartialFunction[Any,Int]表示偏函数接收一个Any类型的值,返回一个Int类型的值<br> //2.isDefinedAt(x: Any)这个方法如果返回true,则会调用apply方法,否则不会调用apply方法<br> //3.apply方法相当于创建一个对象,对传入的值+1,然后返回<br> val unit = new PartialFunction[Any, Int] {<br> override def isDefinedAt(x: Any): Boolean = x.isInstanceOf[Int]<br><br> override def apply(x: Any): Int = x.asInstanceOf[Int] + 1<br> }<br><br> val res = list.collect(unit)<br> println(res) <br>
三.偏函数小结<br><br> 1.偏函数是使用构建特质的实现类(使用方式是PartialFunction的匿名子类)<br> 2.PartialFunction是个特质<br> 3.构建偏函数时,参数形式[Any,Int]是泛型,第一个表示输入参数的类型,第二个是返回参数的类型<br> 4.当使用偏函数时,会遍历集合中的所有元素,编译器执行流程会先执行isDefinedAt(),如果返回true,则会调用apply方法,构建一个新的Int对象返回<br> 5.执行isDefinedAt() 为false,则会过滤这个元素,即不会构建新的Int对象<br> 6.map函数不支持偏函数,因为map底层的机制就是所有元素循环遍历,无法过滤原来集合的元素<br> 7.collect函数支持偏函数,多用于偏函数的使用
偏函数的化简形式
偏函数的简化形式一<br><br> 声明偏函数,需要重写特质中的方法,有时候会很麻烦,而Scala提供了简单的方法<br> implicit def dataCon(n:Double):Int={ //创建一个隐式转换函数<br> n.toInt<br> }<br><br> //偏函数的简化形式<br> def parFun:PartialFunction[Any,Int]={<br> case n:Int => n+1 //利用模式匹配<br> case n:Double => n*2 //这里运用了隐式转换将Double转成了Int<br> }<br><br> val list = List(1,2,3,4,2,2,3,4,"hello")<br> val res = list.collect(parFun)<br> println(res)<br> }
偏函数的简化形式二<br><br> 这种方式不能指定传入值的类型和返回值的类型,只能使用默认的<br><br> //偏函数的简化形式二<br> val res2 = list.collect{<br> case n:Int => n+1<br> case n:Double => n*2<br> }<br><br> println(res2)
做为参数的函数
一.基本介绍<br> 函数作为一个变量传入到了另一个函数中,那么这个作为参数的函数的类型是function1,即:(参数类型) => 返回类型<br><br>二.代码演示<br> def plus(n:Int): Int ={<br> n+3<br> }<br> val list = List(1,2,3,4)<br> val res = list.map(plus(_))<br> println(res.mkString(",")<br><br>三.小结<br> 1.map(plus(_))中的plus(_)就是将plus这个函数当做一个参数传给map,_代表从集合中遍历出来的每一个元素<br> 2.plus(_)这里也可以写成plus表示对List(1,2,3,4)的遍历,将每次遍历的元素传给puls的n<br> 3.进行n+3的运算后,返回新的Int,并加入到新的集合中result1中<br> 4.def map[B,That](f:A=>B) 的声明中的f:A=>B是一个函数
匿名函数
基本介绍
一.基本介绍<br><br> 没有名字的函数就是匿名函数,可以通过函数表达式来设置匿名函数<br><br>二.代码演示<br> val res = (n:Int) => n+3<br> println(res(2))<br><br>三.小结<br> 1.不需要写def函数名<br> 2.不需要写返回值类型,因为默认使用类型推导<br> 3.如果代码块有多行,就使用{}
匿名函数的简写(参数类型推断)
一.基本介绍<br> 参数推断省去类型信息(在某些情况下【需要有应用场景】),参数类型可以推断出来,如list=(1,2,3) list.map() map中函数参数类型是可以推断出来的,同时也可以进行参数的简写<br><br>二参数类型推断的写法说明<br> 1.参数类型是可以推断出来的,可以省略参数类型<br> 2.当传入的函数,只有单个参数时,可以省去括号<br> 3.如果变量只在 => 右边只出现一次,可以用 _ 代替
三.匿名函数的简化写法一(map)<br><br> val list = List(1,2,3,4)<br> //原始写法,将list列表中每个元素加 1 之后返回到一个新列表中<br> val res1 = list.map((n:Int)=>n+1) //传入一个匿名函数到map函数中<br> println(res1)<br><br> //简化1 :因为list列表中的元素都是Int,使用匿名函数的形参的类型就可以使用类型推断<br> //这时就可以省略Int<br> val res2 = list.map(n=>n+1)<br> println(res2)<br><br> //简化2 :如果变量在 => 右边只出现了一次,这时可以将变量写成下划线 _<br> val res3 = list.map(_+1)<br> println(res3)
四.匿名函数的简化写法二(reduce)<br><br> //原始写法,将list列表中的每个元素相加,并返回结果<br> val reduceRes1 = list.reduce((x:Int,y:Int)=>x+y)<br> println(reduceRes1)<br><br> //简化1 :因为形参的类型可以根据list元素的类型进行推断,所以Int可以省略<br> val reduceRes2 = list.reduce((x,y)=>x+y)<br> println(reduceRes2)<br><br> //简化2 : 因为变量在 => 的右边只出现一次,所以变量x y可以用下划线 _ 表示<br> val reduceRes3 = list.reduce(_+_)<br> println(reduceRes3)<br> }
高级函数
一.基本介绍<br><br> 可以传入函数的函数就作高阶函数,在某些情况下也可以返回一个函数
二.代码演示<br><br> //定义一个高阶函数<br> def test(f:Double => Double,n:Double): Double ={<br> f(n)<br> }<br><br> val res = test((n:Double) => n+n,6)<br> println(res)
三.高阶函数返回函数类型<br><br> //创建一个高阶函数,它可以返回一个匿名函数<br> def test(n:Int) ={<br> (m:Int) => n-m<br> }<br><br> //调用这个高阶函数<br> //分步骤操作<br> val f1 = test(2) //这里会将2传递给 n ,即返回的结果是 (m:Int) =>2-m 是一个匿名函数<br> println(f1(4)) //这里会将4 传递给 m ,即会返回 2-4 = -2<br> //可以将上面的分步操作一次性完成<br> val res = test(3)(7)<br> println(res)<br> }
闭包<br>
一.基本介绍<br><br> 闭包(closure)就是一个函数和与其相关的引用环境组合的一个整体(实体)。
二.代码演示<br><br> def test(n:Int) ={<br> (m:Int) => n-m<br> }<br><br> //调用这个高阶函数<br> //分步骤操作<br> val f1 = test(2) //这里会将2传递给 n ,即返回的结果是 (m:Int) =>2-m 是一个匿名函数<br> println(f1(4)) //这里会将4 传递给 m ,即会返回 2-4 = -2<br> //可以将上面的分步操作一次性完成<br> val res = test(3)(7)<br> println(res)
三.代码小结<br><br> 1.test函数返回的是 (m:Int) => n-m 匿名函数,因为该函数引用到了函数外的变量 n ,那么该函数和 n 整体形成了一个闭包。val f1 = test(2)这里的f1就是一个闭包<br><br> 2.可以这样理解,返回函数是一个对象,而n就是该对象的一个字段,它们共同形成了一个闭包<br><br> 3.当多次调用f1时(可以理解多次调用闭包),发现使用的是同一个n ,所以 n 不变<br><br> 4.当使用闭包时,主要搞清楚返回函数引用了函数外的哪些变量,因为它们组合成了一个整体(实体),形成了一个闭包
四.添加后缀名的案例<br><br>object clusureDemon {<br> def main(args: Array[String]): Unit = {<br> val f = makeSuffix(".jpg")<br> println(f("scala")) //scala.jpg<br> println(f("python.jpg")) //python.jpg<br> }<br> /*<br> 传入一个文件名,如果这个文件名带有后缀名,那么直接返回;<br> 如果这个文件名没有后缀名,则为它添加一个后缀名并返回<br> */<br><br> def makeSuffix(suffix:String) ={<br> (fileName:String) => {<br> if (fileName.endsWith(suffix)) fileName<br> else fileName+suffix<br> }<br> }<br>}<br><br>五.闭包的好处<br> 1.返回的匿名函数和 test(n:Int) 的 n 变量组合成了一个闭包,因为返回的函数要引用 n 这个变量<br> 2.可以反复引用上一次传入的值
函数柯里化
一.基本介绍(curry)<br><br> 1.函数编程中,接收多个参数的函数都可转换成接收一个参数的函数,这个转换过程就是函数柯里化<br> 2.柯里化就是证明了函数只需要一个参数而已。在前面的学习中也用到了函数柯里化<br> 3.柯里化是面向函数思想的必要产生结果
二.代码演示<br><br> 编写一个函数,接收两个整数,可以返回两个数的乘积,要求:<br> 1.使用常规的方式完成<br> 2.使用闭包的方式完成<br> 3.使用函数柯里化完成<br><br> //常规方式<br> def fun1(x:Int,y:Int): Int ={<br> x*y<br> }<br><br> //闭包方式(使用匿名函数)<br> def fun2(x:Int) ={<br> (y:Int) => x*y<br> }<br><br> //函数柯里化方式<br> def fun3(x:Int)(y:Int): Int ={<br> x*y<br><br><br> println(fun1(2,3)) //调用普通函数<br> println(fun2(2)(5)) //调用闭包函数<br> println(fun3(2)(7)) //调用柯里化函数
三.案例<br> /*<br> 比较两个字符串在忽略大小写的情况下是否相等,注意,这里是两个任务:<br> 全部转大写(或小写)<br> 比较是否相等<br> 针对这两个操作,我们用一个函数去处理的思想,其实也变成了两个函数处理的思想(柯里化)<br> */<br><br> def eq(s1:String,s2:String): Boolean ={<br> s1.equals(s2)<br> }<br><br> //创建一个隐式类,用来增加集合的操作方法<br> implicit class TestEq(s:String){<br> def checkEq(ss:String)(f:(String,String) => Boolean): Boolean ={ //函数柯里化的方式<br> f(s.toLowerCase(),ss.toLowerCase())<br> }<br> }<br><br> val str = "hello"<br> val res = str.checkEq("HELLO")(eq)<br> println(res)<br><br> //方式二,直接传入一个匿名函数,并利用类型推断简写这个匿名函数<br> val res2 = str.checkEq("Hello")(_.equals(_))<br> println(res2)
控制抽象
一.基本介绍<br><br> 控制抽象是这样的函数<br> 1.参数是函数<br> 2.函数参数没有输入值也没有返回值
二.代码演示<br><br> //像这样,参数是函数,函数没有输入值也没有返回值的函数,就是控制抽象<br> def myRun(f:()=>Unit): Unit ={<br> new Thread{<br> override def run(): Unit = {<br> f()<br> }<br> }.start()<br> }<br><br> myRun { //这里就是调用myRun这个函数<br> () => //这里表示没有传入参数也没有返回值<br> println("开始干活!")<br> Thread.sleep(5000)<br> println("干完了")<br> }<br><br>三.案例<br> /*<br> 利用控制抽象写出until函数,实现一下代码<br> var n = 10<br> while (n > 0){<br> n -= 1<br> println(n)<br> }<br> */<br><br> var x = 10<br> until(x > 0){<br> x -= 1<br> println(x)<br> }<br><br> //创建一个控制抽象函数,这里也用到了函数柯里化<br> def until(f1: => Boolean)(f2: => Unit): Unit = {<br> if (f1){<br> f2<br> until(f1)(f2)<br> }
泛型、泛型上下界、视图界定、上下文界定、逆变|协变|不可变(重要)<br>
泛型
一.基本介绍<br> 1.如果我们要求函数的参数可以接受任意泛型。可以使用泛型,这个类型可以代表任意的数据类型。<br><br> 2.例如List,在创建List时,可以传入整型,字符串,浮点数等等任意类型。那是因为List在类定义时引用了泛型。<br>
代码示例
//Scala的泛型和java类似, 但是功能远比Java的泛型强大<br>object GenericDemo01 {<br><br> def main(args: Array[String]): Unit = {<br> val msg1 = new StringMessage[String]("10")<br> val msg2 = new IntMessage[Int](2)<br><br> println(msg1.get)<br> println(msg2.get)<br> }<br><br>}<br>/*<br>要求:<br><br>编写一个Message类<br>可以构建Int类型的Message和String类型的Message.<br>要求使用泛型来完成设计,(说明:不能使用Any)<br>scala的泛型就是中括号<br>*/<br>abstract class Message[T](value: T) {<br><br> def get = value<br><br>}<br><br>class StringMessage[String](msg: String) extends Message(msg)<br><br>class IntMessage[Int](msg: Int) extends Message(msg)
泛型上界
Java上界
在Java泛型里表示某个类型是A类型的子类型,使用extend关键字,这种形式叫upper bounds(上线或上界)语法如下:<br> <T extend A><br> //使用通配符的形式<br> <? extends A><br>
Scala上界<br><br> 在Scala里表示某个类型是A类型的子类型,也称上限或者上界。使用<:关键字。语法如下<br> [T <: A]<br> //使用通配符<br> [_ <: A]<br>
代码示例
//java中上界<br>//在 Java 泛型里表示某个类型是 A 类型的子类型,使用 extends 关键字,这种形式叫 upper bounds(上限或上界),语法如下: <T extends A><br>//或用通配符的形式: <? extends A><br>object UpperBoundsDemo01 {<br><br> def main(args: Array[String]): Unit = {<br> //1. 第一种方法<br> println("传统方法: " + new CompareInt(1, 2).greater)<br><br> //2. 第二种方法<br> //这里必须要使用Java的Integer。因为Integer实现了Comparable接口<br> println("通用方法: " + new CommonCompare[Integer](Integer.valueOf(1), Integer.valueOf(2)).greater)<br> //这里使用了隐式转换<br><br> //3. 第三种方法<br> //implicit def float2double(x: Float): Double = x.toDouble<br> val commonCompare3 = new CommonCompare[java.lang.Float](10.1f, 10.2f)<br> println(commonCompare3.greater)<br> }<br><br>}<br><br>/*<br>scala中上界应用案例-要求<br>编写一个通用的类,可以进行Int之间、Float之间、等实现了Comparable接口的值直接的比较.//java.lang.Integer<br>分别使用传统方法和上界的方式来完成,体会上界使用的好处.<br>*/<br>//传统方法<br>class CompareInt(n1: Int, n2: Int) {<br><br> def greater = if (n1 > n2) n1 else n2<br><br>}<br><br>//scala的泛型上界<br>//说明<br>//1. [T <: Comparable[T]] 表示T是Comparable的子类<br>//2. T必须要实现Comparable接口<br>//3. T可以使用compareTo方法<br>//4. 这样写通用性更好<br>//5. 通配符的形式: [_ <: Comparable[_]]<br>class CommonCompare[T <: Comparable[T]](n1: T, n2: T) {<br><br> def greater = if (n1.compareTo(n2) > 0) n1 else n2<br><br>}
泛型下界
Java下界
在Java中泛型里表示某个类型是A类型的父类型,使用super关键字<br><T super A><br>//使用通配符<br><? super A>
Scala下界<br> 在Scala中下界,使用 >: 关键字<br> [T >: A] //表示T的下界是A<br> //使用通配符<br> [_ >: A]
代码示例
//Java中下界<br>//在 Java 泛型里表示某个类型是 A类型的父类型,使用 super 关键字。<T super A><br>//或用通配符的形式: <? super A><br><br>//scala中下界<br>//在 scala 的下界或下限,使用 >: 关键字,语法如下:[T >: A]<br>//或用通配符:[_ >: A]<br><br>//scala使用下界小结<br>//对于下界,可以传入任意类型<br>//传入和Animal直系的,是Animal父类的还是父类处理,是Animal子类的按照Animal处理<br>//和Animal无关的,一律按照Object处理<br>//也就是下界,可以随便传,只是处理是方式不一样, 编译器也不会报错, 是可以执行的, 只是丢失了泛型信息<br>//不能使用上界的思路来类推下界的含义<br>object LowerBoundsDemo1 {<br><br> def main(args: Array[String]): Unit = {<br><br> biophony(Seq(new Earth, new Earth)).foreach(_.sound())<br><br> biophony(Seq(new Animal, new Animal)).foreach(_.sound())<br><br> biophony(Seq(new Bird, new Bird)).foreach(_.sound())<br><br> val res = biophony(Seq(new Bird))<br><br> val res2 = biophony(Seq(new Object))<br><br> val res3 = biophony(Seq(new Moon))<br> println("res = " + res)<br> println("res2 = " + res2)<br> println("res3 = " + res3)<br><br> }<br> def biophony[T >: Animal](things: Seq[T]) = things<br>}<br><br><br>class Earth { //Earth 类<br><br> def sound(){ //方法<br> println("hello !")<br> }<br><br>}<br>class Animal extends Earth{<br><br> //重写了Earth的方法sound()<br> override def sound() ={<br> println("animal sound")<br> }<br><br>}<br>class Bird extends Animal{<br><br> //将Animal的方法重写<br> override def sound()={<br> println("bird sounds")<br> }<br><br>}<br><br>class Moon
Scala下界使用小结<br><br> ①对于下界,可以传入任意类型<br><br> ②传入和A直系的,是A父类就按照A父类处理,如果是A的子类,则按照A处理<br><br> ③如果传入的对象与A无关,一律按照Object处理<br><br> ④也就是说下界可以随便传,只是处理方式不一样<br><br> ⑤不能使用上界的思路来类推下界的含义
逆变、协变和不可变
一.基本介绍<br> 1.Scala的协变(+),逆变(-),协变covariant、逆变contravariant、不可变invariant<br><br> 2.对于一个带类型参数的类型,比如 List[T],如果对A及其子类型B,满足 List[B]也符合List[A]的子类型,那么就称为covariance(协变) ,如果 List[A]是 List[B]的子类型,即与原来的父子关系正相反,则称为contravariance(逆变)。如果一个类型支持协变或逆变,则称这个类型为variance(翻译为可变的或变型),否则称为invariance(不可变的)<br><br> 3在Java里,泛型类型都是invariant,比如 List<String> 并不是 List<Object> 的子类型。而Scala支持,可以在定义类型时声明(用加号表示为协变,减号表示逆变),如: trait List[+T] // 在类型定义时声明为协变这样会把List[String]作为List[Any]的子类型。
二.应用实例<br><br> 在这里引入关于这个符号的说明,在声明Scala的泛型类型时,“+”表示协变,而“-”表示逆变 <br>C[+T]:如果A是B的子类,那么C[A]是C[B]的子类,称为协变 <br>C[-T]:如果A是B的子类,那么C[B]是C[A]的子类,称为逆变 <br>C[T]:无论A和B是什么关系,C[A]和C[B]没有从属关系。称为不变.<br><br>val t: Temp[Super] = new Temp[Sub]("hello world1")<br>class Temp3[A](title: String) { //Temp3[+A] //Temp[-A]<br> override def toString: String = {<br> title<br> }}<br><br>//支持协变<br>class Super<br>class Sub extends Super
代码示例
//协变、逆变、不变<br>//Scala的协变(+),逆变(-),协变covariant、逆变contravariant、不可变invariant<br>//对于一个带类型参数的类型,比如 List[T],如果对A及其子类型B,满足 List[B]也符合List[A]的子类型,那么就称为covariance(协变),如果 List[A]是 List[B]的子类型,即与原来的父子关系正相反,则称为contravariance(逆变)。如果一个类型支持协变或逆变,则称这个类型为variance(翻译为可变的或变型),否则称为invariance(不可变的)<br>//在Java里,泛型类型都是invariant,比如 List<String> 并不是 List<Object> 的子类型。而scala支持,可以在定义类型时声明(用加号表示为协变,减号表示逆变),如:traitList[+T]//在类型定义时声明为协变这样会把List[String]作为List[Any]的子类型。<br><br>//在这里引入关于这个符号的说明,在声明Scala的泛型类型时,“+”表示协变,而“-”表示逆变<br>//C[+T]:如果A是B的子类,那么C[A]是C[B]的子类,称为协变<br>//C[-T]:如果A是B的子类,那么C[B]是C[A]的子类,称为逆变<br>//C[T]:无论A和B是什么关系,C[A]和C[B]没有从属关系。称为不变.<br>object Demo01 {<br><br> def main(args: Array[String]): Unit = {<br><br> //不变<br> val t1: Temp3[Sub] = new Temp3[Sub]("abc") //ok<br> // val t2: Temp3[Sub] = new Temp3[Super]("abc") //error<br>// val t3: Temp3[Super] = new Temp3[Sub]("abc") //error<br> val t4: Temp3[Sub] = new Temp3[Sub]("abc") //ok<br><br> //协变<br> val t5: Temp4[Super] = new Temp4[Sub]("abc") //ok<br> val t6: Temp4[Sub] = new Temp4[Sub]("abc") //ok<br><br> //逆变<br> val t7: Temp5[Sub] = new Temp5[Sub]("abc") //ok<br> val t8: Temp5[Sub] = new Temp5[Super]("abc") //ok<br>// val t9: Temp5[Super] = new Temp5[Sub]("abc") //error<br><br> }<br><br>}<br><br>//协变<br>class Temp4[+A](title: String) {<br><br> override def toString = title<br><br>}<br><br>//逆变<br>class Temp5[-A](title: String) {<br><br> override def toString = title<br><br>}<br><br>//不变<br>class Temp3[A](title: String) {<br><br> override def toString = title<br><br>}<br><br><br>class Super<br><br>class Sub extends Super
视图界定
视图界定(View Bounds)<br><% 的意思是“view bounds”(视界),它比<:适用的范围更广,除了所有的子类型,还允许隐式转换类型。<br>def method[A <% B](argList): R = ... 等价于:<br>def method[A](argList)(implicit viewAB: A => B): R = ...<br>并且在调用的地方必须存在相应的隐式转换 A => B<br>或等价于: implicit def conver(a:A): B = …<br><% 除了方法使用之外,class 声明类型参数时也可使用:<br>class A[T <% Int]
代码示例
object ViewBoundsDemo01 {<br><br> def main(args: Array[String]): Unit = {<br> val value = new CompareComm11(10, 20)<br> println(value.greater)<br> }<br><br>}<br><br>/**<br> * 视图界定已经过期了, 推荐使用隐式参数的方式, 科里化的方式写隐式参数<br> */<br>class CompareComm11[T <% Comparable[T]](obj1: T, obj2: T) {<br>//等价于下面的写法。其中(implicit ev$1: T => Comparable[T])是柯里化的写法。<br>//ev$1 可以任意写, 是函数名<br>//class CompareComm11[T](obj1: T, obj2: T)(implicit ev$1: T => Comparable[T]) {<br><br> def greater = if (obj1.<u>compareTo</u>(obj2) > 0) obj1 else obj2<br><br>}
<font color="#ff0000">高版本的Scala已经废弃</font>
上下文界定
与view bounds一样, context bounds(上下文界定)也是隐式参数的语法糖<br>为语法上的方便,引入了"上下文界定"这个概念
代码示例
object ContextBoundsDemo01 {<br><br> implicit val personComparetor = new Ordering[Person] {<br> override def compare(p1: Person, p2: Person): Int =<br> p1.age - p2.age<br> }<br><br> def main(args: Array[String]): Unit = {<br> val p1 = new Person("mary", 30)<br> val p2 = new Person("smith", 35)<br> val compareComm4 = new CompareComm4(p1,p2)<br> println(compareComm4.geatter)<br> val compareComm5 = new CompareComm5(p1,p2)<br> println(compareComm5.geatter)<br> val compareComm6 = new CompareComm6(p1,p2)<br> println(compareComm6.geatter)<br> }<br><br>}<br><br>//这里需要对[T: Ordering]进行说明。<br>//1. 和之前的泛型上下界、视图界定不同。这里的泛型T和Ordering之间没有T必须是Ordering的子类或者是父类<br>//2. 会自动添加一个隐式转换参数。(implicit comparetor: Ordering[T]), 存在着需要将T => Ordering[T]的隐式转换<br>//3. 并且调用CompareComm6方法时必须有一个可用的隐式值Ordering[T]存在<br><br>//方式1。<br>class CompareComm4[T: Ordering](obj1: T, obj2: T)(implicit comparetor: Ordering[T]) {<br><br> def geatter = if (comparetor.compare(obj1, obj2) > 0) obj1 else obj2<br><br>}<br><br>//方式2,将隐式参数放到方法内<br>class CompareComm5[T: Ordering](o1: T, o2: T) {<br><br> def geatter = {<br> def f1(implicit cmptor: Ordering[T]) = cmptor.compare(o1, o2)<br><br> if (f1 > 0) o1 else o2<br> }<br><br>}<br><br>//方式3,使用implicitly语法糖,最简单(推荐使用)<br>class CompareComm6[T: Ordering](o1: T, o2: T) {<br><br> def geatter = {<br> //这句话就是会发生隐式转换,获取到隐式值 personComparetor<br> //在隐式转换的作用域中必须存在Ordering[T]这样的隐式值<br> //从上线文中获取对应的隐式参数<br> val comparetor = implicitly[Ordering[T]]<br> println("CompareComm6 comparetor" + comparetor.hashCode())<br> if (comparetor.compare(o1, o2) > 0) o1 else o2<br> }<br><br>}<br><br>//一个普通的Person类<br>class Person(val name: String, val age: Int) {<br> override def toString = this.name + "\t" + this.age<br>}<br>
Scala中常用特殊符号(重要)
0 条评论
下一页