scala基本入门

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了scala基本入门相关的知识,希望对你有一定的参考价值。

[TOC]

一、scala概述

1.1 简介

? scala是一种多范式的编程语言,其设计的初衷是要集成面向对象编程和函数式编程的各种特性。Scala运行于Java平台(Java虚拟机),并兼容现有的Java程序。它也能运行于CLDC配置的Java ME中。目前还有另一.NET平台的实现,不过该版本更新有些滞后。Scala的编译模型(独立编译,动态类加载)与Java和C#一样,所以Scala代码可以调用Java类库(对于.NET实现则可调用.NET类库)。

1.2 scala安装和配置

? scala运行于JVM之上,所以得先安装jdk,安装过程看之前的文章吧。先到<http://www.scala-lang.org/>下载scala安装包。这里我用的版本是 scala2.11.8。windows上安装基本就是点点点,这里就不演示了。安装好之后就默认就配置好了PATH环境变量。
? 在Linux上安装的话,就下载对用的 .tgz包,然后解压到指定目录。配置PATH环境变量,这过程基本就是这样。最后在命令行下输入 scala,成功进入scala命令行即为成功(和Linux安装jdk一毛一样)

二、scala基础

要注意一点,就是在scala中,任何数据都是对象,可以通过数据本身调用一些方法,而在java中需要将数据赋值给一个引用变量之后,通过引用变量才能调用方法。如

"1".toInt()     这个在scala中是可以运行的,但是类似这样的方式不能在java中运行

2.1 常用数据类型

2.1.1数值类型:

Byte,Short,Int,Long,Float,Double

Byte:  8位有符号数字,从-128 到 127
Short: 16位有符号数据,从-32768 到 32767
Int:  32位有符号数据
Long: 64位有符号数据

例子:scala命令行中定义一个Int变量
val a:Byte = 10
a+10
得到:res9: Int = 20
这里的res9是新生成变量的名字

2.1.2 字符类型

Char和String,前者是单字符,后者是字符串

对于字符串,在scala中可以进行插值操作,如:
scala> val a="king"
a: String = king

scala> s"my name is ${a}"
res1: String = my name is king
注意:前面有个s;相当于执行:"My Name is " + s1

2.1.3 Unit类型

相当于java中的void类型,一般常用于函数方法的返回值类型

2.1.4 Nothing类型

一般是执行过程中,产生的异常exception的类型就是nothing

2.2 变量的声明和使用

var 变量名:类型=value
val 变量名:类型=value

其中类型可以省略,scala会根据value的类型自动推导变量的类型的。
例子:
var a:Int=8

var和val的区别:
val引用指向的内存地址不能改变,但是里面的内容可变。
var引用指向的内存地址可变。而且定义时,必须初始化
如:
scala> val a=2
a: Int = 2

scala> a=3
<console>:12: error: reassignment to val
       a=3
        ^

scala> var b=2
b: Int = 2

scala> b=3
b: Int = 3
可以看到,没办法给a重新赋值

2.3 函数的初步使用

2.3.1 scala内置函数

scala有许多内置函数,可以直接使用,比如 scala.math 包下的各种数学函数

import scala.math._  导入math下的所有函数
scala> import scala.math._
import scala.math._

scala> max(2,3)
res0: Int = 3

2.3.2 自定义函数

def 函数名称 ([参数名:参数类型]*) : 返回值类型 = {}
例子:
1、求和

scala> def sum(x:Int,y:Int) : Int = x+y
sum: (x: Int, y: Int)Int

scala> sum(10,20)
res4: Int = 30

scala> var a = sum(10,20)
a: Int = 30

2、求阶乘,采用递归方式
scala> def myFactor(x:Int) : Int = {
| //实现
| if(x<=1)
| 1
| else
| x*myFactor(x-1)
| }
myFactor: (x: Int)Int

scala> myFactor(3)
res5: Int = 6

注意:没有return语句的话,函数的最后一句话就是函数的返回值。
上面函数有分支,1 和 x*myFactor(x-1) 都有可能是函数的最后一句话。

2.4 scala条件语句和循环语句

2.4.1 条件判断语句if/else:

if (判断条件) {}
else if (判断条件) {}
else {}

2.4.2循环语句:

for循环

//定义个列表,方便讲解操作for循环
var list = List("Mary","Tom","Mike")

println("-----for循环第一种写法-------")
for( s <- list) println(s)
// <-    代表scala中的提取符,把list中的每一个元素都提取出来,赋给s

println("-----for循环第二种写法-------")
//打印名字长度大于3 加判断。可以加上判断
for{
    s <- list
    if(s.length > 3)
} println(s)

println("-----for循环第三种写法-------")
//打印名字长度小于等于3 加判断
for(s <- list if s.length <= 3 ) println(s)

println("-----for循环第四种写法-------")
//使用yield关键字 作用:产生一个新的集合
//把list中的每个元素都变成大写,返回一个新的集合。
var newList = for{
    s <- list
    s1 = s.toUpperCase //把名字变成大写
} yield (s1)  //使用yield将处理后的元素变成新的集合

for( s <- newList) println(s)

while循环:

println("-----while循环写法-------")
    //定义一个循环变量
    var i = 0
    while(i < list.length){
      println(list(i))
      //自增
      //i++ 这种自增在scala是不可行的
      i += 1
    }

println("-----do while循环写法-------")
    //定义循环变量
    var j = 0
    do{
      println(list(j))
      j+=1
    } while (j < list.length)

foreach函数迭代:

println("-----foreach用法-------")
    list.foreach(println)//相当于 for( s <- list) println(s)

    /**
      * foreach说明
      *
      * foreach相当于循环
      * list.foreach(println) 使用了高阶函数(函数式编程)
      *
      * 还有一个循环 map
      * foreach和map的区别:
      * foreach没有返回值,map有返回值
      * spark中类似。
      */
第二种foreach写法:
list.foreach{
    case xxxxx
}
直接内部使用case进行模式匹配

2.4.3 嵌套循环和break语句

/**
      * 题目:判断101-200之间有多少个素数
      *
      * 程序分析:
      * 判断素数的方法:
      * 用一个数分别去 除 2 到sqor(这个数),如果能被整除,则表明此数不是素数,反之是素数
      * 举例:101  2~根号(101)
      *
      * 程序实现方法:
      * 定义两层循环
      *   第一层 101-200
      *     第二层 2 - 根号(第一层)
      *       判断能被整除,则不是素数
      *
      */

    println("-----循环嵌套-------")
    var count : Int = 0 //保存素数的个数
    var index_outer : Int = 0 //外层循环变量
    var index_inner : Int = 0 //内层循环变量
    //until 语句,x until y表示x到y,不包括y
    for (index_outer <- 101 until 200 ){
      var b = false  //标示是否可以被整除

      breakable{
        index_inner = 2
        while (index_inner <= sqrt(index_outer)){
          if(index_outer % index_inner == 0){
            //可以被整除
            b = true
            break
          }
          index_inner += 1
        }
      }

      if (!b){
        count +=1
      }
    }
    println("个数为: " + count)

break用法:

将需要break的语句块用 breakable{} 包含起来,然后里面使用break语句进行break

2.4.4 嵌套循环实现冒泡排序

/**
      * 冒泡排序
      * https://www.cnblogs.com/morethink/p/8419151.html#%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F
      *
      * 算法分析:
      * 1、比较相邻元素,如果第一个比第二个大,就交换位置
      * 2、对每一对相邻元素做相同的工作,这一步做完后,最后的元素会是最大的数
      * 3、针对所有元素,重复以上步骤。
      *
      * 程序分析:
      * 1、两层循环
      * 2、外层循环控制比较的次数
      * 3、内层循环控制到达位置,也就是结束比较的位置
      */

