Scala编程基础

目录

 

一 Scala概述

1.1 什么是scala

1.2 为什么要学scala

1.3 Spark函数式编程初体验

二 Scala开发环境

2.1 安装JDK

2.2 安装Scala

2.2.1 Windows安装Scala编译器

2.2.2 Linux中安装Scala编译器

2.3 IDEA安装

2.4 Scala插件离线安装

2.5 IDEA创建Scala工程

2.6 IDEA创建maven工程

2.7 maven工程设置

2.8 IDEA中的第一个Scala程序

2.9 IDEA常用配置

2.9.1 修改字体:

2.9.2 修改字符集

2.9.3 去除自动进入上次项目

三 Scala基础

3.1 常用类型

3.2 声明变量

3.3 条件表达式

3.4 块表达式

3.5 循环

3.5.1 for循环

3.5.2 while循环

3.6 函数式编程再体验

3.7 调用方法(运算符重载为方法)

四 方法和函数(重难点)

4.1 定义方法

 4.2 定义函数

4.3 方法和函数的区别

 4.4 将方法转换成函数(神奇的下划线)

五 集合框架

5.1 集合综述

5.2 集合框架整体架构(了解)

六 数组

6.1 定长数组和变长数组

6.2 遍历数组

6.3 数组转换

 ​

6.4 数组常用方法

七 元组Tuple

7.1 创建元组

 7.2 获取元组中的值

7.3 将对偶的集合转换成映射

7.4 拉链操作

7.5 元素交换

八 序列List

8.1 不可变序列

8.2 可变序列

九 映射Map

9.1 定义map

9.2 添加元素

9.3 获取映射值

9.4 赋值和修改值

9.5 删除元素

9.6 map遍历

9.7 获得keys和values

10 map|flatten|flatMap|foreach方法的使用

11 set

11.1 不可变的set

11.2 可变的Set

12 Map和Option

13 集合常用方法

14 面向对象

14.1 对象

14.1.1 单例对象

14.1.2 伴生对象

14.1.3 apply方法

14.1.4 应用程序对象

14.2 类

14.2.1 类的定义

14.2.2 构造器

14.2.3 访问权限

14.2.4 抽象类

14.3 特质Trait

14.4 继承

14.4.1 扩展类

14.4.2 重写方法

14.4.3 示例

14.4.4 总结

样例类/样例对象

15 模式匹配

15.1 匹配字符串

15.2 匹配类型

15.3 匹配数组,元组,集合

15.4 样例类

15.5 Option类型

15.6 偏函数


一 Scala概述

1.1 什么是scala

函数式编程:

函数式编程是一种编程思想,主要的思想把运算过程尽量写成一系列的函数调用

Scala是一种多范式的编程语言,其设计的初衷是要集成面向对象编程函数式编程的各种特性。

scala之父:Martin Odersky

helle.scala  ->   .class  运行在jvm上

Scala运行于Java平台(Java虚拟机),并兼容现有的Java程序。

scala是对java的进一步封装,基于java来开发的。

也就是说,scala的代码最终会被编译为字节码文件,并运行在jvm上。

1.2 为什么要学scala

  1. 优雅:这是框架设计师第一个要考虑的问题,框架的用户是应用开发程序员,API是否优雅直接影响用户体验。
  2. 速度快Scala语言表达能力强,一行代码抵得上Java多行,开发速度快。

     scala语言风格简洁,也很可能降低了可读性,所以学习及以后开发过程中,都需要有良好的代码规范

    3.Spark的开发语言,掌握好scala,就能更轻松的学好spark。

    4.能融合到Hadoop生态圈Hadoop现在是大数据事实标准,Spark并不是要取代Hadoop,而是要完善Hadoop生态。JVM语      言大部分可能会想到Java,但Java做出来的API太丑,或者想实现一个优雅的API太费劲。 

1.3 Spark函数式编程初体验

hadoop中wordcount流程:

  1. 读数据,切分
  2. word,1
  3. 拿到一组key相同的数据,计数

Q1: 对上述文件内容使用Spark进行单词个数统计?

Q2: 对上述输出结果进行降序 ?

二 Scala开发环境

2.1 安装JDK

因为Scala是运行在JVM平台上的,所以安装Scala之前要安装JDK

使用 # java -version 验证

确保已安装jdk1.8+

2.2 安装Scala

2.2.1 Windows安装Scala编译器

访问Scala官网Scala 2.11.8 | The Scala Programming Language 下载Scala编译器安装包

安装方式:直接使用免安装版的,解压即可。

安装完成之后,配置环境变量SCALA_HOME和PATH:

可以在cmd窗口下验证:输入scala -version查看scala版本

输入scala可以进入scala shell交互模式

输入:q 或:quit退出scala交互命令行。

该交互模式,有一个高大上的名称:REPL

Read Evaluate Print Loop

(读取-求值-打印-循环)

2.2.2 Linux中安装Scala编译器

下载Scala地址https://downloads.lightbend.com/scala/2.11.8/scala-2.11.8.tgz

1,上传并解压Scala到指定目录

# tar -zxvf scala-2.11.8.tgz -C /usr/local/

2,创建一个软连接(可选项)

# ln -s   源文件目录  软连接目录

# ln -s /usr/local/scala-2.11.8 /usr/local/scala

3,配置环境变量,将scala加入到PATH中

# vi /etc/profile

export SCALA_HOME=/usr/local/scala

export PATH=$PATH:$JAVA_HOME/bin:$SCALA_HOME/bin

2.3 IDEA安装

目前Scala的开发工具主要有两种:Eclipse和IDEA,这两个开发工具都有相应的Scala插件,如果使用Eclipse,直接到官网下载即可

Download Scala IDE for Eclipse - Scala IDE for Eclipse  不推荐使用该种方式

IDEA的Scala插件更优秀,有逼格的Spark攻城狮都选择IDEA(只需一次,就会爱上她)

下载地址:Download IntelliJ IDEA – The Leading Java and Kotlin IDE

下载社区免费版,点击下一步安装即可,安装时如果有网络可以选择在线安装Scala插件。这里我们使用离线安装Scala插件:

  • 安装IDEA,点击下一步即可。由于我们离线安装插件,所以点击Skip All and Set Default

2.4 Scala插件离线安装

  1. 安装Scala插件:Configure -> Plugins -> Install plugin from disk -> 选择Scala插件 -> OK -> 重启IDEA

2.5 IDEA创建Scala工程

安装完成后, 双击打开IDEA, 创建一个新的项目(Create New Project)

选中左侧的Scala -> IDEA -> Next

输入项目名称 -> 点击Finish完成即可

2.6 IDEA创建maven工程

2.7 maven工程设置

创建一个源码包 scala

在pom.xml文件中设置源码包:

重新加载pim.xml文件

添加scalaDK:

ctrl+alt+shift+s

创建包,新建scala object

编写代码

运行程序

2.8 IDEA中的第一个Scala程序

2.9 IDEA常用配置

Ctrl+Alt + s  进入到settings配置页面。

2.9.1 修改字体:

2.9.2 修改字符集

2.9.3 去除自动进入上次项目

如果每次启动IDEA,直接进入到项目页面,没有引导页面,修改配置如下:

修改安装目录的idea.properties文件

该文件在idea的安装目录下:

把idea的缓存及配置移动到其他的盘中,去C盘化

# idea.config.path=${user.home}/.IdeaIC/config

idea.config.path=d:/profiles/develop/IDEACache/config

#---------------------------------------------------------------------

# Uncomment this option if you want to customize path to IDE system folder. Make sure you're using forward slashes.

