我们将来使用Spark/Flink的大量业务代码都会使用到函数式编程。
下面的这些操作是学习的重点,先来感受下如何进行函数式编程以及它的强大。
foreach(f: (A) ⇒ Unit): Unit
foreach | API | 说明 |
---|---|---|
参数 | f: (A) ⇒ Unit | 接收一个函数对象作为参数 函数的输入参数为集合的元素 返回值为空 |
返回值 | Unit | 空 |
scala> val list = List(1, 2, 3, 4) list: List[Int] = List(1, 2, 3, 4) // 定义一个匿名函数传入到foreach方法中 scala> list.foreach((x: Int) => println(x)) // 匿名函数的输入参数类型可以省略,由编译器自动推断 scala> list.foreach(x => println(x)) // 当函数参数,只在函数体中出现一次,而且函数体没有嵌套调用时,可以使用下划线来简化函数定义 scala> list.foreach(println(_)) // 最简写,直接给定println scala> list.foreach(println) 1 2 3 4 // 很神奇的语法,别害怕,盘它就可以了,后期通过scala语言开发spark、Flink程序非常简洁方便
集合的映射操作是将来在编写Spark/Flink用得最多的操作,是我们必须要掌握。
方法描述
def map[B](f: (A) ⇒ B): TraversableOnce[B]
map方法 | API | 说明 |
---|---|---|
泛型 | [B] | 指定map方法最终返回的集合泛型 |
参数 | f: (A) ⇒ B | 传入一个函数对象作为参数 该函数接收一个类型A(要转换的集合的元素类型) 返回值为类型B |
返回值 | TraversableOnce[B] | B类型的集合 |
// 定义一个 list 集合,实现把内部每一个元素乘以10,生成一个新的 list 集合 scala> val list = List(1, 2, 3, 4) list: List[Int] = List(1, 2, 3, 4) // 定义一个匿名函数 scala> list.map((x: Int) => x * 10) // 省略匿名函数参数类型 scala> list.map(x => x * 10) // 最简写法:用下划线 scala> list.map(_ * 10) res1: List[Int] = List(10, 20, 30, 40)
映射扁平化也是将来用得非常多的操作,也是必须要掌握的。
方法描述
def flatMap[B](f: (A) ⇒ GenTraversableOnce[B]): TraversableOnce[B]
flatmap方法 | API | 说明 |
---|---|---|
泛型 | [B] | 最终要转换的集合元素类型 |
参数 | f: (A) ⇒ GenTraversableOnce[B] | 传入一个函数对象作为参数 函数的参数是集合的元素 函数的返回值是一个集合 |
返回值 | TraversableOnce[B] | B类型的集合 |
// 定义一个List集合,每一个元素中就是一行数据,有很多个单词 scala> val list = List("Hadoop HDFS MapReduce Yarn", "Spark Flink") list: List[String] = List(Hadoop HDFS MapReduce Yarn, Spark Flink) // 使用flatMap进行偏平化处理,获取得到所有的单词 scala> list.flatMap(x => x.split(" ")) // 简写 scala> list.flatMap(_.split(" ")) res1: List[String] = List(Hadoop, HDFS, MapReduce, Yarn, Spark, Flink) // flatMap该方法其本质是先进行了map, 然后又调用了flatten scala> list.map(_.split(" ")).flatten res3: List[String] = List(Hadoop, HDFS, MapReduce, Yarn, Spark, Flink)
过滤符合一定条件的元素
方法描述
def filter(p: (A) ⇒ Boolean): TraversableOnce[A]
filter方法 | API | 说明 |
---|---|---|
参数 | p: (A) ⇒ Boolean | 传入一个函数对象作为参数 函数的参数是集合中的元素 此函数返回布尔类型,满足条件返回true, 不满足返回false |
返回值 | TraversableOnce[A] | 列表 |
// 定义一个 list 集合 scala> val list = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) // 过滤出集合中大于 5 的元素 scala> list.filter(x => x > 5) res4: List[Int] = List(6, 7, 8, 9, 10) // 把集合中大于5的元素取出来乘以10生成一个新的list集合 scala> list.filter(_ > 5).map(_ * 10) res5: List[Int] = List(60, 70, 80, 90, 100) // 通过这个案例,应该是可以感受到scala比java的强大了...
// 定义一个 list 集合 scala> val list = List(5, 1, 2, 4, 3) list: List[Int] = List(5, 1, 2, 4, 3) // 默认是升序 scala> list.sorted res6: List[Int] = List(1, 2, 3, 4, 5)
sortBy指定字段排序:根据传入的函数转换后,再进行排序
方法描述
def sortBy[B](f: (A) ⇒ B): List[A]
sortBy方法 | API | 说明 |
---|---|---|
泛型 | [B] | 按照什么类型来进行排序 |
参数 | f: (A) ⇒ B | 传入函数对象作为参数 函数接收一个集合类型的元素为参数 返回B类型的元素进行排序 |
返回值 | List[A] | 返回排序后的列表 |
// 定义一个 list 集合 scala> val list = List("1 hadoop", "2 spark", "3 flink") list: List[String] = List(1 hadoop, 2 spark, 3 flink) // 按照单词的首字母进行排序 scala> list.sortBy(x => x.split(" ")(1)) res7: List[String] = List(3 flink, 1 hadoop, 2 spark)
sortWith自定义排序:根据一个函数来进行自定义排序
方法描述
def sortWith(lt: (A, A) ⇒ Boolean): List[A]
sortWith方法 | API | 说明 |
---|---|---|
参数 | lt: (A, A) ⇒ Boolean | 传入一个比较大小的函数对象作为参数 函数接收两个集合类型的元素作为参数 返回两个元素大小,小于返回true,大于返回false |
返回值 | List[A] | 返回排序后的列表 |
scala> val list = List(2, 3, 1, 6, 4, 5) list: List[Int] = List(2, 3, 1, 6, 4, 5) // 降序 scala> list.sortWith((x, y) => x > y) res0: List[Int] = List(6, 5, 4, 3, 2, 1) // 简写 scala> list.sortWith(_ > _) res1: List[Int] = List(6, 5, 4, 3, 2, 1) // 升序 scala> list.sortWith(_ < _) res2: List[Int] = List(1, 2, 3, 4, 5, 6)
我们如果要将数据按照分组来进行统计分析,就需要使用到分组方法,groupBy表示按照函数将列表分成不同的组。
方法描述
def groupBy[K](f: (A) ⇒ K): Map[K, List[A]]
groupBy方法 | API | 说明 |
---|---|---|
泛型 | [K] | 分组字段的类型 |
参数 | f: (A) ⇒ K | 传入一个函数对象作为参数 函数接收集合元素作为参数 返回一个K类型的key,这个key会用来进行分组,相同的key放在一组中 |
返回值 | Map[K, List[A]] | 返回一个映射,K为分组字段,List为这个分组字段对应的一组数据 |
scala> val a = List("zhangsan" -> "M", "lisi" -> "F", "wangwu" -> "M") a: List[(String, String)] = List((zhangsan,M), (lisi,F), (wangwu,M)) // 按照性别分组 scala> a.groupBy((kv: (String, String)) => {kv._2}) res0: scala.collection.immutable.Map[String,List[(String, String)]] = Map(M -> List((zhangsan,M), (wangwu,M)), F -> List((lisi,F))) // 简写 scala> a.groupBy(_._2) res1: scala.collection.immutable.Map[String,List[(String, String)]] = Map(M -> List((zhangsan,M), (wangwu,M)), F -> List((lisi,F))) // 将分组后的映射转换为性别/人数元组列表 scala> res1.map(x => x._1 -> x._2.size) res2: scala.collection.immutable.Map[String,Int] = Map(M -> 2, F -> 1)
reduce表示将列表,传入一个函数进行聚合计算
方法描述
def reduce[A1 >: A](op: (A1, A1) ⇒ A1): A1
reduce方法 | API | 说明 |
---|---|---|
泛型 | [A1 >: A] | (下界)A1必须是集合元素类型的子类 |
参数 | op: (A1, A1) ⇒ A1 | 传入函数对象,用来不断进行聚合操作 第一个A1类型参数为:当前聚合后的变量 第二个A1类型参数为:当前要进行聚合的元素 |
返回值 | A1 | 列表最终聚合为一个元素 |
scala> val a = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) a: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) scala> a.reduce((x, y) => x + y) res3: Int = 55 // 第一个下划线表示第一个参数,就是历史的聚合数据结果;第二个下划线表示第二个参数,就是当前要聚合的数据元素 scala> a.reduce(_ + _) res4: Int = 55 // 与reduce一样,从左往右计算 scala> a.reduceLeft(_ + _) res5: Int = 55 // 从右往左计算 scala> a.reduceRight(_ + _) res6: Int = 55
fold与reduce很像,但是多了一个指定初始值参数
方法描述
def fold[A1 >: A](z: A1)(op: (A1, A1) ⇒ A1): A1
reduce方法 | API | 说明 |
---|---|---|
泛型 | [A1 >: A] | (下界)A1必须是集合元素类型的子类 |
参数1 | z: A1 | 初始值 |
参数2 | op: (A1, A1) ⇒ A1 | 传入函数对象,用来不断进行折叠操作 第一个A1类型参数为:当前折叠后的变量 第二个A1类型参数为:当前要进行折叠的元素 |
返回值 | A1 | 列表最终折叠为一个元素 |
// 定义一个 list 集合 scala> val a = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) a: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) // 求和 scala> a.sum res0: Int = 55 // 给定一个初始值,折叠求和 scala> a.fold(0)(_ + _) res1: Int = 55 // scala> a.fold(10)(_ + _) res2: Int = 65 // 从左往右 scala> a.foldLeft(10)(_ + _) res3: Int = 65 // 从右往左 scala> a.foldRight(10)(_ + _) res4: Int = 65
// 定义一个数组 scala> val array = Array(1, 2, 3, 4, 5) array: Array[Int] = Array(1, 2, 3, 4, 5) // 定义一个函数 scala> val func = (x: Int) => x * 10 func: Int => Int = <function1> // 函数作为参数传递到方法中 scala> array.map(func) res5: Array[Int] = Array(10, 20, 30, 40, 50)
// 定义一个没有名称的函数----匿名函数 array.map(x => x * 10) res6: Array[Int] = Array(10, 20, 30, 40, 50)
scala> def getAddress(a: String): (String, String) => String = { | (b: String, c: String) => a + "-" + b + "-" + c | } getAddress: (a: String)(String, String) => String scala> val f1 = getAddress("china") f1: (String, String) => String = <function2> scala> f1("shenzhen", "nanshan") res7: String = china-shenzhen-nanshan // 这里就可以用柯里化去定义方法 scala> def getAddress(a: String)(b: String, c: String): String = { | a + "-" + b + "-" + c | } getAddress: (a: String)(b: String, c: String)String // 调用 scala> getAddress("china")("shenzhen", "nanshan") res0: String = china-shenzhen-nanshan scala> val func1 = getAddress("a") _ func1: (String, String) => String = <function2> scala> func1("c", "d") res1: String = a-c-d // 之前学习使用的下面这些操作就是使用到了柯里化 List(1,2,3,4).fold(0)(_ + _) List(1,2,3,4).foldLeft(0)(_ + _) List(1,2,3,4).foldRight(0)(_ + _)
scala> var factor = 1 factor: Int = 1 scala> val f1 = (x: Int) => x * factor f1: Int => Int = <function1> scala> f1(2) res2: Int = 2 scala> factor = 5 factor: Int = 5 scala> f1(2) res3: Int = 10 // 定义的函数f1,它的返回值是依赖于不在函数作用域的一个变量,后期必须要要获取到这个变量才能执行 // spark和flink程序的开发中大量的使用到函数,函数的返回值依赖的变量可能都需要进行大量的网络传输获取得到。这里就需要这些变量实现序列化进行网络传输。 scala> def multiply(x: Double) = (y: Double) => x * y multiply: (x: Double)Double => Double scala> val doubleFunc = multiply(2) doubleFunc: Double => Double = <function1> scala> val tripleFunc = multiply(3) tripleFunc: Double => Double = <function1> scala> doubleFunc(10) res4: Double = 20.0 scala> tripleFunc(10) res5: Double = 30.0
import java.util.Date class Customer { // _表示使用默认值进行初始化 // String类型默认值是null,Int类型默认值是0,Boolean类型默认值是false var name: String = _ var sex: String = _ // val变量不能使用_来进行初始化,因为val是不可变的,所以必须手动指定一个默认值 val registerDate: Date = new Date def sayHi(msg: String) = { println(msg) } } object Main { // main方法必须要放在一个scala的object(单例对象)中才能执行 def main(args: Array[String]): Unit = { val customer = new Customer customer.name = "张三" customer.sex = "男" println(s"姓名: ${customer.name}, 性别: ${customer.sex}, 注册时间: ${customer.registerDate}") // 对象调用方法 customer.sayHi("你好!") } }
class 类名(var/val 参数名: 类型 = 默认值, var/val 参数名: 类型 = 默认值){ // 构造代码块 }
def this(参数名: 类型, 参数名: 类型) { ... }
class Student(val name: String, val age: Int) { val address: String = "shenzhen" // 定义一个参数的辅助构造器 def this(name: String) { // 辅助构造器的第一行必须调用主构造器或其他辅助构造器或者super父类的构造器 this(name, 20) } def this(age: Int) { this("xxx", age) } }
import java.text.SimpleDateFormat import java.util.Date object DateUtils { // 在object中定义的成员变量,相当于Java中定义一个静态变量 // 定义一个SimpleDateFormat日期时间格式化对象 val simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm") // 构造代码 println("构造代码") // 相当于Java中定义一个静态方法 def format(date: Date) = simpleDateFormat.format(date) // main是一个静态方法,所以必须要写在object中 def main(args: Array[String]): Unit = { println { DateUtils.format(new Date()) } } }
class Dog { val id = 1 private var name = "Tom" def printName(): Unit = { println(Dog.CONSTANT + name) } } object Dog { // 伴生对象中的私有属性 private val CONSTANT = "汪汪汪 : " def main(args: Array[String]): Unit = { val dog = new Dog // 访问私有的字段name dog.name = "123" dog.printName() } }
val a = Array(1, 2, 3, 4)
,这种写法非常简单,不需要写一个 new,然后敲一个空格,再写类名。如何直接使用类名来创建对象呢?答案就是:实现伴生对象Array的apply方法
伴生对象的apply方法用来快速地创建一个伴生类的对象。
示例:
class Person(var name: String, var age: Int) { override def toString: String = s"Person($name, $age)" } object Person { // 实现apply方法,返回的是伴生类的对象 def apply(name: String, age: Int): Person = new Person(name, age) // apply方法支持重载 def apply(name: String): Person = new Person(name, 20) def apply(age: Int): Person = new Person("xx", age) def apply(): Person = new Person("xxx", 30) } object Main { def main(args: Array[String]): Unit = { val p1 = Person("张三", 23) val p2 = Person("李四") val p3 = Person(100) val p4 = Person() println(p1) println(p2) println(p3) println(p4) } }
object Main{ def main(args: Array[String]) = { println("hello, scala") } }
object Main extends App { println("hello, scala") }
class Person1 { var name = "super" def getName = this.name } class Student1 extends Person1 object Main1 { def main(args: Array[String]): Unit = { val p1 = new Person1() val p2 = new Student1() p2.name = "张三" println(p1.getName) println(p2.getName) } }
class Person2 { var name = "super" def getName = this.name } object Student2 extends Person2 object Main2 { def main(args: Array[String]): Unit = { println(Student2.getName) } }
class Person3 { val name = "super" def getName = name } class Student3 extends Person3 { // 重写 val 字段 override val name: String = "child" // 重写 getName 方法 override def getName: String = "hello, " + super.getName } object Main3 { def main(args: Array[String]): Unit = { println(new Student3().getName) } }
我们经常要在代码中进行类型的判断和类型的转换。在Java中,我们可以使用instanceof关键字、以及(类型)object来实现,在scala中如何实现呢?
scala中对象提供isInstanceOf和asInstanceOf方法。
Java | Scala | |
---|---|---|
判断对象是否是C类型 | obj instanceof C | obj.isInstanceof[C] |
将对象强转成C类型 | (C ) obj | obj.asInstanceof[C] |
获取类型为T的class对象 | C.class | classOf[C] |
class Person4 class Student4 extends Person4 object Main4 { def main(args: Array[String]): Unit = { val s1: Person4 = new Student4 // 判断 s1 是否为 Student4 类型 if (s1.isInstanceOf[Student4]) { // 将s1转换为Student4类型 val s2 = s1.asInstanceOf[Student4] println(s2) } } }
isInstanceOf 只能判断出对象是否为指定类以及其子类的对象,而不能精确的判断出,对象就是指定类的对象。如果要求精确地判断出对象就是指定类的对象,那么就只能使用 getClass 和 classOf 。
示例:
class Person5 class Student5 extends Person5 object Main5 { def main(args: Array[String]): Unit = { val p: Person5 = new Student5 println(p.isInstanceOf[Person5]) // true println(p.getClass == classOf[Person5]) // false println(p.getClass == classOf[Student5]) // true } }
Java中的访问控制,同样适用于scala,可以在成员前面添加private/protected关键字来控制成员的可见性。但在scala中,没有public关键字,任何没有被标为private或protected的成员都是公共的。
private[this]修饰符:被修饰的成员只能在当前类中被访问,或者可以理解为:只能通过this.来访问
(在当前类中访问成员会自动添加this.)。
class Person6 { private[this] var name = "super" def getName = this.name // 正确 def sayHello(p: Person6): Unit = { println("Hello " + p.name) // 报错,无法访问 } } object Main6 { def showName(p: Person6) = println(p.name) // 报错,无法访问 }
class Person7 { protected[this] var name = "super" def getName = { this.name // 正确 } def sayHello(p: Person7): Unit = { println(p.name) // 编译错误 } } object Person7 { def sayHello(p: Person7) = { println(p.name) // 编译错误 } } class Student7 extends Person7 { def showName = { println(name) // 正确 } def sayHello(p: Person7): Unit = { println(p.name) // 编译错误 } }
实例化子类对象,必须要调用父类的构造器,在scala中,只能在子类的主构造器
中调用父类的构造器
示例:
class Person8(var name: String) { println("name: " + name) } // 直接在子类的类名后面调用父类构造器 class Student8(name: String, var clazz: String) extends Person8(name) object Main8 { def main(args: Array[String]): Unit = { val s1 = new Student8("zhangsan", "32") println(s"${s1.name} - ${s1.clazz}") } }
abstract class Person9(val name: String) { // 抽象方法 def sayHello: String def sayBye: String // 抽象字段 def address: String } class Student9(name: String) extends Person9(name) { // 重写抽象方法或字段,def前不必加override关键字 def sayHello: String = "Hello, " + name def sayBye: String = "Bye, " + name // 重写抽象字段 override def address: String = "shenzhen" } object Main9 { def main(args: Array[String]): Unit = { val s = new Student9("Tom") println(s.sayHello) println(s.sayBye) println(s.address) } }
abstract class Person10 { def sayHello: Unit } object Main10 { def main(args: Array[String]): Unit = { // 直接用new来创建一个匿名内部类对象 val p1 = new Person10 { override def sayHello: Unit = println("这是一个匿名内部类") } p1.sayHello } }