println("------------冒泡排序--------------")
    var listSort = LinkedList(3,9,1,6,5,7,10,2,4)
    var startIndex:Int = 0
    var secondIndex:Int = 0
    var tmp:Int = 0
    for (startIndex <- 0 until(listSort.length - 1)) {
      for (secondIndex <- startIndex + 1 until(listSort.length)) {
        if (listSort(startIndex) > listSort(secondIndex)) {
          tmp = listSort(startIndex)
          listSort(startIndex) = listSort(secondIndex)
          listSort(secondIndex) = tmp
        }
      }
    }

2.5 scala函数的参数

2.5.1 函数参数的求值策略

call by value:

对函数实参求值,并且只求一次
例子:
def test(x:Int)

call by name:

函数的实参在函数体内部用到的时候,才会被求值
例子:
def test(x:=>Int)
注意变量名和类型之间的符号,多了个 => ,不要漏了

可能这么讲,还不清楚,下面看看例子

scala> def test1(x:Int,y:Int) : Int = x+x
test1: (x: Int, y: Int)Int

scala> test1(3+4,8)
res0: Int = 14

scala> def test2(x: => Int,y: => Int) : Int = x+x
test2: (x: => Int, y: => Int)Int

scala> test2(3+4,8)
res1: Int = 14

执行过程对比:
test1  -->  test1(3+4,8)    --> test1(7,8)  --> 7+7 --> 14

test2  -->  test2(3+4,8)    --> (3+4)+(3+4) --> 7+7 --> 14
注意这里,在没用到实参之前,实参一直是3+4,而不是7

下面来一个更明显的例子:

//x 是 call by value , y 是 call by name
def bar(x:Int,y: => Int) : Int = 1

定义一个死循环函数
def loop() : Int = loop

调用 bar 函数:
1、bar(1,loop)
2、bar(loop,1)

哪种方式会产生死循环?
第二种方式
解析:
1、y 每次在函数中用到时,才会被求值,bar函数没有用到y,所以不会调用loop。
2、x call by value ,对函数参数求值(无论是否用到),并且只求一次,所以会产生死循环。

2.5.2 函数参数类型

1、默认参数

当你没有给参数赋值的时候,就使用默认值。
scala> def fun1(name:String="Tom") : String = "Hello " + name
fun1: (name: String)String

scala> fun1("Andy")
res0: String = Hello Andy

scala> fun1()
res1: String = Hello Tom

2、代名参数

过代名参数可以确定给哪个参数赋值。
scala> def fun2(str:String="Good Morning ", name:String="Tom ", age:Int=20)=str + name + " and the age is " + age
fun2: (str: String, name: String, age: Int)String

scala> fun2()
res2: String = Good Morning Tom  and the age is 20

//这里指定给哪个默认参数赋值
scala> fun2(name="Mary ")
res3: String = Good Morning Mary  and the age is 20

3、可变参数

类似于java中的可变参数,即 参数数量不固定,就是在普通参数后面加个 *

例:
求多个数字的和:
def sum(args:Int*) = {
   var result = 0
   for(s <- args) result += s
   result
}

//这里就是可变参数
scala> def sum(args:Int*) = {
| var result = 0
| for(s <- args) result += s
| result}
sum: (args: Int*)Int

scala> sum(1,2,4)
res4: Int = 7

scala> sum(1,2,4,3,2,5,3)
res5: Int = 20

2.6 懒值lazy

定义:如果常量(也就是val定义的)是lazy的,他的初始化会被延迟,推迟到第一次使用该常量的时候,例子:

scala> val x : Int = 10
x: Int = 10

scala> val y:Int = x+1
y: Int = 11

y 不是 lazy,定义后立即触发计算

scala> lazy val z : Int = x+1
z: Int = <lazy>

z 是lazy,初始化会被推迟,定义时不会出发计算。

scala> z
res6: Int = 11

当我们第一次使用z的时候,才会触发计算。

扩展:

Spark的核心是RDD(数据集合),Spark提供很多方法,操作RDD,算子。
算子分为两种:
1、Transformation : 延时加载,不会触发计算
2、Action : 会触发计算

再看一个例子:

(1)读一个存在的文件

    scala> lazy val words = scala.io.Source.fromFile("H:\tmp_files\student.txt").mkString
    words: String = <lazy>

    scala>  words
    res7: String =
    1       Tom     12
    2       Mary    13
    3       Lily    15

    (2)读一个不存在的文件
    scala> val words = scala.io.Source.fromFile("H:\tmp_files\studen1231312312t.txt").mkString
    java.io.FileNotFoundException: H:	mp_filesstuden1231312312t.txt (系统找不到指定的文件。)
      at java.io.FileInputStream.open0(Native Method)
      at java.io.FileInputStream.open(FileInputStream.java:195)
      at java.io.FileInputStream.<init>(FileInputStream.java:138)
      at scala.io.Source$.fromFile(Source.scala:91)
      at scala.io.Source$.fromFile(Source.scala:76)
      at scala.io.Source$.fromFile(Source.scala:54)
      ... 32 elided
    会产生异常

    scala> lazy val words = scala.io.Source.fromFile("H:\tmp_files\studen1231312312312t.txt").mkString
    words: String = <lazy>
    如果是懒值不会产生异常,因为定义的时候并没有执行,所以不会产生异常

2.7 异常exception

和java类似,使用try catch finally捕捉和处理异常

try {}
    catch {
        case ex: exception_type1 => {
            异常处理代码
        }

        case ex: exception_type2 => {
            异常处理代码
        }

        case _:Exception => {
            这里是包含所有exception,如果上面的没有匹配到就到这里来了
        }
    } finally {
        xxxx
    }

2.8 数组

数组类型:

Array[T](N)定长数组,需指定数组长度
ArrayBuffer 可变长数组,需要另外导入包import scala.collection.mutable._

Array操作:

scala> val a = new Array[Int](10)
a: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)

初始化赋值,默认值0

scala> val b = new Array[String](15)
b: Array[String] = Array(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null)

scala> val c = Array("Tom","Mary","Andy")
c: Array[String] = Array(Tom, Mary, Andy)

scala> val c = Array("Tom","Mary",1)
c: Array[Any] = Array(Tom, Mary, 1)

未声明 Array类型,直接赋值,Array中是Any类型,即任何类型都可以

scala> val c:Array[String]=Array("Tom","Andy",1)
<console>:11: error: type mismatch;
found   : Int(1)
required: String
val c:Array[String]=Array("Tom","Andy",1)

当声明 Array[String] 时,数组元素必须全为String
^

scala> val c:Array[String]=Array("Tom","Andy")
c: Array[String] = Array(Tom, Andy)

ArrayBuffer操作:

scala> import scala.collection.mutable._
import scala.collection.mutable._

mutable代表可变

scala> val d = ArrayBuffer[Int]()
d: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer()

往数组中添加元素
scala> d += 1
res8: d.type = ArrayBuffer(1)

访问数组指定下标的元素:d(index)

删除指定元素(不是删除指定下标的元素):
scala> y-=3
res6: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1)

数组常见操作:

1、遍历数组:
scala> var a = Array("Tom","Andy","Mary")
a: Array[String] = Array(Tom, Andy, Mary)

scala> for(s<-a) println(s)
Tom
Andy
Mary

scala> a.foreach(println)
Tom
Andy
Mary

2、求最大和最小值
scala> val myarray = Array(1,2,7,8,10,3,6)
myarray: Array[Int] = Array(1, 2, 7, 8, 10, 3, 6)

scala> myarray.max
res16: Int = 10

scala> myarray.min
res17: Int = 1

