Scala现在已经是全球前二十大开发语言了,并且因为spark的关系,scala在大数据方面的应用场景越来越多,基于scala的spark自定义函数操作以及sparkSQL等已经是很多大公司的开发应用场景,例如平安的陆金所现在就是主打scala+spark的开发应用。
Scala的应用会越来越广泛。
所以现在我们准备开辟基于Scala的教程学习。
我们先在服务器上安装一个scala的语言环境。
打开方式:我们通过scala命令或者spark-shell进入到scala的命令行模式,你也可以在windows电脑上安装scala并且通过编辑器进入都可以,比较常见的scala编辑器有eclipse、ENSIME、IntelliJIDEA、NetBeans等。
我们先从scala的基本语法开始着手进行学习:
一、数据类型
scala是一种强类型语言,强类型是什么意思呢?就是数据类型的定义和声明在变量以及赋值的过程中是必要的和重要的。
例如现在我要把一杯水倒进杯子里,我必须要先说好,这个杯子是保温杯还是茶杯还是马克杯,不同的杯子就是不同的数据类型。
在scala里面,数据类型是特别丰富的:
数据类型 |
描述 |
Byte |
8位有符号补码整数。数值区间为 -128 到 127 |
Short |
16位有符号补码整数。数值区间为 -32768 到 32767 |
Int |
32位有符号补码整数。数值区间为 -2147483648 到 2147483647 |
Long |
64位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807 |
Float |
32 位, IEEE 754 标准的单精度浮点数 |
Double |
64 位 IEEE 754 标准的双精度浮点数 |
Char |
16位无符号Unicode字符, 区间值为 U+0000 到 U+FFFF |
String |
字符序列 |
Boolean |
true或false |
Unit |
表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。 |
Null |
null 或空引用 |
Nothing |
Nothing类型在Scala的类层级的最底端;它是任何其他类型的子类型。 |
Any |
Any是所有其他类的超类 |
AnyRef |
AnyRef类是Scala里所有引用类(reference class)的基类 |
但是一般情况下,用的比较多的,也就是整数类型Int,浮点数类型Float,字符串类型String,字符类型Char,布尔类型Boolean和空值类型Null
我们可以直接在scala的命令行中看到这些数据类型:你输入任意一个值,scala就会显示这个值对应的数据类型给你看。
这里要注意的是,1.7这样的小数,默认是使用double的双精度浮点类型来保存的,如果想要保存成单精度的float类型,需要写成1,7f,double类型整数+小数一共是17位长度,float类型整数+小数是8位长度。
并且在字符串中,\ 也同样是转义符的意思。
这里比较有意思的就是Nothing和Any这两种类型,我们到讲赋值的时候再来看看这两种类型。
二、变量和赋值
如果想要把上面的某个值,存放起来,那么这里就需要使用到变量的概念,就好像现在我这里有一条河,我想要把河里面的水装起来,那么装水的容器就是变量。
2.1 声明变量以及类型
在scala中定义变量,有两个不同的关键字,一个是val,一个是var,我们分别来看看怎么使用,以及它们有什么区别。
我们可以用
val 变量名:数据类型=值
或者
var 变量名:数据类型=值
这两种不同的方式来对变量进行声明和定义:
那现在如果我想修改变量中已经保存好的内容怎么办呢?
我只要写上 变量名=新的值 就可以了
但是如果我们也对ename这个变量进行同样的操作,就会发现代码发生了错误,这是因为age是用var来声明的,而ename是用val来声明的,val声明的变量,不可以再重新赋予新的内容。
所以,在代码中,定义变量用var,不可变的常量用val。
2.2 使用类型推断来定义变量
我们在定义类型的时候,因为之前 var age:Int=18 的这种语法,和其他代码的编写方式都不一样,所以很多人用起来并不习惯,那么我们也可以使用类型推断的方式来定义变量,也就是让scala自已去判断变量属于哪种类型:var 变量名=值
前面我们有提到Nothing和Any,分别来举个例子看看:
这里我声明了一个List数组,但是数组的括号里面没有任何东西,所有scala这个时候它就没有办法帮我推断出类型具体是什么,于是它使用了Nothing来表示。
第二个例子:在我的List数组中,括号里面又有整数又有字符串之类的,类型不只一种,所以scala就用了Any来进行表示,意思是里面的类型多于一种以上。
2.3 使用惰性赋值的方式提高操作效率
有的时候我们需要用变量保存一些数据很大的内容,但是这些内容不需要马上被加载到JVM内存里面,我们就可以用到惰性赋值,但是惰性赋值的变量,只能使用val来声明。
2.4 字符串的拼接:插值表达式
当一个字符串的内容,需要用到其他变量的内容时,我们可以使用插值表达式来完成,这个就比较像是python中的字符串格式化的方法,和shell脚本中变量的引用也差不多。
插值表达式的语法是:s"${变量名}"
我们需要在双引号前面加上s,来表示我们要读取字符串中变量的内容,然后使用${}对变量的内容进行读取和引用。
三、开始写一小段正儿八经的scala
使用某个编辑器来对scala进行编辑,我这里为了方便,使用的是 notepad++
3.1 创建 object 对象和 main 的主函数入口
在文本中,我们编辑出下文,保存为 s1.scala:
然后复制到命令窗口中,使用 scalac s1.scala 对这个文件进行编译,编译出对应的字节码文件
.scala 文件是我们写代码的地方,我们先把代码文件翻译成对机器友好的内容,这个就是 .class 的字节码文件了。
使用 scala MySum 的命令,运行字节码文件,就可以得到运行之后的结果了,这个 MySum 就是 object 对象的名字。
当然了,也可以直接使用 scala s1.scala 运行程序。
我们回到上面这个代码上面来,拆解一下这个代码
object MySum{ //object在这里相当于一个程序的入口
def main(args:Array[String]){ //def是定义,main是所有程序运行的起点
var a=10 //从这里开始都是main方法的内容
var b=20
println(a+b) //对变量的和进行打印
}
}
3.2 创建一个 class 类
现在我们对这个代码再重新改造一下,并且看看改造之后的结果是什么
可以看到这个程序打印了两行信息,这两行信息是怎么出来的呢?我们来一句句的分析下代码:
//class在代码中是类的意思,这里是准备要定义一个叫做Person的类型,这个类型在查看和使用的时候需要往里面输入字符串的xname和整数的xage两个值,我们把括号里面的xname和xage叫做参数。
class Person(xname:String,xage:Int){
//name和age是Person这个类里面的两个变量,我们把它们称为类里面的属性,并且分别给这两个属性定义自己的值
val name="hello "+xname+"!"
val age=xage
}
//定义程序的入口
object ClassAndObject{
//定义程序开始的起点,args:Array[String] 也是一种参数的设置,并且只能这么写
def main(args:Array[String]){
//这里是实例化上面定义的类,把类的所有内容实例化到一个具体的对象身上,并且传入两个xname和xage的值
val p=new Person(xname="lilei",xage=18)
//打印出Person这个类里面的属性
println(p.name)
println(p.age)
}
}
3.3 调用类时重新设置类属性的值
在上面的代码中,我们再添加上scala中的setter的概念
在代码中,我把 age 的变量,改成用 var 来定义,这样 age 在概念上就变成了可变的变量声明类型,在实例化的 p 对象里面,我们就可以直接对类里面的属性(例如 age )进行设置和修改了。
3.4 在类里面再加上自己的def方法
在 class 类里面,我们加了一个 def 方法,这个方法在格式上和 object里面的 def main() 是一样的,只不过 def main() 的括号里面,强制性的要求写上 args:Array[String] ,而这里我们自己定义的方法,括号中的参数是可以自己填的,这里我们先不写输入的参数。
我们先来一行行看看新写的代码:
//用 def 在 class 类中定义一个新的方法,方法的名字叫做 sayName,这个方法没有要求输入参数,方法的内容就是获取用户实例化类的时候输入的名字,然后拼接起来进行打印。
def sayName(){
println(s"My Name Is ${xname}")
}
//这一行代码的意思,是我们实例化 Person 类之后,对对象 p 进行属性的获取,def 定义的方法也属于类的属性之一
p.sayName()
3.5 对于类的重写构造
如果现在有的人只想要存姓名和年龄,有的人想要存姓名、年龄和性别,那么就需要有两种不同的结构来兼容两种不同的需求,那这个时候就需要用到一个重写构造的概念了
我们来分开看看代码
//首先,在 object 里面,我们实例化了一个 p2 的对象,在实例化的过程中,除了姓名和年龄,还加上了一个性别 "female",但是我们之前的 Person 类里面,并没有性别的输入参数,所以我们就需要对原有的 Person 类进行重写构造
val p2=new Person("hanmeimei",20,"female")
//这里是原有的 Person 类定义的地方,代码没有变
class Person(xname:String,xage:Int){
val name="hello "+xname+"!"
var age=xage
//在这里,我们新声明了一个变量 sex 来充当 Person 类的新属性,并且给这个属性一个默认值 "male"
var sex="male"
//重写构造的方法名字,都一定是要写成 this,在 this 方法的括号里面,写上需要的姓名、年龄、性别三个参数,重写构造的写法就是 def this(参数名和参数数据类型){...}
def this(yname:String,yage:Int,ysex:String){
//重写构造方法的第一行,必须要调用默认的构造内容,默认的就是 this(),调用方法如下
this(yname:String,yage:Int)
//我们使用 this.sex 来调用类里面的性别属性,并且将属性的值赋值为重构方法的 ysex
this.sex=ysex
}
def sayName(){
println(s"My Name Is ${xname}")
}
}
在打印结果里面
第一个 male 是对象 p 的打印性别,因为 p 并没有传入性别的内容,所以打印了默认的 male,而对象 p2 打印了传入的参数值。
3.6 访问修饰符 public 和 private 和 protected
在英文里面,private 的意思是私有的,public 是公共的,那么这两个关键字的作用、效果和用法是怎么样的呢?
我们一起来用代码实验看看:
3.6.1 没有任何关键字的变量声明
运行之后我们可以看到,s1 的对象可以成功的调用和打印出来 Student 这个类的 name 属性,在 scala 中,如果变量声明不加任何的修饰关键字,那么就默认是 public 公共的,public 的变量和方法是可以被引用的。
3.6.2 使用了 private 关键字的变量声明
运行代码之后,我们可以看到,运行的代码没有打印出 name 属性的值,并且代码报错了,报错代码的意思是“变量 name 在 Student 类中是不能被访问的”。
所以,private 私有的意思是,用 private 声明的变量和属性,是不能被调用的。
3.6.3 使用了 protected 关键字的变量声明
现在我们通过运行代码可以看到,拥有了protected关键字的 name 属性,在实例化 Users 类之后,它是无法被访问的,报错提示的信息是因为它不是一个 subclass(子类)。
那么什么是 subclass(子类)呢?protected 的修饰符在子类中是怎么被使用的呢?
我们通过代码来看看。
运行后看到打印出来了结果。
这里有句代码是这么写的:
class Student() extends Users
class Student() 是定义声明一个没有参数的 Student 类,extends 是继承的意思,extends Users 表示 Student 类继承自 Users 类,Users 是 Student 的父类,Student 是 Users 的子类,子类可以读取和使用父类中用 protected 修饰的属性,所以在 Student 类中可以使用 Users 类里面的 name 属性和变量的内容。
所以,类里面用 protected 修饰的属性,是不能被实例化的对象读取的,只能被自己的子类使用和读取。
3.7 object 传参的问题,用 apply 方法来解决
我们发现 class 类是可以传入参数的,那么 object 对象能不能传入参数呢?
我们来写个小实验看看例子:
在 object 这一行上面,是没有()小括号的,没有小括号就说明这里没有给你去设置入参的位置,但是调用 object 的时候,能不能写入参数呢?我们在创建对象 a 的时候,传入了100,但是运行的时候代码报错了。
如果想要在使用 object 的时候也传入参数,这里就必须要用到 object 里面的 apply() 方法了。
我们来写个这样的例子:
这样就成功了。
所以虽然 object 这一行代码不能定义参数,但是可以使用它里面的 apply() 方法帮它接收用户传进来的参数。object apply 是一种比较普遍用法,工作中用这种写法主要是用来解决复杂对象的初始化问题。apply() 在 object 中可以有一个,也可以有多个,这里我写个例子:
这个代码中,我在 object ApplyDemo里面写了两个 apply,它们的内容和对参数的要求都是不一样的,这样我们在使用这个 object 的时候,输入的参数是什么样的,它就是自动的帮我们根据输入参数的不同而去选择对应的运行程序了。
四、逻辑判断和循环控制
4.1 逻辑判断的基本语法
在判断中,主要就是三组关键字,if 和 else if 和 else,判断的语法里面,if 一定要用来开头,并且一次判断只能使用一个 if,其它的判断可以在后面用 else if 来执行,在语法中间可以有很多的 else if,那么在逻辑中一定会有些你没有考虑到的其它剩余情况,else 可以对剩下的所有没有写到的逻辑进行总结,如果需要用到 else,那 else 只能放在句子末尾,并且一个句子也只能有一个 else。
来看几个例子就知道了。
4.1.1 只有 if 的语句
这里只对 a>0 的情况进行判断,如果 a 是小于等于0的数字,那么就不会有任何显示了,因为我们没有写这种情况下的执行语句。
4.1.2 有 if 和 else 的语句
else 在这里表达的含义是,除了 a>0 的情况,其它的都属于 else 的逻辑,所以-99是归 else 管的。
4.1.3 有 if 和 else if 和 else 的语句
我们可以使用很多个 else if 将逻辑拆分的更细表达的更为清晰。
4.1.4 用一句话来表达和简写 if 的语句
例子一:
例子二:
4.2 循环控制
在说循环的基本语法之前,我们先来看看两个小的语法,一个是 to 关键字,一个是 until 关键字。
这里的 “1 to 10” 以及“ 1 until 10”我们可以看到结果,都是两个 Range() 集合类型,to 的结果是 大于等于1小于等于10的所有数字,而 until 则是一个前闭后开的集合类型。
那现在我们知道了两种不同的集合类型的描述方法之后,就可以把它们套入到 for 循环的基本语法里面去了。
1 to 10,也可以写成 1.to(10)
1 until 10,也可以写成 1.until(10)
用 .to() 和 .until() 的写法,还可以给范围加上步长:
我们也可以设置数字的范围为从大到小,从大到小的话,步长就要写成负数:
4.2.1 for循环的写法
for 是循环的开头关键字,括号里面是循环的范围,大括号里面是每次循环要执行的代码内容。
1 to 10 表示循环会运行10次,每一次会将 Range() 集合里面的内容按照顺序拿出来一个给到变量 i 进行赋值。
这里我们演示一个九九乘法表的写法:
在代码里面,我们也可以把两个嵌套的 for 循环合并成一个 for 循环:
在 for 循环的括号里面还可以加上 if 判断,来给循环的范围加上不同的条件:
我们可以看到循环的范围,首先是确定范围在1-10的数字内,并且要大于等于5的值,然后还要是除以2余数不等于0的,所以最后得到的结果是5、7、9。
我们还可以将 for 循环的结果提取出来:这里会用到 yield 关键字,将循环中所有的 i 拿出来,重新变成一个结果,for 循环中的 yield 会把当前的元素记下来,保存在集合中。
可以看到变量 result 的结果是一个 Vector 的向量集合。
4.2.2 while 循环和 do...while 循环
我们除了 for 之外呢,还有和 while 相关的两种循环的用法,一个是 while,一个是 do...while,它们的语法和区别是什么呢?我们分别用代码来举两个例子看看:
首先是 while 循环:
我们设置的循环范围是当 num 这个变量小于10的时候,就进入到循环的执行位置,并且每次执行之后都让自己的变量 num 往上面自增数字1,这时候就运行出了对应的结果
现在我们对这个代码进行修改,把初始的 num 改成10,然后循环的判断条件是 num 小于0,那么这个时候,初始值10明显是不在小于0的范围中的,所以不符合进入循环的条件,运行之后也就不会得到任何结果了。
通过这个例子,我们可以看到,while 循环是先对条件进行判断,当判断的结果为真的时候,才会开始对循环体的内容进行执行。
那我们再用 do...while 循环编写一次看看:同样的初始值的数字 num 是10,判断条件是当数字小于0,这个时候,初始值也是不符合判断条件的。
运行之后,我们发现这里打印了一行的内容,它把初始值打印了出来,所以 do...while 的区别就是,不管条件是否能够满足,它都至少会执行一次。
所以,while 是先判断再执行,do...while 是先执行再判断。
五、scala里面函数和方法
函数就是方法,方法就是函数,把它们理解成一个东西是一点问题都没有的。
我们来写一个结构完成的函数:
这里可能就有人有疑惑了,之前我们写函数的时候,def 后面好像没有写过函数返回的数据类型,这里为什么写了呢?写了和没写的区别是什么呢?
这里我写几个类似的代码给大家看看:
5.1 添加了 return 关键字的函数
加上我们发现结果是一样的,这说明在 def 的函数中,我们可以用 return 来返回我们计算之后的结果,那么如果不加 return 关键字呢,那么我们的代码就会将逻辑里面最后一行代码计算的结果当成需要被返回的值自动的进行返回,所以在最开始的时候我们没有加 return 关键字也能看到一个正确的结果。
5.2 不添加函数返回类型的函数
在这个函数中我们没有在 def 函数后面添加函数返回类型,这个时候在 x 和 y 的两个位置就报错了,因为 x 和 y 是我们需要返回的内容,但是你没有在声明函数的时候强调你要做返回这件事,也没有定义返回什么样的数据,所以它报错了。
5.3 函数的递归
所谓递归的意思,就是在一个函数里面,自己调用自己的内容,我们来看个例子:
在这个代码中,我们实现了 5*4*3*2*1的效果
num*jieCheng(num-1) 这句代码可能很多人看着会晕,我们来看看:
num 这个参数我一开始输入的是5,那么第一次运行就是 5*jieCheng(4),
jieCheng(4)的运行结果是 4*jieCheng(3),
jieCheng(3)的运行结果是 3*jieCheng(2),
jieCheng(2)的运行结果是 2*jieCheng(1),
jieCheng(1)的运行结果是 1,
那么我们把所有的结果拼起来,就是:5*4*3*2*1,结果打印就是120了。这个就叫做递归。
5.4 函数中的可变长参数的设置
首先我们先看个普通的参数,下面这个函数设置了一个字符串类型的参数,这样在使用的时候,也只能给它传入一个字符串类型的参数。
那,如果我想传入N个字符串,我也不知道会有多少个字符串,那么参数要怎么定义呢?这个时候就要用到可变长参数类型了。在数据类型后面加个 * 号,写成 String*。
在上面这个代码中,我们的 for 循环,也可以写成下面这个样子:
结果是一样的
代码里面的 String* 其实就是一个字符串数组,我们之前是通过 for 循环的遍历将数组的元素一个个拿出来进行了打印,而这个数组本身就包含了一个 .foreach() 方法,这个方法里面写了
i => {println(i)}
i 就是定义一个变量,用来接收数组中的每一个元素,=> 表示 scala 里面匿名函数的意思(等会就要讲了),它就是一个没有名字的函数,后面的{...} 就是函数的方法体,我们的这个匿名函数想要做的事情就是把变量 i 打印出来。
5.5 匿名函数
匿名函数的基本语法是: () => {...}
小括号里面是定义输入参数,大括号里面是定义方法体,例如
(a:Int, b:Int) => {println(a+b)}
这句代码单个运行是没有任何效果的,因为它没有名字,所以也就无法被调用,那怎么办呢?
我们可以通过给变量赋值的方式,将匿名函数赋值给某个变量:
所以虽然说匿名函数自己没有名字,但是它也需要附身在其它变量上面,所以匿名函数使用比较多的地方还是像上面 .foreach() 方法这样类似的地方,也就是某个方法的参数是个函数的时候。
5.6 偏应用函数
我们定义了一个 showLog() 的函数,这个函数经常需要被调用,但是这个函数中的多个参数里面,一个是固定的,一个是变更的,那么,如果多个参数里面很多个都是固定的只有一个是经常要变动的,那么我们就可以编写偏应用函数。
怎么写一个偏应用函数呢?看看下面的代码:
def sl=showLog(date,_:String)
在调用函数的地方,我们传入了一个下划线“_”,下划线在这里代替了 log 这个输入参数,它的意思是需要变动的那个值,我们把 date 这个参数固定下来,所以 sl() 这个函数不需要再传入 date 了。
5.7 高阶函数
如果一个函数,它的输入参数是函数,或者它的返回是函数,那么它就是一个高阶函数。
5.7.1 当输入参数是函数的时候
在这个函数里面,我们的 advFun() 里面,用 (类型)=>返回值 的方式表示这里需要的是一个函数,在方法体里面,用 f(10,20) 来给这个输入的函数类型输入对应的值。
调用函数的时候,我通过匿名函数的方式,将 f:(Int,Int) 这个函数的方法体进行了实现,告诉 advFun() ,我的 f() 是两个数字相加。
所以,在定义函数的时候,我们只是负责要去拿到数据,调用函数的时候去进行数据逻辑的处理。
5.7.2 返回值是函数的时候
在 advFun() 函数里面,我们希望这个函数能够返回的值是 (Int,Int)=>String ,这是输入两个整数然后返回字符串的函数,所以 advFun() 这个函数,在运行之后会返回另一个函数运行的结果,我在 advFun() 里面嵌套了一个 f(x:Int,y:Int):String 就是要和这个返回类型相匹配,然后把这个函数 f 运行完之后给 return 出去。
调用的时候 val a=advFun("result")(10,20),这个(10,20)就是给 f() 输入参数。
5.7.3 最后来一个输入参数也是函数,输出返回类型也是函数的高阶函数
5.8 柯里化函数
柯里化函数,就是有多个输入参数括号的函数。
六、字符串的操作
在 scala 中,字符串 String 一定要用双引号来表示,单引号 ' ' 只能表示一个单字符的 Char 类型。我们需要学习下字符串里面一些经常用到的方法。
6.1 字符串的比较,对比字符串内容是否一样
.equals() 比较字符串是否完全一致,.equalsIgnoreCase() 比较字符串的时候会忽略它们的大小写
6.2 查找字符串中的字符是否存在,如果存在则返回在原字符串中的下标序号
.indexOf() 括号里面放要查找的字符串内容。
6.3 查看字符串的长度
.length() 方法可以返回字符串的长度是多少
6.4 字符串的拼接
用 .concat() 对字符串进行拼接,也可以直接使用加号 + 拼接字符串。
6.5 读取下标序号对应位置的字符
6.6 判断字符串的开头和结束的组成内容
用 .startsWith() 判断字符串开头是什么字符串,用 .endsWith() 判断字符串结尾是什么字符串。
6.7 对字符串中存在的内容进行替换
使用 .replace() 对字符串中的内容进行替换,去掉字符串中多余的符号(例如空格),我们也是使用 .replace() 方法。
6.8 进行字符串大小写的转换
用 .toUpperCase() 将字符串转换成大写,用 .toLowerCase() 将字符串转换成小写。
6.9 去除掉字符串左右两边的空格
.trim() 是专门去除字符串左右两边多余空格的方法,但是它对字符串中间的空格无效。
6.10 将字符串按照某个字符进行切割
.split() 是将字符串用中间的某类字符进行切割,将字符串切割成一个数组。
6.11 对字符串进行下标和长度的截取
使用 .substring(开始序号, 结束序号) 截取原有字符串中的一部分,这里是按照字符串的下标序号进行截取的,截取部分是大于等于开始序号,小于结束序号,这里截取的范围是一个前闭后开的范围。
七、数组类型的操作
7.1 声明数组的时候,可以先设置数组的名字、包含的类型和数组的长度,而不用先给数组赋值
数组的声明和定义,定义的方法可以用:
var/val 数组名 :Array[数组内的类型]=new Array[数组内的类型](数组长度)
或者
var/val 数组名=new Array[数组内的类型](数组长度)
然后我们可以通过下标序号的方式,来给这些数组来赋值:
7.2 我们也可以在声明数组的同时给数组进行赋值
7.3 对已经存在的数组进行元素的追加
7.4 二维数组的定义和赋值
在 new Array[] 的括号里面,再写上一个 Array[] ,在数组里面定义数组的类型,这便是定义的二维数组。
如果定义的类型是 var a=new Array[Int](3),那结构应该和下图类似:
如果定义的类型是 var a=new Array[Array[Int]](3),那结构如下图:
所以二维数组,其实就是相当于在一个有格子的盒子中,每一个格子里面又放了一个有格子的盒子。
对二维数组进行遍历,就需要用到两层嵌套的 for 循环了。
7.5 数组的几个常见处理
7.5.1 数组内所有数字的求和
基本思路就是遍历数组的元素,准备一个初始值为0的变量,将每次遍历的元素累加到这个初始值上面就行了。
7.5.2 找出数组里面最大的数字
基本思路,就是先创建一个存储最大值的变量,先假设数组的第一个元素是最大值,然后遍历整个数组,用每一个数组中的元素和它做对比,如果比它大,就将这个新的更大的值赋值给最大数变量,然后遍历结束后保存的就是最大值了。
7.5.3 数组的冒泡排序
冒泡排序就是轮流对相邻的两个数进行对比,如果左边的数字比右边的大(或者小),那么就交换它们的位置。
在scala方法里面,也有自带的数组升序或者降序方法:
数组升序:arr.sorted
数组降序:arr.sorted.reverse
7.6 数组操作的各种函数和方法
7.6.1 对数组进行合并
合并的 concat() 方法,在 Array 这个模块里面,我们用 import 方法,导入这个模块里面的所有方法,让我们当前的代码页面可以直接使用模块里面的方法。concat() 方法可以同时合并多个数组的内容。
7.6.2 复制和替换数组的内容
使用 copy() 方法,将一个数组里面的部分内容,复制到另一个数组里面去,下面代码里面的 copy() 方法的作用是,在数组 a 里面,从下标序号5开始,连续取3个元素内容,复制到数组 b 里面,替换数组 b 里面下标 1 开始连续3个元素的内容。
7.6.3 快速生成升序或者降序的数字序列数组
iterate() 方法,可以帮我们快速的生成一个数字序列数组:
iterate(开始数字, 连续长度)(变量名=>{数字变量的步长})
例如代码中的 var a=iterate(10,5)(i=>{i+1}),我们的开始数字是10,连续取5个数字,每个数字递增1,所以是10,11,12,13,14。
7.6.4 对数组的内容进行快速填充
数组方法里面有个 fill() 方法,可以快速用某个值在数组里面填充N个内容。
如果使用 fill(3)('a'),就会得到一个由3个 a 组成的数组,fill(6)(1),就会得到由6个1组成的数组,我的代码中就是得到由5个随机整数组成的数组。
7.6.5 创建数字范围和步长的数组
range() 方法可以快速生成某个数字范围内数组,range() 可以指定开始数字、结束数字和步长。
7.6.6 不可变长数组与可变长数组
我们前面有看到数组的声明方法:
var arr=new Array[Int](5)
这样声明和定义的数组,长度就是固定5,想要在数组中再添加第六个元素内容,是无法添加的,这个数组就是不可变数组。
如果数组有长度变化的需求,那么就需要声明可变长数组了。
可变长数组,是不可以用下标序号的方式,来给数组赋值的,每个下标首次都需要使用 “+=” 的方式赋值,如果是把另一个数组的内容赋值给这个可变长的数组,那么就需要 “++=” 的方式了。
如果是要删除这个数组的内容,可以用 .trimEnd() 的方法删除数组后面的 N 个内容。
在可变长数组中添加元素,还可以使用 .insert() 方法,insert() 可以指定在哪一个下标位置,连续插入什么内容。
删除元素可以使用 .remove() 方法,remove() 可以指定在从哪一个下标序号开始,连续删除多少个元素内容。
八、scala 中的集合(Collection)类型
Scala提供了一套很灵活的集合实现的方法,Scala 集合分为可变的和不可变的集合,集合中分为 List、Set、Map、Tuple、Option、Iterator。
8.1 List 列表类型
列表和数组是非常相似的,它们的区别是,列表的值一旦被定义了就不能被修改(这有点像python中的tuple类型)。
也可以像二维数组一样,我们定义一个二维的列表。
尝试修改列表的内容会报错。
声明和定义列表,也可以用 :: 的方式,Nil 表示空值的意思。
8.1.1 列表的基本操作
Scala列表有三个基本操作:
- head 返回列表第一个元素
- tail 返回一个列表,包含除了第一元素之外的其他元素
- isEmpty 在列表为空时返回true
我们来用实际例子看看这三个操作。
现在编辑一个 li01.scala 的文件:
输出结果为:
8.1.2 列表的拼接合并
我们可以使用 ::: 连续的三个冒号来拼接两个列表的内容,也可以使用列表中的 List.concat() 方法来完成拼接。
可以看到的是,两个不同类型的列表也是可以合并的,合并后的新列表类型变成了 Any:
8.1.3 使用fill对列表进行重复值的填充
List.fill(3) 就是重复填充3次,List.fill(10)就是填充10个一样的内容。
8.1.4 用指定的函数计算结果对列表进行值的填充
List.tabulate() 方法是通过给定的函数来创建列表
n => n+1 在这里的含义:n默认是从0开始的步长为1的一个有序升序序列,每次的循环都是向上加1然后展示出相加的结果,tabulate(4) 是循环4次的意思。
再来一个:这个例子填充的就是每一个当前数的平方了。
我们也可以使用tabulate来填充一个二维数组:
这里可以解释一下,List.tabulate(),假如我们现在是 tabulate(a, b),那么我们就会得到一个二维列表,它的一维数量为a,二维数量为b,a和b都是从0开始步长为1的升序序列,并且执行的是a在外b在内的嵌套循环结构,所以当a等于0时,b要执行完所有数字,然后a等于1,b再重新执行一次。
tabulate(2,3)就有了 0 0,0 1,0 2,1 0,1 1,1 2 这六种组合计算的结果。
8.1.5 对列表的元素内容进行反转输出
我们使用列表的 reverse 属性可以让列表从后往前的反向输出,这个反向和排序没有关系。
8.1.6 列表的其他操作方法
在列表的末尾追加新的元素 +:
通过序号获取列表的内容 apply()
将列表元素连接成一个字符串,类似于python的join或者是hive的concat_ws addString()
这个方法先需要导入 StringBuilder 包,创建一个 StringBuilder 对象,然后将列表的内容用某个分隔符连接后当成字符串来输出。
查看列表中是否有包含某个元素 contains()
使用列表的元素,对数组的内容进行替换 copyToArray()
这个函数的作用是将列表的某几个数据,替换到数组的某个位置上。
copyToArray(被替换的数字, 从第几个位置开始进行替换, 连续替换多少个)
对两个列表进行比对,查看内容是否一致 equals()
对列表的元素内容进行删除操作 drop() / dropRight()
drop是从左往右删除,dropRight是从右往左删除。
使用filter进行列表内容的过滤和筛选 filter()
filter() 可以筛选单个条件,也可以 && 和 || 进行条件组合的查询。
对列表进行内容的升序排序 sorted
如果要降序,加上之前讲过的reverse就好了。
对当前的列表内容进行求和统计 sum
8.2 迭代器的使用
Scala Iterator 迭代器不是一个集合,它是一种用于访问集合的方法。
迭代器的两个基本操作是 next() 和 hasNext。
调用 next() 会返回迭代器的下一个元素,并且更新迭代器的状态。
调用 hasNext 用于检测集合中是否还有元素。
让迭代器逐个返回所有元素最简单的方法是使用 while 循环:
我们可以通过 toIterator 来转换一个迭代器,也可以直接创建一个迭代器。
查询迭代器中的最大值和最小值:
通过max和min对迭代器进行查询的时候要注意,一个迭代器只能遍历查询使用一次,再次使用的时候会报错,所以不能对同一个迭代器连续使用max或者min的方法。
九、结合IDEA对scala打包操作