Scala语言

Scala中函数是一等公民,可以不依赖对象直接创建函数,Scala将函数式编程和面向对象编程融合在一起。 Scala中将对象属性和对象方法与类属性和类方法进行了分离,Scala中的对象相关属性和方法通过 class 关键字定义在对象域中,而类相关的属性和方法通过 object 关键字定义在 伴生对象 中(Scala中没有 static 关键字,伴生对象 编译后会生成 原始对象 的class文件(类名.class)和 伴生对象 的class文件(类名$.class) 笔记语法中的”<xxx>“ 代表用户必填项,”[xxx]“ 代表选填项

一、基本语法

声明变量:var|val <变量名> [: 变量类型] = [变量值]

Scala中分号可不写 val代表不可变量,编译后加上 final 修饰

 

特殊类型说明
Unit 等同于 void
Null 引用类型,但只有一个null实例
Nothing 在Scala继承链最底层,是任何类的子类,利用 多态 在我们确定一个函数不会正常返回的时候,可以将返回值(异常)返回给其他变量(因为是子类)

Scala中值类型转换

  • 向下转型直接赋值

  • 向上转型在值上调用 toXxx(如:toDouble、toInt)强制转换函数

Scala中不支持三目运算符(即:<布尔表达式>?<语句1>:<语句2>),全部使用 if-else 方式实现 Scala中也没有 switch 语句,而是使用 match-case 匹配模式实现

Scala中for循环控制

// 打印1到3,闭区间
for(i<- 1 to 3){
   print(i);
}
// 打印1到3,前闭后开
for(i<- 1 unit 3){
   print(i)
}
// 直接遍历集合
val list = List("h",1,2.2);
for(item<-list){
   println(item)
}
// 循环守卫(也称条件判断式),判断为true进入循环,false则跳过循环
for(i<- 1 to 3 if i!=2){
   println(i);
}
// 循环返回值,对循环中处理的结果返回到一个新的Vector集合中
var res = for(i<- 1 to 3 )yield i
// 控制步长,打印1到10 步长为2
for(i <- Range(1,10,2)){
   println(i)
}

 

二、函数式编程基础

在Scala中函数也被当做一种数据类型,可以作为方法的参数传递,Scala中将没有返回值的函数/方法也称为 过程

在类中定义方法:

def <函数名> ([数名称:参数类型]....)[:返回值类型[=]]{ [语句...] [return 返回值] }

直接定义函数:

val func ([数名称:参数类型]....)[:返回值类型] =>{ [语句...] [return 返回值] }

将对象的方法转换为独立的函数:

val func = obj.func _;

  1. 形参为空可以省略()

  2. def <函数名> ([数名称:参数类型]....)={...} 表示返回值类型不确定(最后一条语句不能带 return 关键字),运行时类型推导完成,语句可以返回 多种类型

  3. def <函数名> ([数名称:参数类型]....):<返回值类型>{...} 表示返回值类型在编译期就确定,语句中只能返回 一种类型

  4. def <函数名> ([数名称:参数类型]....){...} 表示没有返回值(即等价于返回值Unit),即使语句中有return也不生效,这种函数也叫 过程

  5. 如果没有return语句,默认语句的最后一行结果作为返回值

  6. 如果写了语句最后写了return关键字,则返回值类型就不能省略,函数返回不能自行推导

  7. 形参列表和返回值的数据类型可以是值类型和引用类型

  8. 在Scala中声明函数/方法形参列表时,可以指定默认值

可变参数写法

def <函数名> (参数名:参数类型*){[语句...]}

惰性函数

在将函数返回值赋值给其他变量时候,将这个变量声明为lazy,则该函数不会立即执行,只会在取值的时候才会计算,这种函数叫 惰性函数

lazy可以修饰val变量,但不能修饰var变量。指定了该变量后也会在用到的时候才会赋值

三、类与对象

3.1 基础

Scala没有public关键字,定义类的时候 属性 默认为private修饰并且默认编译后会提供 属性名() 的方法用来获取该方法,和 属性名_$eq()方法用来设置该属性,如果定义属性时添加上private则编译后不会再添加这两个方法,需要开发者自己提供。定义类的时候 方法 默认为public修饰,protected修饰的属性或者方法只能对子类访问,而子包不能访问 Scala还通过object关键字来定义 伴生对象 ,将所有类属性和类方法全部定义到 伴生对象

<val|var 对象名> [:类型] = new <类型>();

3.2 构造器

Scala包含 主构造器辅助构造器,在scala中体现了函数编程和面向对象编程的思想结合,定义主构造器放在类名之后,且 主构造器 会执行直接放在类中的所有语句。 伴生对象可以通过def apply():<类名>={}方式定义工厂方法,通过 伴生对象 创建对象时不用new直接使用类名(实参列表)形式

1.想让主构造器变私有,则在()前加上 private 2.主构造器 无参数可以省略类名后面的() 3.构造器参数未使用任何访问符,则形参是局部变量 4.构造器参数使用var定义会则编译后会将变量作为private属性并提供 xxx()方法和xxx_$eq()方法 5.构造器参数使用val定义则编译后变量用private final修饰并提供 xxx()方法 6.创建对象时必须为属性指定初始值,也可以用 - 赋值初始值 7.只有 主构造器 可以调用 父类构造器 ,但是不能通过super调用,可以通过extends 父类(参数)的方式给符类构造器传递参数 8*.在类上加上@BeanProperty注解,会自动生成setter和getter方法

3.3 包

3.3.1 包定义

Scala中的包可以嵌套使用,子包可以 直接访问 父包内容,而父包要通过import来访问子包内容,并且引入包的时候,只能访问该包下面的类,而不能递归访问子包下面的类(即import不是精确引入,不会递归引入) 包中可以包含类对象特质(trait),但是不能包含变量和方法,这是虚拟机局限,因此Scala通过包对象来解决.(每一个包只能有一个包对象)包中对象可以直接调用包对象定义的属性和方法

定义语法:package object <包名>{[语句...]}

定义方法和属性时候privateprotected修饰的成员后面可以通过 [包名]来扩大可见性

3.3.2 包引入

Scala中的包可以在任何需要的地方引入,从而降低import包的作用范围。

// Scala中可以在引包的时候将类定义别名
import java.utils.{HashMap=>JavaHashMap}
// Scala在引入包的时候可以制定忽略某些类
import java.utils.{HashMap=_,_}

3.4 继承

Scala只支持单继承使用extends关键字,子类继承了父类所有的属性和方法,只是私有属性和方法不能在子类访问 Scala中不存在Java继承时属性存在的动态绑定问题,因为Scala继承属性的时会把属性的属性名()属性名_$eq()一起继承过去,相当于 重写方法 Scala中引用类型强制类型转换通过asInstanceOf[父类]来转换,通过isInstanceOf来判断类型 Scala使用trait关键字声明特质(等同java中的接口),当一个类继了特质,就可以用该特质来引用该类的实例对象

声明时使用特质:class <子类> extends <父类|特质1> with <特质2> with <特质3> with<特质4>

创建对象时使用特质(动态混入):<var|val> 对象名 = new 类名 with <特质1> with <特质2> 创建对象时按照定义顺序从左到右加载(初始化特质),而调用覆盖的特质方法时,会从右向左调用,程序运行过程中 不管有无继承关系特质2中 super 指的是特质1 ,如果想让特质2指向自己的 真实父类 ,可以通过 泛型 即:super[特质直接父类].xxx(...)的方式调用

既有抽象方法又有具体方法的特质也叫 富接口 混入了特质的类,特质中的属性直接加入该类作为类的属性(对象属性或者类属性具体看特质如何定义),并且特质中抽象的字段(未赋值)在具体的类中必须初始化

特质 可以继承具体的类(相当于Java中的接口可以继承具体的类),然后可以扩展类的功能,混入该特质的类只能继承该特质的超类

特质 继承了具体的类,如果想限制实现该特质的类 必须是特质超类的子类 才能调用该方法,则可以在方法前加上this.<特质超类名>=>的限定

3.5 内部类

// 创建内部类
new Outter().Inner()
// 创建静态内部类,在伴生对象中定义
new Outter.Inner()

通过 别名=>class 内部类的形式将 外部类.this 替换为前面的 别名 ,这种方式在定义 别名 的时候要将外部类属性 放在别名定义之后,否则编译错误。 非静态内部类 默认与 外部类对象 关联,如果方法需要调用自身 非静态内部类 对象,即屏蔽外对象对内部类对象影响可以通过类型投影,具体做法就是在 非静态内部类 调用自身的方法的形参前面加上 外部类# 前缀,表示忽略内部类对象之间关系。

3.6 隐式

1. 隐式转换函数式通过implicit声明 有且只有一个参数 的函数,这种函数在它作用域范围内 自动调用,因为是 自动调用 它的名称并不重要。只是与 函数签名 有关(函数的 参数列表 以及 返回值

implicit def <函数名>(<需要转换的参数类型> <参数名>):<转换后的参数类型>{[语句...]}

2. 通过implicit声明的变量叫 隐式值,如果函数要使用 隐式值 需要在 形参 前面也加上 implicit,这样调用该 拥有隐式值形参的函数 在调用时如果没有传入参数,则会在作用范围内寻找通过implicit声明的 隐式值 作为实参。隐式值 的优先级要大于定义形参时指定的 形参默认值

3. 隐式类通过`implicit`声明在类、伴生对象、包对象中(即:隐式类不能作为顶级类),**主构造器** 只能包含一个参数作为在该范围内被识别并转换的类型(形参不需要`implicit`修饰),在 **隐式类** 作用范围内通过 **隐式类主构造器参数类型的对象** 调用 **隐式类中的方法**(在该类型对象中没定义该方法),则在编译过后会通过 **将该类型对象作为参数传入隐式类,然后通过隐式类调用隐式类中方法** 的方式调用 **隐式类中的方法**,来在隐式类作用范围内增强指定对象

四、集合基本使用

在Scala中集合分为两种:变长集合定长集合,并且有时候会用到Java中的集合,则会通过 隐式转换 在两者之间互相转换。

4.1 Array

// 直接创建定长数组(泛型是可选的,但必须指定集合长度),不能直接print全部元素
val array = new Array[Int](3);
// 通过集合伴生对象的apply方法创建定长数组,可以直接print全部元素
val array = List(1,2,3);
// 定义多维数组(只能是定长)
val arrayDim = Array.ofDim[Int](3,4) //3行4列,元素Int类型
// 定长集合是定义在scala包中的不需要引入,而变长集合需要单独引用
import scala.collection.mutable.ArrayBuffer
// 定长集合转换为变长集合
val arrayBuffer = array.toBuffer;
// 创建变长数组
val arrayBuffer = new ArrayBuffer[Int](3);
// 向变长数组添加元素
arrayBuffer.append(1);
// 变长集合转换为定长集合
val array = arrayBuffer.toArray;
// Scala数组转换为Java数组(因为Java数组是变长,所以只能中Scala中的ArrayBuffer转换)
// 导入隐式转换函数
import scala.collection.JavaConversions.bufferAsJavaList
val scalaArr = new ArrBuffer[Int](3);
// 该方法会需要Java的ArrayList,调用时会被引入的隐式转换函数识别转换为ArrayList
val javaArr = new ProcessBuilder(scala);
// Java数组转换为Scala数组
import scala.collection.JavaConversions.asScalaBuffer
val scalaArr:mutable.Buffer[Int] = javaArr;

4.2 Tuple

元组是一个容器可以存放相同或者不同的元素作为一个整体,Scala中元组最大可以存储22个元素,Tuple1~Tuple22不同的元素个数是不同的元组类型

// 元组定义
val tuple = (1,2,"hello",4);
// 元组访问
print(tuple._2);// 访问元组第二个元素

4.3 List

默认情况下Scala中的List是不可变(如果用可变则需要ListBuffer),并且是一个对象可以直接存储数据。

//--------------------不可变List
// 创建list
val list = List(1,2,3);
// 创建空集合
val list = Nil;
// 访问list元素
print(list(1));//访问第二个元素
// 在list之后追加数据
val list2 = list:+4;//list并没有改变,而是返回一个添加元素后的新集合
// 在list之前添加数据
val list = 4+:list
// 将每一个元素加到集合中
val list = 1::2::3::List(1,2,3)::Nil;
// 将集合分解,并把集合中的每个元素添加到指定末尾集合中
val list = 1::2::3::List(1,2,3):::Nil;
//----------------------可变ListBuffer
import scala.collection.mutable.ListBuffer
// 创建可变list
val listBuffer = ListBuffer(1,2,3);
// 从可变list删除元素
listBuffer-=1;
// 向可变list添加元素
listBuffer+=1;
// 合并另外一个集合(可以是不变list)
listBuffer++list;

4.4 Queue

在Scala中有scala.collection.mutable.Queuescala.collection.immutable.Queue两种队列,一般开发中用到的是可变队列

// 创建可变队列
import scala.collection.mutable.Queue
val queue = Queue(1,2,3);
// 向队尾增加元素
queue+=4;
// 向对位添加集合中的所有元素
queue++=List(1,2,3);
// 从队列头部取出元素
queue.dequeue();
// 向队列尾部添加元素
queue.enqueue(4,5,6);
// 返回队首元素(对队列无影响)
queue.head;
// 返回队尾元素(对队列无影响)
queue.last;
// 返回除第一个意外的所有元素
queue.tail;

4.5 Map

在Scala中有scala.collection.mutable.Mapscala.collection.immutable.Map两种Map,默认是不可变Map,其中不可变Map有序,可变map无序。在Scala底层Map元素是Tuple2类型

// 创建Map
val map = Map("a"->10,"b"->20);
// 以二元组形式创建Map
val map = Map(("a",10),("b",20));
// 使用map取值,如果key不存在则抛出异常
map.map("a");
// 检查元素是否存在
map.contains("a");
// get取值,如果存在返回some,不存在返回none
map.get("a").get
// getOrElse取值,如果不存在返回默认值(第二个参数指定的值)
map.getOrEles("a","默认值");
// 添加元素
map+=("c"->30,"d"->40);
// 删除元素
map-=("c"->30,"d"->40);
// 取出的元素是Tuple2类型,因此遍历可以通过元组的方式访问
for(ele<-map){ele._1;ele._2}

4.6 Set

在Scala中有scala.collection.mutable.Setscala.collection.immutable.Set两种Map,默认是不可变Map

// 创建可变set
import scala.collection.mutable.Set
val set = Set(1,2,3);
// 向set中添加元素
set+=4;
// 在set中删除元素
set-=4;

###

五、集合高级使用

5.1 映射操作

将集合中的每一个元素通过 映射函数 转换为新结果集合

val list = List(1,2,3);
list.map(x=>x+1)

5.2 扁平化

val list = List(1,2,3);
list.flatMap(_.toString);

5.3 元素过滤

val list = List(1,2,3);
list.filter(_>2);

5.4 化简

val list = List(1,2,3);
// 左边第一个元素开始迭代向后执行函数
list.reduce(_+_);//等同于reduceLeft

5.5 折叠

val list = List(1,2,3);
list.fold(4)(_+_);//等价于在集合左侧添加元素后执行第二个函数参数
4/:list(_+_)

5.6 扫描

// 对集合的每个元素进行fold操作,让后将所有中间结果保存到一个集合中
val list = List(1,2,3);
val list2 = list.scan(4)(_+_);

5.7 拉链合并

val list1 = List(1,2,3);
val list2 = List(4,5,6);
list1.zip(list2);//配对形成元组集合(1,4),(2,5),(3,6)

5.8 迭代器

迭代器遍历元素只能访问一次,第二次访问需要重新获取迭代器

val list = List(1,2,3);
val iterator = list.iterator;
while(iterator.hasNext){
   iterator.next()
}
// 重新获取迭代器,以临另一种方式迭代
for(ele<-iterator){
   ele;
}

5.9 流

steam是一个存放无穷元素的集合,主要用来通过指定规则动态生成元素。末尾元素遵循lazy规则

def numsForm(n:BigInt):Stream[BigIni]=n#::numsForm(n+1)
numsFrom(4).tail

5.10 视图

使用视图对集合的所有操作并不会立即执行,而是在用到集合元素的时候才去执行

val list = List(1,2,3)
list.view.filter(_>1);

5.11 并行集合

val list = List(1,2,3);
list.par.map(_+2);

5.12 操作符扩展

class oper{
   var money:Int = _;
   // 对中置操作符重载
   def +(n:Int):Unit={
       this.money+=n;
  }
   // 对后置操作符重载
   def ++():Unit={
       this.money+=1
  }
   // 对前置操作符重载,方法前需要加上unary_前缀
   def unary_!():Unit={
       this.money=-money;
  }
}

六、模式匹配

6.1 基本介绍

val oper = '+';
oper match{
   case '+'=>print("加法");
   // 如果都没匹配,最终会执行 case_后面的语句
   case _=>
}

6.2 守卫

val num =120
num match{
   //此时的_不表示默认匹配
   case _if(mun>100)=>print(num)
   case _=>
}

6.3 模式中的变量

val a = "aaaa"
a match{
   // 将a中的值传递给mystring
   case mystring => print(mystring);
   case _ =>
}
val b = ‘b’
b match{
   // match中可以有返回值,默认case的最后一条语句为返回值
   case ‘b’ => "hello";
   case _ =>
}

6.4 匹配集合

Array(0) // 匹配只有一个元素且为0的数组
Array(x,y) // 匹配数组有两个元素并将两个元素赋值x,y
Array(0,_*) // 匹配数组以0开始

0::Nil // 匹配只有一个元素且为0的List
x::y::Nil // 匹配有两个元素并将两个元素赋值为x,y
0::tail // 匹配第一个元素为0的List

(0,_) // 匹配第一个元素为0的tuple2
(y,0) // 匹配第二个元素为0的tuple2,并把第一个元素赋值为x

6.5 对象匹配

// 伴生对向的unapply方法返回Some集合为匹配成功,返回None集合为返回失败
Object Square{
   def unapply(d:Double):Option[Double]={
       Some(math.qurt(d))
  }
}

val num = 10.1
num match{
   // 调用Square的unapply方法隐式传入num,返回结果Some的值赋给n
   case Square(n) => print(n);
   case _ =>
}

6.6 样例模式

// 声明样例类后面必须带一个参数,该参数默认用val修饰作为不可变属性
case class A(v:Int){}

6.7 密封类

通过sealed关键字声明的类的所有子类必须在相同源文件中定义


七、函数式高级编程

7.1 偏函数

// 匿名内部类形式定义偏函数,表示接受的参数是Any,返回值类型是Int,collect支持偏函数,map不支持偏函数
val paritalFum = new ParitalFunction[Any,Int]{
   // 如果返回true 会调用 apply方法创建对象,如果为false则过滤掉元素
   override def isDefinedAt(x:Any)={
       
  }
   // 返回Int类型对象
   override def apply(v:Any)={
       
  }
}

// 简化形式,返回类型必须统一,如果不统一返回类型是Any
val list = List(1,2,3).collcet{
   case i:Int=>i+1;
   case j:Double=>(j+1).toInt;
   case _ =>
}

7.2 匿名函数

val fun = (x:Int)=>{
   x+1
}

7.3 高阶函数

能够 接收函数作为参数的函数 或者 能够返回函数的的函数 叫做 高阶函数

// 接受函数作为参数
// 接受Double类型的参数返回Int类型参数
def fun(f:Double=>Int,n:Double){
   f(n)
}
// 返回函数作为参数,也称为闭包
def minusxy(x:Int){
  (y:Int)=>x+y;
}
// 或者直接使用匿名函数返回高阶函数,也称为闭包
val fun = (x:Int)=>{
  (y:Int)=>x+y
}
// 闭包就是一个函数与相关引用环境组合成的一个整体

7.4 参数类型推断

  1. 参数类型可以推断时,可以省略参数类型

  2. 传递函数时,只有单个参数可以省略括号

  3. 如果参数的变量(无论=>左侧有多少变量),在=>右侧只出现一次,则可以省略=>以及左侧内容,右侧内容的所有变量用_表示

7.5 函数柯里化

接收多个参数的函数 转化为接受 单个参数的的函数 的过程就叫 柯里化

// 通过柯里化比较字符串大小并且转换成小写
implicit class TestEq(s:String){
   def checkEq(str:String)(f:(String,String)=>Boolean):Boolean={
       f(s.toLowerCase,str.toLowerCase);
  }
}
// 调用
val str = "Hello";
str.checkEq(str)(_.equals(_));

7.6 抽象控制

抽象控制是一类特殊的函数,它的 参数是函数 ,函数没有输入值也没有返回值

def abstractCtrl(f:=>Unit){
   f
}
// 直接将语句块传入到f函数中
abstractCtrl{
   // 语句块
}

 


原文地址:https://www.cnblogs.com/leon618/p/13783385.html