3、排序
表示使用某个比较函数进行排序
scala> myarray.sortWith(_>_)
res18: Array[Int] = Array(10, 8, 7, 6, 3, 2, 1)

scala> myarray.sortWith(_<_)
res19: Array[Int] = Array(1, 2, 3, 6, 7, 8, 10)

解释:myarray.sortWith(_>_)
完整:myarray.sortWith((a,b)=>{if(a>b) true else false})
(a,b)=>{if(a>b) true else false} 是匿名函数,没有名字,传入两个参数 a b,返回值是bool
sortWith(_>_) 是高阶函数,即参数是函数

多维数组:

和Java一样,通过数组的数组来实现

定义一个固定长度的二维数组

scala> val matrix = Array.ofDim[Int](3,4)
matrix: Array[Array[Int]] = Array(
Array(0, 0, 0, 0), 
Array(0, 0, 0, 0), 
Array(0, 0, 0, 0)
)

scala> matrix(1)(2)=10

scala> matrix
res21: Array[Array[Int]] = Array(
Array(0, 0, 0, 0), 
Array(0, 0, 10, 0), 
Array(0, 0, 0, 0))

定义一个二维数组,其中每个元素都是一个一维数组,其长度不固定
scala> var triangle = new Array[Array[Int]](10)
triangle: Array[Array[Int]] = Array(null, null, null, null, null, null, null, null, null, null)

scala> for(i <- 0 until triangle.length)
| triangle(i)=new Array[Int](i+1)

scala> triangle
res23: Array[Array[Int]] = Array(
Array(0), 
Array(0, 0), 
Array(0, 0, 0), 
Array(0, 0, 0, 0), 
Array(0, 0, 0, 0, 0), 
Array(0, 0, 0, 0, 0, 0), 
Array(0, 0, 0, 0, 0, 0, 0), 
Array(0, 0, 0, 0, 0, 0, 0, 0), 
Array(0, 0, 0, 0, 0, 0, 0, 0, 0), 
Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0))

2.9 映射Map

Map[K,V]() 泛型类型可以根据传入的值自动确定,如果创建为空的话,KV都是nothing类型
scala.collection.mutable.Map  是可变的Map,添加进去的KV可更改
scala.collection.immutable.Map  是不可变Map

例子:
// 初始化赋值方式1
scala> val scores = Map("Tom" -> 80, "Mary"->77,"Mike"->82)
scores: scala.collection.mutable.Map[String,Int] = Map(Mike -> 82, Tom -> 80, Mary -> 77)

// 初始化赋值方式2
scala> val chineses = Map(("Tom",80),("Mary",60),("Lily",50))
chineses: scala.collection.mutable.Map[String,Int] = Map(Tom -> 80, Lily -> 50, Mary -> 60)

映射的操作:

1、获取映射中的值
scala> chineses("Tom")
res25: Int = 80

scala> chineses("To123123m")
java.util.NoSuchElementException: key not found: To123123m
at scala.collection.MapLike$class.default(MapLike.scala:228)
at scala.collection.AbstractMap.default(Map.scala:59)
at scala.collection.mutable.HashMap.apply(HashMap.scala:65)
... 32 elided

解决:先判断key是否存在
if(chineses.contains("To123123m")){
chineses("To123123m")
} else {
1
}

scala> if(chineses.contains("To123123m")){
| chineses("To123123m")
| } else {
| 1}
res27: Int = 1

//获取指定key的value,如果key不存在就返回-1,类似于返回默认值
scala> chineses.getOrElse("To123123m",-1)
res28: Int = -1

//get方法如果对应的key不存在,value返回为none
scala> chineses.get("dlfsjldkfjlsk")
res29: Option[Int] = None

使用get方法不会报错

scala> chineses.get("Tom")
res30: Option[Int] = Some(80)

Option   None    Some   三个类型,在后面做同时的讲解。

==========================================================
2、更新映射中的值
scala> chineses
res31: scala.collection.mutable.Map[String,Int] = Map(Tom -> 80, Lily -> 50, Mary -> 60)

scala> chineses("Tom")
res32: Int = 80

scala> chineses("Tom")=100

scala> chineses
res34: scala.collection.mutable.Map[String,Int] = Map(Tom -> 100, Lily -> 50, Mary -> 60)

==========================================================
3、映射的迭代
可以使用 for foreach
scala> chineses
res35: scala.collection.mutable.Map[String,Int] = Map(Tom -> 100, Lily -> 50, Mary -> 60)

scala> for(s <- chineses) println(s)
(Tom,100)
(Lily,50)
(Mary,60)

scala> chineses.foreach(println)
(Tom,100)
(Lily,50)
(Mary,60)

foreach本质是一个 高阶函数。

==========================================================
4、添加值到映射中
scala> a+="tom"->"king"  前面是K,后面是V

2.10元祖Tuple

元组是不可变类型,不能添加元素和修改元素。Scala 的 Tuple : 是不同类型值的集合

Tuple的声明:   
scala> val t1 = Tuple(1,0.3,"Hello")
<console>:14: error: not found: value Tuple
val t1 = Tuple(1,0.3,"Hello")
    ^

scala> val t1 = Tuple3(1,0.3,"Hello")
t1: (Int, Double, String) = (1,0.3,Hello)

Tuple3 代表 Tuple 中有三个元素

可以使用下面的方式来定义任意元素个数的元祖
scala> val t1 = (1,0.3,"Hello")
t1: (Int, Double, String) = (1,0.3,Hello)

scala> val t1 = (1,0.3,"Hello",1,12,5,"all")
t1: (Int, Double, String, Int, Int, Int, String) = (1,0.3,Hello,1,12,5,all)

访问元素:注意这个特殊的符号
scala> t1._1   访问元祖中的第一个元素
res38: Int = 1

scala> t1._3   访问元祖中的第3个元素
res39: String = Hello

遍历tuple:

注意:Tuple中没有提供foreach函数,我们需要使用 productIterator   
遍历分成2个步骤:
1、使用productIterator 生成迭代器
2、遍历

scala> t1.productIterator.foreach(println)
1cala>
0.3
Hello
1
12
5
all

2.11 文件操作

//读取文件,source本身是一个按字符存储的可迭代对象,默认是单个字符的
val source = scala.io.Source.fromFile(fileName, 字符集)
//将整个文件内容读取成一个字符串
source.mkString
//按行读取,一次一行
source.getLines()
//按单个字符读取
for(c<-source) {
    println(c)
}

//从URL中读取数据内容
val source2 = scala.io.Source.fromFile(URL, UTF-8)

//读取二进制文件,scala没有自己实现相应的类,所以是直接调用java的类
val file=new File(filename)
val in=new FileInputStream(file)
val buffer=new Array[Byte](file,.length().toInt)
in.read(buffer)

//写入文件
val  out=new PrintWriter(filename)
out.println(xxxxx) 写入内容

使用properties类解析resources目录下的properties配置文件:

们可以自定定义个 xx.properties 配置文件,然后使用load方法来读取。
配置文件的基本格式为  key=value 的格式,模块会自定解析成kv格式。

例子:
object Dataloader {
  def main(args: Array[String]): Unit = {
    val properties = new Properties()
    //注意这里因为要采用编译后的文件路径,所以这里是获取resource目录下文件在编译后的路径的输入流
    val propertiesStream = Dataloader.getClass.getClassLoader.getResourceAsStream("dataloader.properties")
    properties.load(propertiesStream)
    println(properties.getProperty("spark.local.cores"))

  }
}

三、scala面向对象特性

3.1 类的定义

scala中类的定义和java类似,同样使用class关键字定义类,但是class关键字前面不存在public等这些修饰符。例子:

