1.val和var的区别:
val : 值
类似 final 不能再次赋值
val 值名称:类型 = xxx
var : 变量
可以再次赋值
2. scala的基本数据类型:
Byte/Char
Short/Int/Long/Float/Double
Boolean
类型转换:
val a:Int = 10
val b = a.asInstanceOf[Double] /** 强制转换为Double
判断是不是该类型
val h = 10.isInstanceOf[Int] 返回结果为true
3. lazy 的用法
lazy在修饰一个属性时,是不会进行赋值操作的,只有当你使用该属性时,它才会被赋值(也可以叫做延迟加载)
scala> lazy val a = 1
a:Int = <lazy> /** 该属性是没有值的
scala> a /** 第一次使用该属性,此时才会进行赋值操作
res0: Int = 1
4. scala常见的IDE
IDEA: 需要自己安装scala插件 重点推荐
Eclipse: 自带scala
5.使用idea整合maven构建scala应用程序
scala中控制版本的标签
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<encoding>UTF-8</encoding>
<scala.version>2.11.8</scala.version>
<spark.version>2.3.0</spark.version>
</properties>
6. scala函数
6.1 方法的定义和使用:
def 方法名(参数名:参数类型,参数名:参数类型): 返回值类型 = {
// 括号内的叫做方法体
//方法体的最后一行是作为返回值的, 不用加return
}
* 没有入参的函数,调用时可以不用带括号
当定义函数时如果不写返回值类型,idea会自动把Unit设置为返回值类型,表示没有返回值类型,类似(void)
6.2 默认参数的使用:
* 在函数定义时,允许指定参数的默认值,但是当你传入之后以传入的值优先.
eg:
def sayName(name:String = "PK"):String = {
println(name)
}
6.3 命名参数的使用:
def speed(distance:Float, time:Float) : Float = {
distance/time
}
调用 println(speed(100,10)) res 10.0
命名参数的用法: println(speed(time=10,distance=100)) 就是在调换顺序的时候可以指定参数名,建议还是按照顺序为参数赋值
6.4 可变参数的使用:
* JDK5之后就已经提供了可变长参数的概念
用法:
def sum(numbers:Int*) = {
var result = 0
for(number <- numbers){
result += number
}
}
只需要在参数的后面加上*,在函数体内部进行循环,然后进行逻辑操作,具体的返回还需要看调用方法时添加的参数的数量
6.5 条件表达式:
if(x>0){
true
}else{
false
}
6.6 循环表达式:
6.6.1 to
scala> 1 to 5
res0: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5)
是一个左右都是闭区间的原因
6.6.2 Range
scala> Range(1,5)
res1: scala.collection.immutable.Range = Range(1, 2, 3, 4)
是一个左闭右开的区间
/**最后添加的一个参数表示步长,就是每次加2
* scala> Range(1,5,2)
res2: scala.collection.immutable.Range = Range(1, 3)
6.6.3 until
scala> 1.until(5)
res3: scala.collection.immutable.Range = Range(1, 2, 3, 4)
是一个左闭右开的区间
总结:实际to和until都是Range下的
* 6.6.4 for循环遍历
val courses = new Array("spark","spark sql","spark streaming"."storm")
/** 右边是需要遍历的集合对象,左边是遍历出的对象
for(course <- courses){
println("遍历的结果是:"+course)
}
* 6.6.5 foreach java8中已经出现的遍历方式
val courses = new Array("spark","spark sql","spark streaming"."storm")
/** 最左边是需要遍历的结合对象,括号里面就是遍历之后的一个个对象
courses.foreach(course => println(course))
6.6.6 while 的循环用法
var (num,sum) = (100,0)
while(num > 0){ //出口
sum = sum + num
num = num - 1 //步幅为1
}
println("1到100的求和"+sum)
7. 面向对象:
7.1 面向对象的概述:
7.1.1 封装: 属性,方法封装到类中
7.1.2 继承: 父类和子类之间的关系
7.1.3 多态: 父类引用指向子类对象 开发框架的基石
7.2 类的定义和使用
7.2.1 成员变量
private [this] val gender = "man" /** 私有成员只有在本类中使用,
var name:String = _ 也可以声明为"";"_"表示占位符
7.3 构造器
7.3.1 主构造器定义的方法:
class Person(val name:String,val age:Int) {...}
7.3.2 从属构造器的定义方法:
/** 首先在类中定义初始化的成员变量school
var school:String = _
/** 由于已经定义了类型,因此不需要再次定义类型,只需要直接添加即可
def this(name:String,age:Int,school:String){
this(name,age) /** 从属构造器方法的第一行必须是调用主构造器
this.school = school
}
7.4 继承和重写
7.4.1: 继承
就是父类有的,子类可以直接使用,但是当子类需要更多的时候,可以自己在子类中进行添加
7.4.2: 重写
子类继承父类的东西是可以进行改写的,可以根据自己的需要进行重写
重写语法:
override def 函数名:返回值类型....
7.5 抽象类
类的一个或者多个方法没有完整的实现(只有定义,没有实现)
abstract class Person2{
def speak
val name:String
val age:Int
}
/** 抽象类不能创建实例对象
class Student2 extends Person2 {
// override def speak: Unit = ??? /** 三个问号指代 方法体{} */
override def speak: Unit ={
println("speak...")
}
override val name: String = "李国辉" //实现的属性必须要赋值,用占位符会报错
override val age: Int = 26
}
7.6 伴生类和伴生对象
在伴生对象中创建apply方法,在主方法中进行测试的时候,创建对象
new AppTest() 指代的意思是穿件伴生对象AppTest并且默认调用伴生对象中的apply方法
总结:
类名() ==> 调用的是对象的apply方法 val b = AppTest() 不需要进行new操作,
因为在伴生对象的apply方法中已经进行了.
对象() ==> 调用的是类的apply方法 val c = new AppTest() c()
通常用法:
在object的apply方法中去new Class
7.7 case class
定义: 当定义一个类时加上case,使用该类时不用new,可以直接拿着用
object CaseClassApp {
def main(args: Array[String]): Unit = {
println(Dog("沈庆华").name)
}
}
case class Dog(name:String)
使用场景:
模式匹配(后续会讲解)
7.8 trait
Trait xxx extends ATrait with BTrait
eg:
class AparkConf(loadDefaults:Boolean)
extends Cloneable
with Logging
with Serializable
.... trait之间是继承,但是当要实现多个trait修饰类时,他们之间除了第一个,
其他之间需要用with进行连接
8. scala 集合
8.1: 数组
8.1.1 定长数组:
创建数组的方式:
方式一:
val b = Array[String](长度);
方式二:
val b = Array("spark","stream") 推荐使用
问题:
创建数组的时候为什么不用new:
因为Array底层点进去就是apply方法,所以不需要进行new操作
数组转换为String类型的方式:
b.mkString 所得到的结果是堆积在一起的,没有分隔符
b.mkString(",") 所得到的结果是以,分隔的字符串
8.1.2 变长数组:
eg:
val c = scala.collection.mutable.ArrayBuffer[Int]()
c += 1
c += 2
c += (5,6,7)
println("数组原内容:"+c)
c.insert(4,20) //在下标的为4的位置插入元素20,原下标为4的元素向后平移
println("添加之后的数组内容:"+c)
c.remove(0) //移除下标为0的元素
println("移除之后的内容:"+c)
c.remove(0,3) // 从下标0开始 移除3个元素
println("移除多个之后的内容:"+c)
c.trimEnd(2) //移除数组最后面的两个元素
for(i <- 0 until c.length){
println("打印结果:"+c(i)) /**寻常的遍历方式
}
for(res <- c){
println("获取数组元素最简便的方式:"+res) /** 最简便的遍历方式
}
for(res <- c.reverse){
println("获取数组元素逆序输出:"+res) /** 逆序输出遍历结果的方式
}
8.2: List
8.2.1 定长集合:
* Nil : 就是一个空的集合,在scala.collection.immutable.Nil下
val l = List(1,2,24,4,6)
println(l.head) res 1 /** head 表示获取一个集合的头
println(l.tail) res List(2,24,4,6) /** tail 表示获取一个集合的身体
val l2 = 1 :: Nil 创建一个只有一个元素1的集合, 1表示头,中间::表示拼接,Nil表示一个集合
val l3 = 1 :: 2 :: 3 :: Nil 表示把1,2,3作为集合元素从左到右放入到集合中
8.2.2 变长集合:
val l4 = scala.collection.mutable.ListBuffer[Int]()
l4 += 2
l4 += (7,5,4)
l4 ++= List(8,3,1) /** ++= 用在进行添加一个集合的时候
l4 -= (1, 4)
l4 --= List(6,7,8) /** --= 用在删除一个集合中所带的元素,没有的话就不删了
常用方法:
变成定长集合的方式:
l4.toList
变成数组的方式:
l4.toArray
是否为空:
l4.isEmpty
集合的头,体:
l4.head l4.tail
总结:
和java中的List集合类似,有序有下标
8.3: Set
val b = scala.collection.mutable.Set[Int]()
b += 3
b ++= Set(5,7,8)
总结:
Set集合无序无下标
8.4: Map
* 不可变Map集合 默认是在immutable包下的
val a = Map("PK" -> 18,"李国辉" -> 25) /** 箭头左边就如同key,箭头右边指代的就是value
println(a("PK")) 也可以 a.get("PK") /** 根据键获取值
* 可以进行修改的Map集合,只需要导入mutable下面的Map即可
val b = scala.collection.mutable.Map
b += ("wamngwu" -> 11,"zhaoliu" -> 23)
空的Map
val c = scala.collection.mutable.HashMap[String,Int]()
* /** 此方法是为了当你通过key获取值时,key不存在,会产生异常,因此,用getOrElse方法,在key没有的情况下给他一个默认值
val a = Map("PK" -> 18,"ligh" -> 25)
println(a.getOrElse("lisi",9))
* Map 集合的遍历:
方式一:
for((key,value) <- b){
println(key + ":" + value)
}
方式二:
for(key <- b.keySet){
println(key + ":" + b.getOrElse(key,9))
}
方式三:
for(value <- b.values){
println(value)
}
方式四: 类似方式一
for((key,_) <- b){
println(key + ":" + b.getOrElse(key,8))
}
☆ 8.5: OPtion&Some&None
eg1:
b.get("ligh")
res11: Option[Int] = Some(26)
eg2:
val a = Map("PK" -> 18,"ligh" -> 25)
println(a.getOrElse("lisi",9))
res:
Some(2)
None
具体的关系等后续再补充
8.6: Tuple(元组)
用法:
val a = (1,2,3,4,5)
println(a) // res (1,2,3,4,5)
println(a._5) // res 5
println("元组的长度:"+a.productArity)
for(i <- 0 until(a.productArity)){
println("遍历的元组的内容为:"+a.productElement(i)) /** 使用的方法不一样
}
应用场景:
val hostport = ("localhost",8080) /** 把ip和端口放在元组中,然后以后在用的时候调用就很方便
hostport._1 //获取localhost
hostport._2 //获取8080
9. 模式匹配
9.1 基本模式匹配
// 如同java中的switch case,需要匹配的参数放在match的前面,然后在方法体的case中进行相应的匹配执行的代码逻辑操作
object MatchApp extends App{
val names = Array("liguohui","shenqinghua","ligou")
val name = names(Random.nextInt(names.length))
name match {
case "liguohui" => println("一个好人")
case "shenqinghua" => println("是一个狗")
case _=> println("不知道你们说的是什么?")
}
def gradeTest(grade:String): Unit = {
grade match{
case "A" => println("成绩很优秀")
case "B" => println("成绩可以")
case "C" => println("成绩勉强")
case _ => println("成绩待提高啊")
}
}
gradeTest("A")
gradeTest("B")
gradeTest("D")
}
9.1.1 加条件的模式匹配:双重过滤
def gradeTest(name:String,grade:String): Unit = {
grade match{
case "A" => println("成绩很优秀")
case "B" => println("成绩可以")
case "C" => println("成绩勉强")
case _ if(name == "lisi")=> println(name+",成绩待提高啊")
case _=> println("成绩有点差啊")
}
}
9.2 Array模式匹配
def greeting(array:Array[String]):Unit = {
array match{
case Array("张三") => println("hello...")
case Array(x,y) => println(x+"==="+y)
case Array("沈庆华",_*) => println("是一头猪...")
case _ => println("你说的是啥啊?")
}
}
greeting(Array("张三"))
greeting(Array("沈庆华","liguohui","sdas"))
9.3 List模式匹配
ist match {
case "liguohui" :: Nil => println("是一个好人...")
case x :: y :: Nil =>println("this is " + x + " , " +y) //匹配只有两个元素的集合
case "ligh" :: "李国辉" :: "GoodMan" :: Nil => println("很舒服啊") //匹配三个元素的集合
case "pk" :: tail => println("头加尾...") // 匹配以pk开头的集合
case _ => println("啥都没有啊...")
}
}
9.4 类型匹配
根据传入的参数的类型进行匹配,获取相应的参数的类型
eg:
obj match{
case i : Int => println("Int")
case s : String => println("String") //匹配到String字符串
case map : Map[_,_] => map.foreach(println) //匹配到map集合并且打印
case list : List[_] => println("List") //匹配到List集合
case _=> println("类型不存在!")
}
}
matchType(Map("li" -> "sda"))
matchType(List("liguohui"))
9.5 Scala异常处理
/** 把需要做处理的代码逻辑放在try块中,然后在catch中进行捕获,最终在finally中进行最终操作.
try{
val i = 10/0
println(i)
}catch {
case e:ArithmeticException => println("除数不能为0")
case e:Exception => println("错误原因:"+e.getMessage)
}finally {
println("最终结束...")
}
9.5 case class 模式匹配
case修饰的类可以直接调用,不需要new
def classMatch(person:Person): Unit ={
person match {
case CTO(name,floor) => println(name+"在第"+floor+"楼层")
case Employee(name,floor) => println(name+"在第"+floor+"是一个员工")
case _=> println("是一个游客...")
}
}
class Person
case class CTO(name:String,floor:Int) extends Person
case class Employee(name:String,floor:Int) extends Person
case class Other(name:String) extends Person
classMatch(CTO("李国辉",10))
classMatch(Employee("张三",22))
classMatch(Other("other"))
总结:
由于所有的类都是继承了Person,所以在传入参数的时候只需要传入所有类的父类即可,另外case修饰的类不用new,可以直接使用
9.6 some&None模式匹配
因为map通过get方法获取的返回值类型为Option,Option下面有Some和Node两种类型,因此可以使用
Some表示有值,None表示没有值
val grades = Map("李国辉" -> "A","ligh" -> "B")
def someMatch(name:String): Unit ={
val grade = grades.get(name)
grade match {
case Some(grade) => println(name + "的成绩是:"+grade)
case None => println(name+"没有成绩啊...")
}
}
someMatch("李国辉")
someMatch("haha")
10.scala函数高级操作:
10.1 字符串的高级操作
知识点一: 字符串的拼接
val a = "hello,"
val b= "liguohui"
println(s"$a+$b") //获取参数的值通过$符号获取,并且在获取前加上一个s
知识点二: 多行字符串
val c =
"""
|这是多行字符串的写法
|在双引号里面,然后按shift+" 就出现三队双引号
|很好用哦
""".stripMargin
println(s"这是多行字符串:$c")
10.2 匿名函数
函数是可以命名的,也可以不命名
val m1 = (x:Int,y:Int) => x+y
然后调用 m1(10,1) res 11
10.3 curry函数
定义: 将原来接收两个参数的一个函数,转换成2个
以前的方法:
def add(x:Int,y:Int) = x+y
调用的时候add(1,2)
curry函数:
def add(x:Int)(y:Int) = x+y
调用的时候直接add(1)(2)
10.4 高阶函数(重要)
10.4.1 map函数
逐个去操作集合中的每个元素
写法一:
val l = List(1,2,3,4,5,6,7)
l.map(x => x+1) /** 符号'=>' 左边的表示入参,就是指代集合中一个一个的元素,'=>'后面表示的就是具体的逻辑操作
写法二:
val l = List(1,2,3,4,5,6,7)
l.map( _ + 1) /** 符号'_'表示占位符,指代任何参数
//filter相当于过滤,里面是过滤条件
//take 函数是获取集合的前几个值
l.take(4).foreach(print) /** 获取l中的前四个值
println("======")
l.map(x => x+1).filter(_ > 6).foreach(println)
10.4.2 filter 过滤函数 flatmap 压缩函数 foreach 遍历函数 reduce 执行操作函数
//filter相当于过滤,里面是过滤条件
//take 函数是获取几个的前几个参数
l.take(4).foreach(print)
println("======")
l.map(x => x+1).filter(_ > 6).foreach(println)
l.reduce(_+_) // 使用前面的占位符加上后面的 如同 1+2 3+3 6+4 依次做累加操作
l.reduce(_-_) // 依次做累减操作
l.reduceLeft(_-_)
val c = l.max
val sum = l.sum
//把各个集合的元素压缩在一个集合中进行输出
val c1 = List(List(1,2),List(3,4),List(6,7)) //此时数据显示 c1: List[List[Int]] = List(List(1, 2), List(3, 4), List(6, 7))
c1.flatten.foreach(print) // 显示结果 res8: List[Int] = List(1, 2, 3, 4, 6, 7)
//把c1集合的元素进行乘以2操作
//原来做法:
c1.map(_.map(_*2)) // 第一个占位符表示那一个集合,第二个表示该集合里面的元素进行乘以2操作 res9: List[List[Int]] = List(List(2, 4), List(6, 8), List(12, 14))
//为了压缩在一个集合中:
c1.flatMap(_.map(_*2)) // res10: List[Int] = List(2, 4, 6, 8, 12, 14)
eg:
val txt = scala.io.Source.fromFile("/Users/fish/hello.txt").mkString; //从磁盘中读取数据
println(txt)
val texts = List(txt)
texts.flatMap(_.split(",")).map(x => (x,1)).foreach(println)
10.5 偏函数
如同match case,但是不同的是,他是被包含在花括号里面的没有macth这个单词的一组case语句
//定义方法: 方法名:PartitalFunction[传过来的参数类型,返回的参数类型]
//入参 返回参数类型
def sayHello:PartialFunction[String,String] = {
case "ligh" => "hha..."
case "李国辉" => "a good man"
}
println(sayHello("ligh"))
11. scala的隐式转换
11.1 隐式转换的概述:
就是为一个已经存在的类添加一个新的方法
java中是动态代理,aop的思想实现
scala中通过隐式转换实现
11.2 隐式转换的实战
//为了实现man类中的人具有飞的功能,因此要用隐式转换
implicit def manToSuperman(name:Man):SuperMan = new SuperMan(man.name)
val man = new Man("pk")
man.fly()
**// 格式: 关键词 def 方法名(名字1:一般类):牛逼的类 = new 牛逼的类(名字1)
本身File没有read方法,自己定义了一个,然后赋给一般类,就有了read方法了
implicit def readAddFile(file:File):RichFile = new RichFile(file);
val file = new File("/Users/fish/hello.txt")
val text = file.read()
print(text)
}
class Man(val name:String){
def eat(): Unit ={
println(s"人[ $name ] eat ...." )
}
}
class SuperMan(name:String) {
def fly(): Unit = {
println(s"人[ $name ] fly ....")
}
}
//将要为file添加一个新的方法read
class RichFile(val file:File){
def read() ={
scala.io.Source.fromFile(file.getPath).mkString
}
隐式转换切面封装:
其实就是上面的关于implicit的方法提取出来,在源类中注释,通过import的方法使用,
object ImplicitAspect extends App{
implicit def manToSuperman(name:Man):SuperMan = new SuperMan(man.name)
implicit def readAddFile(file:File):RichFile = new RichFile(file);
}
然后需要在那个类中使用就可以通过 import ImplicitAspect._ 开始使用
11.3 隐式参数(了解)
定义: 值得是在函数或者方法中,定义一个用implicit修饰的参数,此时,Scala会尝试找到一个指定类型的,
用implicit修饰的对象,即隐式值,并注入参数
eg:
implicit val name = "李国辉";
def testParam(implicit name:String): Unit ={
println(s"[$name]是一个好人...")
}
testParam // res [李国辉]是一个好人...
testParam("liguohui") // res [liguohui]是一个好人...
//但是当出现两个implicit修饰的参数时就会出现错误,因为方法不知道去使用把一个
11.4 隐式类
定义: 就是对类增加implicit限定的类,其主要作用是对类的加强!
implicit class Calculator(x:Int){ //对传入的Int类型的参数做隐式转换,也就意味值所有传过来的整型参数都有add方法
//传入的除了Int类型的参数都不能调用add方法
def add(a:Int) = a+x
}
println(1.add(10))
12 scala操作外部数据
12.1 操作文件
//方式一:
//一行一行的读取文件内容 (重要)
val file = Source.fromFile("/Users/fish/hello.txt")(scala.io.Codec.UTF8)
def readLine() ={
for(line <- file.getLines()){
println("文件的内容:"+line)
}
}
// readLine()
//方式二:
//一个字符一个字符的毒 (重要)
def readChar(): Unit ={
for(ele <- file){
println("读取的结果:"+ele)
}
}
// readChar()
//方式三: (重要)
//根据访问地址进行读取,获取的是该访问路径下的页面的前端代码
def readNet(): Unit ={
val file = Source.fromURL("http://www.baidu.com")
for(line <- file.getLines()){
println(line)
}
}
readNet()
}
总结:
读取方式中,当同时读取一个文件时,有一种方式读取成功,下一个方式就会失效,不再进行读取操作
12.2 读取和操作xml:
其实下面读取配置文件的内容的方法就是对load方法的重载
//读取配置文件的内容
def loadXml(): Unit ={
val text = scala.xml.XML.load(this.getClass.getClassLoader.getResource("test.xml"))
println(text)
}
loadXml()
var xml = XML.load(new FileInputStream("/Users/fish/scala-project/scala-train/src/main/resources/test.xml"))
println("另外一种方式读取:"+xml)
12.3 操作MySQL数据:
// val driver = "com.mysql.jdbc.Driver"
val url = "jdbc:mysql://115.29.32.62:3306/ligh"
val username = "root"
val password = "7d428bc667"
var connection:Connection =null
try{
classOf[com.mysql.jdbc.Driver]
connection = DriverManager.getConnection(url,username,password)
val statement = connection.createStatement()
val resuleSet = statement.executeQuery("select * from user")
while (resuleSet.next()){
val id = resuleSet.getString("id")
val name = resuleSet.getString("name")
println(s"获取的id=[$id]<===>name=[$name]")
}
}catch {
case e:Exception => e.printStackTrace()
}finally {
if (connection==null){
connection.close()
}
}
}
12.4 操作xml (重要)
//读取配置文件的内容
// def loadXml(): Unit ={
val xml = scala.xml.XML.load(this.getClass.getClassLoader.getResource("test.xml"))
println(xml)
// }
// loadXml()
// var xml = XML.load(new FileInputStream("/Users/fish/scala-project/scala-train/src/main/resources/test.xml"))
// println("另外一种方式读取:"+xml)
//获取的是header标签下的field标签下的内容
// val text = xml \ "header" \ "field"
// println("获取的标签下的值:"+text)
//获取所有field标签及其内容
/* val fields = xml \\ "field"
for (field <- fields){
println("所有的field标签内容:"+field)
}*/
//获取header下的field下的name属性的值 下面两种方式均可以
/* val fieldAttributes = (xml \ "header" \ "field" \\ "@name")
//val fieldAttributes = (xml \ "header" \ "field").map(_ \ "@name")
for (res <- fieldAttributes){
println("结果是:"+res)
}*/
//首先是获取所有symbol标签下的值,然后找到ricker属性,转成字符串,进行比较是否是IBM,然后输出
/* val filters = (xml \\ "symbol")
.filter(x => (( x \ "@ricker").text).equals("IBM"))
for (filter <- filters){
println("过滤之后的结果:"+filter)
}*/
(xml \ "symbols" \ "symbol")
.map(x => (x \ "@ricker",x.text,x \ "@hah"))
.foreach(println)
}