#---------------------------------------------------------------------

# idea.system.path=${user.home}/.IdeaIC/system

idea.system.path=d:/profiles/develop/IDEACache/system

三 Scala基础

3.1 常用类型

Scala和java一样,

AnyVal

有7种数值类型:Byte、Char、Short、Int、Long、Float和Double(没有基本类型和包装类型的区分)

2种非数值类型: Boolean 和 Unit  

注意:Unit表示无值,相当于java中的void。用作不返回任何结果或者结果为空的类型。Unit类型只有一个实例值,写成()。(小括号)

String是属于引用类型

AnyRef

3.2 声明变量

定义变量使用var 或者 val关键字

语法:    var|val 变量名称 (: 数据类型) = 变量值

使用val定义的变量是不可变的,相当于java中用final修饰的变量

使用var定义的变量是可变的,推荐使用val

优先使用val,在循环的时候,会使用到var

Scala编译器会自动推断变量的类型,必要的时候可以指定类型

lazy val 变量名

lazy关键字修饰的变量,是一个惰性变量,实现延迟加载(懒加载),在使用的时候才会加载。

lazy关键字不能修饰 var类型的变量

可以使用通配符(占位符)_来指定变量

var  name:String = _

需要注意:

  1. 使用占位符的时候,必须指定变量的类型
  2. 变量只能使用var来修饰。
  3. 使用占位符定义变量的时候,不能在main方法中定义。
object VariableTest {
  def main(args: Array[String]) {
    // 使用val定义的变量值是不可变的,相当于java里用final修饰的变量
//变量名在前,类型在后
    val name: String = “nvshen”
    // 使用var定义的变量是可变的,在Scala中鼓励使用val
    var age = 18
    //Scala编译器会自动推断变量的类型,可以省略变量类型
    val str = "world"
// 声明多个变量
var age,fv = 18
//var str: String = _
  }
}

可以同时声明多个变量,可以使用通配符声明变量:

java中的通配符是*,scala中的通配符是_

定义一个变量,必须赋予初始值,如果没有初始值,可以使用_占位符代替,但是变量必须指定类型。而且占位符变量不能定义在main方法内部。

3.3 条件表达式

表达式都是有返回值的。

条件表达式的值可以赋值给一个变量

支持混合类型的表达式。

Scala的条件表达式比较简洁,例如:

object ConditionTest {
  def main(args: Array[String]) {
    val x = 1
    //判断x的值,将结果赋给y
    val y = if (x > 0) x else -1
    //打印y的值
    println(y)
    //如果缺失else,相当于if (x > 2) 1 else ()
    val m = if (x > 2) 1
    println(m)
    //在scala中每个表达式都有返回值,scala中有个Unit类,写做(),相当于Java中的void
    val n = if (x > 2) 1 else ()
    println(n)
//支持混合类型表达式
    val z = if (x > 1) 1 else "error"
    //打印z的值
    println(z)
    混合类型会返回父类类型。

    //if和else if
    val k = if (x < 0) 0 else if (x >= 1) 1 else -1
    println(k)
  }
}

条件表达式总结:

1.条件表达式,是有返回值的,可以使用变量接收条件表达式的值

2.条件表达式的返回值是由谁来决定的?

由每一个分支,最后一行的值来决定的。

(比如最后一行是3>2 ,返回值是true;val a = 123 ,返回值是())

3.如果缺少某一个分支,默认的返回值类型是Unit,值是()

if (age>10) age    ==   if(age>10) age   else ()

4.在混合类型中,返回值的类型,一般情况下是所有分支返回值类型的一个父类(如果两个分支的数据类型可以转换,)

val result = if(x >10){
 x // Int
}else {
99.9
}
result: Double

5.当每一个分支,只有一行内容的时候,就可以省略大括号,而且可以写在一行。推荐大家都写上{}

3.4 块表达式

当每一个分支,只有一行内容的时候,就可以省略大括号,而且可以写在一行。推荐大家都写上{}

object BlockExpressionTest {
  def main(args: Array[String]) {
    val x = 0
    //在scala中{}中可包含一系列表达式,块中最后一个表达式的值就是块的值
    //下面就是一个块表达式
    val result = {
      if (x < 0){
        -1
      } else if(x >= 1) {
        1
      } else {
        "error"
      }
    }
    // result的值就是块表达式的结果
    println(result)
  }
}

3.5 循环

在scala中有for循环和while循环, for循环最常用

3.5.1 for循环

语法结构:for (i <- 表达式/数组/集合)

java for循环方式:

// for(i=1;i<10;i++)  // 传统for循环

// for(Int I :arr)  // 增强for循环

object ForTest {
  def main(args: Array[String]) {
    //for(i <- 数组)
    val arr = Array("a", "b", "c")
// 遍历打印数组中的每个元素
    for (i <- arr) // 类似Java中的增强for
      println(i)    
// 通过角标获取数组中的元素
    val index = Array(0,1,2)
// 遍历打印数组中的每个元素
    for (i <- index) // 类似Java中的传统for
      println(arr(i))    // 获取元素的方式是(),java中是[]


//for(i <- 表达式),表达式1 to 10返回一个Range(区间)
//每次循环将区间中的一个值赋给i
    for (i <- 1 to 6)
      println(i)
println(arr(i)) // 报错,如果不加{},只会把for后面的一行当做循环的内容。

for (i <- 1 to 6){
    println(i)
println(arr(i))
}

    for(i <- 1 until 6) { // 0 until 6 => 会生成一个范围集合Range(0,1,2,3,4,5)
        println(array(i))
    }

    // 打印数组中的偶数元素
// 在for循环中,通过添加守卫来实现输出满足特定条件的数据
    for(e <- arr  if e % 2 == 0) { // for表达式中可以增加守卫
        println(e)
    }

    //高级for循环
    //每个生成器都可以带一个条件
    for(i <- 1 to 3; j <- 1 to 3 if i != j){ 
      print((10 * i + j) + " ")
}


    //for推导式:如果for循环的循环体以yield开始,则该循环会构建出一个集合
    //每次迭代生成集合中的一个值
    val v = for (i <- 1 to 10) yield i * 10
    println(v)
  }
}

两个生成器: to   until

1 to 10   生成  Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)    左闭右闭区间  [  ]

1 until 10 生成  Range(1, 2, 3, 4, 5, 6, 7, 8, 9)       左闭右开区间  [  )

for循环总结:

循环的标识: <-

增强for循环: for(I <- arr)

普通for循环: to   until

带守卫的for循环 (if 条件)

嵌套for循环 (for(i<- 1 to 3 ;j <- 1 to 3 if( I != j ))))

yield推导式  返回一个满足条件的数组

3.5.2 while循环

Scala 的 while 循环和其它语言如 Java 功能一样,它含有一个条件,和一个循环体,只要条件满足,就一直执行循环体的代码。

语法结构:while(condition){ 循环体内容 }

var i = 0
while(i<5) {
  println(i)
  i += 1  // i = i + 1
}

Scala 也有 do-while 循环,它和 while 循环类似,只是检查条件是否满足在循环体执行之后检查。

i = 0
// while 直接判断
while(i>0 && i<=5) {
  println(i)
  i += 1
}
i = 0
// do while 先执行一次循环,再进行判断
do{
  println(i)
  i += 1
}while(i>0 && i<=5)

 循环也有返回值,只不过都是Unit

val res = while(i>0 && i<=5) {
  println(i)
  i += 1
}
println(s"res = $res")

3.6 函数式编程再体验

