大数据开发语言Scala(一)——scala入门

累了,基础配置不想写了,直接抄了→Scala的环境搭建

这里需要注意的是,创建新项目时,不要用默认的Class类,用Object,原因看→scala中的object为什么可以直接运行

一、Scala简介

1.1 图解Scala和Java的关系

1.2 关键字说明

package: 包,等同于java中的package

object:关键字,声明一个单例对象(伴生对象)

main方法:从外部可以直接调用执行的方法

def 方法名称 ( 参数名称 : 参数类型 ) : 返回值类型 = { 方法体 }

Scala 完全面向对象,故scala去掉了Java中非面向对象的元素,如static关键字,void类型

1) static

scala无static关键字,由object实现类似静态方法的功能(类名.方法名)

class关键字和Java中的class关键字作用相同,用来定义一个类

2) void

对于无返回值的函数,scala定义其返回值类型为Unit类型

1.3 代码案例

package com.scala.chapter1
object Hello {
  def main(args: Array[String]): Unit = {
    println("hello scala")
    System.out.println("hello scala")
  }
}

1.4 Scala语言特点

Scala是一门以Java虚拟机 (JVM)为运行环境并将面向对象和函数式编程的最佳特性结合在一起的静态类型编程语言(静态语言需要提前编译的如: Java、C、C++等,动态语言如:JS)。

1)Scala是一门多范式的编程语言,Scala支持面向对象和函数式编程。 (多范式,就是多种编程方法的意思。有面向过程、面向对象、泛型、函数式四种程序设计方法。)

2)Scala源代码 (.scala) 会被编译成Java字节码 (.class),然后运行于JVM之上,并可以调用现有的Java类库,实现两种语言的无缝对接。

3)Scala单作为一门语言来看,非常的简洁高效

4)Scala在设计时,马丁·奥德斯基是参考了Java的设计思想,可以说Scala是源于Java,同时马丁·奥德斯基也加入了自己的思想,将函数式编程语言的特点融合到JAVA中,因此,对于学习过Java的同学,只要在学习Scala的过程中,搞清楚Scala和Java相同点和不同点,就可以快速的掌握Scala这门语言。

二、变量和数据类型

Any: 所有类型的超类(顶级类型)

AnyVal: 值类型的超类

AnyRef: 引用类型的超类,对应java.lang.Object

Unit: 无值,类似java中的void

Nothing: 所有类型的子类

Null: 表示null或空引用

2.1 注释

Scala注释使用和Java完全一样。

注释是一个程序员必须要具有的良好编程习惯。将自己的思想通过注释先整理出来,再用代码去体现。

1)基本语法

(1)单行注释://
(2)多行注释:/* */
(3)文档注释:/**
            *
            */

2)案例实操

object Test1 {
def main(args: Array[String]): Unit = {
//单行注释
println("单行")
//多行
/*
println("多行")
*/
//文档注释
/**
   * println("文档注释")
   */
}
}

3) 代码规范

(1)使用一次tab操作,实现缩进,默认整体向右边移动,用shift+tab整体向左移

(2)或者使用ctrl + alt + L来进行格式化

(3)运算符两边习惯性各加一个空格。比如:2 + 4 * 5

(4)一行最长不超过80个字符,超过的请使用换行展示,尽量保持格式优雅

2.2 变量和常量(重点)

常量:在程序执行的过程中,其值不会被改变的变量

1)基本语法

​ var 变量名 [: 变量类型] = 初始值 var i:Int = 10

​ val 常量名 [: 常量类型] = 初始值 val j:Int = 20

​ 注意:能用常量的地方不用变量

2)案例实操

(1)声明变量时,类型可以省略,编译器自动推导,即类型推导

(2)类型确定后,就不能修改,说明Scala是强数据类型语言。

(3)变量声明时,必须要有初始值

(4)在声明/定义一个变量时,可以使用var或者val来修饰,var修饰的变量可改变,val修饰的变量不可改。

object TestValueTransfer {
def main(args: Array[String]): Unit = {
var n = 1 + 2.2222
println(n) //Double
var n2: Double = 2.2222
//    var n3: Int = n2 //报错
var n4 : Byte = 44
//    var c1 : Char = n4 //报错
var n5 : Int = n4
var n6 : Byte = 66
var c2 : Char = 22
//    var n : Short = n6 + c2  //报错,结果是Int类型
//    var n7 : Short = 10 + 20  //报错,byte,short,char他们三者可以计算,在计算时首先转换为int类型。
}
}

(5)var修饰的对象引用可以改变,val修饰的对象则不可改变,但对象的状态(值)却是可以改变的。(比如:自定义对象、数组、集合等等)

object TestVar {
def main(args: Array[String]): Unit = {
var age = 22
age = 29
//    age = "hh"  //报错
var n1 = 1 //可变
n1 = 2
val n2 = 2 //不可变
//n2 = 3
//p1 是var修饰的,p1的属性可以变,p1本身也可以变
var p1 = new Person()
p1.name = "varp1"
p1 = null
val p2 = new Person()
p2.name = "valp2"
//    p2 = null //报错
}
class Person {
var name: String = "who"
}
}

2.3 标识符的命名规范

Scala对各种变量、方法、函数等命名时使用的字符序列称为标识符。即:凡是自己可以起名字的地方都叫标识符。

1)命名规则

Scala中的标识符声明,基本和Java是一致的,但是细节上会有所变化,有以下三种规则:

(1)以字母或者下划线开头,后接字母、数字、下划线

(2)以操作符开头,且只包含操作符(+ - * / # !等)

(3)用反引号....包括的任意字符串,即使是Scala关键字(39个)也可以

• package, import, class, object, trait, extends, with, type, for
• private, protected, abstract, sealed, final, implicit, lazy, override
• try, catch, finally, throw
• if, else, match, case, do, while, for, return, yield
• def, val, var
• this, super
• new
• true, false, null

2)案例实操

需求:判断hello、Hello12、1hello、h-b、x h、h_4、ab、Int、、+-/#!、+-/#!1、if、if,这些名字是否合法。

object TestName {
  def main(args: Array[String]): Unit = {
    //1.以字母或者下划线开头,后接字母、数字、下划线
    var hello: String = ""
    var hello2: String = " "
    //    var 1 hello:String = ""  数字不能开头
    //    var h - b: String = ""  不能用-
    //    var x h: String = ""  不能用空格
    var h_4: String = ""
    val _ab: String = ""
    var Int: String = "" //ok 因为在Scala中Int是预定义的字符,不是关键字,但不推荐
    var _: String = "" // ok 单独一个下划线不可以作为标识符,因为_被认为是一个方法
    //2.以操作符开头,且只包含操作符(+ - * / # ! 等)
    var +*-/#! : String = ""
    //    var +*-/#!1 : String = "" 以操作符开头必须都是操作符
    //3.用反引号`...`包括的任意字符串,即使是Scala关键字(39)个也可以
    //    var if:String = ""  不用用关键字
    var `if`: String = ""
  }
}

2.4 字符串输出

1)基本语法

(1)字符串,通过+号连接

(2)printf用法:字符串,通过%传值。

(3)字符串模板(插值字符串):通过$获取变量值

2)案例实操

object TestCharType {
def main(args: Array[String]): Unit = {
var name: String = "zhangsan"
var age: Int = 22
//1.字符串 通过 + 号连接
println(name + " " + age)
//2.printf 用法字符串,通过%传值
printf("name=%s age=%d\n", name, age)
//3.字符串,通过$引用
//多行字符串,在Scala中,利用三个双引号包围多行字符串就可以实现。//输入的内容,带有空格、\t之类,导致每一行的开始位置不能整洁对齐。
//应用scala的stripMargin方法,在scala中stripMargin默认是“|”作为连接符,//在多行换行的行头前面加一个“|”符号即可。
val s =
"""
  | select
  | name
  |,
  |age
  |from user
  |where name = "zhangsan"
""".stripMargin
println(s)
//如果需要对变量进行运算,那么可以加${}
val s1 =
s"""
   | select
   | name
   |,
   |age
   |from user
   |where name="$name" and age=${age + 2}
""".stripMargin
val s2 = s"name=$name"
println(s2)
}
}

2.5 键盘输入

在编程中,需要接收用户输入的数据,就可以使用键盘输入语句来获取。

1)基本语法

StdIn.readLine()、StdIn.readShort()、StdIn.readDouble()

2)案例实操

需求:可以从控制台接收用户信息,【姓名,年龄,薪水】。

import scala.io.StdIn
object TestInput {
def main(args: Array[String]): Unit = {
//    输入姓名
println("input name:")
var name = StdIn.readLine()
//    输入年龄
println("input age:")
var age = StdIn.readShort()
//    输入薪水
println("input sal:")
var sal = StdIn.readDouble()
//    打印
println("name=" + name)
println("age=" + age)
println("sal=" + sal)
}
}

从文件中读取数据:

import scala.io.Source;
Source.fromFile("C:\\Users\\90513\\Desktop\\datas.csv").foreach(print)

