这篇文章是我跟着视频学,再加上看博客总结的Scala关键知识点,用来开发Spark完全够用。
第一节:基础
- 变量声明 var val
- 七种值类型(Byte,Char,Short,Int,Long,Float,Double)
- 条件表达式
val y = if (x>1) 1 else "error"
Java中所有类的基类是Object,Scala中所有类的基类是Any
Unit相当于Java中的void
for循环
1 to 10 返回 1,2,3...10
1 until 10 返回1,2,3...9
for(i <- 1 to 10){
println(i)
}
for(i <- 1 until 10){
println(i)
}
//for循环遍历集合
var a = 0;
val numList = List(1,2,3,4,5,6);
// for 循环
for( a <- numList ){
println( "Value of a: " + a );
}
//嵌套for循环的高级写法&for循环过滤
for (i<- 1 to 3; j<- 1 to 3 if(i!=j)){
println(i*10+j)
}
- 方法和函数的声明以及方法转换成函数
Scala 有方法与函数,二者在语义上的区别很小。Scala 方法是类的一部分,而函数是一个对象可以赋值给一个变量。换句话来说在类中定义的函数即是方法。
Scala 中使用 val 语句可以定义函数,def 语句定义方法。
方法定义格式如下:
def functionName ([参数列表]) : [return type] = {
function body
return [expr]
}
//声明求两数之和的方法:
def addInt( a:Int, b:Int ) : Int = {
var sum:Int = 0
sum = a + b
return sum
}
函数定义格式如下:
val addInt = (x: Int,y: Int) => x + y
第二节 数组、映射、元组、集合
- 定长数组
val arr1 = new Array[Int](8)
var arr2 = Array("java", "scala")
//小括号下标
println(arr2(0))
- 变长数组
注意要引入import scala.collection.mutable.ArrayBuffer
然后使用ArrayBuffer 类
追加操作: += ; ++= ; insert方法
常用方法: reverse、delete、sum、max、min、sorted、yield关键字
- 映射
创建映射
//用静态类创建
val map1 =Map("scala"-> 1, "java"->2, "python"->3)
//用元组创建
val map2 = Map(("scala",1),("java",2),("python",3))
获取值
map1("scala")
可变映射:
//必须要引用这个
import scala.collection.mutable.Map
map1("scala")=6
常用函数:getOrElse
- 元组
K-V对的集合
val t = ("scala", 100L, 3.14, ("spark",1))
//取元组的第一个位置的值,“scala”
t._1
note:元组的取值下标从1开始。
//这样可以用a,b,c,d去取元组的值
val t,(a,b,c,d) = ("scala", 100L, 3.14, ("spark",1))
println(a)
两个方法:
toMap
用于把数组(元素是元组)转换成不可变的Map
zip
拉链操作。用于把多个Array的值对应起来生成一个新的Array,新的Array内容为元组。
arr1 = Array(24,25,26)
arr2 = Array("a","b","c")
arr1.zip(arr2)
如果两个数组的长度不等,那么就会把长的数组的后面截取掉。
- 集合
Seq(序列)
val list1 = List(1,2,3)
val list2 = 0:: list1 //在List头部添加0
val list3 = 0 +:list1 //也是在List头部添加0
val list6 = list1 :+ 4 //在List尾部部添加4
合并两个List:使用++
++:
:::
可变List:使用ListBuffer
类
Set(集合)
集合有去重功能
import scala.collection.immutable.HashSet
val set1 = new HashSet[Int ]()
不可变集合可以使用++
合并两个Set
可变集合使用add
+=
追加元素
可变集合使用 ++=
合并两个集合
可变集合使用 -=
remove
删除元素
Map(映射)
val map1 = new HashMap[String,Int]()
map1("scala")=1
map1 += (("java",2))
map1. put("C++",5)
map1 - ="java"
map1 . remove("C++")
第三节 函数式编程
lazy关键字修饰的是惰性变量
-
map
-
filter
-
flatMap
-
reduce
-
fold
-
aggregate
交并差集
union、intersect、diff实现wordcount
object Test2{
def main(args: Array[String]): Unit = {
val list = List("hello java hello scala hello python","hello java hello scala hello python")
val words = list.flatMap(_.split(" "))
val tuples = words.map( x=>(x,1) )
val grouped = tuples.groupBy( (_._1) )
val mapvalues = grouped.mapValues(_.size)
println(grouped)
}
}
第四节 面向对象
- 创建类、属性
package day01
class Person{
val id = "100"
var name:String = _
//只有本类才能访问,伴生对象也可以访问
private var age = 120
//只有本类才能访问,伴生对象访问不到
private [this] val gender = "男"
}
//伴生对象
object Person{
def main(args: Array[String]): Unit = {
val p = new Person()
//可以修改var变量
p.age=200
println(p.age)
}
}
object Test1{
def main(args: Array[String]): Unit = {
val p = new Person
//p.age出错,因为是private
//p.age
}
}
- 构造器、辅助构造器
//主构造器的参数列表要放到类名的后面,和类名放在一起
//此时的faceValue:Int只能在本类调用,伴生对象也无法调用,虽然没有用val或var修饰,但默认是val
class StructDemo(val name:String, var age:Int, faceValue:Int=90) {
var gender:String = _
def getFaceValue():Int={
return faceValue
}
//辅助构造器
def this(name:String,age:Int,faceValue:Int,gender:String){
this(name,age,faceValue) //辅助构造器第一行必须先调用主构造器
this.gender = gender
}
}
object StructDemo{
def main(args: Array[String]): Unit = {
//val s = new StructDemo("ningning",26,98)
//println(s.faceValue) //访问不到,因为没用val或var修饰
val s = new StructDemo("ningning",26,98,"女")
println(s.name)
println(s.age)
val face = s.getFaceValue()
println(face)
println(s.gender)
}
}
- 单例对象
就是用object修饰的类
创建对象的时候可以不用new
直接 val test = Object1
调用里面的方法直接类名.方法名即可
- 伴生对象
与类名相同,并且用object修饰的对象叫伴生对象
类和伴生对象之间可以相互访问私有的方法和属性
- apply、unapply方法
apply方法经常用在伴生对象中,用来构造对象而不用显式地使用new。
unapply是当做是伴生对象的apply方法的反向操作。apply方法接受构造参数,然后将他们变成对象。而unapply方法接受一个对象,然后从中提取值。unapply方法返回的是一个Option.
object ScalaRunner {
def main(args: Array[String]): Unit = {
testApply2()
testApplyUnApply()
testCaseClass()
testNumber()
testUnapplyCheck()
}
private def testUnapplyCheck(): Unit = {
"Hello World fdf" match {
case Name(first, last@IsCompound()) => println(s"first: ${first}, last: ${last}")
}
}
private def testNumber() = {
val Number(n) = "12345"
println(s"n: ${n}")
}
private def testCaseClass(): Unit = {
val p: Person = Person("Sky", 20)
p match {
case Person(name, 20) => println(s"name: ${name}")
}
}
private def testApplyUnApply() {
val nameObj = Name("hello", "world")
println(s"a: ${nameObj.a}, b: ${nameObj.b}")
val Name(arg11, arg12) = "Hello World"
println(s"arg11: ${arg11}, arg12: ${arg12}")
val Name(arg21, arg22) = Name("hello", "world").toString
println(s"arg21: ${arg21}, arg22: ${arg22}")
}
private def testApply(): Unit = {
Name()
(new Name) ()
(new Name()) ()
Name()()
}
private def testApply2(): Unit = {
Name(1)
(new Name()) (1)
(new Name) (1)
Name(1)(1)
}
}
case class Person(val name: String, val age: Int)
object Number {
def unapply(input: String): Option[Int] = {
try{
Some(Integer.parseInt(input.trim))
} catch {
case ex: NumberFormatException => None
}
}
}
class Name {
var a = ""
var b = ""
def this(a: String, b: String){
this
println("Call class construct.")
this.a = a
this.b = b
}
def apply() = {
println("Call class apply.")
}
def apply(i: Int) = {
println(s"Call class apply ${i}.")
}
override def toString: String = {
a + " " + b
}
}
object Name {
def apply(): Name = {
println("Call object apply.")
new Name()
}
def apply(i: Int): Name = {
println(s"Call object apply ${i}.")
new Name()
}
def apply(a: String, b: String): Name = {
println(s"Call object apply.")
new Name(a, b)
}
def unapply(input: String): Option[(String, String)] = {
println(s"Call object unapply.")
val pos = input.indexOf(" ")
if(pos == -1) None
else Some((input.substring(0, pos), input.substring(pos + 1)))
}
}
object IsCompound {
def unapply(input: String) = {
println(s"Call IsCompound unapply ${input}.")
input.contains(" ")
}
}
运行结果:
- private关键字
参考资料:Scala访问权限修饰符:private和private[this]
作为参考和对比,首先从Java开始。
在Java中,方法可以访问该类的所有对象的私有字段,例如:
public class Person {
private String name;
private int age; // 该字段无getter/setter方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private String getName() { // 该私有方法只是为了和Scala对比
return name;
}
private void setName(String name) { // 该私有方法只是为了和Scala对比
this.name = name;
}
public boolean equals(Person other) {
return this.name.equals(other.name) && this.age == other.age;
}
}
Person类的equals方法内,能够直接访问其他Person对象的私有字段(other.name/other.age)。
在Scala中,方法也可以访问该类的所有对象的私有字段,称为类私有字段;
但在Scala中,允许定义更加严格的字段访问控制,通过private[this]来实现,称为对象私有字段,即只能在对象内部访问的字段,请看下图:
在图中,job属于类私有字段,salary属于对象私有字段;
方法内不能访问其他对象的对象私有字段(salary)。
Scala编译器分别对private和private[this] 都做了什么?
首先对gao.Person.scala文件做一下修改,如下:
class Person(val name:String, var age:Int) {
private var job = "Programmer"
private[this] var salary = 3000F
}
然后编译该文件,再使用javap查看编译后的Person.class文件,可以看到:
$ javap -p target/scala-2.11/classes/gao/Person.class
Compiled from "Person.scala"
public class gao.Person {
private final java.lang.String name;
private int age;
private java.lang.String job; // private修饰,生成的private的getter/setter方法
private float salary; // private修饰,没有生成getter/setter方法
public java.lang.String name();
public int age();
public void age_$eq(int);
private java.lang.String job(); // job字段的private的getter方法
private void job_$eq(java.lang.String); // job字段的private的setter方法
public gao.Person(java.lang.String, int);
}
- 特质
scala中的特质可以类比成Java中的接口:
1.特质中定义的方法可以实现,【有了大括号的就是已经实现过的方法,例如下面Animal中的listen和run】;也可以不实现【例如Animal类中的speak方法】
package cookBook.chapter8
trait Animal{
//没有实现
def speak
def listen: Unit ={
}
def run: Unit ={
println("I'm running")
}
}
class People extends Animal{
override def speak: Unit ={
println("I'm speaking English")
}
}
object People extends App{
var people = new People
people.speak
people.listen//don't have result
people.run
}
运行结果如下:
I'm speaking English
I'm running
同时,如果我们将上面类People中的speak给删除,会显示报错。class People must either be declared abstract or implements abstract members...。这就是说:
2.如果一个类实现特质,那么必须实现特质中未定义的方法。否则这个类应该为抽象类。scala中有抽象类,但是更加倾向于使用特质。
3.scala中可以实现一个类同时继承多个特质
package cookBook.chapter8
//轮胎
trait tire{
def run: Unit ={
println("I can run fast")
}
}
//方向盘
trait SteeringWheel{
def control: Unit ={
println("I can control the cars'direction")
}
}
//同时继承多个特质
//使用with,后面可接多个with
class Roadster extends tire with SteeringWheel {
def display(): Unit ={
println("I'm a Roadster")
}
}
//敞篷跑车
object Roadster extends App{
var roadster = new Roadster
roadster.display()
roadster.run
roadster.control
}
运行结果如下:
I'm a Roadster
I can run fast
I can control the cars'direction
注:特质构造顺序
1、首先调用超类的构造器
2、然后调用特质构造器,特质构造器在超类构造器之后,类构造器之前
3、特质由左到右被构造
4、在每个特质当中,父特质先被构造
5、如果多个特质公用一个父特质,而那个父特质已经被构造过了,则不会再被构造
6、所有特质构造完毕,子类被构造
介绍特质比较详细的一篇文章https://www.cnblogs.com/nowgood/p/scalatrait.html
- 抽象类
//抽象类
abstract class Animal1{
//抽象字段
var name:String
var size:Int
//抽象方法
def walk
}
//抽象类实现类
class Cat(var length:Int)extends Animal1{
override var name = "cat"
override var size = 100
override def walk{
println(this.name + ":" + this.size + ":" + this.length)
}
}
object AbstractClassTest {
def main(args: Array[String]): Unit = {
val cat = new Cat(200)
cat.walk
println("name:" + cat.name)
println("size:" + cat.size)
println("length:" + cat.length)
}
}
cat:100:200
name:cat
size:100
length:200
Scala抽象类不能被实例化,包含若干定义不完全的方法,具体的实现由子类去实现。
第五章 模式匹配
以下部分来自:菜鸟教程
Scala 提供了强大的模式匹配机制,应用也非常广泛。
一个模式匹配包含了一系列备选项,每个都开始于关键字 case。每个备选项都包含了一个模式及一到多个表达式。箭头符号 => 隔开了模式和表达式。以下是一个简单的整型值模式匹配实例:
object Test {
def main(args: Array[String]) {
println(matchTest(3))
}
def matchTest(x: Int): String = x match {
case 1 => "one"
case 2 => "two"
case _ => "many"
}
}
执行以上代码,输出结果为:
$ scalac Test.scala
$ scala Test
many
match 对应 Java 里的 switch,但是写在选择器表达式之后。即: 选择器 match {备选项}。match 表达式通过以代码编写的先后次序尝试每个模式来完成计算,只要发现有一个匹配的case,剩下的case不会继续匹配。接下来我们来看一个不同数据类型的模式匹配:
object Test {
def main(args: Array[String]) {
println(matchTest("two"))
println(matchTest("test"))
println(matchTest(1))
println(matchTest(6))
}
def matchTest(x: Any): Any = x match {
case 1 => "one"
case "two" => 2
case y: Int => "scala.Int"
case _ => "many"
}
}
执行以上代码,输出结果为:
$ scalac Test.scala
$ scala Test
2
many
one
scala.Int
实例中第一个 case 对应整型数值 1,第二个 case 对应字符串值 two,第三个 case 对应类型模式,用于判断传入的值是否为整型,相比使用isInstanceOf来判断类型,使用模式匹配更好。第四个 case 表示默认的全匹配备选项,即没有找到其他匹配时的匹配项,类似 switch 中的 default。
- 样例类
使用了case关键字的类定义就是就是样例类(case classes),样例类是种特殊的类,经过优化以用于模式匹配。
以下是样例类的简单实例:
object Test {
def main(args: Array[String]) {
val alice = new Person("Alice", 25)
val bob = new Person("Bob", 32)
val charlie = new Person("Charlie", 32)
for (person <- List(alice, bob, charlie)) {
person match {
case Person("Alice", 25) => println("Hi Alice!")
case Person("Bob", 32) => println("Hi Bob!")
case Person(name, age) =>
println("Age: " + age + " year, name: " + name + "?")
}
}
}
// 样例类
case class Person(name: String, age: Int)
}
执行以上代码,输出结果为:
$ scalac Test.scala
$ scala Test
Hi Alice!
Hi Bob!
Age: 32 year, name: Charlie?
在声明样例类时,下面的过程自动发生了:
构造器的每个参数都成为val,除非显式被声明为var,但是并不推荐这么做;
在伴生对象中提供了apply方法,所以可以不使用new关键字就可构建对象;
提供unapply方法使模式匹配可以工作;
生成toString、equals、hashCode和copy方法,除非显示给出这些方法的定义。
- 偏函数
第六节 柯里化、隐式转换
有关逆变协变
https://blog.csdn.net/zero__007/article/details/52245475
这篇文章是讲Java的。不过意思一样。
文章中提到了PECS原则,就是什么时候使用逆变协变的。