map:对集合或者数组中的每一个元素进行操作,该方法接收一个函数,具体的业务逻辑是自己定义的。

filter: 过滤,过滤出满足条件的元素。

3.7 调用方法(运算符重载为方法)

Scala中的+ - * / %等操作符的作用与Java一样。只是有一点特别的:

这些操作符实际上是方法。操作符被重载为方法。

例如:

a + b

是如下方法调用的简写:

a.+(b)

a 方法 b可以写成 a.方法(b)

四 方法和函数(重难点)

方法:一段业务逻辑的综合。

4.1 定义方法

java中的方法:

public int add(int a,int b){
return a + b;
}

def methodName ([list of parameters]) : [return type] = {}

/**
  * 方法的定义及调用
  *
  * 定义方法的格式为:
  * def methodName ([list of parameters]) : [return type] = {}
  * 如果不使用等号和方法体,则隐式声明抽象(abstract)方法。
  */
object ScalaMethod extends App{

    // 定义个sum方法, 该方法有2个参数, 参数类型为整型, 方法的返回值为整型
    def sum(a:Int, b: Int): Int = {
        a + b
    }
   // 定义有可变参数的方法,
   def sumAll(b: Int*): Int = {
    var v = 0
    for (i<- b){
       v += i
     }
    v  // 返回值
   }


    // 调用
    val result1 = sum(1, 5)
    println(result1)
println(sumAll(1,11,13))

    // 该方法没有任何参数, 也没有返回值
    def sayHello1 = println("Say BB1")
    def sayHello2() = println("Say BB2")
    sayHello1 // 如果方法没有() 调用时不能加()
    sayHello2 // 可是省略(), 也可以不省略
}

如果有return关键字,必须要有返回值类型,否则报错如下:

方法总结:

  1. 定义方法的关键字,def    

格式: def   方法的名称(参数列表):返回值类型 = {方法体内容}

  1. 方法的返回值,最后一行的内容,如果是循环,那么返回值是Unit
  2. 如果空参方法,定义的时候有(),调用的时候可以省略(),但是如果定义的时候没有(),调用方法的时候,不能加()
  3. 方法的返回值类型,可以省略,但是特殊情况下,必须加上:

4.1,方法有return关键字

4.2,递归调用的方法。

5,方法不能最为最终的表达式存在,(空参的方法调用除外)

 4.2 定义函数

和方法类似,基本能实现相同的功能

val| var   函数名称=(函数的参数列表) =>  函数体

函数可以作为最终的表达式存在,返回的内容就是函数的签名

签名(函数的名称,函数的参数,函数的返回值类型)

这种定义方式不需要指定返回值类型,编译器会自动推断

第二种定义方式:

复杂全面的定义

val | var  函数名称:(输入参数类型)=> 返回值类型 = (参数的引用)=> 函数体

定义一个无参的函数

不同于方法,没有参数的函数定义,也必须加()

val f2:()=>Unit =() => println(123)  val f2 =() => println(123)  返回值类型为Unit

val f2:()=>Int =() => 123   val f2=() => 123  返回值类型为Int

4.3 方法和函数的区别

在函数式编程语言中,函数是“头等公民”,它可以像任何其他数据类型一样被传递和操作

区别和联系:

  1. 方法用def关键字定义,函数的标识 =>
  2. 方法不能作为最终的表达式存在,但是函数可以,返回函数的签名信息
  3. 方法和函数调用的时候都需要显示的传入参数
  4. 函数可以作为方法的参数,和返回值类型。

案例:首先定义一个方法,再定义一个函数,然后将函数传递到方法里面

object MethodAndFunctionTest {
  //定义一个方法
  //方法m2参数要求是一个函数,函数的参数必须是两个Int类型
  //返回值类型也是Int类型
  def m1(f: (Int, Int) => Int) : Int = {
    f(2, 6)
  }

  //定义一个函数f1,参数是两个Int类型,返回值是一个Int类型
  val f1 = (x: Int, y: Int) => x + y
  //再定义一个函数f2
  val f2 = (m: Int, n: Int) => m * n

  //main方法
  def main(args: Array[String]) {

    //调用m1方法,并传入f1函数
    val r1 = m1(f1)
    println(r1)

    //调用m1方法,并传入f2函数
    val r2 = m1(f2)
    println(r2)
  }
}

// 定义一个普通方法
def max(x:Int,y:Int) = if(x>y)x else y

// 定义一个方法,参数是一个函数,参数只需要函数签名,在调用的时候具体再传入函数体
def max1(f:(Int,Int)=>Int) = f(20,10)
def max2(f:(Int,Int) => Int,x:Int,y:Int)= f(x,y)

// 定义一个方法,方法返回值是函数
def max3()= (x:Int,y:Int)=> if (x>y) x else y
def main(args: Array[String]): Unit = {

  println(max(10,20))
  println(max1((x:Int,y:Int)=>if(x>y) x else y))
  println(max2((x:Int,y:Int)=>if(x>y) x else y,10,20))
  println(max3()(10,20))
}

 4.4 将方法转换成函数(神奇的下划线)

五 集合框架

5.1 集合综述

Scala的集合有三大类:序列Seq、集Set、映射Map,所有的集合都扩展自Iterable特质

在Scala中集合有可变(mutable)和不可变(immutable)两种类型(两个不同的包), 

包的全局路径:scala.collection.immutable   默认使用的是该包中的集合

如果想使用可变的集合,就需要导包        scala.collection.mutable

immutable类型的集合初始化后就不能改变了(注意与val修饰的变量进行区别)

使用val这个关键字修饰一个变量(相当于java中的final),那么就意味着该变量的引用不可变,该引用中的内容是不是可变,取决于这个引用指向的集合的类型

val 集合 ,该集合中的内容是否可变,取决于集合的特性。

5.2 集合框架整体架构(了解)

顶级抽象类或父类:

不可变集合(scala.collection.immutable):

可变集合(scala.collection.mutable):

六 数组

6.1 定长数组和变长数组

Nothing是所有类型的子类型,表示程序出错了。

Null ,所有引用类型的子类型,只有一个值,就是null

定长数组,是不可变的,长度不可变,内容可变,默认类型

变长数组,可变的,长度可变,内容可变

创建定长数组:

指定数组类型,并赋值,类型可省略,编译器会自动推导。

val arr1 = Array[Int](1,2,3,4,5)

通过new关键字创建的数组,必须指定数组的类型和长度。
val arr2 = new Array[Int](10)

取值赋值,下标从0开始:

arr1(0)

arr1(0) =100

object ArrayTest {

  def main(args: Array[String]) {

    //初始化一个长度为8的定长数组,其所有元素均为0
    val arr1 = new Array[Int](8)
    //直接打印定长数组,内容为数组的hashcode值
    println(arr1)
    //将数组转换成数组缓冲,就可以看到原数组中的内容了
    //toBuffer会将数组转换长数组缓冲
    println(arr1.toBuffer)

    //注意:如果不是new,相当于调用了数组的apply方法,直接为数组赋值
    //初始化一个长度为1的定长数组
    val arr2 = Array[Int](10)
    println(arr2.toBuffer)

    //定义一个长度为3的定长数组
    val arr3 = Array("hadoop", "storm", "spark")
    //使用()来访问元素
    println(arr3(2))

    //
    //变长数组(数组缓冲)
    //如果想使用数组缓冲,需要导入import scala.collection.mutable.ArrayBuffer包
    val ab = ArrayBuffer[Int](1,3,4)
val ab = new ArrayBuffer[Int]()
    //向数组缓冲的尾部追加一个元素
    //+=尾部追加元素
    ab += 1
    //追加多个元素
    ab += (2, 3, 4, 5)

ab -= (3, 4)
    //追加一个数组++=
    ab ++= Array(6, 7)
    //追加一个数组缓冲
    ab ++= ArrayBuffer(8,9)
//减少一个数组++=
    ab --= Array(6, 7)

    //在数组某个位置插入元素用insert,第一个参数为插入元素的位置,后面的可变参数为插入的元素
    ab.insert(0, -1, 0)
    //删除数组某个位置的元素用remove,第一个参数为要删除的元素位置,第二个参数为删除几个元素
    ab.remove(8, 2)
    println(ab)
// 清空
ab1.clear()
println(ab1)
  }
}