2.6 数据类型(重点)

  • Java 数据类型

    Java基本数据类型:char、byte、short、int、long、float、double、boolean

    Java引用类型:(对象类型)

    由于Java有基本类型,而且基本类型不是真正意义的对象,即使后面产生了基本类型的包装类,但是仍然存在基本数据类型,所以Java语言并不是真正意义上的面向对象

    Java基本类型的包装类:Character、Byte、Short、Integer、Long、Float、Double、Boolean

    注意:Java中基本类型和引用类型没有共同的祖先。

    • Scala 数据类型

      2.7 整数类型(Byte、Short、Int、Long)

      Scala的整数类型就是用于存放整数值的,比如12,30,3456等等。

      1)整型分类

      数据类型

      描述

      Byte [1]

      8位有符号补码整数。数值区间为 -128 到 127

      Short [2]

      16位有符号补码整数。数值区间为 -32768 到 32767

      Int [4]

      32位有符号补码整数。数值区间为 -2147483648 到 2147483647

      Long [8]

      64位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807 = 2的(64-1)次方-1

      2)案例实操

      (1)Scala各整数类型有固定的表示范围和字段长度,不受具体操作的影响,以保证Scala程序的可移植性。

      (2)Scala的整型,默认为Int型,声明Long型,须后加‘l’或‘L’

      object TestDataType {
      def main(args: Array[String]): Unit = {
      // 正确
      var n1: Byte = 127
      var n2: Byte = -128
      // 错误
      //    var n3:Byte = 128
      //    var n4:Byte = -129
      // Scala的整型,默认为Int型,声明Long型,然后加”i“ 或者 ”L“
      var n5 = 10
      println(n5)
      var n6 = 98989898989898989L
      println(n6)
      var n7 = 2.43434309898f
      var n8 = 2.4343662343223
      println(n7)
      println(n8)
      var God = new God()
      God = null
      //var n1 : Int = null //错误
      //println("n1:" + n1)
      }
      class God {
      }
      }
      

      (3)Scala程序中变量常声明为Int型,除非不足以表示大数,才使用Long

      2.8 浮点类型(Float、Double)

      Scala的浮点类型可以表示一个小数,比如123.4f,7.8,0.12等等。

      1)浮点型分类

      数据类型

      描述

      Float [4]

      32 位, IEEE 754标准的单精度浮点数

      Double [8]

      64 位 IEEE 754标准的双精度浮点数

      2)案例实操

      Scala的浮点型常量默认为Double型,声明Float型常量,须后加‘f’或‘F’。

      object TestDataType {
      def main(args: Array[String]): Unit = {
      // 正确
      var n1: Byte = 127
      var n2: Byte = -128
      // 错误
      //    var n3:Byte = 128
      //    var n4:Byte = -129
      // Scala的整型,默认为Int型,声明Long型,然后加”i“ 或者 ”L“
      var n5 = 10
      println(n5)
      var n6 = 98989898989898989L
      println(n6)
      var n7 = 2.43434309898f
      var n8 = 2.4343662343223
      println(n7)
      println(n8)
      var God = new God()
      God = null
      //var n1 : Int = null //错误
      //println("n1:" + n1)
      }
      class God {
      }
      }
      

      2.9 字符类型(Char)

      1)基本说明

      字符类型可以表示单个字符,字符类型是Char。

      2)案例实操

      (1)字符常量是用单引号 ’ ’ 括起来的单个字符。

      (2)\t :一个制表位,实现对齐的功能

      (3)\n :换行符

      (4)\\ :表示\

      (5)" :表示"

      package com.atguigu.chapter1
      object TestCharType2 {
      def main(args: Array[String]): Unit = {
      var c1: Char = 'a'
      println("c1=" + c1)
      //注意:这里涉及自动类型提升,其实编译器可以自定判断是否超出范围
      //不过idea提示报错
      var c2: Char = 'a' + 1
      println(c2)
      //    \t :一个制表位,实现对齐的功能
      println("姓名\t年龄")
      //   \n :换行符
      println("西门庆\n潘金莲")
      //(4)\\ :表示\
      println("c:\\天黑了\\饿狼来了")
      //(5)\" :表示"
      println("你过来:\"看我一拳打死你\"")
      }
      }
      

      2.10 布尔类型:Boolean

      1)基本说明

      (1)布尔类型也叫Boolean类型,Booolean类型数据只允许取值true和false

      (2)boolean类型占1个字节。

      2)案例实操

      object TestBooleanType {
      def main(args: Array[String]): Unit = {
      var isResult : Boolean = false
      var isResult2 : Boolean = true
      }
      }
      

      2.11 Unit类型、Null类型和Nothing类型(重点)

      1)基本说明

      数据类型

      描述

      Unit

      表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。

      Null

      null , Null 类型只有一个实例值null

      Nothing

      Nothing类型在Scala的类层级最低端;它是任何其他类型的子类型。 当一个函数,我们确定没有正常的返回值(如抛出异常),可以用Nothing来指定返回类型,这样有一个好处,就是我们可以把返回的值(异常)赋给其它的函数或者变量(兼容性)

      在Java中有时候有返回值,如int,但有些情况下会抛出异常。为了兼容多种可能性,就可以使用nothing类型。

      2)案例实操

      (1)Unit类型用来标识过程,也就是没有明确返回值的函数。

      由此可见,Unit类似于Java里的void。Unit只有一个实例——( ),这个实例也没有实质意义

      def m1():Unit={
          println("m1被执行")
      }
      val a:Unit=m1()
      println("a="+a)
      // 输出:a=()

      (2)Null类只有一个实例对象,Null类似于Java中的null引用。Null可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal)

      object TestDataType {
      def main(args: Array[String]): Unit = {
      // 正确
      var n1: Byte = 127
      var n2: Byte = -128
      // 错误
      //    var n3:Byte = 128
      //    var n4:Byte = -129
      // Scala的整型,默认为Int型,声明Long型,然后加”i“ 或者 ”L“
      var n5 = 10
      println(n5)
      var n6 = 98989898989898989L
      println(n6)
      var n7 = 2.43434309898f
      var n8 = 2.4343662343223
      println(n7)
      println(n8)
      // null可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal)
      var God = new God()
      God = null
      //    var n1: Int = null //错误
      //    println("n1:" + n1)
      }
      class God {
      }
      }
      

      (3)Nothing,可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方法不会正常返回,而且由于Nothing是其他任意类型的子类,他还能跟要求返回值的方法兼容。

      object TestSpecialType {
      def test(): Nothing = {
      throw new Exception
      }
      test()
      }
      }
      

      2.12 类型转换

      2.12.1 数值类型自动转换

      当Scala程序在进行赋值或者运算时,精度小的类型自动转换为精度大的数值类型,这个就是自动类型转换(隐式转换)。数据类型按精度(容量)大小排序为:

      1)基本说明

      (1)自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度大的那种数据类型,然后再进行计算。

      (2)把精度大的数值类型赋值给精度小的数值类型时,就会报错,反之就会进行自动类型转换。

      (3)(byte,short)和char之间不会相互自动转换。

      (4)byte,short,char他们三者可以计算,在计算时首先转换为int类型。

      2)案例实操

      object TestValueTransfer {
      def main(args: Array[String]): Unit = {
      //(1)自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度大的那种数值类型,然后再进行计算。
      var n = 1 + 2.2222
      println(n) //Double
      //(2)把精度大的数值类型赋值给精度小的数值类型时,就会报错,反之就会进行自动类型转换。
      var n2: Double = 2.2222
      //    var n3: Int = n2 //报错
      //(3)(byte,short)和char之间不会相互自动转换。
      var n4: Byte = 44
      //    var c1 : Char = n4 //报错
      var n5: Int = n4
      //(4)byte,short,char他们三者可以计算,在计算时首先转换为int类型。
      var n6: Byte = 66
      var c2: Char = 22
      //    var n : Short = n6 + c2  //报错,结果是Int类型
      //    var n7 : Short = 10 + 20  //报错,byte,short,char他们三者可以计算,在计算时首先转换为int类型。
      }
      }
      

      注意:Scala还提供了非常强大的隐式转换机制(隐式函数,隐式类等),我们放在高级部分专门用一个章节来讲解。

      2.12.2 强制类型转换

      1)基本说明

      自动类型转换的逆过程,将精度大的数值类型转换为精度小的数值类型。使用时要加上强制转函数,但可能造成精度降低或溢出,格外要注意。

      2)案例实操

      (1)将数据由高精度转换为低精度,就需要使用到强制转换

      (2)强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级

      object TestForceTransfer {
      def main(args: Array[String]): Unit = {
      //(1)将数据由高精度转换为低精度,就需要使用到强制转换
      var n1: Int = 2.5.toInt // 这个存在精度损失
      //(2)强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级
      var r1: Int = 10 * 3.5.toInt + 6 * 1.5.toInt // 10 *3 + 6*1 = 36
      var r2: Int = (10 * 3.5 + 6 * 1.5).toInt // 44.0.toInt = 44
      println("r1=" + r1 + " r2=" + r2)
      }
      }
      

      2.12.3 数值类型和String类型间转换

      1)基本说明

      在程序开发中,我们经常需要将基本数值类型转成String类型。或者将String类型转成基本数值类型。

      2)案例实操

      (1)基本类型转String类型(语法:将基本类型的值+“” 即可)

      (2)String类型转基本数值类型(语法:s1.toInt、s1.toFloat、s1.toDouble、s1.toByte、s1.toLong、s1.toShort)

      object TestStringTransfer {
      def main(args: Array[String]): Unit = {
      //(1)基本类型转String类型(语法:将基本类型的值+"" 即可)
      var str1: String = true + "你kin"
      println(str1)
      var str2: String = 4.444 + "拜拜"
      println(str2)
      var str3: String = 4444 + ""
      println(str3)
      //(2)String类型转基本数值类型(语法:调用相关API)
      var s1: String = "11"
      var n1: Byte = s1.toByte
      var n2: Short = s1.toShort
      var n3: Int = s1.toInt
      var n4: Long = s1.toLong
      println(s1)
      println(n1)
      println(n2)
      println(n3)
      println(n4)
      }
      }
      

      (3)注意事项

      在将String类型转成基本数值类型时,要确保String类型能够转成有效的数据,比如我们可以把"123",转成一个整数,但是不能把"hello"转成一个整数。

      var n5:Int = “12.6”.toInt会出现NumberFormatException异常。

      2.13 字符串String

      Scala中本身没有String数据类型,字符串本质上是Java String。和Java String 一样,String是一个不可变对象,该对象不可修改。

      三、运算符

      Scala运算符的使用和Java运算符的使用基本相同,只有个别细节上不同。

      3.1 算术运算符

      1)基本语法

      运算符

      运算

      范例

      结果

      +

      正号

      +3

      3

      -

      负号

      b=4; -b

      -4

      +

      5+5

      10

      -

      6-4

      2

      *

      3*4

      12

      /

      5/5

      1

      %

      取模(取余)

      7%5

      2

      +

      字符串相加

      “He”+”llo”

      “Hello”

      (1)对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。

      (2)对一个数取模a%b,和Java的取模规则一样。

      2)案例实操

      object TestArithmetic {
      def main(args: Array[String]): Unit = {
      //    1)对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。
      var r1: Int = 10 / 3
      println(r1) //3
      var r2: Double = 10 / 3
      println(r2) //3.0
      var r3: Double = 10.0 / 3
      println(r3) //3.3333333333333335
      println(r3.formatted("%.2f")) //3.33  含义:保留小数点2位,使用四舍五入
      //(2)对一个数取模a%b,和Java的取模规则一样。
      var r4 = 10 % 3
      println(r4) //1
      }
      }
      

      3.2 关系运算符(比较运算符)

      1)基本语法

      运算符

      运算

      范例

      结果

      ==

      相等于

      4==3

      false

      !=

      不等于

      4!=3

      true

      <

      小于

      4<3

      false

      >

      大于

      4>3

      true

      <=

      小于等于

      4<=3

      false

      >=

      大于等于

      4>=3

      true

      2)案例实操

      (1)需求1:

      object TestRelation {
      def main(args: Array[String]): Unit = {
      // 测试:>、>=、<=、<、==、!=
      var a: Int = 2
      var b: Int = 1
      println(a > b) // true
      println(a >= b) // true
      println(a < b) // false
      println(a <= b) // false
      println("a==b" + (a == b)) // false
      println(a != b) // true
      }
      }
      

      (2)需求2:Java和Scala中关于==的区别

      Java:==比较两个变量本身的值,即两个对象在内存中的首地址;equals比较字符串中所包含的内容是否相同。

      public class TestRelation2 {
      public static void main(String[] args) {
        String s1 = "hhhh";
        String s2 = new String("hhhh");
        System.out.println(s1 == s2);   // false
        System.out.println(s1.equals(s2));  // true
      }
      }
      

      Scala:==更加类似于Java中的equals,参照jd工具

      object TestRelationScala {
      def main(args: Array[String]): Unit = {
      val s1 = "aaa"
      val s2 = new String("aaa")
      println(s1 == s2) //  true
      println(s1.eq(s2))   // false
      }
      }
      

      3.3 逻辑运算符

      1)基本语法

      用于连接多个条件(一般来讲就是关系表达式),最终的结果也是一个Boolean值。

      假定:变量A为true,B为false

      运算符

      描述

      实例

      &&

      逻辑与

      (A && B) 运算结果为 false

      ||

      逻辑或

      (A || B) 运算结果为 true

      !

      逻辑非

      !(A && B) 运算结果为 true

      2)案例实操

      object TestLogic {
      def main(args: Array[String]): Unit = {
      //  测试:&&、||、!
      var a = true
      var b = false
      println("a&&b=" + (a && b)) //  a&&b=false
      println("a || b =" + (a || b))  //  a || b =true
      println("!(a&&b)" + (!(a && b)))  // !(a&&b)true
      }
      }
      

      3.4 赋值运算符

      1)基本语法

      赋值运算符就是将某个运算后的值,赋给指定的变量。

      运算符

      描述

      实例

      =

      简单的赋值运算符,将一个表达式的值赋给一个左值

      C = A + B 将 A + B 表达式结果赋值给 C

      +=

      相加后再赋值

      C += A 等于 C = C + A

      -=

      相减后再赋值

      C -= A 等于 C = C - A

      *=

      相乘后再赋值

      C *= A 等于 C = C * A

      /=

      相除后再赋值

      C /= A 等于 C = C / A

      %=

      求余后再赋值

      C %= A 等于 C = C % A

      <<=

      左移后赋值

      C <<= 2等于 C = C << 2

      >>=

      右移后赋值

      C >>= 2 等于 C = C >> 2

      &=

      按位与后赋值

      C &= 2 等于 C = C & 2

      ^=

      按位异或后赋值

      C ^= 2 等于 C = C ^ 2

      |=

      按位或后赋值

      C |= 2 等于 C = C | 2

      注意:Scala中没有++、–操作符,可以通过+=、-=来实现同样的效果;

      2)案例实操

      object TestAssignment {
      def main(args: Array[String]): Unit = {
      var r1 = 11
      //  没有++
      r1 += 1 //12
      println(r1)
      // 没有--
      r1 -= 2 //10
      println(r1)
      }
      }
      

      3.5 位运算符

      1)基本语法

      下表中变量 a 为 60,b 为 13。

      运算符

      描述

      实例

      &

      按位与运算符

      (a & b) 输出结果 12 ,二进制解释: 0000 1100

      |

      按位或运算符

      (a | b) 输出结果 61 ,二进制解释: 0011 1101

      ^

      按位异或运算符

      (a ^ b) 输出结果 49 ,二进制解释: 0011 0001

      ~

      按位取反运算符

      (~a ) 输出结果 -61 ,二进制解释: 1100 0011, 在一个有符号二进制数的补码形式。

      <<

      左移动运算符

      a << 2 输出结果 240 ,二进制解释: 0011 0000

      >>

      右移动运算符

      a >> 2 输出结果 15 ,二进制解释: 0000 1111

      >>>

      无符号右移

      a >>>2 输出结果 15, 二进制解释: 0000 1111

      2)案例实操

      object TestPositon {
      def main(args: Array[String]): Unit = {
      //  测试:1000 << 1 =>10000
      var n1: Int = 8
      n1 = n1 << 1
      println(n1) // 16
      }
      }
      

      3.6 Scala运算符本质

      在Scala中其实是没有运算符的,所有运算符都是方法。

      1)当调用对象的方法时,点.可以省略

      2)如果函数参数只有一个,或者没有参数,()可以省略

      object TestOpt {
         def main(args: Array[String]): Unit = { 
      // 标准的加法运算
       val i:Int = 1.+(1)
       
       // (1)当调用对象的方法时,.可以省略
       val j:Int = 1 + (1)
       
       // (2)如果函数参数只有一个,或者没有参数,()可以省略
       val k:Int = 1 + 1
       
       println(1.toString())
       println(1 toString())
       println(1 toString)
         }
       }
      

      四、流程控制

      4.1 分支控制if-else

      让程序有选择的的执行,分支控制有三种:单分支、双分支、多分支

      4.1.1 单分支

      1)基本语法

      if (条件表达式) {

      执行代码块
      }

      说明:当条件表达式为ture时,就会执行{ }的代码。

      2)案例实操

      需求:输入人的年龄,如果该同志的年龄小于18岁,则输出“童年”

      if (age < 18){
          println("童年")
      }

      4.1.2 双分支

      1)基本语法

      if (条件表达式) {

      执行代码块1
      } else {

      执行代码块2
      }

      2)案例实操

      需求:输入年龄,如果年龄小于18岁,则输出“童年”。否则,输出“成年”。

      if (age < 18) {
          println("童年")
      } else {
          println("成年")
      }

      4.1.3 多分支

      1)基本语法

      if (条件表达式1) {

      执行代码块1
      }
      else if (条件表达式2) {

      执行代码块2
      }
      ……
      else {

      执行代码块n
      }

      2)案例实操

      (1)需求1:需求:输入年龄,如果年龄小于18岁,则输出“童年”。如果年龄大于等于18且小于等于30,则输出“中年”,否则,输出“老年”。

      if (age < 18) {
          println("童年")
      } else if (age >= 18 && age < 30) {
          println("中年")
      } else {
          println("老年")
      }

      (2)需求2:Scala中if else表达式其实是有返回值的,具体返回值取决于满足条件的代码体的最后一行内容。

      val res :String = if (age < 18) {
          "童年"
      } else if (age >= 18 && age < 30) {
          "中年"
      } else {
          "老年"
      }
      println(res)

      (3)需求3:Scala中返回值类型不一致,取它们共同的祖先类型。

      val res :Any = if (age < 18) {
          "童年"
      } else if (age >= 18 && age < 30) {
          "中年"
      } else {
          100
      }
      println(res)

      (4)需求4:Java中的三元运算符可以用if else实现

      如果大括号{}内的逻辑代码只有一行,大括号可以省略。如果省略大括号,if只对最近的一行逻辑代码起作用。

      // Java中三元运算符:a?b:c
      // 如:int result = flag ? 1 : 0
      // Scala
      val res:String = if(age>=18) "成年" else "未成年"
      println(res)

      4.2 嵌套分支

      在一个分支结构中又完整的嵌套了另一个完整的分支结构,里面的分支的结构称为内层。分支外面的分支结构称为外层分支。嵌套分支不要超过3层。

      1)基本语法

      if(){
      if(){
      }else{
      }
      }

      2)案例实操

      需求:如果输入的年龄小于18,返回“童年”。如果输入的年龄大于等于18,需要再判断:如果年龄大于等于18且小于30,返回“中年”;如果其他,返回“老年”。

      import scala.io.StdIn
      object TestIfElse {
      def main(args: Array[String]): Unit = {
      // Scala
      println("input age:")
      var age = StdIn.readShort()
      val res: Any = if (age < 18) {
      "童年"
      } else {
      if (age >= 18 && age < 30) {
        "中年"
      } else {
        "老年"
      }
      }
      println(res)
      }
      }
      

      4.3 Switch分支结构

      在Scala中没有Switch,而是使用模式匹配来处理。

      模式匹配涉及到的知识点较为综合,因此我们放在后面讲解。

      4.4 For循环控制

      Scala也为for循环这一常见的控制结构提供了非常多的特性,这些for循环的特性被称为for推导式或for表达式。

      4.4.1 范围数据循环(To)

      1)基本语法

      // java for循环语法
      for(int i = 0; i < 10; i++){
          System.out.println("i=" + i);
      }
      // scala for循环语法
      for(i <- 1 to 10){
          println("i=" + i)
      }
      // 输出:从1~10(包含 1 和 10 )

      (1)i 表示循环的变量,<- 规定to

      (2)i 将会从 1-3 循环,前后闭合

      4.4.2 范围数据循环(Until)

      1)基本语法

      // 方法一:
      for(i <- 1 until 10) {
          print("i=" + i)
      }
      // 输出:从1~10(包含 1,不包含 10 )
      // 方法二:
      for(i <- Range(1,10)) {
          print("i=" + i)
      }

      (1)这种方式和前面的区别在于i是从1到3-1

      (2)即使前闭合后开的范围

      4.4.3 循环守卫

      1)基本语法

      for(i <- 1 to 3 if i != 2) {
          print(i + " ")
      }

      说明:

      (1)循环守卫,即循环保护式(也称条件判断式,守卫)。保护式为true则进入循环体内部,为false则跳过,类似于continue。

      (2)上面的代码等价

      for (i <- 1 to 3){
          if (i != 2) {
              print(i + " ")
          }
      }

      4.4.4 循环步长by

      1)基本语法

      for (i <- 1 to 10 by 2) {
          println(“i=” + i)
      }

      说明:by表示步长

      2)案例实操

      需求:输出1到10以内的所有奇数

      for (i <- 1 to 10 by 2) {
      println(“i=” + i)
      }
      输出结果
      i=1
      i=3
      i=5
      i=7
      i=9

      4.4.5 嵌套循环

      1)基本语法

      for(i <- 1 to 3; j <- 1 to 3) {
          println(" i =" + i + " j = " + j)
      }

      说明:没有关键字,所以范围后一定要加;来隔断逻辑

      2)基本语法

      上面的代码等价

      for (i <- 1 to 3) {
          for (j <- 1 to 3) {
              println(“i =” + i + " j=" + j)
          }
      }

      4.4.6 引入变量

      1)基本语法

      for(i <- 1 to 3; j = 4 - i) {
        println("i=" + i + " j=" + j)
      }

      说明:

      (1)for推导式一行中有多个表达式时,所以要加 ; 来隔断逻辑

      (2)for推导式有一个不成文的约定:当for推导式仅包含单一表达式时使用圆括号,当包含多个表达式时,一般每行一个表达式,并用花括号代替圆括号,如下:

      for {
          i <- 1 to 3
          j = 4 - i
      } {
          println(“i=” + i + " j=" + j)
      }

      2)案例实操

      上面的代码等价于

      for (i <- 1 to 3) {
          var j = 4 - i
          println(“i=” + i + " j=" + j)
      }

      4.4.7 循环返回值

      1)基本语法

      val res = for(i <- 1 to 10) yield i
      println(res)

      说明:将遍历过程中处理的结果返回到一个新Vector集合中,使用yield关键字。

      注意:开发中很少使用。

      2)案例实操

      需求:将原数据中所有值乘以2,并把数据返回到一个新的集合中。

      var res = for(i <-1 to 10) yield {
        i * 2
      }
      println(res)

      输出结果:

      Vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)

      4.4.8 倒序打印

      1)说明:如果想倒序打印一组数据,可以用reverse。

      2)案例实操:

      需求:倒序打印10到1

      for(i <- 1 to 10 reverse){
         println(i)
       }
      

      4.5 While和do…While循环控制

      While和do…While的使用和Java语言中用法相同。

      4.5.1 While循环控制

      1)基本语法

      循环变量初始化
      while (循环条件) {
         循环体(语句)
         循环变量迭代
      }
      

      说明:

      (1)循环条件是返回一个布尔值的表达式

      (2)while循环是先判断再执行语句

      (3)与for语句不同,while语句没有返回值,即整个while语句的结果是Unit类型()

      (4)因为while中没有返回值,所以当要用该语句来计算并返回结果时,就不可避免的使用变量,而变量需要声明在while循环的外部,那么就等同于循环的内部对外部的变量造成了影响,所以不推荐使用,而是推荐使用for循环。

      object For01 {
      def main(args: Array[String]): Unit = {
      //    1.范围循环 To
      for (i <- 1 to 3)
      println("生如")
      println("to----------------------")
      //  2.范围循环 Unit
      for (i <- 1 until 3)
      println("夏花")
      println("until----------------------")
      //  3.循环守卫
      for (i <- 1 to 6) {
      if (i != 2) {
        println(i)
      }
      }
      println("!=----------------------")
      //    4.循环步长 by
      for (i <- 1 to 6 by 2)
      println(i)
      println("by----------------------")
      for (i <- -10 to -1 by 2)
      println(i)
      println("by----------------------")
      //    5.循环嵌套
      for (i <- 1 to 4; j <- 1 to 6) {
      print("i=" + i + ",j=" + j + "\t")
      if (j == 6)
        println()
      }
      println("嵌套----------------------")
      //  6.引入变量
      for (i <- 1 to 10; j = 10 - i) {
      println("i=" + i + ",j=" + j)
      }
      println("引入变量----------------------")
      for {
      i <- 1 to 3
      j = 3 - i
      } {
      println("i=" + i + ",j=" + j)
      }
      println("引入变量----------------------")
      //  7.循环返回值
      val res = for (i <- 1 to 10) yield i
      println(res)
      println("----------------------")
      //需求:将原数据中所有值乘以2,并把数据返回到一个新的集合中。
      var res1 = for (i <- 1 to 10) yield {
      i * 2
      }
      println(res1)
      println("循环返回值----------------------")
      //  8.倒序打印
      for (i <- 1 to 10 reverse) {
      println(i)
      }
      println("倒序----------------------")
      }
      }
      

      4.5.2 do…while循环控制

      1)基本语法

       循环变量初始化;
        do{
          循环体(语句)
          循环变量迭代
        } while(循环条件)
      

      说明

      (1)循环条件是返回一个布尔值的表达式

      (2)do…while循环是先执行,再判断

      4.6 循环中断

      1)基本说明

      Scala内置控制结构特地去掉了break和continue,是为了更好的适应函数式编程,推荐使用函数式的风格解决break和continue的功能,而不是一个关键字。Scala中使用breakable控制结构来实现break和continue功能。

      2)案例实操

      需求1:采用异常的方式退出循环

      //  1.正常结束循环
      try {
       for (elem <- 1 to 10) {
         println(elem)
         if (elem == 5) throw new RuntimeException
       }
      } catch {
       case e =>}
      println("正常结束循环")
      }
      

      需求2:采用Scala自带的函数,退出循环

      //  2.采用scala自带的函数,退出循环
      Breaks.breakable(
          for (e <- 1 to 5) {
              println(e)
              if (e == 5) Breaks.break()
          }
      )
      println("正常结束循环")

      需求3:对break进行省略

      import scala.util.control.Breaks._
      breakable {
          for (elem <- 1 to 10) {
               println(elem)
            if (elem == 5) break
          }
      }
      println("正常结束循环")

      需求4:循环遍历10以内的所有数据,奇数打印,偶数跳过(continue)

      //循环遍历10以内的所有数据,奇数打印,偶数跳过(continue)
      for (elem <- 1 to 10) {
          if (elem % 2 == 1){
               println(elem)
          }else{
               println("continue")
          }
      }

      4.7 多重循环

      1)基本说明

      (1)将一个循环放在另一个循环体内,就形成了嵌套循环。其中,for,while,do…while均可以作为外层循环和内层循环。【建议一般使用两层,最多不要超过3层】

      (2)设外层循环次数为m次,内层为n次,则内层循环体实际上需要执行m*n次。

      2)案例实操

      需求:打印出九九乘法表

      for (i <- 1 to 10) {
          for (j <- 1 to i) {
            print(j + "*" + i + "=" + (i * j) + "\t")
          }
      println()
      }

      五、函数式编程

      1)面向对象编程

      解决问题,分解对象,行为,属性,然后通过对象的关系以及行为的调用来解决问题。

      对象:用户

      行为:登录、连接JDBC、读取数据库

      属性:用户名、密码

      Scala语言是一个完全面向对象编程语言。万物皆对象

      对象的本质:对数据和行为的一个封装

      2)函数式编程

      解决问题时,将问题分解成一个一个的步骤,将每个步骤进行封装(函数),通过调用这些封装好的步骤,解决问题。

      例如:请求->用户名、密码->连接JDBC->读取数据库

      Scala语言是一个完全函数式编程语言。万物皆函数。

      函数的本质:函数可以当做一个值进行传递

      3)在Scala中函数式编程和面向对象编程完美融合在一起了。

      5.1 函数基础

      5.1.1 函数基本语法

      1)基本语法

      def sum ( x : Int , y : Int ) : Int = {
          x + y
      }

      2)案例实操

      需求:定义一个函数,实现将传入的名称打印出来。

      object TestFunction {
          def main(args: Array[String]): Unit = {
      ​ // (1)函数定义
              def f(arg: String): Unit = {
      ​            println(arg)
              }
      ​ // (2)函数调用
      ​ // 函数名(参数)
              f(“hello world”)
          }
      }

      5.1.2 函数和方法的区别

      1)核心概念

      (1)为完成某一功能的程序语句的集合,称为函数。

      (2)类中的函数称之方法。

      2)案例实操

      (1)Scala语言可以在任何的语法结构中声明任何的语法

      (2)函数没有重载和重写的概念;方法可以进行重载和重写

      (3)Scala中函数可以嵌套定义

      5.1.3 函数定义

      1)函数定义

      (1)函数1:无参,无返回值

      (2)函数2:无参,有返回值

      (3)函数3:有参,无返回值

      (4)函数4:有参,有返回值

      (5)函数5:多参,无返回值

      (6)函数6:多参,有返回值

      2)案例实操

      object TestFunctionDeclare {
          def main(args: Array[String]): Unit = {
      //1.无参,无返回值
              def t1():Unit={
                  println("1.无参,无返回值")
              }
              t1()
      //2.无参,有返回值
              def t2():String ={
                  return "2.无参,有返回值"
              }
              println(t2())
          
      //3.有参,无返回值
              def t3(s:String):Unit={
                  println(s)
              }
              t3("t3")
          
      //4.有参,有返回值
              def t4(s:String):String={
                  return s+"4.有参,有返回值"
              }
              println(t4("hello"))
          
      //5.多参,无返回值
              def t5(name:String,age:Int):Unit={
                  println(s"$name,$age")
              }
              t5("monika",22)
          }
      }

      5.1.4 函数参数

      1)案例实操

      (1)可变参数

      (2)如果参数列表中存在多个参数,那么可变参数一般放置在最后

      (3)参数默认值,一般将有默认值的参数放置在参数列表的后面

      (4)带名参数

      def main(args: Array[String]): Unit = {
      //1.可变参数
          def test(s: String*): Unit = {
              println(s)
              for (str<-s) println(str)
          }
          //有输入参数:输出Array
          test("Hello", "Scala")
          //无输入参数:输出List()
          test()
      //2.如果参数列表中存在多个参数,那么可变参数一般放置在最后
          def test2(name: String, s: String): Unit = {
              println(name + "," + s)
          }
          test2("jinlian", "dalang")
      //3.参数默认值
          def test3(name: String, age: Int = 30): Unit = {
              println(s"$name,$age")
          }
          //如果参数传递了值,那么会覆盖默认值
          test3("jinlian", 20)
          //如果参数有默认值,在调用的时候,可以省略这个参数
          test3("dalang")
          //一般情况下,将有默认值的参数放置在参数列表的后面
          def test4(sex: String = "男", name: String): Unit = {
              println(s"name,sex")
          }
          //Scala函数中参数传递是,从左到右
          //test4("wusong") //报错
          test4("女", "jinlian")
      //4.带名参数
          test4(name = "ximenqing")
      }

      5.1.5 函数至简原则(重点)

      函数至简原则:能省则省

      1)至简原则细节

      (1)return可以省略,Scala会使用函数体的最后一行代码作为返回值

      (2)如果函数体只有一行代码,可以省略花括号

      (3)返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)

      (4)如果有return,则不能省略返回值类型,必须指定

      (5)如果函数明确声明unit,那么即使函数体中使用return关键字也不起作用

      (6)Scala如果期望是无返回值类型,可以省略等号

      (7)如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加

      (8)如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略

      (9)如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略(匿名函数,lambda表达式)

      lambda表达式:

      ( name: String ) => { println( name ) }

      2)案例实操

      def main(args: Array[String]): Unit = {
          def f(s: String): String = {
              return s + "f"
          }
      // 1.return可以省略,scala会使用函数体的最后一行代码作为返回值
          def f1(s: String): String = {
              s + "f1"
          }
      // 2.如果函数体只有一行代码,可以省略花括号
          def f2(s: String): String = s + "f2"
      // 3.返回值类型如果能够推断出来,那么可以省略
          def f3(s: String) = s + "f3"
      // 4.如果有return,则不能省略返回值类型,必须指定
          def f4(): String = {
              return "f4"
          }
      // 5.如果函数明确声明unit,那么即使函数体中使用return关键字也不起作用
          def f5(): Unit = {
              return "f5"
          }
      // 6.如果期望是无返回值类型,那么可以省略等号
          def f6() {
              "f6"
          }
      // 7.如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
          def f7() = "f7"
          println(f7())
          println(f7)
      // 8.如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
          def f8() = "f8" 
      简化为=>     def f8 = "f8"
          println(f8)
      // 9.如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
          def f9(str: String) = { println("f9") }
      简化为=>    // 匿名函数,lambda表达式
          (name: String) => { println(name) }
      }

      5.2 函数高级

      5.2.1 高阶函数

      在Scala中,函数是一等公民。怎么体现的呢?

      对于一个函数我们可以:定义函数、调用函数

      object TestFunction {
        def main(args: Array[String]): Unit = {
          // 调用函数
          foo()
        }
        // 定义函数
        def foo():Unit = {
          println("foo...")
        }
      }
      

      但是其实函数还有更高阶的用法

      1)函数可以作为值进行传递
      def f(n: Int):Int = {
          println("f调用")
          n + 1
      }
      // 调用f(123),返回值124,将返回值赋值给val变量result
      val result: Int = f(123)    
      println(result)        // 输出124
      2)函数可以作为参数进行传递
      // 正常定义函数
      def funTest03(msg1:String, msg2:String) : String = {  }
      // msg1替换为:f:(Int,Int)=>Int (lambda表达式)
      def funTest( f:(Int,Int)=>Int, msg:String ):String={
        val a = 100;
        val b = 200;
        val result = f(a,b)
        msg + result
      }
      // 函数调用
      println("funTest:"+funTest(_*_,"abc"))    // funTest:abc20000
      // 重新定义f函数
      val f:(Int,Int)=>Int = (a:Int,b:Int)=>a*b
      println("funTest:"+funTest(f,"abc"))      // funTest:abc20000
      备注:f函数说明
      // 参数的定义:
          val a: Int = 12
      // 函数的定义:
          def f(a:Int,b:Int):Int = { a + b }
          // lambda表达式
          val f:(Int,Int)=>Int = { (a:Int,b:Int)=>a*b }
      其中:
      (Int,Int)=>Int    是函数类型,表示输入两个Int类型参数,输出一个Int类型值。
                         类似参数定义中的Int类型值,可省略
      (a:Int,b:Int)=>a*b  a,b为形参,a*b为返回值
      // 简化:
      // 1. 函数体只有一条语句,省略{}
          val f:(Int,Int)=>Int = (a:Int,b:Int)=>a*b
      // 2. 函数类型可省略
          val f = (a:Int,b:Int)=>a*b

      函数简化后我们就比较容易看出,f函数本质是匿名函数作为值传递给一个变量f

      3)函数可以作为返回值进行传递
      // 正常定义函数
      def funTest03(num:Int) : String = {  }
      // 返回类型:函数
      // (Int,Int)=>Int:传入两个Int类型的参数,返回一个Int类型的参数
      def funTest03(num:Int) : (Int,Int)=>Int = {
        println("num value is " + num)
        def sum(a: Int, b: Int): Int = a + b
        def sub(a: Int, b: Int): Int = a - b
        if(num%2==0) sum else sub
      }
      // 方法调用
      val result = funTest03(10)(10,20)
      println(result)
      // 上面方法调用等效于如下:
      val resultFun:(Int, Int) => Int = funTest03(9)
      val resultVal = resultFun(10,20)
      println(resultFun)

      5.2.2 匿名函数

      1)说明

      没有名字的函数就是匿名函数。

      ( x : Int ) => { 函数体 }

      x:表示输入参数类型;Int:表示输入参数类型;函数体:表示具体代码逻辑

      2)案例实操

      ​ 需求1:传递的函数有一个参数

      传递匿名函数至简原则:

      (1)参数的类型可以省略,会根据形参进行自动的推导

      (2)类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过1的永远不能省略圆括号。

      (3)匿名函数如果只有一行,则大括号也可以省略

      (4)如果参数只出现一次,则参数省略且后面参数可以用_代替

      // 函数原型
          f( (name: String) => { println(name) } ) 
      // (1)参数的类型可以省略,会根据形参进行自动的推导
          f( (name) => { println(name) })
      // (2)类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过1的永远不能省略圆括号。
          f( name => { println(name) })
      // (3)匿名函数如果只有一行,则大括号也可以省略
          f( (name) => println(name) )
      // (4)如果参数只出现一次,则参数省略且后面参数可以用_代替
          f( println(_) )
      // (5)如果可以推导出,当前传入的println是一个函数体,而不是条用语句,可以直接省略下划线
          f( println )

      实际案例:

      定义 一个“二元运算”函数

      // 基础函数
      def sum(a:Int, b:Int):Int = a + b
      println(sum(1,2))
      // 函数作为值进行传递
      val add = (a:Int, b:Int) => a + b
      println(add(1,2))
      val add2: (Int, Int) => Int = (a:Int, b:Int)=>{
                                          println("a + b = "+ (a+b))
                                          a + b
                                      }
      // 用函数作为参数传递
      def funTest( a:Int, msg:String ):String={ }    //原函数
      def funTest( f:(Int,Int)=>Int, msg:String ):String={
          val a = 100
          val b = 200
          val result = f(a,b)
          msg + result
        }
      def funcTest2(num:Int, f1:(Int,Int)=>Int,f2:(Int,Int)=>Int):Int={
          val value1 = 1000
          val value2 = 2000
          val res:Int = if (num%2==0) f1(value1,value2) else f2(value1,value2)
          res
        }
       println(funcTest2(9 , _+_ , _-_ ))

      5.3 函数柯里化

      柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数

      def fun(a:Int,b:Int,c:Int,d:Int):Int={
          a+b+c+d
      }
      def fun2(a:Int,b:Int)(c:Int,d:Int):Int={
          a+b+c+d
      }
      def fun3(a:Int,b:Int,c:Int)(d:Int):Int={
          a+b+c+d
      }
      // fun3:传入三个Int类型的参数,输出一个Int类型的值
      private val fun: (Int, Int, Int) => Int => Int = fun3 _
      def fun4(a:Int)(b:Int)(c:Int)(d:Int):Int={
          a+b+c+d
      }
      private val fun: Int => Int => Int => Int => Int = fun4 _

      5.4 偏函数

      所谓偏函数,就是以原函数为基础,将某个位置上的参数固定住(默认是从第一个参数固定),后续参数重新扩展传递给原函数,对外则是生成一个新函数。

      使用collect方法并行计算

      // 普通方法定义一个函数
      def funPartition:(Int, Int)=> Int= { }
      def funPartition:PartialFunction[String,Int] = {
          case "hello" => 1
          case "world" => 2
          case _       => 3
      }
      println(funPartition("scala"))        // 输出3
      // 输入一个数组,并进行计算
      val strings = Array("hello","java","scala","world")
      val ints: Array[Int] = strings.collect(funPartition)
      println(ints.mkString(","))        // 1,3,3,2

      六、面向对象

      Scala的面向对象思想和Java的面向对象思想和概念是一致的。

      Scala中语法和Java不同,补充了更多的功能。

      6.1 Scala包

      1)基本语法

      package 包名

      2)Scala包的三大作用(和Java一样)

      (1)区分相同名字的类

      (2)当类很多时,可以很好的管理类

      (3)控制访问范围

      6.1.1 包的命名

      1)命名规则

      只能包含数字、字母、下划线、小圆点.,但不能用数字开头,也不要使用关键字。

      2)案例实操

      demo.class.exec1 //错误,因为 class 关键字
      demo.12a //错误,数字开头

      3)命名规范

      一般是小写字母+小圆点

      com.公司名.项目名.业务模块名

      6.1.2 包说明(包语句)

      1)说明

      Scala有两种包的管理风格,一种方式和Java的包管理风格相同,每个源文件一个包(包名和源文件所在路径不要求必须一致),包名用“.”进行分隔以表示包的层级关系,如com.atguigu.scala。另一种风格,通过嵌套的风格表示层级关系,如下:

      package com{
          package baidu{
              package scala{
              }
          }
      }

      第二种风格有以下特点:

      (1)一个源文件中可以声明多个package

      (2)子包中的类可以直接访问父包中的内容,而无需导包

      6.1.3 包对象

      在Scala中可以为每个包定义一个同名的包对象,定义在包对象中的成员,作为其对应包下所有class和object的共享变量,可以被直接访问。

      1)定义

      package object com{
          val shareValue="share"
          def shareMethod()={}
      }

      2) 说明

      若使用Java的包管理风格,则包对象一般定义在其对应包下的package.scala文件中,包对象名与包名保持一致。

      6.1.4 导包说明

      1)和Java一样,可以在顶部使用import导入,在这个文件中的所有类都可以使用。

      2)局部导入:什么时候使用,什么时候导入。在其作用范围内都可以使用

      3)通配符导入:import java.util._

      4)给类起名:import java.util.{ArrayList=>JL}

      5)屏蔽类:import java.util.{ArrayList =>,}

      6)导入相同包的多个类:import java.util.{HashSet, ArrayList}

      7)导入包的绝对路径:new root.java.util.HashMap

      6.2 类和对象

      类:可以看成一个模板

      对象:表示具体的事物

      6.2.1 定义类

      0)回顾:Java中的类

      如果类是public的,则必须和文件名一致。

      一般,一个.java有一个public类

      注意:Scala中没有public,一个.scala中可以写多个类。

      1)基本语法

      [修饰符] class类名 {
          类体
      }

      说明

      (1)Scala语法中,类并不声明为public,所有这些类都具有公有可见性(即默认就是public)

      (2)一个Scala源文件可以包含多个类

      2)案例实操

      //    (1)Scala语法中,类并不声明public,所有这些类都具有共有可见性(即默认就是public)
      class Person {
          // 定义属性
          private var name: String = "alice"
          var age: Int = _
          var sex: String = _
      }
      //    (2) 一个Scala源文件可以包含多个类
      class Teacher {
      }
      def main(args: Array[String]): Unit = {
          // 创建一个对象
          val stu = new Student;
          stu.name        // error,不能访问
          stu.age
          stu.sex
      }

      6.3 封装

      封装就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作。Java封装操作如下,

      (1)将属性进行私有化

      (2)提供一个公共的set方法,用于对属性赋值

      (3)提供一个公共的get方法,用于获取属性的值

      Scala中的public属性,底层实际为private,并通过get方法(obj.field())和set方法(obj.field_=(value))对其进行操作。所以Scala并不推荐将属性设为private,再为其设置public的get和set方法的做法。但由于很多Java框架都利用反射调用getXXX和setXXX方法,有时候为了和这些框架兼容,也会为Scala的属性设置getXXX和setXXX方法(通过@BeanProperty注解实现)。

      6.3.1 访问权限

      1)说明

      在Java中,访问权限分为:public,private,protected和默认。在Scala中,你可以通过类似的修饰符达到同样的效果。但是使用上有区别。

      (1)Scala 中属性和方法的默认访问权限为public,但Scala中无public关键字。

      (2)private为私有权限,只在类的内部和伴生对象中可用。

      (3)protected为受保护权限,Scala中受保护权限比Java中更严格,同类、子类可以访问,同包无法访问。

      (4)private[包名]增加包访问权限,包名下的其他类也可以使用

      6.3.3 创建对象

      1)基本语法

      val | var 对象名 [:类型] = new类型()

      2)案例实操

      (1)val修饰对象,不能改变对象的引用(即:内存地址),可以改变对象属性的值。

      (2)var修饰对象,可以修改对象的引用和修改对象的属性值

      (3)自动推导变量类型不能多态,所以多态需要显示声明

      class Person05 {
          var name: String = "taotao"
      }
      object Person05 {
          def main(args: Array[String]): Unit = {
      //    val 修饰对象,不能改变对象的引用(即内存地址),可以改变对象属性的值。
              val person = new Person05()
              person.name = "bingbing"
          }
      }

      6.3.4 构造器

      和Java一样,Scala构造对象也需要调用构造方法,并且可以有任意多个构造方法。

      Scala类的构造器包括:主构造器和辅助构造器

      1)基本语法

      class 类名 (形参列表){ / /主构造器

      // 类体

      def this(形参列表){ // 辅助构造器

      }

      def this(形参列表){ // 辅助构造器可以有多个。。。

      }
      }

      说明:

      (1)辅助构造器,函数的名称this,可以有多个,编译器通过参数的个数及类型来区分。

      (2)辅助构造方法不能直接构建对象,必须直接或者间接调用主构造方法。

      (3)构造器调用其他另外的构造器,要求被调用构造器必须提前声明

      2)案例实操

      // 定义一个类
      class Student{
          // 定义属性
          var name: String = _
          var age: Int = _
          println("1. 主构造方法被调用")
          
          // 声明辅助构造方法
          def this(name: String){
              this()    // 直接调用主构造器
              println("2. 辅助构造方法一被调用")
              this.name = name
          }
          def this(name: String, age:Int){
              this(name)
              println("3. 辅助构造方法二被调用")
              this.age = age
          }
      }

      6.3.5 构造器参数

      1)说明

      Scala类的主构造器函数的形参包括三种类型:未用任何修饰、var修饰、val修饰

      (1)未用任何修饰符修饰,这个参数就是一个局部变量

      (2)var修饰参数,作为类的成员属性使用,可以修改

      (3)val修饰参数,作为类只读属性使用,不能修改

      2)案例实操

      // 定义类
      // 无参构造器
      class Student{
          // 单独定义属性
          var name : String = _
          var age : Int = _
      }
      // 上面等价于:
      class Student(var name: String, var age: Int)
      val stu = new Student("bob",20)

      6.4 继承

      1)基本语法

      class 子类名 extends 父类名 { 类体 }

      (1)子类继承父类的属性和方法

      (2)scala是单继承

      2)案例实操

      (1)子类继承父类的属性和方法

      (2)继承的调用顺序:父类构造器->子类构造器

      6.5 抽象属性和抽象方法abstract

      6.5.1 抽象属性和抽象方法

      1)基本语法

      ​ (1)定义抽象类:abstract class Person{} //通过abstract关键字标记抽象类

      ​ (2)定义抽象属性:val|var name:String //一个属性没有初始化,就是抽象属性

      ​ (3)定义抽象方法:def hello():String //只声明而没有实现的方法,就是抽象方法

      abstract class Person09 {
          val name: String  //抽象属性
          def hello(): Unit   //抽象方法
      }
      class Teacher extends Person09{
          val name:String = "teacher"
          def hello():Unit = {
              println("hello teacher")
          }
      }
      

      2)继承&重写

      (1)如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类

      (2)重写非抽象方法需要用override修饰,重写抽象方法则可以不加override。

      (3)子类中调用父类的方法使用super关键字

      (4)子类对抽象属性进行实现,父类抽象属性可以用var修饰;

      子类对非抽象属性重写,父类非抽象属性只支持val类型,而不支持var。

      因为var修饰的为可变变量,子类继承之后就可以直接使用,没有必要重写

      (5)Scala中属性和方法都是动态绑定,而Java中只有方法为动态绑定。

      6.5.2 匿名子类

      1)说明

      Java一样,可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类。

      2)案例实操

      // 定义抽象类
      abstract class Persion{
          var name: String
          def eat(): Unit
      }
      def main(args:Array[String]): Unit = {
          val person:Person = new Persion{
              override var name: String = "candy"
              override def eat(): Unit = println("eat...")
          }
          println(person.name)
          person.eat()
      }

      6.6 特质(Trait)

      Scala语言中,采用特质trait(特征)来代替接口的概念,也就是说,多个类具有相同的特质(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明。

      Scala中的trait中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质。这种感觉类似于Java中的抽象类。

      Scala引入trait特征,第一可以替代Java的接口,第二个也是对单继承机制的一种补充。

      6.6.1 特质声明

      1)基本语法

      trait 特质名 {
        trait主体
      }
      

      6.6.2 特质基本语法

      一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了extends关键字,如果有多个特质或存在父类,那么需要采用with关键字连接。

      1)基本语法

      没有父类:class 类名 extends 特质1 with 特质2 with 特质3 ...

      有父类:class 类名 extends 父类 with 特质2 with 特质3 ...

      2)说明

      (1)类和特质的关系:使用继承的关系。

      (2)当一个类去继承特质时,第一个连接词是extends,后面是with。

      (3)如果一个类在同时继承特质和父类时,应当把父类写在extends后。

      package org.example
      object Person {
        def main(agrs: Array[String]): Unit = {
          val student:Student = new Student
          student.age = 20
          student.say()
          student.study
          student.dating
          student.play
          println(student.age)
        }
      }
      class Person {
        val name: String = "person"
        var age: Int = 18
        def say(): Unit = {
          println("hello from " + name )
        }
      }
      // 定义一个特质
      trait Young {
        // 声明抽象和非抽象属性
        var age: Int
        val name: String = "young"
        // 声明抽象和非抽象方法
        def play(): Unit = {
          println("young people is playing")
        }
        def dating(): Unit
      }
      class Student extends Person with Young{
        // 重写冲突的属性
        override val name: String = "student"
        // 实现抽象方法
        def dating(): Unit = println(s"student $name is dating")
        def study(): Unit = println(s"student $name is studing")
        // 重写父类方法
        override def say(): Unit = {
          super.say()
          println(s"hello from: student $name")
        }
      }

      6.6.3 特质叠加

      由于一个类可以混入(mixin)多个trait,且trait中可以有具体的属性和方法,若混入的特质中具有相同的方法(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。冲突分为以下两种:

      第一种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法。

      第二种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait继承自相同的trait(TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala采用了特质叠加(从右到左)的策略。

      所谓的特质叠加,就是将混入的多个trait中的冲突方法叠加起来,案例如下,

      object Test14_Trait {
        def main(args: Array[String]): Unit = {
          val myBall = new MyBall()
          println(myBall.describe()) //yellow-foot-ball
        }
      }
      //定义一个父特征
      trait Ball {
        def describe(): String = "ball"
      }
      //定义子特征
      trait Color extends Ball {
        var color: String = "yellow"
        override def describe(): String = color + "-" + super.describe()
      }
      trait Category extends Ball {
        var category: String = "foot"
        override def describe(): String = category + "-" + super.describe()
      }
      //定义子类,继承两种特征
      class MyBall extends Category with Color {
        override def describe(): String = "my ball is a" + super.describe()
      }
      

      结果如下:

      6.6.4 特质叠加执行顺序(从右到左按顺序叠加)

      当一个类混入多个特质的时候,scala会对所有的特质及其父特质按照一定的顺序进行排序,而此案例中的super.describe()调用的实际上是排好序后的下一个特质中的describe()方法。,排序规则如下:

      6.6.5特质和抽象类的区别

      1)优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类。

      2)如果你需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数,而特质不行(有无参构造)。

      6.6.6 动态混入特质

      在Java中,当一个类定义之后,没有继承的接口不可以实现。而在sacla中,可以动态混入特质,如下:

      // 1. 父类-抽象类
      abstract class Car {
        def brand:String
        def engine:String
        def laba():Unit = {
          println("鸣笛")
        }
      }
      // 2. 定义子类,继承Car父类,实现父类抽象方法
      class LingYun extends Car{
        override def brand: String = {
          println("凌云")
          "凌云骑车"
        }
        override def engine: String = {
          println("纯电动汽车")
          "电动车"
        }
      }
      // 3. 定义特质
      trait Balance {
        def balance:String
      }
      trait FlyAndSea {
        def fly={
          println("冲上云霄")
        }
        def downSea():Unit
      }
      // 4. 实例化对象
      object TestCar{
        def main(args: Array[String]): Unit = {
          // 动态混入特性,{ 花括号内重写方法 }
          val lingyun:LingYun with Balance with FlyAndSea = 
              new LingYun with Balance with FlyAndSea{
            override def balance: String = { 
              println("空气悬挂")
              "空气悬挂" 
            }
            override def downSea(): Unit = { println("下还") }
          }
        }
      }

      七、模式匹配match

      Scala中的模式匹配类似于Java中的switch语法,但scala从语法中补充了更多的功能。

      7.1 基本语法

      模式匹配语法中,采用match关键字声明,每个分支采用case关键字进行声明,当需要匹配时,会从第一个case分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有case都不匹配,那么会执行case _分支,类似于Java中default语句。

      1)说明

      (1)如果所有case都不匹配,那么会执行case _ 分支,类似于Java中default语句,若此时没有case _ 分支,那么会抛出MatchError。

      (2)每个case中,不需要使用break语句,自动中断case。

      (3)match case语句可以匹配任何类型,而不只是字面量。

      (4)=> 后面的代码块,直到下一个case语句之前的代码是作为一个整体执行,可以使用{}括起来,也可以不括。

      // 模式匹配
        def match1(x:Int):String={
          x match {
            case 1 => "one"
            case 2 => "two"
            case _ => "many"
          }
        }
        println("match1=>"+match1(1))    // match1 => one

      2)案例实操:用匹配模式实现简单的二元运算

       def matchs(a:Int,b:Int)(op:Char):Int = {
          op match{
            case '+' => a+b
            case '-' => a-b
            case '*' => a*b
            case '/' => a/b
            case _   -> -1
          } 
        }
        println(matchs(3,2)('*'))

      7.2 模式守卫

      1)说明

      如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫。

      2)案例实操:

      // "A" 优秀 "B"良好 "C"合格 "D"不合格  "韩梅梅" 特殊学生
        def match4(name:String,score:String):Unit={
          score match {
            case "A" => println(name + "优秀")
            case "B" => println(name + "良好")
            case "C" => println(name + "合格")
            case _ if name=="韩梅梅" => println("特殊学生")
            case _ => println(name + "不合格")
          }
        }

      7.3 模式匹配类型

      7.3.1 匹配常量

      1)说明

      Scala中,模式匹配可以匹配所有的字面量,包括字符串,字符,数字,布尔值等等。

      2)实操

       def defcribeConst(x:Any):String=x match {
          case 1 => "Int one"
          case "hello" => "String hello"
          case true => "Boolean true"
          case '+' => "Char +"
          case _ => "else"                // _ 下划线为占位符,也可以换成其他符号
        }
        println(defcribeConst(2.3))

      7.3.2 匹配类型

      注意:

      (1)我们在底层操作的时候,JVM有“泛型擦除”,判断底层类型的时候,只能判断出是一个List类型,而List里面的泛型会被擦除。所以不管List里面的泛型是Int还是String,都是List,所以能够直接匹配成功。但Array没有泛型擦除。

      (2)_表示占位符,当数组中有多个元素时,用 _* 表示多个元素的占位符

       def defcribeType(x:Any):String=x match {
          case i:Int => "Int " + i
          case s:String => "String "+s
          case list:List[String] => "List "+list.mkString("-")
          case _ => "something else"
        }
        println(defcribeType(List(1,3,2,4)))    // List 1-3-2-4
        println(defcribeType(2.3))               // something else

      7.3.3 匹配数组

      1)说明

      scala模式匹配可以对集合进行精确的匹配,例如匹配只有两个元素的、且第一个元素为0的数组。

      2)案例实操

       def match5(arr:Array[String]):Unit={
          arr match {
            case Array("jack") => println("hello jack")
            case Array(str1,str2) => println(str1,str2)
            case Array("aaa",_*) => println("多元素数组")
            case Array(s1,s2,"nancy",_*) => println("第三个元素是nancy的数组")
            case Array(_,_,"bob",_*) => println("第三个元素是bob的数组",arr.mkString("-"))
            case _ => println("who are u")
          }
        }

      7.3.4 匹配列表

      1)说明

      匹配列表与匹配数组类似。

      2)案例实操

       def defcribeList(list:List[String]) = {
          list match {
            case List("1") => println(1)
            case List("acdc",_) =>println("acdc开头的list")
            case List(l1,l2) => println(l1+","+l2)
            case _ => println("其他")
          }
        }
        defcribeList(List("acdb","1"))        // acdc开头的list

      7.3.5 匹配对象、样例类case

      1)匹配对象的方式

      (1)定义类

      (2)定义伴生对象

      (3)实现apply方法、实现unapply方法,用来对对象属性进行拆解

      上面这种方法尽管实现了匹配对象的功能,但是过于复杂。于是引入了样例类 case的概念。

      样例类的定义只需要在类的前面加关键字case。默认定义了伴生对象,并实现了apply、unapply方法

      object FunctionDemo {
        // 定义样例类
        case class Teacher(name:String, age:Int)
        def match(teacher:Teacher):Unit={
          teacher match {
            case Teacher("gree", 39) => println("hello gree teacher")
            case Teacher("burlin", 18) => println("hello burlin")
            case Teacher(name, age) if name=="李伟" => println("大神")
            case x:Teacher => println("hello "+x.name)
          }
        }
        match3(Teacher("gree",39))    // hello gree teacher
        match3(Teacher("gree",40))    // hello gree,age不符合
      }

      八、隐式转换

      当编译器第一次编译失败的时候,会在当前的环境中查找能让代码编译通过的方法,用于将类型进行转换,实现二次编译。

      8.1 隐式函数

      1)说明

      scala一切皆对象,通过toXxx的方法进行类型转换。但是当对象数量较多的时候,挨个调用方法进行转换,会大量增加代码量。于是引入了隐式转换的概念。

      隐式转换可以在不需改任何代码的情况下,扩展某个类的功能。

      // 当传入参数和输出参数不匹配时,运行时会报错
      val a:Int = "1"
      val s:String = 10
      // scala一切皆对象,通过toInt方法进行转换。
      val a:Int = "1".toInt
      val s:String = 10.toString

      2)案例实操

      需求:通过隐式转化为Int类型增加方法。

      // 使用implicit关键字声明的函数称之为隐式函数
      // 当想调用对象功能时,如果编译错误,那么编译器会尝试在当前作用域范围内查找能调用相应功能的转换规则
      // 这个调用过程是由编译器完成的,所以称之为隐式转换,也叫自动转换
      implicit def stringToInt(value:String):Int=Integer.parseInt(value)
      implicit def intToString(value:Int):String=value.toString
      val a:Int = "1"
      val s:String = 10

      8.2 隐式参数

      普通方法或者函数中的参数可以通过implicit关键字声明为隐式参数,调用该方法时,就可以传入该参数,编译器会在相应的作用域寻找符合条件的隐式值。

      1)说明

      (1)同一个作用域中,相同类型的隐式值只能有一个

      (2)编译器按照隐式参数的类型去寻找对应类型的隐式值,与隐式值的名称无关。

      (3)隐式参数优先于默认参数

      2)案例实操

      // 定义sum函数
        def sum(a:Int)(b:Int,name:String):Int={
          println(name)
          a + b
        }
        println(sum(10)(20,"张三"))
      // 定义隐式参数
        implicit val num1:Int = 11        // 与参数名无关,与类型有关
        implicit val name:String = "张三"
        def sum2(a:Int)(implicit name:String, b:Int):Int={
          println("sum2="+name)
          a + b
        }
        println(sum2(10))               // sum2=张三,21
      // 对隐式参数重新赋值
        println(sum2(10)("李四",13))    // sum2=李四,23

      8.2.3 优先级比较

      方法传值 >> 隐式传值 >> 默认值

      // 1. 隐式值
        implicit val num1:Int = 11
        implicit val name:String = "张三"
      // 2. 方法传值 > 隐式值
        def sum(a:Int)(b:Int,name:String):Int={
          println(name)
          a + b
        }
        println(sum(10)("李四",13))        // sum2=李四,23
      // 3.默认值 < 隐式值
        def sum2(a:Int)(implicit name:String="赵六",b:Int=5):Int={
          println("sum3="+name)
          a + b
        }
        println(sum2(10))                // sum3=张三,21
      // 4.当调用方法,但不进行传参。使用默认值,隐式失效
        def sum3(a:Int)(implicit name:String="赵六",b:Int=5):Int={
          println("sum3="+name)
          a + b
        }
        println(sum3(10)())            // sum3=赵六,15

      应用:

      如下代码,输入的类型为Double,输出类型为String,会调用隐式函数,完成+500的操作。和输入输出的类型有关。

      // 传入一个double的数,+500后以String类型输出
      implicit def doubleFun(value:Double):String={(value+500).toString}
      val d:String = 23.0
      println(d)                // 输出523.0

      8.3 隐式类

      在Scala2.10后提供了隐式类,可以使用implicit声明类,隐式类的非常强大,同样可以扩展类的功能,在集合中隐式类会发挥重要的作用。

      1)隐式类说明

      (1)其所带的构造参数有且只能有一个

      (2)隐式类必须被定义在“类”或“伴生对象”或“包对象”里,即隐式类不能是顶级的。

      Object Test_Implicit {
        def main(args: Array[String]):Unit = {
      // 需要比较两数大小的时候new一个函数。
      // 缺点:不通用,每来一个整数都需要new一个对象
          val newInt = new MyInt(12)
          println(newInt.myMas(15))
        }
      }
      // 自定义类
      class MyInt(val self: Int) {
        // 自定义比较大小方法
        def myMax(n:Int):Int = if(n < self) self else n
        def myMin(n:Int):Int = if(n > self) self else n
      }

      2)案例实操

      Object Test_Implicit {
        // 1. 隐式类
        implicit class MyInt2(val self: Int){
          // 自定义比较大小方法
          def myMax(n:Int):Int = if(n < self) self else n
          def myMin(n:Int):Int = if(n > self) self else n
        }
        println(12,myMax(15))
      }

      九、泛型

      9.1 协变和逆变

      1)语法

      class MyList[ +T ]{ //协变 } 
      class MyList[ -T ]{ //逆变 }
      class MyList[ T ] //不变

      2)说明

      协变:Son是Father的子类,则MyList[Son] 也作为MyList[Father]的“子类”。

      逆变:Son是Father的子类,则MyList[Son]作为MyList[Father]的“父类”。

      不变:Son是Father的子类,则MyList[Father]与MyList[Son]“无父子关系”。

      3)实操

      object Test_Generics {
        def main(args: Array[String]): Unit = {
          // 1. 协变和逆变
          val child: Child = new Child
          val childList:MyCollection1[Parent] = new MyCollection1[Child]
          val childList:MyCollection1[SubChild] = new MyCollection1[Child]
          // 2. 上下限
          def test[A <: Child](a: A):Unit = {
            println(a.getClass.getName)
          }
          test[Parent](new Child)    // 报错
          test[Child](new Child)     // Child
          test[Child](new SubChild)  // SubChild
        }
      }
      // 定义继承关系
      class Parent {}
      class Child extends Child {}
      class SubChild extends Child {}
      // 定义带泛型的集合类型
      class MyCollection1[+E] {}
      class MyCollection1[-E] {}

      9.2 泛型上下限

      1)语法

      Class PersonList[T <: Person]{ //泛型上限 }
      Class PersonList[T >: Person]{ //泛型下限 }

      2)说明

      泛型的上下限的作用是对传入的泛型进行限定。