class Student1 {
  //定义学生的属性
  private var stuId : Int = 0
  private var stuName : String = "Tom"
  private var age : Int = 20

  //定义成员方法(函数) get set
  def getStuName() : String = stuName
  def setStuName(newName :String) = this.stuName = newName

  def getStuAge() : Int = age
  def setStuAge(newAge : Int) = this.age = newAge
}

实际上scala会给属性自动生成对应的get和set方法,但是要注意以下原则:

1、默认情况下,如果属性没有用任何修饰符修饰,那么默认是private,但是自动生成的get和set方法是公有的.

2、而如果显式的声明属性为私有(显式使用private关键字),如:private var a = 10,那么生成的get和set方法都是私有的。只能在伴生对象中使用,或者在本类中使用

3、如果使用 private [this] var a = 10,这样定义属性,表示该属性完全不能被外部访问(包括伴生类),且不会生成set和get方法

4、如果值希望scala生成get方法,不生成set方法,可以将其定义为常量,因为常量的值不可改变
================== 本质 ===================
自动生成的set和get方法名是和属性名一致的,如:
var student = new Student1()
student.stuId   这里实际上是直接调用该属性的get方法的,只不过方法同名而已
student.age=10  这里实际上调用属性的set方法的

3.2 内部类

scala的内部类没有java这么复杂,就是简单在外部类中,直接定义一个类而已。例子:

class Student2 {

  //定义学生的属性
  private var stuName : String = "Tom"
  private var stuAge : Int = 20

  //定义一个数组保存学生的课程成绩
  private var courseList = new ArrayBuffer[Course]()

  //定义一个函数,用于添加学生的课程成绩
  def addNewCourse(cname:String,grade:Int) = {
    //创建课程的成绩信息
    var c = new Course(cname,grade)

    //添加到学生的对象中
    courseList += c

  }
  //定义课程类 主构造器就是写在类的后面
  class Course(var courseName:String,var grade:Int){
    //定义属性

    //定义函数
  }

}

可以通过外部类的方法来获取内部类的对象。
或者通过以下方式创建内部类对象
val test = new InnerClassTest() 只有当外部类对象是用val 定义时才可以
var testIn = new test.myClass("king")

注意,下面的方式定义是会报错的
var test = new InnerClassTest()   这里是var
var testIn = new test.myClass("king")

个人理解是:
scala中,内部类是属于外部类的对象的,不是属于外部类的(这是官方的话)
所以内部类对象也是跟随这外部类对象。要是外部类对象用var定义,表示其是可变的
要是它变了,内部类对象还怎么通过外部类对象的引用获取自己?

3.3 类的构造器

3.3.1 主构造器

主构造器就是在定义类的后面同时定义的,如:

class Student3(var stuName : String , var age:Int) 
括号里面的就是主构造器,只能是一些属性。

要注意一点,就是,括号里面定义的属性变量,前面的var或者val千万不能丢,如果丢掉了,则该参数只能被当做一个类内不可变参数使用,不能被当做类的字段,既外部不能访问该变量。就会造成以下后果,看例子:

//首先两个类,有主构造器,有var和没var 的区别
class a(var name:String)
class b(name:String)

object IODemo {
  def main(args: Array[String]): Unit = {
    //这里是可以正常创建对象的
    val king = new a("king")
    val wang = new b("wang")

    //到这里就有问题了
    king.name  这里是可以的
    wang.name  这会无法访问name这个属性,因为压根没有,只是作为class中的一个普通变量而已
  }
}

还有一个点,就是如果类里面有任何代码没有包含在类中的任何方法中时,实际上在创建类对象时,这些代码是会被执行的,所以其实也算是主构造器的一部分。如:
class a(var name:String){
    println("hahha")  //这个语句在创建对象时是会执行的  

    def test()={
        println("test")
    }
}

3.3.2 辅助构造器

一个类可以有多个辅助构造器,通过关键字 this 来实现,如:
class Student3(var stuName : String , var age:Int) {
  //属性
  private var gender:Int = 1

  //定义一个辅助构造器,辅助构造器可以有多个
  //辅助构造器就是一个函数,只不过这个函数的名字叫 this

  def this (age : Int){
    this("Mike",age) // new Student3("Mike",age)
    println("这是辅助构造器")
  }

  def this (){
    this(10)// new Student3("Mike",10)
    println("这是辅助构造器2")
  }

}

3.4 object对象(伴生对象)

object对象是scala中比较特殊的对象,有几个特性

1、Object中的内容都是静态的。所以里面定义的属性,方法都是静态的,可以直接通过类名调用,无需创建对象
2、scala中,没有static关键字,所有static都定义在object中。比如main函数
3、如果class的名字,跟object的名字一样,就把这个object叫做类的伴生对象。伴生对象。
4、main函数需要写在object中,但是不一定必须写在伴生对象中
5、本质上来说,object其实类似于一个单例对象,本身就是静态,不需要实例化对象来调用里面定义的方法和属性。
所以从这点上,可以算是弥补了scala没有静态属性的一个缺陷

使用object对象的单例对象特性的例子:

1、生成信用卡号
object CreditCard {

  //定义一个变量保存信用卡卡号
  private [this] var creditCardNumber : Long = 0

  //定义函数来产生卡号
  def generateCCNumber():Long = {
    creditCardNumber += 1
    creditCardNumber
  }

  //测试程序
  def main(args: Array[String]): Unit = {
    //产生新的卡号
    println(CreditCard.generateCCNumber())
    println(CreditCard.generateCCNumber())
    println(CreditCard.generateCCNumber())
    println(CreditCard.generateCCNumber())
    println(CreditCard.generateCCNumber())
    println(CreditCard.generateCCNumber())
    println(CreditCard.generateCCNumber())
}

}

2、扩展使用,App这个类
定义object类,继承App这个类,可以省略main函数,默认object中的所有代码都在main函数里面。如:
object AppTest extends App {
  println("test")   这个会直接执行
}

3.5 apply方法

说到这里,前面的估计会有有疑问,为什么有时候创建对象时,需要用new关键字,有时候又不需要了呢?这就要说到apply这个方法了。如:

var t1 = Tuple3(1,0.1,"Hello")

可以看到,创建一个tuple对象,但是并没有用new关键字,其实省略new 关键字时,是调用了该类的伴生对象中的apply方法。apply方法一般会返回创建好的类的对象,要注意以下几点:

1、使用apply方法,让程序更加简洁。
2、apply方法必须写在伴生对象中。因为直接调用apply方法需要是静态的,只能写在object伴生对象中
3、这种方式类似于通过类名.method 的方式来创建对象,
用java的方式来理解的话,即object是一个静态类,可以直接使用类名
调用里面的静态方法
4、而且apply方法内部,其实也是使用了 new 来创建对应类的对象的

例子:

//定义一个原始类
class Student4 (var stuName : String)

//定义上面类的伴生对象
object Student4{
  //定义apply方法
  def apply(name : String) = {
    println("调用apply方法")
    //返回原始类的对象
    new Student4(name)
  }

  //测试程序
  def main(args: Array[String]): Unit = {
    //通过主构造器创建学生对象
    var s1 = new Student4("Tom")
    println(s1.stuName)

    //通过apply方法创建学生对象,可以不写new关键字
    var s2 = Student4("Mary")
    println(s2.stuName)

  }
}

3.6 继承

3.6.1 普通继承

scala中,也是使用extends关键字继承父类的,而且也只允许单继承,如:

//定义父类
class Person(val name: String,val age:Int){
  //定义函数
  def sayHello() : String = "Hello " + name + " and the age is " + age
}