6.2 遍历数组

1.增强for循环

2.生成器 to或者until,0 until 10 包含0不包含10

object ForArrayTest {

  def main(args: Array[String]) {
    //初始化一个数组
    val arr = Array(1,2,3,4,5,6,7,8)
    //增强for循环
    for(i <- arr)
      println(i)

    //好用的until会生成一个Range
    //reverse是将前面生成的Range反转
    for(i <- (0 until arr.length).reverse)
      println(arr(i))
  }
}

    for(i <- (arr.length-1 until (-1,-1)))
      println(arr(i))
  }

6.3 数组转换

toArray 变长数组转换成定长数组

toBuffer 定长数组转换成变长数组

yield关键字将原始的数组进行转换会产生一个新的数组,原始的数组不变

 

object ArrayYieldTest {
  def main(args: Array[String]) {
    //定义一个数组
    val arr = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)
    //将偶数取出乘以10后再生成一个新的数组
    val res = for (e <- arr if e % 2 == 0) yield e * 10
    println(res.toBuffer)

    //更高级的写法,用着更爽
    //filter是过滤,接收一个返回值为boolean的函数
    //map相当于将数组中的每一个元素取出来,应用传进去的函数
    val r = arr.filter(x => x % 2 == 0).map(x => x * 10)
    println(r.toBuffer)
  }
}

6.4 数组常用方法

在Scala中,数组上的某些方法对数组进行相应的操作非常方便!

sorted默认是升序排序的。

arr.  然后输入Tab,可查看所有的方法。

val arr = Array(1,3,4,6,8)

数组反转:arr1.reverse

数组切片:arr.slice(1,4)    第一个起始位置,包含;第二个终止位置,不包含  [ )

七 元组Tuple

与数组或列表不同,元组可以容纳不同类型的对象,但它们也是不可变的。

元组是不同类型元素的集合

7.1 创建元组

定义元组时,使用小括号将多个元素括起来,元素之间使用逗号分隔,元素的类型可以不同,元素的个数任意多个(不超过22个)

注意:元组没有可变和不可变之分,都是不可变的。

val t = (12.3, 1000, "spark")

val t1 = new Tuple1(1) // 必须1个元素

val t4 = new Tuple4(1,2.0,"",3) // 必须4个元素

 7.2 获取元组中的值

获取元组的值使用下标获取,但是元组的下标时从1开始的,而不是0

7.3 将对偶的集合转换成映射

7.4 拉链操作

zip命令可以将多个值绑定在一起,生成元组

val name=Array("xx1","xx2","xx3",”xx4”)

val values=Array(1,2,3)

name.zip(values)

多个zip  # name zip values zip values.map(_*10)

zipWithIndex  类似于zip,自带了索引,索引是从0 开始的。

注意:如果两个数组的元素个数不一致,拉链操作后生成的数组的长度为较小的那个数组的元素个数

7.5 元素交换

可以使用 Tuple.swap 方法来交换对偶元组的元素。

// 定义元组
var t = (1, "hello", true)
// 或者
val tuple3 = new Tuple3(1, "hello", true)

// 访问tuple中的元素
println(t._2) // 访问元组总的第二个元素

// 对偶元组
val tuple2 = (1, 3)
// 交换元组的元素位置, tuple2没有变化, 生成了新的元组
val swap = tuple2.swap

元组类型Tuple1,Tuple2,Tuple3等等。目前Scala中只能有22个上限,如果需要更多个元素,那么可以使用集合而不是元组。

八 序列List

不可变list 长度不可变,内容也不可变 List

可变list 长度和内容都可变 ListBuffer

8.1 不可变序列

默认就是不可变序列,长度和内容都不可变

构造列表的两个基本单位是 Nil 和 ::

Nil 表示为一个空列表。

创建List集合的两种方式:

val list1= List[Int](1,2,3)

val list2 = 9::5::2::Nil

注意:: 操作符是右结合的,该操作符就是将给定的头和尾创建一个新的列表

如9 :: 5 :: 2 :: Nil相当于 9 :: (5 :: (2 :: Nil))

注意:在Scala中列表要么为空,要么是一个head元素加上一个tail列表。

列表的连接:

可以使用++ 或 ::: 运算符或或 List.concat() 方法来连接两个或多个列表

注意:对不可变List的所有的操作,全部生成新的List

object ImmutListTest {

  def main(args: Array[String]) {
    //创建一个不可变的集合
    val lst1 = List(1,2,3)
    //将0插入到lst1的前面生成一个新的List
    val lst2 = 0 :: lst1
    val lst4 = 0 +: lst1 

    //将一个元素添加到lst1的后面产生一个新的集合
    val lst6 = lst1 :+ 3

    val lst0 = List(4,5,6)

    //将2个list合并成一个新的List
    val lst7 = lst1 ++ lst0
val lst8 = lst1 ++: lst0
val lst9 = lst1 ::: lst0
val lst10 = List.concat(lst1,lst0)

// 列表反转
lst1.reverse

// 列表头元素
lst1.head
// 列表的尾列表
lst1.tail
  }	
}

List和Array的对比:

list不可变,表示长度不可变,内容也不可变

array       长度不可变,但是内容可变

不可变list和可变List的比较(List和ListBuffer):

不可变List,长度不可变,值不可变

可变List,长度可变,值可变

8.2 可变序列

ListBuffer   

需要显示导包 import scala.collection.mutable._

创建ListBuffer的两种方式:

val lb1 = ListBuffer[Int]() // 创建的同时可赋初值
val lb2 = new ListBuffer[Int]() // 类型必须显示指定

添加元素:

+=方法和append方法,都可以添加多个元素。

import scala.collection.mutable.ListBuffer

object MutListTest {
  def main(args: Array[String]) {
  //构建一个可变列表,初始有3个元素1,2,3
  val lst0 = ListBuffer[Int](1,2,3)
  //创建一个空的可变列表
  val lst1 = new ListBuffer[Int]
  //向lst1中追加元素,注意:没有生成新的集合
  lst1 += (4,6)
  lst1.append(5)

  //将lst0和lst1合并成一个新的ListBuffer 注意:生成了一个新集合
  val lst2 = lst0 ++ lst1

  //将lst1中的元素追加到lst0中, 注意:没有生成新的集合
  lst0 ++= lst1

  //将元素追加到lst0的后面生成一个新的集合
  val lst3 = lst0 :+ 5

   //将元素追加到lst0的前面
val lst4 = 5 +: lst0 
// 去除元素
lb2 -= (1,3)
lb2 --= List(7,9)
lb2.remove(1,2) //去除元素 第一个参数是下标,第二个参数的个数
// 判断集合是否为空 
Lb2.isEmpty()
}}

List的转换:

可变list可以通过toList方法,生成新的List

list可以通过toBuffer,转变成ArrayBuffer

九 映射Map

