scala
scala安装
1.scp 远程拷贝到linux(jdk-1.8 和 Scala , idea)
2. 解压 tar -zxvf fileName -C filePath
3.配置环境变量
jdk 的和 Scala的
vim /etc/profile
source /etc/profile
4.开启idea
bin/idea.sh
scala 使用方式
1. touch test.scala
vim test.scala
object ScalaTest{
def main (args:Array[String]){
val a=15
val b=6
println("a-B \t"+(a-b))
println("a*B \t"+(a*b))
println("a/B \t"+(a/b))
println("a%B \t"+(a%b))
}
}
scalac test.scala
scala test.scala
2.[zhangsan@master bin]$scala
Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_171).
Type in expressions for evaluation. Or try :help.
scala>println("helloword");
helloword
3.Idea
object ScalaTest {
def main(args:Array[String]): Unit ={
val c="qq9"
val d="pp6"
val e="qq6"
println("c+d\t"+(c+d))
println("c+e\t"+(c+e))
}
}
函数的声明
class Counter{
private var value=0
//完整版
def incre1()={value+=1}
def get1():Int={value}
//只有一行可省略{}
def incre2()=value+=1
def get2()=value
//返回值为空可省略()
def incre3=value+=1
def get3=value
//无返回值或为空的返回值可不写=
def incre4{value+=1}
def get4={value}
}
object ClassSetGet {
def main(args: Array[String]): Unit = {
val g1=new Counter();
val g2=new Counter;//无参数可省略()
g1.incre1();
println(g1.get1());
}
}
总结
1.只有一行可省略{}
方法的构造 2.返回值为空可省略()
3.无返回值或为空的返回值可不写=
4.方法声明时无参数可省略()
编译和执行
在命令窗口执行时
vim xxx.scala
编写代码
scalac xxx.scala//编译,
这时会生成几个文件找到main方法的object名字
例如object yyy{main...}
scala yyy //执行
getter和setter
class A_Person{
private var name: String =""
private var age:Int=0
def ageValue=age
def ageValue_=(newAge:Int): Unit ={
age=newAge
}
def nameValue: String =name
def nameValue_=(newName:String): Unit ={
name=newName
}
}
object ClassSetGet {
def main(args: Array[String]): Unit = {
val test=new A_Person()
println(test.ageValue)
test.ageValue=19;
//test.ageValue=10;
println(test.ageValue)
}
}
0
19
//也可写成Java风格
def getAge=age
def setAge(age:Int){
this.age=age
}
//再进行调用
主辅构造器
1.主构造器
class Ma(var name:String,var age:Int){
def getName=name;
def getAge=age
def setName(name:String)={
this.name=name
}
def setAge(age:Int)={
this.age=age
}
}
object MaTest {
def main(args: Array[String]): Unit = {
val t=new Ma("lyb",24)
println(t.name+" "+t.age)
println(t.getName+" "+t.getAge)
t.setAge(18);t.setName("zhangs")
println(t.getName+" "+t.getAge)
}
}
lyb 24
lyb 24
zhangs18
2.多辅构造器
class FuTest{
private var name=""
private var age=0
//一个辅助
def this( name:String, age:Int)={
this()
this.age=age
this.name=name;
}//第二个辅助
def this(name:String){
this()
this.name=name
}
def getName=name;
def getAge=age
def setName(name:String)={
this.name=name
}
def setAge(age:Int)={
this.age=age
}
}
object Fu {
def main(args: Array[String]): Unit = {
val t=new FuTest("lyb",24)
println(t.getName+" "+t.getAge)
val p=new FuTest("zhangfei")
println(p.getName+" "+p.getAge)
t.setAge(18);t.setName("liuyuebin")
println(t.getName+" "+t.getAge)
}
}
lyb 24
zhangfei 0
liuyuebin 18
总结
主构造器:构造器就是整个类体字段的全构造---->class(var 字段:类型){}
只允许一个
辅构造器:相当于Java的构造方法;可以写多个构造字段
可以写多个构造方法
def this(字段:类型){
this()//调用本类的无参构造***必写
//如果不写this.字段就无法调用类中的字段
this.字段=字段
}
伴生与单例对象
单例对象:
Java中单例模式
public class Single {
private Single(){}
private static Single s;
public static Single getIntance(){
if(s==null){
synchronized (Single.class){
if(s==null){
s=new Single();
}
}
}
return s;
}
Scala中就是 object 对象名{}}
伴生对象
//类名与对象名相同
class BanShengObject{
private var name=""
private var id=BanShengObject.id
def this(name:String)={
this()
this.name=name
}
}
object BanShengObject {
private var id=10
def getId() ={
id+=1
id
}
def main(args: Array[String]): Unit = {
val t=new BanShengObject("lyb")
println(t.id)
println(BanShengObject.getId())
println(t.name)
}
}
总结
1.单例对象
object 对象名{}
通过new 类名().(字段/方法)来调用类中的私有字段和方法
2.伴生对象
class 类名{}
object 类名{}
特征:
在类中和对象中通过 对象名.(字段/方法) 来调用对象中的私有字段和方法
要求类和对象在同一个文件中
实际上就实现了Java中静态(static)方法的功能;
Scala源代码编译后都会变成JVM字节码。
实际上,源代码编译后,Scala里面的class和object在Java层面都会被合二为一
class里面的成员成了实例成员,object成员成了static成员。
Apply和UpDate
apply
class ApplyDemo {
def apply() = println("apply method in class is called!")
def greetingOfClass = println("Greeting method in class is called.")
}
object ApplyDemo {
def apply() = { println("apply method in object is called."); new ApplyDemo() }
}
object ApplyTest {
def main(args: Array[String]): Unit = {
val a = ApplyDemo() // 这里调用伴生对象中的apply方法
a.greetingOfClass
a() // 这里调用伴生类中的apply方法
}
}
update
val myArray = Array(1,2,3,4,5,6)
myArray(1)=3;//调用update和
myArray.update(1,3)//等价调用的update
总结
1.apply:用括号传递给变量(对象)一个或多个参数时,Scala 会把它转换成对apply方法的调用
apply分为两种
<1>伴生类中的apply
<2>伴生生对象中的apply
2.update:当对带有括号并包括一到若干参数的对象进行赋值时,编译器将调用对象的update方法,
在调用时,将括号里的参数和等号右边的对象一起作为update方法的输入参数来执行调用
继承和特质(Trait=Java中的interface)
继承–extends
特征:
(1)重写父类的非抽象方法必须写override
(2)只有主构造器可以调用超类的主构造器;
(3)在子类中重写超类的抽象方法时,不需要使用override关键字;
(4)可以重写超类中的字段。
(5)不允许类从多个超类继承。
注意:
(1)定义一个抽象类,需要使用关键字abstract;
(2)定义一个抽象类的抽象方法,不要关键字abstract,要把方法体空着,不写方法体就可以。
def student()不能写def student(){}
(3)抽象类中定义的字段,只要没有给出初始化值,就表示是一个抽象字段。
不要写abstract 写了编译会报错
抽象字段必须要声明类型,比如:val carBrand: String,就把carBrand声明为字符串类型。
不能省略类型,否则编译会报错
特质 trait
与Java中的interface相同,是代码重用的基本单元,可以同时拥有抽象方法和具体方法;
Scala中,一个类只能继承自一个超类,却可以实现实现多个特质,从而重用特质中的方法和字段,实现了多重继承;
trait 名称{}
使用extends关键字混入CarId特质,后面可以反复使用with关键字混入更多特质
class 类名 extends trait1 with trait2 with ....{}
函数式编程
1、样例类
1、样例类是种特殊的类,经过优化多用于模式匹配。
2、用case class进行声明,case class其实有点类似于Java中的JavaBean的概念。即只定义field,并且由Scala编译时自动提供getter和setter方法,但是没有method,自动提供 toString、equals、hashCode和copy,apply,unapply继承了Product和Serializable
3、 case class的主构造函数接收的参数通常不需要使用var或val修饰,Scala自动就会使用val修饰(但是如果你自己使用var修饰,那么还是会按照var来)
4、 Scala自动为case class.定义了伴生对象,也就是object,并且定义了apply()方法,该方法接收主构造函数中相同的参数,并返回case class对象
case类在模式匹配和actor中经常使用到,当一个类被定义成为case类后,Scala会自动帮你创建一个伴生对象并帮你实现了一系列方法
案例
1.实现了apply方法,意味着不需要使用new关键字就能创建该类对象
scala> case class People(name:String,age:Int)
scala> val p = People(“lyb”,28) //省略了new关键字
p: People = People(lyb,28)
2.实现了unapply方法,可以通过模式匹配来获取类属性,是scala中抽取器的实现和模式匹配的关键方法。
3.实现类构造参数的getter方法(构造方法默认被声明为val),但当构造参数声明为var类型时,将会实现setter和getter方法(不建议将构造参数声明为var)
scala> p.name
res0: String = luge
scala> p.name = “hezong” //报错,因为构造参数被声明为val所以并没有帮你实现setter方法
< console >:10: error: reassignment to val
p.name = "hezong
4.默认实现了toString,equals,copy和hashCode等方法
5.通过反编译产看定义一个case类时编译器的结果
case class Person(name:String ,age :Int)
通过终端编译该文件后生成两个class文件,Person.class和Person$.class
通过javap 命令产看反编译文件
javap -private类名
case本就旨在创建的是不可变数据,所以在使用模式匹配时显得极为容易
2、模式匹配
1.Scala是没有Java中的switch case语法的,相对应的,Scala提供了更加强大的match case语法,即模式匹配,类替代switch case—>match case也被称为模式匹配
2.Scala的match case与Java的switch case最大的不同点在于,Java的switch case仅能匹配变量的值,比1、2、3等;而Scala的match case可以匹配各种情况,比如变量的类型、集合的元素、有值或无值
3.match case的语法如下:变量 match { case 值[:类型]=> 代码 }。如果值为下划线,则代表了不满足以上所有情况下的默认情况如何处理。此外,match case中,只要一个case分支满足并处理了,就不会继续判断下一个case分支了。(与Java不同,java的switch case需要用break阻止)
4.match case语法最基本的应用,就是对变量的值进行模式匹配
常量匹配
如果匹配成功,则执行 => 后面的代码
匹配的顺序是从上到下,匹配到一个就行执行对应的代码
=> 后面的代码块,不需要些break,会自动退出match
如果一个都没有匹配到,则执行case _后面的代码块
oper match {
case ‘+’ => res = n1 + n2
case ‘-’ => res = n1 - n2
case ‘*’ => n1 * n2
case ‘/’ => n1 / n2
//if后面称为条件守卫
case _ if(oper == ‘o’) => println(“oper is o”)
case _ => println(“oper error”)//通用匹配
}
类型匹配
object Test {
def main(args: Array[String]): Unit = {
for(elem<- List(1.3,2,“spark”,'Haoop)){
val str=elem match{
case i:Int => i + " is an int value"
case d:Double => d + " is an double value"
case “spark” => “spark is found”
case s:String => s + " is an string value"
case _ => “This is an unexpected value”
}
println(str)
}
}
}
Array匹配
object Test {
def main(args: Array[String]): Unit = {
val arr=Array(8,3,1)
def arrayMatch(arr: Any) = arr match {
case Array(0) => println(“只有一个0元素的数组”)
case Array(0, _) => println(“以0开头的,拥有2个元素的数组”)
case Array(0, _, 3) => println(“以1开头,3结尾,中间为任意元素的三个元素的数组”)
case Array(8, *) => println(“以8开头的,任意个元素的数组”)
case Array(, _) => println(“数组种有两个元素”)
//匹配数组种有三个元素,将数组中的元素赋值给x,y,z然后我们就可以做顺序的变换了
case Array(x, y, z) => Array(z, x, y)
}
arrayMatch(arr)
}
}
List集合匹配与Array匹配类似
模式匹配
class Person
case class Teacher(name:String,subject:String) extends Person
case class Student(name:String,classroom:Int) extends Person
case class Worker(name:String,work:String) extends Person
case class Stranger() extends Person
object test{
def main(args: Array[String]): Unit = {
def entranceGuard(p:Person): Unit ={
p match {
case Student(name,classroom)=>println(s"hello,$name,welcome to school,your classroom is
c
l
a
s
s
r
o
o
m
"
)
c
a
s
e
T
e
a
c
h
e
r
(
n
a
m
e
,
s
u
b
j
e
c
t
)
=
>
p
r
i
n
t
l
n
(
s
"
h
e
l
l
o
,
classroom") case Teacher(name,subject)=>println(s"hello,
classroom")caseTeacher(name,subject)=>println(s"hello,name,welcome to school,your teach
s
u
b
j
e
c
t
"
)
c
a
s
e
W
o
r
k
e
r
(
n
a
m
e
,
w
o
r
k
)
=
>
p
r
i
n
t
l
n
(
s
"
h
e
l
l
o
,
subject") case Worker(name,work) =>println(s"hello,
subject")caseWorker(name,work)=>println(s"hello,name,you should leave school afternoon")
case Worker(name,work)=>println(s"hello,$name,you should leave school 2 hours later")
case _=>println(s"stranger,you can not into school")
}
}
entranceGuard(Worker(“luge”,“runner”))
}
}
Option类型
Scala 为可选值定义了一个Option的标准类型。这种类型只有两种形式。一种是用样例子类Some包装起来的——Some(x)形式,其中x是实际值,另一种是样例对象None,代表缺省的值或者说没有值。
Option支持泛型
Scala集合类的某些标准操作会产生可选值。比如Map类的get方法返回一个Option,如果对于给定的键有对应的值,就会把值包装到Some中返回,否则就返回None
总结
Option类型在Scala中经常用到。与之相较,在Java中最常用的是代表没有值的null。例如java.util.HashMap的get方法要么返回储存参HashMap中的值,要么返回null。这种方式对Java起效,不过可能会隐藏错误,因为很难在实际记住程序中那个变量可以为null。如果变量允许为null,那么在每次使用时都必须检查是否为null。一旦忘记检查,就很难避免运行时发生的NullPointerException异常。又因为这种异常不是经常发生,所以想要通过测试发现是很困难的。对于Scala来说,这种方式根本不起作用,因为可以在哈希映射中存储值类型,而null不是值类型的合法元素。也就是说,HashMap[Int, Int] 不能返回null来表明没有元素。
Scala鼓励对Option的使用说明值是可选的。这种处理可选值的方式有若干超越Java的优点。首先,对于代码读者来说,Option[String] 类型的变量是可选的String,这比String类型的变量或可能有时是null来说要更为明显。但最重要的是,之前描述的因为使用可能为null而没有首先检查是否为null的变量产生的编程错误在Scala中就变成了类型错误。如果变量是Option[String]类型,而你把它当作String类型使用,是无法通过编译的。
object CaseOps {
def main(args: Array[String]): Unit = {
caseOps
}
def caseOps: Unit = {
def optionOps: Unit = {
val capitals = Map("France" -> "Paris", "Japan" -> "Tokyo", "BeiJing" -> "通州")
println(capitals.get("Japan") + show(capitals.get("Japan")))
println(capitals.get("BeiJing") + show(capitals.get("India")))
}
def show(x: Option[String]) = x match {
case Some(s) => s
case None => "?"
}
optionOps
}
}
Option[T]实际上就是一个容器,可以把它看做是一个集合,只不过这个集合中要么只包含一个元素(被包装在Some中返回),要么就不存在元素(返回None);
既然是一个集合,当然可以对它使用map、foreach或者filter等方法;
foreach遍历遇到None的时候,什么也不做,不会执行println操作。
getOrElse()方法
如果some指定了一个数,那从这里面取getOrElse方法结果都是该指定的数,若Option为空,则取出的getOrElse值是后面的指定的值,与option无关。就是一个默认的缺省值一样。
object Test5 {
def main(args: Array[String]): Unit = {
var map=Map[Int,String]()
map+=(1->"one",2->"two")
println(map.getOrElse(1,"default"))
println(map.getOrElse(2,"default"))
println(map.getOrElse(3,"default"))
}
}
函数
1、函数字面量
理解:函数的“值”,就是“函数字面量”。
函数的类型和值
传统定义函数格式:
def func(value: Int) :Int = {value += 1} //函数中有int型参数value,函数返回类型为Int
上面定义的函数类型可表示为: (Int) => Int,也就是输入参数为Int类型,返回值Int类型,若只有输入参数,可以省略括号,即Int => Int,这样就是体现了函数的“类型”。
那么对于函数的值,可以将函数中定义的类型声明部分去掉,剩下的就是函数的“值”:
(value) => (value += 1) ,注意这个值需要使用 => 表示,而不是=
有了上述概念,我们就能清楚地明白函数的类型和值的关系,并能够将其剥离出来。
一般我们声明变量时:
val a : Int = 5 //其中Int是类型,5是值
那么同样可以声明Scala中的函数:
val b : Int => Int = { (value) => value+=1 } //其中Int => Int是类型,{}中的东西是函数的值,此时b就是一个函数类型了
2、匿名函数、Lambda表达式
匿名函数用于快速完成一个函数的定义。
例如:(num : Int) => {num * 2} 这种形式的匿名函数也称作Lambda表达式。其基本格式是:
(参数) => 表达式 //若参数只有一个则括号可省略
定义好的匿名函数可以直接将其赋给变量,后面可以直接拿来使用:
val func1 = Int => Int = (num : Int) => num*2 //将匿名函数作为Int => Int函数的值
println(fun1(3)) //可以直接用变量来使用这个匿名函数
Scala具有类型推断功能,那么定义的函数也可以像定义变量那样在合适的时候省去类型的指定:
val func1 = Int => Int = {(num:Int) => num2}
等价于:
val func2 = {(num:Int) => num2}
3、占位符
为了让函数字面量更加简洁,可以使用下划线作为一个或多个参数的占位符,只要每个参数在函数字面量内仅出现一次。
val list1 = List(1,2,3,4,5)
list1.filter(x => x >3) //lambda表达式
list1.filter(_ > 3) //这种写法与上面的写法是一样的
使用把下划线当作是参数的占位符,但是编译器可能并不认识,例如:
val f = + 运行时会报错,因为编译器推断不出来类型。
应为占位符参数指定类型: val f = (:Int) + (:Int)
4、高阶函数
1. 概念
Scala 混合了面向对象和函数式的特性,我们通常将可以作为参数传递到方法中的表达式叫做函数。在函数式编程语言中,函数是“头等公民”,高阶函数包含:作为值的函数、匿名函数、闭包、柯里化等等。
2. 作为值的函数
可以像任何其他数据类型一样被传递和操作的函数,每当你想要给算法传入具体动作时这个特性就会变得非常有用。
定义函数时格式:val 变量名 = (输入参数类型和个数) => 函数实现和返回值类型
“=”表示将函数赋给一个变量
“=>”左面表示输入参数名称、类型和个数,右边表示函数的实现和返回值类型
3. 匿名函数
在 Scala 中,你不需要给每一个函数命名,没有将函数赋给变量的函数叫做匿名函数
用占位符
4. 柯里化
4.1 什么是柯里化
柯里化(Currying)指的是把原来接受多个参数的函数变换成接受一个参数的函数过程,并且返回接受余下的参数且返回结果为一个新函数的技术。
4.2 案例
普通函数定义
scala> def a(x:Int,y:Int)=x+y
a: (x: Int, y: Int)Int
scala> a(1,2)
res0: Int = 3
使用“柯里化”技术来定义这个加法函数,原来函数使用一个参数列表,“柯里化”,把函数定义为多个参数列表:
第一种定义方式:
scala> def b(x:Int)(y:Int)=x+y
b: (x: Int)(y: Int)Int
scala> b(1)(2)
res1: Int = 3
当调用 b (1)(2)时,实际上是依次调用两个普通函数(非柯里化函数),
第一次调用使用一个参数 x,返回一个函数类型的值,
第二次使用参数 y 调用这个函数类型的值。
第二种定义方式:
首先定义第一个函数:
scala> def first(x:Int)=(y:Int)=>x+y
first: (x: Int)Int => Int
然后使用参数 1 调用这个函数来生成第二个函数:
scala> val second =first(1)_
second: Int => Int =
scala> second(2)
res2: Int = 3
5、偏函数
- 偏函数
偏函数(Partial Function),是一个数学概念它不是"函数"的一种, 它跟函数是平行的概念。
Scala中的Partia Function是一个Trait,其的类型为PartialFunction[A,B],其中接收一个类型为A的参数,返回一个类型为B的结果。
在Scala通常使用偏函数进行类型转换,拆解。可以很容易的把tuple拆解成key和value。(常用、重点)
val a = List((“hadoop”, 10), (“HBase”, 5), (“Hive”, 5), (“kafka”, 1), (“flume”, 1))
a.map{case (key, value) => value}
偏函数 与 全函数(其概念来自于数学)
偏函数,只接受和处理其参数定义域范围内的子集,对于这个参数范围外的参数则抛出异常;
def fraction(d: Int) = 42 / d
val fraction: PartialFunction[Int, Int] =
{case d: Int if d != 0 => 42 / d}
fraction(0) // 都报错,错误类型不一样
集合 (可变与不可变)
1.分类
Traversable-->Iterable-->
Seq(序列)
IndexedSeq
LinearSeq
Set(集)
SortedSet
BitSet
Map(映射)
SortMap
2.List
<1>列表和数组的不同
(1)列表是不可变的。即列表的元素不能通过赋值改变。
(2)列表的结构是递归的,而数组是平的;
(3)数组存放的是相同类型的;列表可以存放的不同类型
(4)列表没有长度限制,遍历时用迭代(数组是for)
<2> 操作
1.获取
val list=List("a",1,"22")
val b=list.tail.tail
-->b: List[Any] = List(22)
list.head 获取第一个元素
list.tail 获取除了第一个的所有元素
2.增加(:: )
val mylist=5::list
//是把5增加到已有的列表前端获取新的列表
//列表不变 但val mylist = list::5
//是不对的 最后只能是列表
val a=1::2::3::4::Nil
//Nil代表空列表
3.拼接 (::: )
val list1=List(1,2,3)
val list2=List(4,5,6)
val list=list1:::list2 //list1在前
4.求和
val b=list1.sum //求列表中的和
底层实现使用递归
def sum (list:List[Int]):Int={
//1
if (list==Nil) 0 else list.head+sum(list.tail)
//2
case Nil =>0
case head::tail =>head +sum(tail)
}
5.flatten和flatMap
flatten 压实:把嵌套在列表中的结构展开
List(List(1,2,3,4),List(a,b,List(c,d))).flatten
-->List(1,2,3,4,a,b,List(c,d))//只能展开一层
flatMap=flatten+map
List(List(1,2,3,4),List(3,4)).flatMap(_.map(_*2))
List(List(1,2,3,4),List(3,4)).flatten.map(_*2)
//注意最多两层List否则报错
6.GroupBy(条件) 分组
(条件:一般为元组下标)
List((1,2),(1,3),(2,3),(3,3),(3,4)).groupBy(_._1)
//根据第一个元素分组
7.reduce (_ + _)
包含reduceLeft(_+_)//默认
包含reduceRight(_+_)//从右往左
8.fold()
//操作和reduce类似但其需要一个初始的种子值开始
val a=List(1,2,3,4)
a.fold(10)(_*_)
-->10*1*2*3*4=240
a.fold(10)((x,y)=>{println(s"x=$x,y=$y");x*y})
//柯理化
foldLeft()
foldRight()
9.遍历(foreach map)
val a=List(1,2,3,4)
a.foreach(println)
a.foreach(println _)
a.foreach(ele=>print(ele+" "))
a.map(_>2) //返回的是boolean数组
a.map(_*2) //换回一个新数组
foreach是遍历
map是一个集合->另一个集合
10.filter(条件) 过滤选择
val a=List(1,2,3,4)
a.filter(_%2==0)
a.filter(_.startWith("?"))
a.filter(关于集合的条件)
3.Set
<1>定义
集合包括可变集和不可变集,
scala.collection.mutable包
scala.collection.immutable包,
缺省情况下创建的是不可变;
<2>操作
1.移除可变
val set=Set(1,2,3,4)
set.remove(1)
val a=Set(1,2,3,4,5)
val b=Set(3,4,5,6,7)
2.交集 (& instersect )
a&b
a.instersect(b)
a instersect b
3.并集 (++ | union)
a++b
a|b
a.union(b) a union b
4.差集 (–、 &~ 、diff)
a--b
a&~b
a.diff(b)
4.Seq
1.定义
具有一定长度的可迭代访问的对象
Vector是ArrayBuffer的不可变版本,可通过下标快速的随机访问
在Java中vector支持线程安全但效率低,一般用ArrayList
2.常用类型
String、List、Queue、Stack
val a=(1 to 10) = Range(1,10)
val a=(1 to 10 by 2) = Range(1,10,2)
3.操作
(1)迭代器 Iterator
Next(返回下一个元素)
hasNext(是否有下一个)
for和迭代器的区别
for会暴露数据的组织信息,组织结构,数据顺序
迭代器Next(返回下一个元素)hasNext(是否有下一个)
(2)List操作见上面
函数式编程
1.隐函数
1.1 定义:
implicit def 函数名(x:输入类型)={x.to输出类型}
implicit def pp(x:Double)=x.toInt
val x:Int=3.14
x:Int =3
//这个定义不可取容易语义混淆并且与自带的冲突
Scala会考虑如下的隐式转换规则:
1、位于源或目标类型的伴生对象中的隐式函数
2、位于当前作用域可以以单个标示符指代的隐式函数
import java.io.File
import scala.io.Source
//隐式的增强File类的方法
class RichFile(val commonFile: File) {
def read = Source.fromFile(commonFile.getPath).mkString
}
object RichFile {
//隐式转换方法,功能是提供“低级类型”到“增强类型”的转换
implicit def file2RichFile(commonFile: File) = new RichFile(commonFile)
}
object MainApp{
def main(args: Array[String]): Unit = {
//导入隐式转换
import RichFile._
//低级类型自动具有了增强类型的方法
println(new File("c://words.txt").read)
}
}
//先将 File 转换为 RichFile,再调用 RichFile.read 方法
1.2 隐式参数
定义:
隐式参数有点类似缺省参数,如果在调用方法时没有提供某个参数,
编译器会在当前作用域查找是否有符合条件的 implicit 对象可以作为参数传入;
不同于缺省参数,隐式参数的值可以在方法调用前的上下文中指定,这使隐式参数更加灵活;
函数或方法可以带有一个标记为implicit的参数列表。这种情况下,编译器将会查找缺省值,提供给该函数或方法。
注意:
1)当函数没有柯里化时,implicit关键字会作用于函数列表中的的所有参数;
2)隐式参数使用时要么全部不指定,要么全不指定,不能只指定部分;
3)同类型的隐式值只能在作用域内出现一次,即不能在同一个作用域中定义多个相同类型的隐式值;
4)在指定隐式参数时,implicit 关键字只能出现在参数开头;
5)如果想要实现参数的部分隐式参数,只能使用函数的柯里化;
如要实现这种形式的函数:
def test(x:Int, implicit y: Double),必须使用柯里化实现:
def test(x: Int)(implicit y: Double)
6) 柯里化的函数, implicit 关键字只能作用于最后一个参数。否则,不合法;
7)implicit 关键字在隐式参数中只能出现一次,柯里化的函数也不例外;
8)匿名函数不能使用隐式参数;
9)柯里化的函数如果有隐式参数,则不能使用其偏应用函数;
object context{
implicit val aa = "guanyu" // 隐式值
}
object ImplicitDemo {
// 隐式参数,给定了缺省值
def say (implicit name:String = "zhangfei"): Unit = println(s"hello $name")
def main(args: Array[String]): Unit = {
// 不使用隐式参数
say
say("liubei")
import context._
say // 使用隐式参数,import语句导入了一个隐式参数
}
}
hello zhangfei
hello liubei
hello guanyu
1.3 比较器
Comparable可以认为是一个内比较器,实现了Comparable接口的类有一个特点,就是这些类是可以和自己比较的,至于具体和另一个实现了Comparable接口的类如何比较,则依赖 compareTo方法的实现,compareTo方法也被称为自然比较方法。
如果开发者add进入一个Collection的对象想要Collections的sort方法帮你自动进行排序的话,
那么这个对象必须实现Comparable接口。compareTo方法的返回值是int,有三种情况:
1、比较者大于被比较者(也就是compareTo方法里面的对象),那么返回正整数
2、比较者等于被比较者,那么返回0
3、比较者小于被比较者,那么返回负整数
private static class Person implements Comparable<Person>{
int age;
String name;
...
/**
* @desc 实现 “Comparable<String>” 的接口,即重写compareTo<T t>函数。
* 这里是通过“person的名字”进行比较的
*/
@Override
public int compareTo(Person person) {
return name.compareTo(person.name);
//return this.name - person.name;
}
Comparator可以认为是是一个外比较器,个人认为有两种情况可以使用实现Comparator接口的方式:
1、一个对象不支持自己和自己比较(没有实现Comparable接口),但是又想对两个对象进行比较。
2、一个对象实现了Comparable接口,但是开发者认为compareTo方法中的比较方式并不是自己想要的那种比较方式。
Comparator接口里面有一个compare方法,方法有两个参数T o1和T o2,是泛型的表示方式,分别表示待比较的两个对象,方法返回值和Comparable接口一样是int,有三种情况:
1、o1大于o2,返回正整数
2、o1等于o2,返回0
3、o1小于o3,返回负整数
private static class AscAgeComparator implements Comparator<Person> {
@Override
//升序
public int compare(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
}
Ordered 混入(mix)Java的Comparable接口
Ordering 则混入Comparator接口
实现Comparable接口的类,其对象具有了可比较性;自己比较
实现comparator接口的类,则提供一个外部比较器,用于比较两个对象。
Comparable是排序接口。若一个类实现了Comparable接口,就意味着该类支持排序。实现了Comparable接口的类的对象的列表或数组可以通过Collections.sort或Arrays.sort进行自动排序。
Comparator是比较接口。通过实现Comparator来新建一个比较器,然后通过这个比较器对类进行排序。
Ordered与Ordering的区别与之相类似:
Ordering特质定义了相同类型间的比较方式,但这种内部比较方式是单一的
Ordered则是提供比较器模板,可以自定义多种比较方式
定义compare方法,Ordered特质会利用这个方法定义<、>、<=、>=。从而,Ordered特质让你可以通过仅仅实现一个compare方法,使类具有了全套的比较方法:
case class Person1(name: String)
case class Person2(name: String) extends Ordered[Person2] {
def compare(other: Person2) = name compare other.name
}
val p1 = Person1("Tom")
val p2 = Person1("Bob")
val p3 = Person2("Tom")
val p4 = Person2("Bob")
// p1、p1 不能比较,因为没有混入Ordered接口
p1 < p2
p3 < p4
// 有两种等价的定义方式(数字类型有减法,字符串没有减法操作)
case class Person2(age: Int) extends Ordered[Person2] {
def compare(other: Person2) = age compare other.age }
case class Person2(age: Int) extends Ordered[Person2] {
def compare(other: Person2) = this.age - other.age }
Sort/SortBy/SortWith
单集合
val xs = Seq(1, 5, 3, 4, 6, 2)
xs.sorted // 升序
xs.sortBy(d=>d)
xs.sortWith(_<_)
xs.sorted.reverse // 降序
xs.sortBy(d=>d).reverse
xs.sortWith(_>_)
类的排序
排序规则:按年龄升序;按名称降序
case class Person(name:String, age:Int)
val p1 = Person("brid", 100)
val p2 = Person("dog", 23)
val p3 = Person("pig", 25)
val p4 = Person("cat", 23)
val pairs = Array(p1, p2, p3, p4)
// 排序方法一
// 年龄升序,名称降序
pairs.sortBy(person => (person.age, person.name)) (Ordering.Tuple2(Ordering.Int, Ordering.String.reverse))
// 年龄升序,名称升序
pairs.sortBy(person => (person.age, person.name))
case class Person(name:String, age:Int) extends Ordered[Person]{
override def compare(other : Person): Int = {
if (age - other.age != 0) age - other.age
else other.name.compare(name)
}
}
val p1 = Person("bird", 100)
val p2 = Person("dog", 23)
val p3 = Person("pig", 25)
val p4 = Person("cat", 23)
val pairs = Array(p1, p2, p3, p4)
pairs.sorted
总结:
ordered-->comparable
ordering-->comparator
排序定义规则
case class ClassName(变量1:类型,变量2:类型):返回值类型={
// def compare(other:ClassName)=this.变量1-other.变量1
// def compare(other:ClassName)=变量1.compare(other.变量1)
override def compare(other:ClassName):返回值类型={
if(this.变量1-other.变量1 !=0 )
this.变量1-other.变量1
}else
other.变量1.compare(this.变量1)
}
2.递归和尾递归
val l=List(1,2,1,3)
l.sum //递归的实现
def sum(x:List[Int]):Int={
if(x.isEmpty)或者if(x==Nil)
0
else
x.head+sum(x.tail)
}
递归算法需要保持调用堆栈,效率较低,如果调用次数较多,会耗尽内存或栈溢出。然而,尾递归可以克服这一缺点。
尾递归是指递归调用是函数的最后一个语句,而且其结果被直接返回,这是一类特殊的递归调用。
由于递归结果总是直接返回,尾递归比较方便转换
为循环,因此编译器容易对它进行优化。
3.函数式编程 — — 特性
1.immutable data 不可变数据:默认变量是不可变的,
如果要改变变量,需要把变量copy出去修改。这样一来,
可以让程序少很多Bug。因为,程序中的状态不好维护,
在并发的时候更不好维护。(如果程序有个复杂的状态,
当以后别人改代码的时候,是很容易出bug,在并行中
这样的问题就更多了)
2.first class functions 函数是一等公民:
可以让函数像变量一样来使用。也就是说,函数可以像变量
一样被创建,修改,并当成变量一样传递,返回或是在函数
中嵌套函数。
3.尾递归优化:
如果递归很深的话,stack受不了,并会导致性能大幅度下降。
使用尾递归优化技术——每次递归时都会重用stack,这样能够提
升性能,需要语言或编译器的支持。
4. 函数式编程 — — 相关技术
1.少用循环和遍历,多使用 map 、 reduce等函数。
函数式编程最常见的技术就是对一个集合做Map和Reduce操作。这比
起过程式的语言来说,在代码上要更容易阅读。(传统过程式的语言
需要使用for/while循环,然后在各种变量中把数据倒过来倒过去的)
2.递归(recursing)
递归最大的好处就简化代码,把一个复杂的问题用很简单的代码描述出来。
归的精髓是描述问题,而这正是函数式编程的精髓
3.柯里化(currying)
把一个函数的多个参数分解成多个函数,然后把函数多层封装起来,每层
函数都返回一个函数去接收下一个参数这样,可以简化函数的多个参数
f(x:Int,y:Int)===>>f(x:Int)(y:Int)
={函数体}
4.高阶函数(higher order function)
所谓高阶函数就是函数当参数,
把传入的函数做一个封装,然后返回这个封装函数。现象上就是函数传进
传出,就像面向对象满天飞一样
xx.map(_.map(_*2))
5.管道(pipeline)
把函数实例成一个一个的action,然后,把一组
action放到一个数组或是列表中,然后把数据传给这个action list,数
据就像一个pipeline一样顺序地被各个函数所操作,最终得到我们想要的结果
5.函数和方法的区别
1.不同点
(1)Scala 中的方法与 Java 的类似,方法是组成类的一部分;
(2)Scala 中的函数则是一个完整的对象。
Scala 中用 22 个特质(trait)抽象出了函数的概念,
这 22 特质从 Function1 到 Function22;
a = (x: Int, y: Int) => x+y
a:(Int, Int) => Int = <function2> //两个参数function2
(3)Scala 中通常用 val 语句定义函数,def 语句定义方法;
def 函数名(参数):返回值类型={函数体}
val 函数名=(变量2:类型,变量3:类型)=>{函数体:x+y}
val 函数名:(输入类型,类型)=>返回值类型=(输入值)=>{函数体}
(4)方法不能作为单独的表达式存在(参数为空的方法除外)而函数可以:
def m1(x: Int) = x + x
val f1 = (x: Int) => x + x
m1 // 报错,方法不能单独存在
f1
val a = m1 // 报错
val a = m1 _ // 将方法转换为函数
(5)函数必须要有参数列表,而方法可以没有参数列表
def m1 = 100
def m2() = 100
def f1 = () => 100
val f4 = => 100 // 报错,函数必须要参数列表
2.定义
函数:
def 函数名(参数):返回值类型={函数体}
方法
val 函数名=(变量2:类型,变量3:类型)=>{函数体:x+y}
val 函数名:(输入类型,类型)=>返回值类型=(输入值)=>{函数体}
// 定义了一个函数,下面三个方法等价。其中方法二最常见
val adder1: (Int, Int) => Int = (x, y) => x+y
val adder2 = (x: Int, y: Int) => x+y
val adder3 = new Function2[Int, Int, Int]{
def apply(x: Int, y: Int): Int = {
x + y
}
}
6.下划线
(1)作用
1. 导入通配符
在Scala中是合法的方法名,导入包时要使用_代替。
Java import Java.util.*
scala import scala.util._
2. 类成员默认值
Java中类成员可以不赋初始值,编译器会自动帮你设置一个合适的初始值:
class f{
String s;
}
Scala中必须要显式指定,可以用_让编译器自动帮你设置初始值:
class f{
//默认为null
var s:String=_
}
该语法只适用于类成员,而不适用于局部变量。
3.可变参数
val list=List(1,2,3,4)
list.map(_+2)
4.通配通配符
def list(lis:List[_])=lis.foreach(elem=>println(elem))
_可为任意类型
(2)模式匹配
1.默认匹配
val list = List(12,21,21)
for(elem<-list)
val a=elem match{
case elem%2==0 => println(elem)
case _ =>println("xxx")
}
2.匹配集合元素
_* :任意长度的元素
// 匹配以0开头,长度为三的列表
val expr = List(0,1,2)
expr match {
case List(0, _, _) => println("found it")
case _ =>
}
// 匹配以0开头,长度任意的列表
val expr = List(0,1,2,3,4)
expr match {
case List(0, _*) => println("found it")
case _ => println("no found")
}
// 匹配元组元素
val expr = (0,1)
expr match {
case (0, _) => println("found it")
case _ => println("no found")
}
// 将首元素赋值给head变量
val List(head, _*) = List("a", "b", "c", "d")
3.Scala特有语法
1.访问Tuple元素
val t = (1, 2, 3) = val t=Tuple(1,2,3)
println(t._1, t._2, t._3)
2.简写函数字面值
如果函数的参数在函数体内只出现一次,则可以使用下划线代替:
val f1 = (_: Int) + (_: Int)
//等价于
val f2 = (x: Int, y: Int) => x + y
list.foreach(println(_))
//等价于
list.foreach(e => println(e))
list.filter(_ > 0)
//等价于
list.filter(x => x > 0)
3.定义一元操作符 (1+1-->二元操作符)
-2
//等价于
//unary左置操作符
2.unary_-
4.定义赋值操作符
我们通过下划线实现赋值操作符,从而可以精确地控制赋值过程:
class Foo {
def name = { "foo" }
def name_=(str: String) {
println("set name " + str)
}
}
val m = new Foo()
m.name = "Foo" //等价于: m.name_=("Foo")
5.定义部分应用函数
def sum(a: Int, b: Int, c: Int) = a + b + c
val b = sum(1, _: Int, 3)
// b: Int => Int = <function1>
b(2)
//6
可以为某个函数只提供部分参数进行调用,
返回的结果是一个新的函数,即部分应用函数。
因为只提供了部分参数,所以部分应用函数也因此而得名。
6.将方法转换为函数
def m1(x:Int)={x+x}
val p=m1 _
p(2)-->4
7.Type
通过type关键字来声明类型;
经常被用来作为复杂类型的别名,以提供可读性;
type相当于声明一个类型别名:
type x=类型
val c:x=值