//定义子类
/**
  *class Emplyee(val name:String,val age:Int,val salary:Int) extends Person(name,age)
  * 上述写法报错
  * 如果想用子类的属性,覆盖父类的属性。直接这样写不行 需要override
  *
  *override就是希望使用子类中的值 覆盖父类中的值
  *
  */
class Emplyee(override val name:String,override val age:Int,val salary:Int) extends Person(name,age){
    //重写父类的方法
  override def sayHello(): String = "子类中的sayHello"
}

覆盖父类的方法或者属性时,都需要在最前面加上override关键字

3.6.2 匿名子类继承

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

    //创建Person对象
    var p1 = new Person("Tom",20)
    println(p1.name+"	"+p1.age)
    println(p1.sayHello())

    //创建Employee对象
    var p2 : Person = new Emplyee("Mike",25,1000)
    println(p2.sayHello())

    //通过匿名子类来实现继承:没有名字的子类,就叫匿名子类
    var p3 : Person = new Person("Mary",25){
      override def sayHello(): String = "匿名子类中的sayHello"
    }
    println(p3.sayHello())
  }
}

3.6.3 抽象类和抽象字段

抽象类其实就是使用abstract关键字定义的类,如:

abstract class Teacher {
  var i:Int
  var name:String="king"
  def test = {

  }

  def sayHello

  def talk:Int
}

其中,有以下几点注意
1、抽象类中,没有初始化的字段为称为抽象字段,或者叫属性,如果初始化了就不是抽象字段了。同样的,没有实现的方法就称为抽象方法。
2、抽象类只能用于继承,不能实例化对象
3、非抽象子类中,继承了抽象父类,必须初始化抽象字段以及实现抽象方法。
初始化抽象字段时,可以在子类的主构造器或者在类实现中初始化也可以。但是在主构造器中可以不用立马初始化,可以等实例化对象时传入参数进行初始化。而在类实现中则必须立马初始化
4、抽象类中对于抽象属性,只会自动生成对应的get方法,不会生成set方法
5、继承抽象父类也是使用extends关键字。

3.7 trait特质

在scala中,一般只支持单继承,但是有时候需要多继承,所以就出现了trait,它可以让子类多继承。trait的定义基本和抽象类类似。如:

//定义父类
trait Human{
  //定义抽象属性
  val id : Int
  val name : String
}

//定义代表动作的trait
trait Action{
  //定义一个抽象函数
  def getActionName():String
}

//定义子类 从两个父类继承
class Student5(val id:Int,val name:String) extends Human with Action{
  override def getActionName(): String = "Action is running"
}

从上面也可以看到,继承的trait的时候,用到了extends + with 两个关键字,当父类多于一个时,则必须使用with关键字继承,且该类必须是trait。而extends后面的类可以是trait或者其他class类。多重继承如下:

class Father extends class1 with trait1 with trait2 with 。。。。。。{

}

3.8 包和包对象

scala包的使用和java类似,而且scala中,import语句可以在任何地方。一般情况下,java和scala的包下面,只能有类,对象,特质,不能直接定义函数以及变量。而scala的包对象可以解决这个问题。Scala中的包对象可以包含:常量,变量,方法,类,对象,trait(特质)。如:

package object MyPackage {
  def test = {

  }

  var x:Int = 0

  class a {}
}

然后可以直接通过包对象名直接调用里面的方法和变量了

四、scala中的集合

scala中集合一般分为可变集合和不可变集合,分别在 scala.collection.mutable(可变)和scala.collection.immutable(不可变)包下。不可变集合中不允许添加元素,不允许更改某个元素的值,允许删除元素。

4.1 列表List

List[T]() 不可变列表
LinkedList[T]() 可变列表

List[T]相关操作:

scala> val nameList = List("Tom","Andy")
nameList: List[String] = List(Tom, Andy)

scala> val intList = List(1,2,3)
intList: List[Int] = List(1, 2, 3)

空列表
scala> val nullList : List[Nothing] = List()
nullList: List[Nothing] = List()

二维列表
scala> val dim:List[List[Int]] = List(List(1,2,3),List(10,20))
dim: List[List[Int]] = List(List(1, 2, 3), List(10, 20))

返回第一个元素
nameList.head

返回除去最后一个元素的列表
nameList.tail

访问指定index 的元素
nameList(index)

LinkedList[T]相关操作:

定义可变列表
scala> val myList = scala.collection.mutable.LinkedList(1,2,3,4,5)
    warning: there was one deprecation warning; re-run with -deprecation for details
    myList: scala.collection.mutable.LinkedList[Int] = LinkedList(1, 2, 3, 4, 5)

遍历修改列表
var cur = myList  指向nameList的内存地址引用

    while(cur != Nil){
    cur.elem = cur.elem*2  迭代每个元素,并将每个元素 *2
    cur = cur.next
    }

col:+ ele
//将元素的添加到集合的尾部(seq)
ele +:col
//将元素添加到集合的头部(seq)
col + (ele,ele)
//将其他集合添加到集合尾部(set/map)
col -(ele,ele)
//将子集合从集合中删除(set/map/ArrayBuffer)

col1 ++ col2
//将其他集合添加到集合尾部(Iterator)

col2 ++: col1 
//将其他集合添加到集合的头部(Iterator)

ele::list
//将元素添加到list头部(list)
list2::list1
//将其他list2添加到list1
的头部(list)
list1:::list2 
//将其他list2添加到list1的尾部(list)

4.2 序列

序列分为Vector,Range,两个都是不可变的
Vector操作:

Vector是一个带下标的序列,可以通过下标(索引号),来访问Vector元素
scala> var v = Vector(1,2,3,4,5,6)
v: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3, 4, 5, 6)

Range操作:

Range:是一个整数的序列

第一种写法:
scala> Range(0,5)
res48: scala.collection.immutable.Range = Range(0, 1, 2, 3, 4)
解释:从0开始,不包含5

第二种写法:
scala> print(0 until 5)
Range(0, 1, 2, 3, 4)

第三种写法:前后闭区间
scala> print(0 to 5)
Range(0, 1, 2, 3, 4, 5)

两个Range可以相加
scala> (‘0‘ to ‘9‘) ++ (‘A‘ to ‘Z‘)
res51: scala.collection.immutable.IndexedSeq[Char] = Vector(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z)

Range转换成list
scala> 1 to 5 toList
warning: there was one feature warning; re-run with -feature for details
res52: List[Int] = List(1, 2, 3, 4, 5)

4.3 集合set

不重复元素的集合,默认是HashSet,与java类似,且不可变,相关操作

创建一个set
scala> var s1 = Set(1,2,10,8)
s1: scala.collection.immutable.Set[Int] = Set(1, 2, 10, 8)
注意:属于immutable

scala> s1 + 10
res53: scala.collection.immutable.Set[Int] = Set(1, 2, 10, 8)

scala> s1 + 7
res54: scala.collection.immutable.Set[Int] = Set(10, 1, 2, 7, 8)
返回一个新Set

scala> s1
res55: scala.collection.immutable.Set[Int] = Set(1, 2, 10, 8)
s1本身未改变

创建一个可排序Set
scala> var s2 = scala.collection.mutable.SortedSet(1,2,3,10,8)
s2: scala.collection.mutable.SortedSet[Int] = TreeSet(1, 2, 3, 8, 10)

判断元素是否存在:
scala> s1.contains(1)
res56: Boolean = true

判断一个集是否是另一个集的子集
scala> var s2 = Set(1,2,10,8,7,0)
s2: scala.collection.immutable.Set[Int] = Set(0, 10, 1, 2, 7, 8)

scala> s1
res57: scala.collection.immutable.Set[Int] = Set(1, 2, 10, 8)