在Scala中,把哈希表这种数据结构叫做映射

映射是K/V对 类型的值。

可变的map,还是不可变的map,都是Map

9.1 定义map

在Scala中,有两种Map,一个是immutable包下的Map,该Map中的内容不可变;另一个是mutable包下的Map,该Map中的内容可变。

默认是immutable包下的map,

// 默认是immtable包下的Map

val mp1 = Map(("a",1),("b",2))
val mp2 = Map("a"->1,"b"->2)
// 添加元素之后生成新的map
val mp3 = mp2+("c"->1)

可使用mutable.Map

// 导包
import scala.collection.mutable
// 创建集合
val mp4 = new mutable.HashMap[String,Int]()
val mp5 = mutable.Map[String,Int]()

9.2 添加元素

针对可变集合,多种赋值方式

mp4 += ("e"->8, "f"->9)
mp4+= (("b1",121))
mp4.put("lyf",21)
mp4("nvshen")=18 

9.3 获取映射值

判断key是否存在 contains

mp4.contains(“b”)

mp4(“a”)

mp4.get(“xxoo”)

如果没有值,赋予默认值:

mp4.getOrElse(“xxoo”,9527)  

9.4 赋值和修改值

m1.getOrElse("d", 0)

m1("b") = 22

m1.updated("b",22) // 如果是不可变的Map,那么会生成一个新的map集合

9.5 删除元素

根据key来删除一个或多个值

m1 -= ("a")

m1.remove("a")

去除多个key:

m1 -= ("a","b")

m1 --= List(“key”)

9.6 map遍历

for(i <- mp) println(i)

for((k,v) <- mp){println(k)}

for((_,v) <- mp){println(v)}

_是占位符,如果只需要遍历value,不需要遍历key,就可以用占位符

交换k,v

for((k,v) <- mp) yield (v,k)

mp.map(x=>(x._2,x._1))

mp.map(x=>x.swap)

9.7 获得keys和values

合并

使用 ++ 运算符或 mp.++() 方法来连接两个 Map,Map 合并时会移除重复的 key。

// 合并时,相同的key元素会被覆盖
val colors1 = Map("nvshen" ->18,"nanshen" -> 35)
val colors2 = Map("bq" -> 40,"nvshen" -> 20)
var colors = colors1 ++ colors2

10 map|flatten|flatMap|foreach方法的使用

map   迭代集合中的每一个元素,然后按照自己的逻辑来修改元素

map方法操作集合中的每一个元素,返回值类型。

map方法有一个参数,是函数类型,该函数作用于集合的每一个元素上,对集合的每一个元素执行操作。有返回值。

map方法的内层返回值,是由函数的返回值决定的。

flatten   压平  如果有嵌套集合,就会把内层的嵌套去掉了

flatMap  先map 再flatten   先map 再压平

foreach   对每一个元素执行操作,相当于遍历  println   返回值是Unit

filter 过滤

map和foreach的区别:

1,底层实现不同,map方法利用隐式转换实现的Builder,foreach底层使用的是迭代器。

2,返回值不同,map返回值类型一般由函数返回值类型决定(map方法的参数就是一个函数),foreach返回值为Unit

怎么选择?如果要求有返回值,就用map,如果是打印或者没有返回值的要求,使用foreach。

// 定义一个数组
val array = Array[Int](2,4,6,9,3)
// map方法是将array数组中的每个元素进行某种映射操作, (x: Int) => x * 2 为一个匿名函数, x 就是array中的每个元素
val y = array map((x: Int) => x * 2)
// 或者这样写, 编译器会自动推测x的数据类型
val z = array.map(x => x*2)
// 亦或者, _ 表示入参, 表示数组中的每个元素值
val x = array.map(_ * 2)

println(x.toBuffer)


println("--------分割线--------")

// 定义一个数组
val words = Array("hello tom hello jim hello jerry", "hello Hatano")

// 将数组中的每个元素进行分割
// Array(Array(hello, tom, hello, jim, hello, jerry), Array(hello, Hatano))
val splitWords: Array[Array[String]] = words.map(wd => wd.split(" "))

// 此时数组中的每个元素经过split之后变成了Array, flatten是对splitWords里面的元素进行扁平化操作
// Array(hello, tom, hello, jim, hello, jerry, hello, Hatano)
val flattenWords = splitWords.flatten


// 上述的2步操作, 可以等价于flatMap,  意味先map操作后进行flatten操作
val result: Array[String] = words.flatMap(wd => wd.split(" "))

// 遍历数组, 打印每个元素
result.foreach(println)

11 set

11.1 不可变的set

长度和值都不可变,set中的元素不能重复

object ImmutSetTest {
  def main(args: Array[String]) {
  // 默认是immtable包下的Set
val set1 = Set (1,4,6)
// 执行添加,删除的操作,都是生成了新的Set集合
val s2: Set[Int] = set1 + (10,12)
set1 - (1)
println(set1)

  // 查看set集合内容
set1.foreach(println)

  //set中元素不能重复
  val set3 = set1 ++ Set(5, 6, 7)
  val set0 = Set(1,3,4) ++ set1
  }
}

11.2 可变的Set

可变Set中,remove方法,移除的是元素,而不是下标

ListBuffer中,remove方法,参数是下标

import scala.collection.mutable

object MutSetTest {
  def main(args: Array[String]) {
    import scala.collection.mutable.Set // 可以在任何地方引入 可变集合
    val mSet = new mutable.HashSet[Int]()
    val mutableSet = Set(1,2,3)

    mutableSet.add(4)
//    mutableSet += 5
    mutableSet += (5,15)
    // 添加set集合
    mutableSet ++= Set(12,14)
//    mutableSet -= 4
    mutableSet -= (4,2)

    // remove方法,删除的不是下标,而是元素
    mutableSet.remove(2)
    println(mutableSet)
    // 转换为不可变集合
    val another = mutableSet.toSet
    println(another.getClass.getName) // scala.collection.immutable.Set
}
}

12 Map和Option

Option 表示可以有值,也可能没有值的返回类型,有两个子类,

Some,有值的结果  Some(值)   想要获取值,使用 get方法

None,没有值

getOrElse

在Scala中Option类型样例类用来表示可能存在或也可能不存在的值(Option的子类有Some和None)。Some包装了某个值,None表示没有值。

/ OptionSomeNone的父类
// Some代表有(多例),样例类
// None代表没有(单例),样例对象

val mp = Map("a" -> 1, "b" -> 2, "c" -> 3)
val r: Int = mp("d")

// Map get方法返回的为Option, 也就意味着 rv 可能取到也有可能没取到
val rv: Option[Int] = mp.get("d")

// 如果rv=None时, 会出现异常情况
val r1 = rv.get

// 使用getOrElse方法,
// 第一个参数为要获取的key,
// 第二个参数为默认值, 如果没有获取到key对应的值就返回默认值
val r2 = mp.getOrElse("d", -1)

println(r2)

13 集合常用方法

map 

filter 

过滤出满足条件的所有元素,并返回一个集合

find

过滤出满足条件的一个元素,并返回一个Option

如果说有结果值,返回值的是Some(元素值),通过get方法来获取值

sorted

按元素的升序排序

sortBy

按照指定的条件排序

sortWith

接收两个参数,并进行比较

mapValues

类似于map,只是处理的是k-v类型中的v值,只能作用于map类型集合

groupBy

按照指定条件分组

grouped

按照指定元素个数进行分组

count

统计满足条件的元素个数

reduce  *****

元素归并

参数是一个函数,这个函数有两个参数   累加值  元素值   调用的就是reduceLeft

