Spark、Kafka等相关大数据技术框架底层都是由Scala编程语言编写的,Spark我们自己编写分布式程序时,Spark提供了多种编程语言风格,但是我们比较常用的是使用Scala编程。
Scala是一门多范式编程语法,所谓多范式指的就是多种编程风格的语法,Scala主要是一门面向对象编程语言和函数式编程语言。
Scala的发明人是马丁奥德斯基,Scala语言2001年左右诞生的,融合了Java和JS中很多特性。
同时Java中很多特性也是从Scala语言中吸收到,JDK8.0版本拉姆达表达式就是从Scala中吸收到
Java中函数编程式接口、拉姆达表达式、方法引用 接口的组成、时间日期类、Stream API、Optional类(解决NullPonitException的)
函数式接口:只有一个抽象方法的接口称之为函数式接口,一般情况函数式接口需要使用@FunctionalInterface
Java中的拉姆达(λ)表达式是和Java中的一个接口紧密相关的,接口函数式编程接口(接口中只有一个抽象方法)
语法:(形参列表)-> {方法体;}
lambda表达式: 就等用于scala中匿名函数,使用函数式接口时,我们对函数式接口的简化操作,使用函数式接口大部分场景下都是使用匿名内部类的形式实现的,匿名内部类最核心的就是重写函数式接口的唯一的抽象方法把lambda就是简化匿名内部类的操作的(形参列表) -> {方法体}
简化: 方法体只有一行 {} 可以省略;形参的类型可以省略的,如果形参只有一个那么 () 可以省略的
package lambda;
import java.io.PrintStream;
public class Demo {
public static void test(Flyable flyable){
flyable.fly("zs");
}
public static void test1(A a){
}
/**
* 使用函数式编程接口时,如果我们采用匿名内部类的形式,必须要去重写唯一的抽象方法,而且匿名内部类最核心的也是抽象方法
* 所以此时我们就是使用拉姆达表达式将匿名内部类的代码给简化了即可
*
* (形参列表) -> {方法体} 就是抽象方法的简化
* @param args
*/
public static void main(String[] args) {
test(System.out::println);
test(name-> System.out.println(name));
}
}
interface A{
default void run(String name){}
default int call(){return 0;}
}
package lambda;
@FunctionalInterface
public interface Flyable {
void fly(String name);
static int run(){ return 0;}
}
拉姆达表达式的简化:
Java中的方法引用相当于是拉姆达表达式的升级版本,主要就是用来替换整个拉姆达表达式的,当拉姆达表达式的方法体是引用了另外一个类的方法,并且方法体中没有多余的代码时,可以使用方法引用来简化代码
方法引用:是为了进一步的简化我们的lambda表达式,只有当lambda表达式的方法体只有一行,而且这一行还是引用的其他类的方法完成的,而且一般方法无参的,有参也可以,那么lambda就可以使用方法引用简化:
对象名::方法名
类名::方法名
xxx.scala
源码文件,源码文件中可以使用Scala的SDK也可以使用Java的SDKxxx.scala
源码文件成为Java的二进制字节码文件xxx.class
xxx.class
加载到Java的JVM虚拟机当中运行的Scanner
StdIn.readxxx()
System.out.xxx
print()/println()
print/println(s"xxxxx$变量名")
print/println("xxxxx%s %d",变量,变量)
语法: var|val 变量名|常量名【:数据类型】 = 初始值;
【注意】虽然Scala中数据类型可以省略,但是Scala是一门强类型编程语法
Scala是一门纯面向对象的编程语言,因此在Scala中所有的数据类型都是对象
Scala中所有类型的顶尖父类:Any,Any有两个直接子类:AnyVal、AnyRef
AnyVal是值类型:Byte、Short、Int、Long、Float、Double、Char、Boolean、Unit
AnyRef是引用类型:Java中所有类、Scala中所有类、Scala中所有集合、Null
Unit、Null、Nothing三个比较特殊的类型
没有++ –
+= -=…
Scala中==代表比较值相等,比较地址相等用eq函数
【注意】Scala运算符本质上是一个函数,函数的名字是数学符号,正常情况下运算符的使用语法应该如下: 1 + 1 1.+(1)
函数调用的时候可以简化:
- 函数调用的点可以省略的
- 如果函数的参数只有一个,那么 () 可以省略的
顺序流程:代码遵循从上而下依次执行
if类型的分支:Java一模一样的
模式匹配
语法:
x match{
case 值|x [模式守卫if] => case分支语句
case 值|x [模式守卫if] => case分支语句
case _ => case分支语句
}
【模式守卫】模式守卫可以做范围匹配,一般使用模式守卫时,case需要x
for循环
until型的for循环:
for(i <- start until end)
to型的for循环:
for(i <- start to end)
增强的for循环——遍历集合或者数组:
for(elem <- 集合/数组的变量)
for循环的步长(迭代,默认情况下迭代+1):
for(i <- start until|to end by num)
循环守卫(满足某个条件再执行循环体):
for(i <- start until|to end 【by num】 if 语句)
多重循环:
for(i <- start until|to end by num 循环守卫 ;j<- start until|to end by num 循环守卫)
循环的返回值问题(将循环的值赋予给一个Scala集合):
var array = for(i <- start until|to end by num 循环守卫) yield i
while循环
do while循环
2~3:和Java语法是一模一样的
def 函数名(形参列表):函数的返回值类型={ 函数体 }
可变长形参 参数名:数据类型*
,一个函数只能有一个可变长形参,而且形参必须位于函数形参列表的末尾
形参的默认值,Scala函数当中,形参是可以赋予默认值的,一旦形参赋予默认值,那么调用参数的时候,带有默认值的形参就可以不用传递参数了,带有默认值的形参一般要求放到形参列表的最后,如果没有放到最后,那么调用的时候,给其他形参传递参数,需要带有参数名传递
def test(name:String=zs,age:int){}
test(age=1)--具名实参
【注意】函数有两种特殊的返回值:Unit、Nothing
【注】在Scala中,函数是一等公民,函数可以在Scala的任何位置充当任何的角色,函数可以声明在类中,也可以声明在函数中,还可以声明在参数列表中、还可以当作返回值,还可以当作一个变量
var d:函数的定义 = 函数名 _
【注意】函数的类型如何声明: (形参类型列表) => 返回值类型
示例:
函数的定义:
def test(a:Int,b:Int):Int={a+b}
函数的类型写法:
(Int,Int) => Int
语法:
def test(a:Int,f:(Int,Int)=>Int):Unit={ }
语法:
def test():(Int,Int)=>Int={ }
4.4.2~4.4.3 :Scala中存在匿名函数,专门使用在这两个场景
函数也是一种数据类型,函数类型的语法:
(形参类型列表) => 返回值类型
如果形参只有一个,那么 () 可以省略
简化的前提:函数的类型必须是显示声明确定的
函数闭包指的是将不属于本函数的变量或者对象也包含进来,直到该函数运行完成,外部变量或者对象才可以被释放。
var x:Int = 1 def test(a:Int):Int={ a*x }
将一个接受多个参数的函数给他转换成为一个接受单个参数的函数的过程
将一个接受多个参数的函数转换成为一个返回了函数的函数,返回的函数传递的值就是原先的第二个参数
其实闭包的一个使用场景
函数内部调用本函数
递归三要素
惰性加载指的是将函数的调用延迟到第一次使用函数的返回值的时候才会调用
使用语法: lazy val 变量名 = 函数名(实参)
此时函数的调用在第一次使用变量的时候才会调用 一旦惰性加载,变量名只能使用val修饰
如果函数没有参数,那么函数的括号可以省略 def test:Unit={}
函数的返回值可以省略的,可以根据函数体的最后一行自动推断,
【注意】如果函数体的最后一行使用return 关键字返回数据,那么函数的返回值一定不能省略的
def test = { 1 }
函数体中,函数的返回值前的return关键字可以省略的,自动根据最后一行推断函数的返回值
如果函数的返回值类型是Unit类型 那么**=号和函数**的返回值都可以省略 def test{}
匿名函数
(形参列表) => {函数体}
调用的语法:对象名|类名.函数名(实参列表)
调用的点.可以省略的,对象名|类名 函数名(实参列表)
如果实参列表为空,那么 () 可以省略,如果声明函数的时候没有加 () 那么调用的时候一定不能加 ()
如果函数的实参列表只有一个 那么 () 也可以省略
对象名|类名 函数名 唯一的实参
Scala源于Java中,因此在Scala中也存在面向对象编程思想,面向对象编程最核心的逻辑就是以类来组织代码,以对象来调用代码。Scala的面向对象和Java基本上思维是一致的,只不过就是语法稍微不一样而已。
包package:包是用来分类管理Scala代码的,将不同类型的代码放到不同的包下,便于我们管理
采用和Java一样的管理机制,新建包,包下可以新建类和子包
采用包对象的管理机制,实现一个文件中存在多个包和多个scala类
在当前Scala类中,如果要使用非本包下的代码,那么我们就得需要通过import关键字导入才能使用。
每一个Scala类默认导入三个包
java.lang.
_scala._
scala.Predef._
Scala类中导入有一些规则和Java有点不一样
Scala可以在任何位置进行导包操作,代码只能在导包位置之后使用 我们可以把所有的包放到package之后 class之前
如果我们要导入一个包下的所有代码,那么可以使用_当作通配符使用
我们现在只想导入某一个包下的两个类,而非所有类 import xxxx.{x1,x2}
导包重命名操作:可以将一个类重命名为另外一个名字在当前类中使用
import xxxx.{x1=>x2}
x1类在当前类中可以使用x2名字来替代
屏蔽某一个包下的部分类:导入一个包下的所有代码,除了某几个类之外
import xxxx{x1=>_,x2}
导入xxxx包下的x2类,不导入x1这个类
访问控制修饰符 class ClassName 访问控制修饰符 (主构造器参数列表){
类体
}
【注意】在一个Scala文件可以存在多个类 多个类的访问控制修饰符没有要求的
三个 private protected public–不写
在同一个Scala文件中可以存在多个Scala类,权限没要求的
属性用来描述类的特征
访问控制修饰符 var|val 属性名:属性类型 = 值;
属性声明的时候必须加值,但是我不想给其他值,只想给默认值,那么值使用 _ 来代替(属性值可以填写 _ , _ 代表给属性赋予默认值,默认值看类型的)
【注意】
val修饰的属性 不能赋予 _ 默认值,必须给一个显示的值
属性前加一个注解
@BeanProperty
Scala中方法就是函数,函数声明在类体中,称之为方法
访问控制修饰符 def 方法名(形参列表):方法的返回值类型 ={ 方法体 }
{} 类中类
构造器是创建该类的实例对象的
Scala有两种构造器
主构造器:声明在类上的 class ClassName 访问控制修饰符 (形参列表)--主构造器
辅助构造器:声明在类中的构造器,语法: def this(形参列表){ }
辅助构造器必须在首行直接或者间接的调用主构造器代码
【注意】
Scala中每一个辅助构造器必须直接或者间接的调用主构造器(显示声明调用)
主构造器因为没有构造器体,因此一般主构造器都是无参构造器,如果主构造器想给类中的属性赋值,那么我们需要把属性声明在主构造器的内部(构造器的形参既是类的属性还是构造参数)
主构造器的参数只要加上 var | val 那么就会变成类的属性主构造器我们一般都是要求是无参构造器,而且主构造器一旦是无参的,那么主构造器的 () 可以省略
对象的创建就是变量|常量的定义
var|val 对象名:类 = new 类名(实参)
简化: 类型可以省略,自动推断;如果调用的构造器的实参为空,那么 () 也可以省略的
和Java的封装一样的概念,合理隐藏、合理暴露,控制类、属性、方法、构造器、内部类能被谁访问使用,不能被谁访问使用。
Scala的封装权限只有三种:private protected 不写(public)
封装性和Java的区别在于两个地方
和Java的概念是一样的,extends继承,Scala也是单继承,一旦继承父类,拥有父类中非私有化的属性和方法
定义变量的时候,分为编译时类型和运行时类型
向上转型:将儿子的对象给了父类的变量引用,可以直接给,不需要任何多余的操作
var s:Father = new Son()
向下转型:将父亲给儿子,不能直接给,需要使用asInstanceof[子类] 赋值,要求父亲的运行时类型必须是儿子或者儿子的子类类型
向下转型不是使用(子类)强转的,而是使用asInstanceof[子类]
函数
var s:Father = new Son()
var s1:Son = s.asInstanceof[Son]
如果我们要进行向下转型,最好先使用Scala提供的以下代码先判断父类的运行时类型是不是子类的类型
obj.isInstanceof[ClassName]
abstract class ClassName{ 抽象属性 抽象方法 }
没有 = 赋值的属性就是抽象属性
没有函数体的方法称为抽象方法
Scala中非抽象子类重写抽象类的属性和方法时,为了表示重写的概念,加了override关键字表示重写的过程
伴生对象是为了Scala和Java语言更好的集成,在Scala中不存在static关键字,但是Java中有静态的概念,Scala就推出了一个伴生对象,用来存放类似于静态的类内容
类名(形参列表)
的形式构建类的对象def apply(形参列表):Class ={ new ClassName(实参); }
样例类就等同于Java中JavaBean,同时Scala提供case class是为了丰富模式匹配的功能的
case class(形参列表) extends Father......
case class ClassName(形参列表) extends F with T1
形参列表既是类的属性 还是类的主构造器,而且形参列表默认是使用private val修饰符的 ,同时样例类自动给我们生成类的toString、hashCode、equals、apply方法
和模式匹配结合使用的
1、样例类的形参列表既是样例类的主构造器,还是样例类的属性,而且属性默认还是使用val修饰的
2、样例类默认会给当前类生成
toString、hashCode、equals、Object伴生对象的apply方法
(参数和主构造器的参数保持一致的)3、如果想给样例类的属性生成getter和setter方法,只需要在每一个形参前加上一个
@BeanProperty
注解4、样例类可以应用于模式匹配中
特质就是Java中的接口,只不过在Java中称之为接口,在Scala中称之为特质。Java中的所有接口都可以当作Scala的特质使用。
Scala中特质既可以包含抽象属性和抽象方法,还可以包含普通的属性和普通的方法。
trait 特质名{ 抽象属性 抽象方法 普通的属性 普通的方法 }
Scala中特质也是需要继承的,父类也是继承的 class Son extends Father with Trait1 with trait2
Scala中的特质不是用来实现的,而是用来继承的
class A extends F with T1 with T2.....
class A extends T1 with T2......
Scala中可以使用Java的集合,但是Scala也给我们提供了一套集合体系,Spark Kafka既支持Scala集合体系,还支持Java集合集合
List:元素可重复、且元素加入有序、可以根据索引取值
Set:元素不可重复、没有索引的概念
每一次操作集合都是返回一个新的集合对象,而原有的集合不受任何的影响
scala.collection.immutable
每一次操作集合都是对原有的集合对象进行操作,而不是返回新的集合对象
scala.collection.mutable
元素无序不重复的
【元组】不是集合
Scala比较特殊的一种容器,也可以存放一组数据,但是数据的类型可以不一样
元组最多能存放22个元素
元组的定义语法有两种: TupleN[xxx...] (xxx...)
获取元组的某一个元素,元组名._N
函数 |
---|
xxx.length |
xxx.size |
xxx.foreach(函数) |
xxx.mkString(“分隔符”) |
xxx.contains(xxxx) |
xxx.iterator |
sorted(implicit ord: Ordering[B])
:根据当前集合中的元素进行大小比较 然后去排序,前提是集合中的元素必须是可以比较大小的,如果不能比较大小,那么需要传递一个比较器,比较器返回一个int类型的值sortedBy(f: xx=>xxx)((implicit ord: Ordering[xxx]))
:将集合中元素转换称为另外一种类型 然后根据转换的类型做比较sortWith(f:(xx,xx)=>Boolean)
: 自定义元素的比较规则,返回一个boolean类型的值扩展类的功能的,让原本两个毫无关系的类,也可以互相调用里面的方法,隐式转换的内容会自动触发
implicit def 函数名(类型):另外一个类型={ 如何创建另外一个类型 }
将输入类型转换称为输出类型,从而扩展某个类型的功能和方法
可以实现在调用参数的时候,哪怕参数没有默认值,也可以不用传递,也能调用函数
如果使用隐式转换参数,需要做两步操作
1、首先需要将函数的参数声明为隐式转换参数 implicit 变量名:数据类型
2、在函数调用的作用域当中,声明一个同类型的隐式变量,这样的话调用函数的时候,函数会从作用域当中去找一个同类型的参数自动填充 implicit 变量名:数据类型 = 值
【注意】在同一个作用域下,不能存在两个同类型的隐式变量
import java.lang.
import java.util.{Arrays=>_}_