scala> s1 subsetOf(s2)
res58: Boolean = true

集的运算:union 并集, intersect 交集,diff 差集
scala> var set1 = Set(1,2,3,4,5,6)
set1: scala.collection.immutable.Set[Int] = Set(5, 1, 6, 2, 3, 4)

scala> var set2 = Set(5,6,7,8,9,10)
set2: scala.collection.immutable.Set[Int] = Set(5, 10, 6, 9, 7, 8)

scala> set1 union set2
res59: scala.collection.immutable.Set[Int] = Set(5, 10, 1, 6, 9, 2, 7, 3, 8, 4)

scala> set1 intersect set2
res60: scala.collection.immutable.Set[Int] = Set(5, 6)

scala> set1 diff set2
res61: scala.collection.immutable.Set[Int] = Set(1, 2, 3, 4)

scala> set2 diff set1
res62: scala.collection.immutable.Set[Int] = Set(10, 9, 7, 8)

五、scala中的函数

前面说的到普通函数的定义方式为:

关键字 def 函数名(参数):返回值 = 函数实现

除了普通函数之外,还有其他函数。

5.1 匿名函数

没有名字的函数,定义方式为:

(参数列表)=> {函数实现}
注意,是 => 不是 =

一般来说,匿名函数就是用来临时定义一个函数的,只使用一次,常常和高阶函数结合使用。例子:

调用匿名函数
定义一个数组,把数组中每个元素乘以三
把Array(1, 2, 3)中的每个元素,传到匿名函数中(x:Int) => x*3

scala> Array(1,2,3).map((x:Int) => x*3)
res1: Array[Int] = Array(3, 6, 9)

把 (x:Int) => x*3 作为map的函数参数传入   ---> 高阶函数

(_,1)这个是啥意思  _+_  都是匿名函数。

 定义匿名函数的时候,参数列表可以省略,直接用函数体,如:
(i:int)=>i*2 可以省略为 _*2,其中_ 就表示参数,输出类型自动检测

要注意一点:
匿名函数的输入参数中,不强制需要指定参数的类型,可以自动检测,如:

xxx.map(pair=>pair._2.toString)
map里面的匿名函数是合法的,没有指定输入参数类型,也是OK的。

但是普通函数是一定要指定参数类型的。

5.2 高阶函数

高阶函数就是参数列表中使用另外的函数作为参数。使用方式:

定义一个高阶函数:
def 函数名(传入函数名,假设为f:(入参类型)=>(出参类型),参数2,。。。。) = 处理逻辑,这里面会调用传入的函数

例子:
object IODemo {

  def main(args: Array[String]): Unit = {
    //调用高阶函数,并传入函数作为参数
    highFun(innerFun,2)
  }

  //高阶函数,有两个参数,一个是函数f,指定入参和出参类型均为Int,另一个参数是x:Int
  def highFun(f:(Int)=>(Int),x:Int) = {
    f(x)
  }

  //这个是接着用来传入到高阶函数中的函数
  def innerFun(x:Int) = {
    x+2
  }

}

除了使用已有函数作为高阶函数的参数之外,也可以使用匿名函数,如:
highFun((x:Int)=>x*3, 3),(x:Int)=>x*3就是个匿名函数

5.3 scala中常用高阶函数

5.3.1 map函数

相当于一个循环,对某个集合中的每个元素进行操作(按照接收函数中的逻辑),返回一个处理后的数据的新的集合。例子:

scala> var numbers = List(1,2,3,4,5,6,7,8,9,10)
numbers: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> numbers.map((i:Int)=>i*2)
res8: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
在(i:Int)=>i*2中,i是循环变量,整个函数

map函数不改变numbers值
scala> numbers
res10: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

匿名函数可以简写为;
_ 相当于循环变量 i
_*2 与 (i:Int)=>i*2 功能相同
_+_ 与 (i:Int,j:Int)=>i+j

注意:如果传给map的是一个Map类型的,如果key没有给map中的函数处理,只处理value,那么最后map处理后返回的新的集合就只包含value

5.3.2 foreach函数

和map类似,唯一不同的就是它没有任何返回值

scala> numbers
res11: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> numbers.foreach(_*2)

scala> numbers
res13: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> numbers.foreach(println(_))
1
2
3
4
5
6
7
8
9
10

scala> numbers.map(_*2).foreach(println)
2
4
6
8
10
12
14
16
18
20

5.3.3 filter

过滤,选择满足的数据,返回true则保留,false则丢弃。最终返回的新集合中包含满足条件的元素。例子:

举例:查询能够被2整除的数字
scala> numbers
res15: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> numbers.filter((i:Int)=>i%2==0)
res16: List[Int] = List(2, 4, 6, 8, 10)

说明:(i:Int)=>i%2==0  如果是true 就返回

5.3.4 zip

合并两个集合,例子:

scala> List(1,2,3).zip(List(4,5,6))
res18: List[(Int, Int)] = List((1,4), (2,5), (3,6))

scala> List(1,2,3).zip(List(4,5))
res19: List[(Int, Int)] = List((1,4), (2,5))

可以看到,他是两个集合中的元素两两集合成一个元祖,并且按照最少元素的list为准

scala> List(3).zip(List(4,5))
res20: List[(Int, Int)] = List((3,4))

5.3.5 partition

根据断言(就是某个条件,可以通过一个匿名函数来实现)的结果,进行分区。函数返回的类型必须是boolean类型的。最后也就是返回true的为一个分区,为false的为另一个分区。如:

把能够被2整除的分成一个区,不能整除的分成另一个区
scala> numbers
res21: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> numbers.partition((i:Int)=>i%2==0)
res22: (List[Int], List[Int]) = (List(2, 4, 6, 8, 10),List(1, 3, 5, 7, 9))

5.3.6 find

查找第一个满足条件(断言)的元素

举例:查找第一个能够被3整除的数字
scala> numbers.find(_%3==0)
res23: Option[Int] = Some(3)

5.3.7 flatten

把嵌套的结果展开

scala> List(List(2,4,6,8,10),List(1,3,5,7,9)).flatten
res24: List[Int] = List(2, 4, 6, 8, 10, 1, 3, 5, 7, 9)

合并成为一个集合

5.3.8 faltmap

相当于map+flatten

scala> var myList = List(List(2,4,6,8,10),List(1,3,5,7,9))
myList: List[List[Int]] = List(List(2, 4, 6, 8, 10), List(1, 3, 5, 7, 9))

scala> myList.flatMap(x=>x.map(_*2))
res25: List[Int] = List(4, 8, 12, 16, 20, 2, 6, 10, 14, 18)

执行过程:
1、将List(2,4,6,8,10)和List(1,3,5,7,9)循环调用了x=>x.map(_*2)  这里x代表某个List,里面的_表示list中的元素
List(4, 8, 12, 16, 20)  和 List(2, 6, 10, 14, 18)

2、合并成一个List
List(4, 8, 12, 16, 20, 2, 6, 10, 14, 18)

5.4 闭包

闭包也就是函数的嵌套。在一个函数里面,包含了另外一个函数的定义,可以在内函数中访问外函数的变量。如:

def mulBy(factor:Double)=(x:Double)=>x*factor里面调用了外函数的factor
            外                       内

5.5 柯里化

柯里化函数,是把具有多个参数的函数,转换成为一个函数链,每个节点上都是单一参数函数

def add(x:Int,y:Int)=x+y
def add(x:Int)(y:Int)=x+y
以上两个函数定义时等价的

说明:
普通函数:
def add(x:Int,y:Int)=x+y

柯里化函数:利用上面函数闭包的方式
def add(x:Int)=(y:Int)=>x+y