val arr=Array("aa","bb","cc","dd")

arr.reduce((x,y)=>自定义操作)

自定义操作的方法,必须是x数据类型上支持的方法。x可以是任意类型

arr.reduce(_ + _)

arr.reduce(_ ++ _)

val lst = List(List("a"),List("b"),List("c"))

lst.reduce(_++_)

lst.reduce(_:::_)

reduceLeft reduceRight

reduce底层调用的就是reduceLeft,只不过,reduce要求函数的输入类型和返回值类型必须一致,而reduceLeft,可以不一致。

fold foldLeft foldRight

fold有两个参数,第一个参数是默认值,第二个参数是一个函数,该函数有两个参数 累加值  元素值   调用的就是reduceLeft

fold 要求函数的2个输入参数类型必须一致,foldLeft 可以允许函数的2个输入参数类型不一致

示例:求平均值

val d1 = Array(("bj",28.1), ("sh",28.7), ("gz",32.0), ("sz", 33.1))
val d2 = Array(("bj",27.3), ("sh",30.1), ("gz",33.3))
val d3 = Array(("bj",28.2), ("sh",29.1), ("gz",32.0), ("sz", 30.5))

// 1,需要把数据组装到一起
 val data1: Array[(String, Double)] = d1.union(d2).union(d3)  // d1 union d2 union d3
   //d1 ++ d2 ++ d3

// 2 分组   按照城市名称来分组
val data2: Map[String, Array[(String, Double)]] = data1.groupBy(t=>t._1)

// 统计
val data4 = data2.mapValues({
  kv=>
    // kv数据类型: Array[(String,Double)]
    // t的数据类型是元组(String,Double)
   
})
println("--------111------------")
println(data4)


// 3 统计  拿到这几个月份的温度的总值,然后再求平均
val result: Map[String, Double] = data2.map {
  t =>
    // t   (String,Array[(String, Double)])
    val city = t._1
    // foldLeft 第一个是累加值  第二个是元素值
    val wendu: Double = t._2.foldLeft(0d)({
          // total是Double   a是(String, Double)
      (total, a) =>
        total + a._2
    }) / t._2.length
    (city, wendu)
}
println(result)

交集,并集 差集

intersect  union  diff

union是一个轻量级的方法

//比较常用的方法:先union,然后再分组

distinct 元素去重

mkString 把集合中的所有元素拼接成字符串

mkString(分隔符)

take(n) 获取集合中前几个元素  没有排序

slice(from,until) 截取元素,提取元素列表中的from 到until位置的元素

聚合  aggregate

val arr = List(List(1, 2, 3), List(2))

val result = arr.aggregate(0)(_+_.sum, _+_)

14 面向对象

14.1 对象

14.1.1 单例对象

Scala中没有静态方法和静态字段,没有static,

java中,没有关键字修饰的方法,只能用new class().方法

so 对于一个class来说,所有的方法和成员变量在实例被 new 出来之前都无法访问

虽然可以在class中定义main方法,然并卵…

但是可以使用object这个语法结构来达到同样的目的

object关键字修饰的对象是单例的,称为单例对象,静态对象。

//单例对象
object ScalaSingleton {
    def saySomething(msg: String) = {
        println(msg)
    }
}
object test {
    def main(args: Array[String]): Unit = {
        ScalaSingleton.saySomething("singleton....")
        println(ScalaSingleton)
        println(ScalaSingleton)
        // 输出结果:
        // cn.demo.ScalaSingleton$@28f67ac7
        // cn.demo.ScalaSingleton$@28f67ac7
    }
}

14.1.2 伴生对象

伴生对象是一种特殊的单例对象。是一种相对概念,需要满足两个条件:

条件1:在同一个源文件中,

条件2:对象名和类名相同

这样的单例对象,被称作是这个类的伴生对象。类被称为是这个单例对象的伴生类

结论:类和伴生对象之间可以相互访问私有的方法和属性

class Dog {
  val id = 1
  private var name = "xiaoqing"

  def printName(): Unit ={
    //在Dog类中可以访问伴生对象Dog的私有属性
    println(Dog.CONSTANT + name )
  }
}

/**
  * 伴生对象
  */
object Dog {

  //伴生对象中的私有属性
  private val CONSTANT = "汪汪汪 : "

  def main(args: Array[String]) {
    val p = new Dog
    //访问私有的字段name
    p.name = "123"
    p.printName()
  }
}

14.1.3 apply方法

通常我们会在类的伴生对象中定义apply方法,当遇到对象名(参数1,...参数n)时apply方法会被调用

正常情况下,对象调用时是不能带参数的,但是如果能找到对应的apply方法,就能调用成功。

当使用对象(参数列表)来调用对象时,会去对象中找对应参数的apply方法,如果找到就执行相应的逻辑,如果找不到,就报错。

注意:只能找到和参数列表对应的apply方法。

要和对象区分开来

ApplyDemo   // 对象

ApplyDemo() // ApplyDemo.apply()   方法

该语法的目的:不需通过new关键字,更方便的完成类和实例对象的初始化。

object ApplyDemo {
def apply(msg:String) = {
print(s"主食 油泼面,小菜:$msg")
}
def apply(i:Int):Int = {
i * i
}


  def main(args: Array[String]) {
    //调用了Array伴生对象的apply方法
    //def apply(x: Int, xs: Int*): Array[Int]
    //arr1中只有一个元素5
    val arr1 = Array(5)
    println(arr1.toBuffer)

    //new了一个长度为5的array,数组里面包含5个null
    var arr2 = new Array(5)

  println(ApplyDemo("海参炒面"))
    println(ApplyDemo.apply("油炸煎饼"))
println(ApplyDemo(1))
  }
}

14.1.4 应用程序对象

Scala程序都必须从一个对象的main方法开始,可以通过扩展App特质,不写main方法。

object AppObjectDemo extends App{
  //不用写main方法
  println("I love you Scala")
}

14.2 类

类和对象,应该怎么使用???

优先使用对象,如果需要构造器,那么只能使用类。

如果需要封装数据的时候,Person,需要使用到类。

object,class   伴生类

14.2.1 类的定义

在Scala中,类并不用声明为public。

Scala源文件中可以包含多个类,所有这些类都具有公有可见性。

var 修饰的变量, 这个变量对外提供getter setter方法
val 修饰的变量,是只读属性 对外提供了getter方法,没有setter(相当于java中用final修饰的变量)

class Student {

  val id = 666

    // _ 表示一个占位符, 编译器会根据变量的具体类型赋予相应初始值
    // 注意: 使用_ 占位符是, 变量类型必须指定
    var name: String = _

  //用var修饰的变量既有getter又有setter
  var age: Int = 20
}

object Test{
    val name: String = "zhangsan"
    def main(args: Array[String]): Unit = {

        // 调用空参构造器, 
        val student = new Student()
        student.name = "laowang"

        // 类中使用val修饰的变量不能更改
        // student.age = 20

        println(s"student.name ====== ${student.name} ${student.age}")
        println("Test.name ======" + Test.name)
    }
}

14.2.2 构造器

1,主构造器

2,辅助构造器

同java中的构造方法

构造器分为两类:主构造器,辅助构造器

主构造器直接在类名后面定义。

每个类都有主构造器,主构造器的参数直接放置类名后面,与类交织在一起。

如果没有定义构造器, 类会有一个默认的空参构造器
辅助构造器自定义,使用def this关键字,而且必须调用主构造器,或者其他的辅助构造器

注意:主构造器会执行类定义中的所有语句

