1.变量定义
val msg: String = "hello"
val msg = "hello"
上面这两种定义方法是一样的,因为scala编译器可以自动推断类型。
定义变量可以使用val也可以使用var,val关键字相当于java的final,一旦定义就不可更改。
其实经过val修饰的变量已经不可以叫做变量了,它相当于经过final修饰的常量。
我们如果想要定义可变的变量可以使用var关键字。
2.常用类型
java中有基本类型,也有包装类型。
而scala中只有7中数值类型Byte,Char,Short,Int,Long,Float和Double和Boolean
3.条件表达式
scala的条件判断比较简洁
val i = 1
val m = if(i>0) 100 else 0
上面的代码也可以这样写:
val m = {
if(i > 1){
100
}
else{
0
}
}
在scala中代码块的最后一行就是return ,而且我们写return的话会报错。
还支持混合类型表达式:
val x = 1
val z = if(x >1) 1 else "error"
这里其实返回的是Any类型
如果缺失else相当于 val m = if(x >2) 1 else ()
val m = if(x >2 ) 1
在scala中每个表达式都有值,scala中有个Unit类,写作(),相当于java中的void,()为Unit的一个实例
假如说我们通过上述混合类型的表达式拿到了一个AnyVal的值我们像把他强转为Int可以使用:
val mm = m.asInstanceOf[Int]
块表达式
在scala{}中可以包含一系列表达式,块中最后一个表达式的值就是块的值。
循环:
在scala中有for循环和while循环,用for循环比较多
for循环语法结构 : for(i <-表达式/数组/集合)
for(i <- 1 to 10)
println(i)
val arr = Array("a","b","c")
for(i <- arr)
println(i)
高级for循环
每个生成器都可以带一个条件,注意:if前面没有分号
for(i <- 1 to 3; j <- 1 to 3 if i != j)
println(i +" "+j)
那么 1 to 10 返回的是什么呢?
返回的是Range类型,是集合类型的一个子类。
如果我们想取前面的但是不想取后面的值可以怎么做?我们可以使用until关键字:
for(i <- 1 until 10)
println(i)
我们还可以循环字符串:
for(i <- str)
println(i)
我们还可以通过下标取字符串的值
for(i<-0 until str.length)
println(str.charAt(i))
yield关键字:
在java中yield是和线程相关的关键字,而在scala中yield会在for循环中生成新的数组或集合,具体生成什么要看遍历的是什么。
生成新的数组或集合并不会对原来的数组或集合产生影响。
在java中Thread.yield()方法会暂停当前正在执行的线程,并执行其他线程。
在python中如果一个函数定义中包含yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator(生成器)
generator和函数的执行流程不一样。函数是顺序执行,遇到return
语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。
var arr = Array(1,2,3,4,5,6)
val arr1 = for(i <- arr) yield i * 10
在scala中可以将函数穿到方法中,那么这个函数就是业务逻辑
要实现上面的功能我们还可以这样做:
arr.map(_ * 10)
写的完整一点就是
arr.map(x => x * 10)
当我们想要在循环的时候过滤一些值我们可以这样做
for(i <- arr if i % 2 ==0 ) yield i * 10
但是我们同样也可以这样做:
arr.filter(_%2==0).map(_*10)
或者
arr.filter( x => x %2 ==0).map(i => i * 10)
操作符重载:
在scala中要实现加法我们可以这样做:
val i = 1 +2
但是我们也可以这样做
val i = 1.+(2)
还有我们之前写过的
1 to 10
还可以这样写
1.to(10)
所以在scala中将操作符重载成了方法。
如何定义方法?
def m(x: Int,y: Int): Int = {
x + y
}
方法的定义
我们定义方法的时候如果代码很少只有一行,则可以不写{}
def add(x: Int,y: Int): Int = x + y
我们甚至可以不写返回值类型:
def add(x:Int,y:Int) = x + y
那么为什么我们不写返回值类型也可以?
因为scala编译器可以根据我们的参数自动推断出返回值的类型
但是有一种特殊情况必须写返回值类型,递归方法
因为递归方法有一个出口,我们如果不写返回值类型,它根本就不知道我们要返回什么。
我们可以看看无参方法的结果:
def say() {println("Hi")}
say表示方法名,()表示无参列表,Unit表示无返回值
我们调用的时候可以有两种方式:
say()
或
say
上面定义方法的方式为省略了返回值,所以没有写=,默认为Unit
但是一旦有了返回值就一定要写=
完整的方法应该为:
def say():Unit = {
println("Hi")
}
那我们只写=不写返回值可以吗?也是可以的
def say() = {
println("Hi")
}
这样写的话可以自动推断类型。
函数的定义
匿名函数:
(x:Int,y:Int) => x+y
给函数取名:
val f1 = (x:Int,y:Int) => x + y
当代码多的时候我们可以加代码块:
val f1 = (x:Int,y:Int) => {
x + y
}
上面的写法其实是一种简写的方法,还有一种详细的写法:
val f1: (Int,Int) => Int = { (x,y) => x+y }
val f1: Int => Double = x => x.toDouble
val f1= (x:Int) => x.toDouble
val f1 = () => println("haha")
调用函数的时候如果没有参数,就要加(),因为如果直接使用名字,调用的只是引用。
我们还有第三种定义函数的方式就是new Function接口有几个参数Function后面就加参数的数量假如有两个参数,一个返回值,那么就可以new Function2[参数1的类型,参数二的类型,返回值的类型],这个Function实际上是一个trait接口,既然是接口就要实现它未实现的方法 apply我们可以在apply方法中实现自己的逻辑。
val f1 = new Function2[Int,Int,Double]{
override def apply(v1:Int,v2:Int):Double ={
(v1+v2).toDouble
}
}
现在总结一下定义函数的三种方式:
函数的说明
在scala中函数是函数,方法是方法,两者不同。最不同的地方就是函数可以作为参数传递给方法。
方法的操作比较简单,可能只是取数据,而函数的操作更加具体
这样做的好处有什么呢?
假如说我们的方法通过方法取出来之后,我们有n中不同的算法对其机型处理,这个时候就可以通过函数传递进去。
例如下面这样:
方法转换成函数
我们前面说过函数可以作为参数传递给方法是函数与方法最大的不同,但是其实方法也可以作为参数传递给方法,可是这样的话函数和方法的不同不久体现不出来了吗?其实当方法作为参数传递给方法的时候,他在内部会转换成函数。我们下面来看一下:
我们知道当方法如果有参数的话是不能直接使用m1这样调用的。那为什么直接将m1传递进去可以执行呢?那是因为它通过内部使用m1 _将方法转换成函数了。
集合
我们知道数组可以这样定义:
也可以定义一个长度固定的初始化数组:
那假如我们想定义一个和java中的StringBuffer一样的数组可以使用这样
val arr = new ArrayBuffer[Int]()
但是如果直接使用的话会报错,因为scala中默认没有变长的集合。
所以需要导包
我们可以这样:
import scala.collection.mutable._
这样scala中所有的变长集合就导进来了
ArrayBuffer在删除元素的时候是从前往后删除的。
我们甚至可以将一个ArrayBuffer的内容追加到另一个ArrayBuffer中。
访问数组的时候使用()
在数组中的某个位置插入元素用insert
我们甚至可以在某个位置插入多个元素
scala中的集合分为两种,可变集合和不可变集合,不可变集合一旦定义好,它里面的内容也是不可变的。
那么这种不可变集合有什么用呢?
当我们定义一些常量,我们不希望它被修改,这样定义的值,即使在多线程中也会很安全。
那么如果我们想定义可变的集合呢?
我们可以看到顺序不是追加的,因为hashmap没有顺序
我们甚至可以不加()
list也是一样的有可变的也有不可变的,我们先来看看不可变的;
我们再来看看可变的:
Set也分为可变和不可变的。
scala数组和集合常用方法
元组
scala中元组的下标是从1开始的,而且要取元组中的值必须这样取:
其实我们在定义map的时候不仅可以使用 ->的方式还可以使用元组的方式:
用Scala实现单机版wordcount
接下来我们再看看grouped的用法:
接下来是fold和reduce的用法
聚合
假如说我们有这样的list
我们希望求和,我们可以使用
但是我们也可以先求出每个list的值然后再加起来
x相当于初始值10,y.sum相当于每个list要进行的操作,m,n分别为list操作之后的值。
取并集
或者也可以 arr.union(arr2)
求交集:
求差集
scala并行集合
这条程序在加了par之后会多线程运行。
怎么证明呢?
我们可以看到同样的代码会有不同的结果,这个结果受分配给程序的线程数量的影响。
scala虽然很强大,但是它逃不出的坑就是它只能在一台机器上运行,所以才会有spark这样的分布式架构进行任务分配调度容错,然后底层使用scala进行计算。
构造器的使用
主构造器中的属性会成为成员变量。
package com.test.gouzao
class Person (val id:Long,val name:String){
var age:Int = _
def this(id:Long ,name:String,age:Int){
//辅助构造器的第一行必须先调用主构造器
this(id,name)
this.age = age
}
}
object Person{
def main(args: Array[String]): Unit = {
val zhangsan: Person = new Person(1, "zhangsan", 18)
println(zhangsan.age)
println(zhangsan.id)
println(zhangsan.name)
}
}