简写:
def add(x:Int)(y:Int)=x+y

scala> def add(x:Int)(y:Int)=x+y
add: (x: Int)(y: Int)Int

scala> add(1)(2)
res28: Int = 3

5.6 解答疑问

有时候调用方法时,方法名后面不需要加括号,有时候需要?为什么呢?

主要看定义函数的时候,后面有没有带括号,当然这是在函数没有参数的情况下,有参数肯定就有括号的。
当函数是无参函数,且定义时没有带括号,那么调用时必须不带括号
当函数是无参函数,且定义是带括号了,那么调用时括号可有可无

同样的情况在定义类的时候也是类似的,不重复

六、高阶特性

6.1 scala中特殊的类型

* Any 表示任意类型 相当于java中的Object
* Unit 表示无值,相当于 void
* Nothing Nothing类型在Scala的类层级的最低端;它是任何其他类型的子类型
* Null 是所有应用类型的子类,值为null
*
* 特殊的类型:
* Option : Scala Option(选项)类型用来表示一个值是可选的(有值或者无值)
* Some :如果值存在,Option 就是 Some
* None : 如果值不存在,Option 就是 None
*
* scala> var myMap = Map("Andy"->90)
myMap: scala.collection.immutable.Map[String,Int] = Map(Andy -> 90)

scala> myMap.get("Andy")
res0: Option[Int] = Some(90)

scala> myMap.get("ykjdfhsdajfkajshd")
res1: Option[Int] = None
*
* Nil类型:是一个空的List
*
* 四个N总结:None Nothing Null Nil
* None : 如果map中的值不存在,Option 就是 None
* Nothing : 如果方法抛出异常,返回值类型是Nothing,Nothing是任何其他类型的子类型
* Null : 可以赋值给所有引用类型,不能赋值给值类型。
* Nil : 空的List

6.2模式匹配

类似于java中的switch/case,用法:

xx match {
    case xx1=>。。。。  匹配1
    case xx2=>。。。。  匹配2
    case _ => ......    这里是默认值的意思,相当于default   
   }

例子:

object Demo1 {

  def main(args: Array[String]): Unit = {
    //1、相当于switch case
    var chi = ‘-‘
    var sign = 0 //标识符  如果 chi 为 ‘-’ 则sign赋值为-1

    chi match {
      case ‘+‘ => sign = 1
      case ‘-‘ => sign = -1
      case _ => sign=0 // _ 表示其他的值
    }
    println(sign)

    /**
      * 2、Scala 的守卫:匹配某种类型的所有值 case _ if
      *
      * 举例 匹配所有的数字,如果ch2是一个数字,则digit赋值为ch2
      */

    var ch2 = ‘6‘
    var digit : Int = -1

    ch2 match {
      case ‘+‘ => println("这是一个加号")
      case ‘-‘ => println("这是一个减号")
      case _ if Character.isDigit(ch2) => digit = Character.digit(ch2,10)// 10 表示 10进制
      case _ => println("其他")
    }

    println(digit)

    /**
      * 3、在模式匹配中使用变量
      */
    var mystr = "Hello World"
    //取出某个字符,赋给模式匹配的变量
    mystr(7) match {
      case ‘+‘ => println("这是一个加号")
      case ‘-‘ => println("这是一个减号")
      case ch => println(ch)//case语句中使用变量 ch代表传递进来的字符
    }

    /**
      * 4、匹配类型:相当于java中的instanceof
      */
    var v4 : Any = 100 // 最终 v4 是一个整数
    v4 match {
      case x : Int => println("这是一个整数")
      case s : String => println("这是一个字符串")
      case _ => println("其他类型")
    }

    /**
      * 5、匹配数组和列表
      */
    var myArray = Array(1,2,3)
    myArray match {
      case Array(0) => println("数组中只有一个0")
      case Array(x,y) => println("数组中包含两个元素")
      case Array(x,y,z) => println("数组中包含3个元素")
      case Array(x,_*) => println("这是一个数组,包含多个元素")
    }

    var myList = List(1,2,3,4,5,6)
    myList match {
      case List(0) => println("列表中只有一个0")
      case List(x,y) => println("列表中包含两个元素,和是: " + (x+y))
      case List(x,y,z) => println("列表中包含3个元素,和是: "+ (x+y+z))
      case List(x,_*) => println("这是一个列表,包含多个元素,和是: " + myList.sum)
    }

  }

}

6.3 样本类case

样本类比起普通的类,多了一个特点,可以用上面的case语句中,作为匹配类型,普通的类是不可以的。其他用法和普通类一致。定义方式:

case class a(x:int.....) {}

注意一点,样本类中,自动会将所有字段都声明为val,所以我们声明字段时可以省略val关键字

具体其实没什么好说的,用法很简单。常用来作为一些数据的存储类

6.4 泛型

scala中泛型的定义和java类似,这里不重复,直接讲用法。

6.4.1 泛型类

定义类的时候,带有一个泛型,如:

class Father[T]{
  xxxxx
}

定义的方式和java中的一样,scala中很多都带有泛型,比如:

Array[T]
List[T]
Map[K,V]

6.4.2 泛型函数

定义函数的时候,定义一个泛型,如:

def mkArray[T:ClassTag]
ClassTag解释:表示在scala在运行时候的状态信息,这里表示调用时候的数据类型

例子:

scala> import scala.reflect.ClassTag
import scala.reflect.ClassTag

定义了一个泛型数组,其中elem:_*表示所有元素
scala> def mkArray[T:ClassTag](elem:T*) = Array[T](elem:_*)
mkArray: [T](elem: T*)(implicit evidence$1: scala.reflect.ClassTag[T])Array[T]

6.5 隐式转换

6.5.1 隐式转换函数

通常意思是scala自动将某些类型转换为指定类型,只要用户定义相关的隐式转换函数,scala会自动根据隐式函数的入参和出参类型来调用。例子:

class Fruit(name:String){
  def getFruitName() : String = name
}

class Monkey(f:Fruit){
  //输出
  def say() = println("Monkey like " + f.getFruitName())
}

object ImplicitDemo {
  def main(args: Array[String]): Unit = {
    //定义一个水果对象
    var f : Fruit = new Fruit("Banana")

    f.say()
    /**
      * 问题:可否 f.say
      * 直接写会报错,因为 Fruit里面没有say函数
      * 但是 Monkey里面有say函数
      *
      * 如果可以把 Fruit 转换成 Monkey的话,就可以调用say
      *
      * 所以 我们定义隐式转换函数
      *
      */
  }

  //定义隐式转换函数
  implicit def fruit2Monkey(f:Fruit):Monkey = {
    new Monkey(f)
  }

  /**
    * 注意:谨慎使用隐式转换,隐式转换会导致scala可读性进一步变差
    *
    * 隐式转换函数命名:xxx2xxx
    */
}

6.5.2 隐式参数

隐式参数就是在函数定义时,在每个参数前面加上一个 implicit关键字,该参数就是隐式参数。例子:

定义一个带有隐式参数的函数
scala> def testParam(implicit name:String) = println("The value is " + name)
testParam: (implicit name: String)Unit

接着定义一个隐式变量
scala> implicit val name:String = "AAAAAA"
name: String = AAAAAA

传入参数调用方法,可以运行的
scala> testParam("dfsfdsdf")
The value is dfsfdsdf

没有传递参数时,自动寻找已定义的隐式变量,就是前面的name,当前,调用隐式变量时并不是按照名字调用的
scala> testParam
The value is AAAAAA

有多个隐式变量时,如果同类型的有多个,会报错。如果是不同类型就可以
scala> implicit val name2:String = "AAAAAACCCCCCCCCCCCCC"
name2: String = AAAAAACCCCCCCCCCCCCC
scala> testParam
<console>:18: error: ambiguous implicit values:
both value name of type => String
and value name2 of type => String
match expected type String
testParam