/**
  *每个类都有主构造器,主构造器的参数直接放置类名后面,与类交织在一起
  */
class Person(val name: String, val age: Int){
  //主构造器会执行类定义中的所有语句
  println("执行主构造器")

  private var gender = "male"

  //用this关键字定义辅助构造器
  def this(name: String, age: Int, gender: String){
    //每个辅助构造器必须以主构造器或其他的辅助构造器的调用开始
this(name, age)
    println("执行辅助构造器")
    this.gender = gender
  }
  var account1:String=_

  def this(name:String,age:Int,gender:String){
    this(name,age)
    this.gender=gender
  }
  def this(name:String,age:Int,gender:String,account:String){
    this(name,age,gender)
    this.account1=account
  }

    println("尼玛,这里还是主构造器")

}

object Person {
    def main(args: Array[String]): Unit = {
        val s = new Person ("laoduan", 38)
        println(s"${s.name} ${s.age}")

        val s1 = new Person (“dingding", 18, "female")
        println(s"${s1.gender}")
val p2 =new Person("xx2",12,"female","9527")
println(s"${p2.age},${p2.account1}")
    }
}

scala的构造器

主构造器和辅助构造器

创建对象的同时对属性赋值,初始化赋值

主构造器,和类的定义交织在一起,如果主构造器的变量使用了val或var修饰,变成了类的一个成员属性。

辅助构造器,def this(参数列表)

辅助构造器的第一行,必须调用其他的构造器(主,辅助。。。)

辅助构造器和主构造器的参数列表,不能完全一致,参数类型和个数一致。

如果想定义空参的辅助构造器,调用的主构造器的参数必须赋值(初始化)

作用域范围:

主: 类中除了属性和方法之外,都是构造器的作用域

辅助: 只在自己的方法范围之内

14.2.3 访问权限

成员变量的访问权限

默认权限是public 任何地方都可以访问

private 作用域 类和其伴生对象中

private [this] ,作用域为当前类中,伴生对象中无效  

private [packageName]  指定包及其子包有效

/*
*   private var age
*   age 在这个类中是有getter setter方法的
*   但是前面如果加上了private 修饰, 也就意味着, age只能在这个类的内部以及其伴生类对象中可以访问修改
*   其他外部类不能访问
* */
class Student3 private (val name: String, private var age: Int) {

    var gender: String = _

    // 辅助构造器, 使用def this
    // 在辅助构造器中必须先调用类的主构造器
    def this(name: String, age:Int, gender: String){
        this(name, age)
        this.gender = gender
    }
    // private[this]关键字标识该属性只能在类的内部访问, 伴生类不能访问
    private[this] val province: String = "北京市"

    def getAge = 18
}

// 类的伴生对象
object Student3 {
    def main(args: Array[String]): Unit = {
        // 伴生对象可以访问类的私有方法和属性
        val s3 = new Student3("Angelababy", 30)
        s3.age = 29
        println(s"${s3.age}")
        // println(s"${s3.province}") 伴生类不能访问
    }
}

方法的访问权限

通用于主构造器,辅构造器,以及普通方法

默认权限是共有的

private 作用域为类和其伴生对象

private [this] ,作用域为当前类中,伴生对象中无效  

private [packageName]  指定包及其子包有效   包名的写法,直接写报名,不需要层级路径

主构造器上一样适用于该方法的访问权限

private [cn.ed.day03]    错误的   

private [day03] 正确的

/*
*   private 加在主构造器前面标识这个主构造器是私有的, 外部不能访问这个构造器
* */
class Student2 private (val name: String, var age: Int) {
    var gender: String = _

    // 辅助构造器, 使用def this
    // 在辅助构造器中必须先调用类的主构造器
    def this(name: String, age:Int, gender: String){
        this(name, age)
        this.gender = gender
    }
}
object Student2 {
    def main(args: Array[String]): Unit = {
        val s1 = new Student2("laoYang", 18, "male")
        println(s"${s1.gender}")
    }
}

类包的访问权限

private 作用域为当前包及其子包  同 private [this]

private [packageName] 作用域为指定包及其子包

/*
* private[包名] class 放在类最前面, 是修饰类的访问权限, 也就是说类在某些包下不可见或不能访问
* private[edu360] class 代表student4在edu360包下及其子包下可以见, 同级包中不能访问
* */
private[this] class Student4(val name: String, private var age: Int) {
    var xx: Int = _
}
object Student4{ 
    def main(args: Array[String]): Unit = {
        val s = new Student4("张三", 20)

        println(s"${s.name}")
    }
}

14.2.4 抽象类

在Scala中, 使用abstract修饰的类称为抽象类. 在抽象类中可以定义属性、未实现的方法(抽象方法)和具体实现的方法。

/*
* abstract修饰的类是一个抽象类
* */
abstract class Animal {
    println("Animal's constructor ....")
    // 定义一个name属性
    val name: String = "animal"
    // 没有任何实现的方法
    def sleep()
    // 带有具体的实现的方法
    def eat(f: String): Unit = {
        println(s"$f")
    }}

类和对象,该怎么选择?

可以认为,对象本质上拥有类的所有特性,但是object没有构造器,必须是无参的。

如果不需要构造器封装数据,都优先使用object。

14.3 特质Trait

scala中没有interface   implements  

Trait(特质)相当于 java的接口。比接口功能更强大。特质中可以定义属性和方法的实现。

Scala的类只能够继承单一父类,但是可以实现(继承,混入)多个特质(Trait)使用的关键字是 with和extends

特质不能有任何的类参数,即传递给类的主构造器的参数。

trait PointTest(x: Int, y: Int) // 编译不过

trait T1 {
  // 定义普通方法,有方法实现
  def youcanfly()={
    println("tai feng lai le you can fly")
  }
}

trait T2 {
    // 定义一个属性
    val className: String = "NB大神班"

    // 定义一个没有实现的方法,默认就是抽象方法
    def teacherSay(name: String)

    // 定义带有具体的实现的方法
    def doSomething() = {
        println("群主开始发红包了...")
    }
}

动态混入特质

object test{

  def main(args: Array[String]): Unit = {

    // 动态混入特征,让类有了特质的方法
    val t1 = new Teacher with T1
    println(t1.youcanfly())

    // 动态混入特质不能使用extends关键字,可同时混入多个特质
    val t = new Teacher() with T1 with T2{
      // 如果特质中有抽象方法,则必须重写该抽象方法,可以不使用override关键字
      def teacherSay(name:String)={
        println(s"最高face,${name}")
      }
    // 重写一个有具体的实现的方法,必须使用关键字override
   override def doSomething() = {
        println("群主:抢到红包继续接龙...")
    }
    }
    println(t.teach("laozhao"))
    println(t.doSomething)
    println(t.youcanfly())
  }
}
class Teacher{
}

比较:scala的trait和java中的interface的异同?

1,java的interface只定义方法名称和参数列表,不能定义方法体。而trait则可以定义方法体。

2,在java中实现接口用implements,而在scala中,实现trait用extends和with。

3,java的interface和scala的trait的最大区别是,scala可以在一个class实例化的时候动态混入trait。

特质还是用抽象类?

1,优先使用特质。一个类可以扩展多个特质,但却只能扩展一个抽象类。

2,如果需要构造方法,使用抽象类。因为抽象类可以定义带参数的构造器,而特质不行。

14.4 继承

14.4.1 扩展类

在Scala中扩展类的方式和Java一样都是使用extends关键字

14.4.2 重写方法

在Scala中重写一个非抽象的方法必须使用override修饰符

14.4.3 示例

/ 定义一个抽象类
abstract class Animal(val age: Int) {
  println("Animal`s main constructor invoked" + age)
  //定义一个抽象方法
  def run()
  def breath(): Unit = {
    println("呼吸氧气")
  }
}

// 定义一个特质,定义一个普通方法
trait Fightable {
  def fight(): Unit = {
    println("用嘴咬")
  }
}

// 定义一个特质,一个抽象方法
trait Flyable {
// 定义一个抽象方法
   def fly()
}


定义一个bird类,实现多个特质
//在scala中,不论是继承还是实现特质,第一个都用extends关键字
class Bird extends Flyable with Fightable {
  override def fly(): Unit = {
    println("用翅膀飞")
  }
}

object Bird {
  def main(args: Array[String]): Unit = {
    val b = new Bird
    b.fly()
  }
}

定义一个类,继承类,并实现多个trait
class Monkey(age: Int) extends Animal(age) with Flyable with Fightable {
  //重写抽象的方法, 可以不加override关键字
  def run(): Unit = {
//    super()
    println("跳着跑")
  }