注意:定义隐式参数时,只能有一个隐式参数,而且有时候为了便于看,会将隐式参数和普通参数分开定义,如:
def test1(c:Int)(implicit a:Int): Unit = {}
注意,普通参数一定要在前面的括号,不能放在后面

还有一个特别的例子,将隐式参数和隐式转换函数结合:

定义一个隐式参数,实现如下需求:找到两个值中比较小的那个值
100 23 ----> 23
"Hello" "ABC"--->ABC

def smaller[T](a:T,b:T)(implicit order : T => Ordered[T]) = if(a<b) a else b
scala> def smaller[T](a:T,b:T)(implicit order : T => Ordered[T]) = if(a<b) a else b
smaller: [T](a: T, b: T)(implicit order: T => Ordered[T])T

scala> smaller(100,23)
res1: Int = 23

scala> smaller("Hello","ABC")
res2: String = ABC

首先泛型T,我们不确定是否能比较的,也就是里面有没有 < 这个方法(不要奇怪,< 是一个方法名,不是符号),因为我们需要确定是否有,则我们定义一个隐式转换函数和参数:
implicit order : T => Ordered[T]  
首先这是一个匿名的隐式转换函数,将T转为Order[T],并作为参数order的值

6.5.3 隐式类

在类名前加 implicit关键字
作用:通过隐式类来增强某个类的功能,会将某个类的对象转换为隐式类对象,然后就可调用隐式类中定义的方法了。例子:

object ImplicitClassDemo {

  def main(args: Array[String]): Unit = {
    //执行两个数字求和
    println("两个数字的和是: " + 1.add(2)) // 使用 1.add(2)  替换 1+2
    /**
      * 1.add(2) 报错 因为 1 没有 add方法
      *
      * 定义一个隐式类来增强 1 的功能
      *
      * 执行过程:
      * 首先 把 1 转换成 Calc(1)
      * 再调用Calc(1) add方法
        是根据隐式类中接收的参数类型来自动调用的
      */

    implicit class Calc(x:Int){
      def add(y:Int):Int = x + y
    }
  }

}

6.6 泛型的上界和下界

有时候想限制定义的泛型只能是某些类的时候,就会用到上下界的概念。
例子:

(*)规定泛型的取值范围
举例:定义一个泛型:T
类的继承关系   A---->B----->C----->D  箭头指向子类
可以规定T的取值范围    D  <:  T   <:  B
T 的取值范围只能是 B C D

<:  就是上下界表示方法

(*)定义:
上界  s <: T 规定了S的类型必须是T的子类或本身
下界  u >: T 规定了U的类型必须是T的父类或本身

关于下界的一个小问题:

class Father
class Son extends Father
class Son1 extends Son
class Son2 extends Son1

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

    var father : Father = new Son2
    fun2(father)

    //fun2(new Son2)  这个能正常运行,为什么?
    主要是因为子类的对象可以赋给父类的引用,类似于自动转型。所以new son2的时候,实际上是可以当做是父类的对象的,所以可以正常运行。由此可以看出,在继承的情况下其实泛型的下界是没什么用的。其实也就是多态的原因
  }

  def fun[T<:Son](x:T)={
    println("123")
  }

  def fun2[T>:Son](x:T): Unit ={
    println("456")
  }

}

6.7 视图界定

上界和下界的一种扩展,除了可以接收上界和下界规定的类型之外,还可以接收能够通过隐式转换过去的类型。如:

用法 :
def addTwoString[T<%String](x:T,y:T) = x + "  " + y
含义:
1、可以接收String及其子类
2、可以接收能够转换成String的其他类型。这个是重点,也就是隐式转换来的

例子:

scala> def addTwoString[T<%String](x:T,y:T) = x + " **** " + y
addTwoString: [T](x: T, y: T)(implicit evidence$1: T => String)String

scala> addTwoString(1,2)
<console>:14: error: No implicit view available from Int => String.
addTwoString(1,2)

报错解决:定义隐式转换函数,把int转换成String

scala> implicit def int2String(n:Int):String=n.toString
warning: there was one feature warning; re-run with -feature for details
int2String: (n: Int)String

scala> addTwoString(1,2)
res13: String = 1 **** 2

分析执行过程:
1、调用 int2String 方法,把Int转换成String(scala在后台调用,不需要显示调用)
2、调用addTwoString方法,拼接字符串

隐式转换函数,不需要显示调用它,它就能被调用。

6.8 协变和逆变

将泛型变量的类型嫁接到泛型类中

概念:
协变:泛型变量的值可以是本身类型或者其子类型,
逆变:泛型变量的值可以是本身类型或者其父类型

表示: 
协变 +  class A[+T] 表示将T泛型的类型嫁接到A中
逆变 -

6.9 Ordered和Ordering比较

这两个类主要用于比较时使用
Ordered类似于java的comparable接口
Ordering类似于java的compartor接口。

trait Ordered[A] extends scala.Any with java.lang.Comparable[A] {
  def compare(that : A) : scala.Int
  def <(that : A) : scala.Boolean = { /* compiled code */ }
  def >(that : A) : scala.Boolean = { /* compiled code */ }
  def <=(that : A) : scala.Boolean = { /* compiled code */ }
  def >=(that : A) : scala.Boolean = { /* compiled code */ }
  def compareTo(that : A) : scala.Int = { /* compiled code */ }
}

这其实就是一个trait,定义了< 等方法,不用奇怪,这就是方法名。
子类可以继承该trait,重写里面的方法,该类就可以用于比较了。

trait Ordering[T] extends java.lang.Object with java.util.Comparator[T] with scala.math.PartialOrdering[T] with scala.Serializable {
 this : scala.math.Ordering[T] =>
  def tryCompare(x : T, y : T) : scala.Some[scala.Int] = { /* compiled code */ }
  def compare(x : T, y : T) : scala.Int
  override def lteq(x : T, y : T) : scala.Boolean = { /* compiled code */ }
  override def gteq(x : T, y : T) : scala.Boolean = { /* compiled code */ }
  override def lt(x : T, y : T) : scala.Boolean = { /* compiled code */ }
  override def gt(x : T, y : T) : scala.Boolean = { /* compiled code */ }
  override def equiv(x : T, y : T) : scala.Boolean = { /* compiled code */ }
  def max(x : T, y : T) : T = { /* compiled code */ }
  def min(x : T, y : T) : T = { /* compiled code */ }
  override def reverse : scala.math.Ordering[T] = { /* compiled code */ }
  def on[U](f : scala.Function1[U, T]) : scala.math.Ordering[U] = { /* compiled code */ }
  class Ops(lhs : T) extends scala.AnyRef {
    def <(rhs : T) : scala.Boolean = { /* compiled code */ }
    def <=(rhs : T) : scala.Boolean = { /* compiled code */ }
    def >(rhs : T) : scala.Boolean = { /* compiled code */ }
    def >=(rhs : T) : scala.Boolean = { /* compiled code */ }
    def equiv(rhs : T) : scala.Boolean = { /* compiled code */ }
    def max(rhs : T) : T = { /* compiled code */ }
    def min(rhs : T) : T = { /* compiled code */ }
  }

ordering的方法就比较多,用法类似

以上是关于scala基本入门的主要内容,如果未能解决你的问题,请参考以下文章

linux打开终端如何启动scala,如何在终端下运行Scala代码片段?

在Scala项目中使用Spring Cloud

大数据进阶之路——Scala入门

Scala入门系列

Scala学习之路----基础入门

Scala——基本类型