  //重写非抽象的方法,必须加override关键字
  override def breath(): Unit = {
    println("猴子呼吸")
  }

  override def fly(): Unit = {
    println("乘着筋斗云飞")
  }

  override def fight(): Unit = {
    println("用棒子打")
  }
  println("monkey`s main constructor invoked" + age)
}

object Monkey {
  def main(args: Array[String]): Unit = {
    val a: Animal = new Monkey(100)
    a.breath()
  }
}

14.4.4 总结

在继承抽象类或者特质的时候,继承抽象类使用extends,实现特质用with和extends,当类只实现了特质的时候,第一个关键字必须是extends

实现类还是特质,抽象方法都必须被实现,而且可以不使用override关键字

重写非抽象方法,必须使用override关键字

特质支持动态混入,在类实例化的时候,可以通过with关键字混入特质。

特质不能有构造器,抽象类可以有。

样例类/样例对象

样例类:使用case关键字 修饰的类,重要的特征就是支持模式匹配,多例

样例object:使用case关键字修饰的对象,支持模式匹配,单例

case class 和 class的一些区别:

case class在初始化的时候,不用new,而普通类初始化时必须要new。

case class 重写了toString方法。

默认实现了equals和hashCode

case class 实现了序列化接口

case class 支持模式匹配(最重要的特征),所有case class 必须要有参数列表

有参数用case class,无参用case object

/*
* 样例类,使用case 关键字 修饰的类, 其重要的特征就是支持模式匹配
* */
case class Message(msg: String)

/**
  * 样例object, 不能封装数据, 其重要特征就是支持模式匹配
  */
case object CheckHeartBeat

object TestCaseClass extends App{
    // 可以不使用new 关键字创建实例 
    val msg = Message("hello")
    println(msg.msg)
}

15 模式匹配

Scala有一个十分强大的模式匹配机制,可以应用到很多场合:如switch语句、类型检查等。

并且Scala还提供了样例类,对模式匹配进行了优化,可以快速进行匹配

模式匹配就是match  和 一系列的case 语法实现

其中,每一个case 里面就是一个匿名函数  =>

模式匹配的基本关键字 就是  match  case

15.1 匹配字符串

import scala.util.Random

object CaseDemo01 extends App{
  val arr = Array("YoshizawaAkiho", "YuiHatano", "AoiSola")
  val name = arr(Random.nextInt(arr.length))
  name match {
    case "YoshizawaAkiho" => println("xx老师...")
    case "YuiHatano" => println("oo老师...")
    case _ => println("真不知道你们在说什么...")
  }
}

15.2 匹配类型

import scala.util.Random

object CaseDemo02 extends App{
  //val v = if(x >= 5) 1 else if(x < 2) 2.0 else "hello"
  val arr = Array("hello", 1, 2.0, CaseDemo2)
  val v = arr(Random.nextInt(arr.length))
  println(v)
  v match {
    case x: Int => println("Int " + x)
    case y: Double if(y >= 0) => println("Double "+ y) // if 守卫
    case z: String => println("String " + z)
case CaseDemo02 => {
    println("case demo 2")
    //throw new Exception("not match exception")
  }
    case _ => throw new Exception("not match exception")
  }
}

注意case y: Double if(y >= 0) => ...

模式匹配的时候还可以添加守卫条件。如不符合守卫条件,将掉入case _中

15.3 匹配数组,元组,集合

object CaseDemo03 extends App{

  val arr = Array(1, 3, 5)
  arr match {
    case Array(1, x, y) => println(x + " " + y)
    case Array(0) => println("only 0")
    case Array(0, _*) => println("0 ...")
    case _ => println("something else")
  }

  val lst = List(3, -1)
  lst match {
    case 0 :: Nil => println("only 0")
    case x :: y :: Nil => println(s"x: $x y: $y")
    case 0 :: tail => println("0 ...")
    case _ => println("something else")
  }

  val tup = (2, 3, 5)
  tup match {
    case (2, x, y) => println(s"1, $x , $y")
    case (_, z, 5) => println(z)
    case  _ => println("else")
  }
}

15.4 样例类

在Scala中样例类是一中特殊的类,可用于模式匹配。case class是多例的,后面要跟构造参数,case object是单例的,无需参数

import scala.util.Random

case class SubmitTask(id: String, name: String)
case class HeartBeat(time: Long)
case object CheckTimeOutTask

object CaseDemo04 extends App{
  val arr = Array(CheckTimeOutTask, HeartBeat(12333), SubmitTask("0001", "task-0001"))

  arr(Random.nextInt(arr.length)) match {
    case SubmitTask(id, name) => {
      println(s"$id, $name")
    }
    case HeartBeat(time) => {
      println(time)
    }
    case CheckTimeOutTask => {
      println("check")
    }
  }
}

15.5 Option类型

在Scala中Option类型样例类用来表示可能存在或也可能不存在的值(Option的子类有Some和None)。Some包装了某个值,None表示没有值

object OptionDemo {
  def main(args: Array[String]) {
    val map = Map("a" -> 1, "b" -> 2)
    val v = map.get("b") match {
      case Some(i) => i
      case None => 0
    }
    println(v)
    //更好的方式
    val v1 = map.getOrElse("c", 0)
    println(v1)
  }
}

15.6 偏函数

被包在大括号内没有match的一组case语句是一个偏函数,它是PartialFunction[A, B]的一个实例,A代表输入参数类型,B代表返回类型,常用作输入模式匹配

object PartialFuncDemo  {

  def func1(num: String) : Int = num match {
    case "one" => 1
    case "two" => 2
    case _ => -1
  }
  def func2: PartialFunction[String, Int] = {
    case "one" => 1
    case "two" => 2
    case _ => -1
  }
  def main(args: Array[String]) {
    println(func1("one"))
    println(func2("one"))
  }
}

偏函数本质上是由多个case语句组成的针对每一种可能的参数分别进行处理的一种“结构较为特殊”的函数,就是一个参数的函数。(Function1

object PartialFunctionDemo2 {
  def f:PartialFunction[Any,Int]={
    case i:Int => i *10
case _ => i *10
  }
  def main(args: Array[String]): Unit = {

    val arr = Array(1,3,5,"seven")
    //    arr.map{case t:Int =>t*10}
    val collect: Array[Int] = arr.collect {
      case t: Int
      => t * 10
    }
    println(collect)

    arr.collect(f).foreach(println)
  }
}

偏函数最常用的就是方法中要求传入偏函数的类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大数据私房菜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值