scala

本文介绍了Scala编程语言的基本概念,包括它的多范式特性、在大数据框架中的应用及其与Java的对比。文章通过示例展示了Scala在类定义、变量声明、条件表达式、循环结构等方面的优势,如简洁的语法和类型推断。同时,讲解了安装Scala SDK和IDEA插件的步骤,以及如何启动Scala解释器。文中还探讨了Scala的变量、字符串、类型系统、条件表达式、循环结构、方法定义、抽象类与特质的使用,为初学者提供了全面的入门指导。
摘要由CSDN通过智能技术生成

scala简介

scala是运行在JVM上的多范式(多种编程方法)编程语言,同时支持面向对象和面向函数编程

 

早期,scala刚出现的时候,并没有怎么引起重视,随着Spark和Kafka这样基于scala的大数据框架的兴起,scala逐步进入大数据开发者的眼帘。

为什么使用scala

  1. 开发大数据应用程序(Spark程序、Flink程序)
  2. 表达能力强,一行代码抵得上Java多行,开发速度快
  3. 兼容Java,可以访问庞大的Java类库,例如:操作mysql、redis、freemarker、activemq等等

scala对比Java

下面通过两个案例,分别使用java和scala实现的代码数量

案例

定义三个实体类(用户、订单、商品)

Java代码

/**
* 用户实体类
*/
public class User {
   private String name;
   private List<Order> orders;

   public String getName() {
  return name;
  }

   public void setName(String name) {
  this.name = name;
  }

   public List<Order> getOrders() {
  return orders;
  }

   public void setOrders(List<Order> orders) {
  this.orders = orders;
  }
}

/**
* 订单实体类
*/
public class Order {
   private int id;
   private List<Product> products;

   public int getId() {
  return id;
  }

   public void setId(int id) {
  this.id = id;
  }

   public List<Product> getProducts() {
  return products;
  }

   public void setProducts(List<Product> products) {
  this.products = products;
  }
}

/**
* 商品实体类
*/
public class Product {
   private int id;
   private String category;

   public int getId() {
  return id;
  }

   public void setId(int id) {
  this.id = id;
  }

   public String getCategory() {
  return category;
  }

   public void setCategory(String category) {
  this.category = category;
  }
}

scala代码

case class User(var name:String, var orders:List[Order]) // 用户实体类
case class Order(var id:Int, var products:List[Product]) // 订单实体类
case class Product(var id:Int, var category:String) // 商品实体类

开发环境安装

学习如何编写scala代码之前,需要先安装scala编译器以及开发工具

Java程序编译执行流程

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

Scala程序编译执行流程

uploading.4e448015.gif转存失败重新上传取消

uploading.4e448015.gif转存失败重新上传取消

scala程序运行需要依赖于Java类库,必须要有Java运行环境,scala才能正确执行

根据上述流程图,要编译运行scala程序,需要

  1. jdk(jvm)
  2. scala编译器(scala SDK)

接下来,需要依次安装以下内容:

  1. 安装JDK
  2. 安装scala SDK
  3. 安装IDEA插件

安装JDK

安装JDK 1.8  64位版本,并配置好环境变量

安装scala SDK

scala SDK是scala语言的编译器,要开发scala程序,必须要先安装SDK

本次安装的版本是: 2.11.12

步骤

  1. 下载、安装SDK
  2. 测试是否安装成功

具体操作

  1. 双击scala-2.11.12.msi,将scala安装在指定目录,例如:c:/opt
  2. 打开控制台,输入scala -version

安装IDEA scala插件

IDEA默认是不支持scala程序开发,所以需要来安装scala插件来支持scala语言。

步骤

  1. 下载指定版本IDEA scala插件
  2. IDEA配置scala插件
  3. 重新启动IDEA

具体操作

操作1:查看IDEA的版本号

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

操作2:到IDEA官网下载对应版本的IDEA scala插件

请务必下载IDEA版本一致的scala插件

操作3:选择配置 > 选择插件

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

操作4:点击小齿轮 > 选择从本地安装插件

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

操作5:找到下载的插件位置,点击OK

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

操作6:重新启动IDEA

操作7:查看scala插件

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

scala解释器

后续我们会使用scala解释器来学习scala基本语法,scala解释器像Linux命令一样,执行一条代码,马上就可以让我们看到执行结果,用来测试比较方便。

启动scala解释器

要启动scala解释器,只需要以下几步:

  1. 按住windows键 + r
  2. 输入scala即可

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

执行scala代码

在scala的命令提示窗口中输入println("hello, world"),回车执行

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

退出解释器

在scala命令提示窗口中执行:quit,即可退出解释器

声明变量

语法格式

Java变量定义

int a = 0;

在scala中,可以使用val或者var来定义变量,语法格式如下:

val/var 变量标识:变量类型 = 初始值

其中

  1. val定义的是不可重新赋值的变量
  2. var定义的是可重新赋值的变量
  3. scala中定义变量类型写在变量名后面
  4. scala的语句最后不需要添加分号

定义一个变量

示例:定义一个变量保存一个人的名字"tom"

参考代码

scala> val name:String = "tom"
name: String = tom

val和var变量

示例

给名字变量进行重新赋值为Jim,观察其运行结果

参考代码

scala> name = "Jim"
<console>:12: error: reassignment to val
      name = "Jim"

示例

使用var重新定义变量来保存名字"tom",并尝试重新赋值为Jim,观察其运行结果

参考代码

scala> var name:String = "tom"
name: String = tom

scala> name = "Jim"
name: String = Jim

类型推断定义变量

scala的语法要比Java简洁,我们可以使用一种更简洁的方式来定义变量。

示例

使用更简洁的语法定义一个变量保存一个人的名字"tom"

参考代码

scala> val name = "tom"
name: String = tom

scala可以自动根据变量的值来自动推断变量的类型,这样编写代码更加简洁。

惰性赋值

在企业的大数据开发中,有时候会编写非常复杂的SQL语句,这些SQL语句可能有几百行甚至上千行。这些SQL语句,如果直接加载到JVM中,会有很大的内存开销。如何解决?

当有一些变量保存的数据较大时,但是不需要马上加载到JVM内存。可以使用惰性赋值来提高效率。

语法格式:

lazy val  变量名 = 表达式

参考代码

scala> lazy val sql = """insert overwrite table adm.itcast_adm_personas
    |     select
    |     a.user_id,
....
    |     left join gdm.itcast_gdm_user_buy_category c on a.user_id=c.user_id
    |     left join gdm.itcast_gdm_user_visit d on a.user_id=d.user_id;"""
sql: String = <lazy>

字符串

scala提供多种定义字符串的方式,将来我们可以根据需要来选择最方便的定义方式。

  1. 使用双引号
  2. 使用插值表达式
  3. 使用三引号

使用双引号

语法

val/var 变量名 = “字符串”

参考代码

val name="hadoop6"
scala> println(name + name.length)
hadoop6

使用插值表达式

插值表达式可以有效避免大量字符串的拼接。

语法

val/var 变量名 = s"${变量/表达式}字符串"

在定义字符串之前添加s

在字符串中,可以使用${}来引用变量或者编写表达式

示例

若干个变量,分别保存:"zhangsan"、30、"male",定义一个字符串,保存这些信息。

打印输出:name=zhangsan, age=30, sex=male

 

参考代码

scala> val name = "zhangsan"
name: String = zhangsan

scala> val age = 30
age: Int = 30

scala> val sex = "male"
sex: String = male

scala> val info = s"name=${name}, age=${age}, sex=${sex}"
info: String = name=zhangsan, age=30, sex=male

scala> println(info)
name=zhangsan, age=30, sex=male

使用三引号

大段的文本需要保存,可以使用三引号来定义字符串。例如:保存一大段的SQL语句。三个引号中间的所有字符串都将作为字符串的值。

语法

val/var 变量名 = """字符串1
字符串2"""

参考代码

val sql = """select
    | *
    | from
    |     t_user
    | where
    |     name = "zhangsan""""

println(sql)

数据类型与操作符

scala中的类型以及操作符绝大多数和Java一样

数据类型

基础类型

类型说明

Byte

8位带符号整数

Short

16位带符号整数

Int

32位带符号整数

Long

64位带符号整数

Char

16位无符号Unicode字符

String

Char类型的序列(字符串)

Float

32位单精度浮点数

Double

64位双精度浮点数

Boolean

true或false

注意下 scala类型与Java的区别

  1. scala中所有的类型都使用大写字母开头
  2. 整形使用Int而不是Integer
  3. scala中定义变量可以不写类型,让scala编译器自动推断

运算符

类别

操作符

算术运算符

+、-、*、/

关系运算符

>、<、==、!=、>=、<=

逻辑运算符

&&、||、!

  1. scala中没有,++、--运算符
  2. 与Java不一样,在scala中,可以直接使用==、!=进行比较,它们与equals方法表示一致。而比较两个对象的引用值,使用eq

示例

有一个字符串"abc",再创建第二个字符串,值为:在第一个字符串后拼接一个空字符串。

然后使用比较这两个字符串是否相等、再查看它们的引用值是否相等。

参考代码

val str1 = "abc"
val str2 = str1 + ""
str1 == str2
str1.eq(str2)

scala类型层次结构

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

类型

说明

Any

所有类型的父类,,它有两个子类AnyRef与AnyVal

AnyVal

所有数值类型的父类

AnyRef

所有对象类型(引用类型)的父类

Unit

表示空,Unit是AnyVal的子类。它类似于Java中的void,但scala要比Java更加面向对象

Null

Null是AnyRef的子类,也就是说它是所有引用类型的子类。可以将null赋值给任何对象类型

Nothing

所有类型的子类 不能直接创建该类型实例,某个方法抛出异常时,返回的就是Nothing类型,因为Nothing是所有类的子类,那么它可以赋值为任何类型

nothing

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

    val c = m3(1,0)

}

 

def m3(x:Int, y:Int):Int = {

    if(y == 0) throw new Exception("这是一个异常")

    x / y

}

问题

以下代码是否有问题?

val b:Int = null

scala会解释报错:

Null类型并不能转换为Int类型,说明Null类型并不是Int类型的子类

条件表达式

条件表达式就是if表达式。scala条件表达式的语法和Java一样。

有返回值的if

参考代码

scala> val sex = "male"
sex: String = male

scala> val result = if(sex == "male") 1 else 0
result: Int = 1

与Java不一样的是,

  1. 在scala中,条件表达式也是有返回值的
  2. 在scala中,没有三元表达式,可以使用if表达式替代三元表达式

块表达式

  1. scala中,使用{}表示一个块表达式
  2. 和if表达式一样,块表达式也是有值的
  3. 值就是最后一个表达式的值

问题

请问以下代码,变量a的值是什么?

scala> val a = {
    | println("1 + 1")
    | 1 + 1
    | }

循环

在scala中,可以使用for和while,但一般推荐使用for表达式,因为for表达式语法更简洁

for表达式

语法

for(i <- 表达式/数组/集合) {

    // 表达式

}

简单循环

使用for表达式打印1-10的数字

参考代码1

scala> val nums = 1.to(10)                                                              nums: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)                                                                                  

scala> for(i <- nums) println(i)                                                                                                                        

参考代码2

// 中缀调用法

scala> for(i <- 1 to 10) println(i)

嵌套循环

使用for表达式,打印以下字符

*****

*****

*****

参考代码

for(i <- 1 to 3; j <- 1 to 5) {print("*");if(j == 5) println("")}

守卫

for表达式中,可以添加if判断语句,这个if判断就称之为守卫。我们可以使用守卫让for表达式更简洁。

语法

for(i <- 表达式/数组/集合 if 表达式) {

    // 表达式

}

示例

使用for表达式打印1-10之间能够整除3的数字

参考代码

// 添加守卫,打印能够整除3的数字

for(i <- 1 to 10 if i % 3 == 0) println(i)

for推导式

在for循环体中,可以使用yield表达式构建出一个集合,我们把使用yield的for表达式称之为推导式

示例

生成一个10、20、30...100的集合

参考代码

// for推导式:for表达式中以yield开始,该for表达式会构建出一个集合

val v = for(i <- 1 to 10) yield i * 10

while循环

scala中while循环和Java中是一致的

示例

打印1-10的数字

参考代码

scala> var i = 1

i: Int = 1

 

scala> while(i <= 10) {

     | println(i)

     | i = i+1

     | }

break和continue

  1. scala中,没有break/continue关键字
  2. 如果一定要使用break/continue,就需要使用scala.util.control包的Break类的breablebreak方法

实现break

用法

  1. 导入Breaks包import scala.util.control.Breaks._
  2. 使用breakable将for表达式包起来
  3. for表达式中需要退出循环的地方,添加break()方法调用

示例

使用for表达式打印1-100的数字,如果数字到达50,退出for表达式

参考代码

// 导入scala.util.control包下的Break

import scala.util.control.Breaks._

breakable{

    for(i <- 1 to 100) {

        if(i >= 50) break()

        else println(i)

    }

}

实现continue

用法

  1. continue的实现与break类似,但有一点不同:
  2. 实现continue是用breakable{}将for表达式的循环体包含起来

示例

打印1-100的数字,使用for表达式来遍历,如果数字能整除10,不打印

// 导入scala.util.control包下的Break    
import scala.util.control.Breaks._
for(i <- 1 to 100 ) {
   breakable{
       if(i % 10 == 0) break()
       else println(i)
  }
}

方法

一个类可以有自己的方法,scala中的方法和Java方法类似。但scala与Java定义方法的语法是不一样的。

定义方法

语法

def methodName (参数名:参数类型, 参数名:参数类型) : [return type] = {
   // 方法体:一系列的代码
}

  1. 参数列表的参数类型不能省略
  2. 返回值类型可以省略,由scala编译器自动推断
  3. 返回值可以不写return,默认就是{}块表达式的值

示例

  1. 定义一个方法,实现两个整形数值相加,返回相加后的结果
  2. 调用该方法

参考代码

scala> def add(a:Int, b:Int) = a + b
m1: (x: Int, y: Int)Int

scala> add(1,2)
res10: Int = 3

返回值类型推断

scala定义方法可以省略返回值,由scala自动推断返回值类型。

定义递归方法,不能省略返回值类型

示例

定义递归方法(求阶乘)

10 * 9 * 8 * 7 * 6 * ... * 1

参考代码

scala> def m2(x:Int) = {

     | if(x<=1) 1

     | else m2(x-1) * x

     | }

<console>:13: error: recursive method m2 needs result type

       else m2(x-1) * x

方法参数

scala中的方法参数,使用比较灵活。它支持以下几种类型的参数:

  • 默认参数
  • 带名参数
  • 变长参数

默认参数

在定义方法时可以给参数定义一个默认值。

参考代码

// x,y带有默认值为0

def add(x:Int = 0, y:Int = 0) = x + y

add()

带名参数

在调用方法时,可以指定参数的名称来进行调用。

参考代码

def add(x:Int = 0, y:Int = 0) = x + y

add(x=1)

变长参数

如果方法的参数是不固定的,可以定义一个方法的参数是变长参数。

语法格式:

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

    方法体

}

在参数类型后面加一个*号,表示参数可以是0个或者多个

参考代码

scala> def add(num:Int*) = num.sum

add: (num: Int*)Int

 

scala> add(1,2,3,4,5)

res1: Int = 15

方法调用方式

在scala中,有以下几种方法调用方式,

  • 后缀调用法
  • 中缀调用法
  • 花括号调用法
  • 无括号调用法

在后续编写spark、flink程序时,我们会使用到这些方法调用方式。

后缀调用法

这种方法与Java没有区别。

语法

对象名.方法名(参数)

示例

使用后缀法Math.abs求绝对值

参考代码

scala> Math.abs(-1)

res3: Int = 1

中缀调用法

语法 注意空格

对象名 方法名 参数

例如:1 to 10

如果有多个参数,使用括号括起来

示例

使用中缀法Math.abs求绝对值

scala> Math abs -1

res4: Int = 1

操作符即方法

scala中,+ - * / %等操作符都是方法,操作符是一个方法名字是符号的方法。

花括号调用法

语法

Math.abs{

    // 表达式1

    // 表达式2

}

方法只有一个参数,才能使用花括号调用法

示例

使用花括号调用法Math.abs求绝对值

参考代码

scala> Math.abs{-10}

res13: Int = 10

无括号调用法

如果方法没有参数,可以省略方法名后面的括号

示例

  1. 定义一个无参数的方法,打印"hello"
  2. 使用无括号调用法调用该方法

参考代码

def m3()=println("hello")

m3()

函数

scala支持函数式编程,将来编写Spark/Flink程序中,会大量使用到函数

定义函数

语法

val 函数变量名 = (参数名:参数类型, 参数名:参数类型....) => 函数体

  1. 函数是一个对象(变量)
  2. 类似于方法,函数也有输入参数和返回值
  3. 函数定义不需要使用def定义
  4. 无需指定返回值类型

参考代码

scala> val add = (x:Int, y:Int) => x + y

add: (Int, Int) => Int = <function2>

 

scala> add(1,2)

res3: Int = 3

方法和函数的区别

  1. 方法是隶属于类或者对象的,在运行时,它是加载到JVM的方法区中
  2. 可以将函数对象赋值给一个变量,在运行时,它是加载到JVM的堆内存中
  3. 函数是一个对象,继承自FunctionN,函数对象有apply,curried,toString,tupled这些方法。方法则没有

示例

方法无法赋值给变量

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

add: (x: Int, y: Int)Int

 

scala> val a = add

<console>:14: error: missing arguments for method add;

follow this method with `_' if you want to treat it as a partially applied function

       val a = add

方法转换为函数

  • 有时候需要将方法转换为函数,作为变量传递,就需要将方法转换为函数
  • 使用_即可将方法转换为函数

参考代码

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

add: (x: Int, y: Int)Int

 

scala> val a = add  _

a: (Int, Int) => Int = <function2>

数组

scala中,有两种数组,一种是定长数组,另一种是变长数组

定长数组

  1. 定长数组指的是数组的长度不允许改变
  2. 数组的元素可以改变

语法

// 通过指定长度定义数组
val/var 变量名 = new Array[元素类型](数组长度)

// 用元素直接初始化数组
val/var 变量名 = Array(元素1, 元素2, 元素3...)

  1. 在scala中,数组的泛型使用[]来指定
  2. 使用()来获取元素

参考代码

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

scala> a(0) = 110

scala> println(a(0))
110

变长数组

变长数组指的是数组的长度是可变的,可以往数组中添加、删除元素

变长数组

创建变长数组,需要提前导入ArrayBuffer类import scala.collection.mutable.ArrayBuffer

语法

创建空的ArrayBuffer变长数组,语法结构:

  • val/var a = new ArrayBuffer[元素类型]()

创建带有初始元素的ArrayBuffer

  • val/var a = ArrayBuffer(元素1,元素2,元素3....)

示例一

定义一个长度为0的整型变长数组

参考代码

val a = ArrayBuffer[Int]()

示例二

定义一个包含"hadoop", "storm", "spark"元素的变长数组

参考代码

scala> val a = ArrayBuffer("hadoop", "storm", "spark")

a: scala.collection.mutable.ArrayBuffer[String] = ArrayBuffer(hadoop, storm, spark)

添加/修改/删除元素

  1. 使用+=添加元素
  2. 使用-=删除元素
  3. 使用++=追加一个数组到变长数组

参考代码

// 定义变长数组

scala> val a = ArrayBuffer("hadoop", "spark", "flink")

a: scala.collection.mutable.ArrayBuffer[String] = ArrayBuffer(hadoop, spark, flink)

 

// 追加一个元素

scala> a += "flume"

res10: a.type = ArrayBuffer(hadoop, spark, flink, flume)

 

// 删除一个元素

scala> a -= "hadoop"

res11: a.type = ArrayBuffer(spark, flink, flume)

 

// 追加一个数组

scala> a ++= Array("hive", "sqoop")

res12: a.type = ArrayBuffer(spark, flink, flume, hive, sqoop)

遍历数组

可以使用以下两种方式来遍历数组:

  1. 使用for表达式直接遍历数组中的元素
  2. 使用索引遍历数组中的元素

参考代码

scala> val a = Array(1,2,3,4,5)

a: Array[Int] = Array(1, 2, 3, 4, 5)

 

scala> for(i<-a) println(i)

1

2

3

4

5

参考代码

scala> val a = Array(1,2,3,4,5)

a: Array[Int] = Array(1, 2, 3, 4, 5)

 

scala> for(i <- 0 to a.length - 1) println(a(i))

1

2

3

4

5

 

scala> for(i <- 0 until a.length) println(a(i))

1

2

3

4

5

0 until n——生成一系列的数字,包含0,不包含n

0 to n ——包含0,也包含n

数组常用算法

以下为常用的几个算法:

  1. 求和——sum方法
  2. 求最大值——max方法
  3. 求最小值——min方法
  4. 排序——sorted方法

// 升序排序

scala> a.sorted

res53: Array[Int] = Array(1, 2, 4, 4, 10)

 

// 降序

scala> a.sorted.reverse

res56: Array[Int] = Array(10, 4, 4, 2, 1)

元组

元组可以用来包含一组不同类型的值。例如:姓名,年龄,性别,出生年月。元组的元素是不可变的。

定义元组

使用括号来定义元组

val/var 元组 = (元素1, 元素2, 元素3....)

使用箭头来定义元组(元组只有两个元素)

val/var 元组 = 元素1->元素2

参考代码

scala> val a = ("zhangsan", 20)

a: (String, Int) = (zhangsan,20)

 

scala> val a = "zhangsan" -> 20

a: (String, Int) = (zhangsan,20)

访问元组

使用_1、_2、_3....来访问元组中的元素,_1表示访问第一个元素,依次类推

参考代码

scala> val a = "zhangsan" -> "male"

a: (String, String) = (zhangsan,male)

 

// 获取第一个元素

scala> a._1

res41: String = zhangsan

 

// 获取第二个元素

scala> a._2

res42: String = male

列表

列表是scala中最重要的、也是最常用的数据结构。List具备以下性质:

  1. 可以保存重复的值
  2. 有先后顺序

在scala中,也有两种列表,一种是不可变列表、另一种是可变列表

定义

不可变列表就是列表的元素、长度都是不可变的

语法

val/var 变量名 = List(元素1, 元素2, 元素3...)

使用Nil创建一个不可变的空列表

val/var 变量名 = Nil

使用::方法创建一个不可变列表

val/var 变量名 = 元素1 :: 元素2 :: Nil

使用::拼接方式来创建列表,必须在最后添加一个Nil

示例一

创建一个不可变列表,存放以下几个元素(1,2,3,4)

参考代码

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

示例二

使用Nil创建一个不可变的空列表

参考代码

scala> val a = Nil
a: scala.collection.immutable.Nil.type = List()

示例三

使用::方法创建列表,包含-2、-1两个元素

参考代码

scala> val a = -2 :: -1 :: Nil
a: List[Int] = List(-2, -1)

可变列表

可变列表就是列表的元素、长度都是可变的

要使用可变列表,先要导入import scala.collection.mutable.ListBuffer

  1. 可变集合都在mutable包中
  2. 不可变集合都在immutable包中(默认导入)

定义

使用ListBuffer[元素类型]()创建空的可变列表,语法结构:

val/var 变量名 = ListBuffer[Int]()

使用ListBuffer(元素1, 元素2, 元素3...)创建可变列表,语法结构:

val/var 变量名 = ListBuffer(元素1,元素2,元素3...)

示例一

创建空的整形可变列表

参考代码

  scala> val a = ListBuffer[Int]()

  a: scala.collection.mutable.ListBuffer[Int] = ListBuffer()

示例二

创建一个可变列表,包含以下元素:1,2,3,4

参考代码

scala> val a = ListBuffer(1,2,3,4)

a: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3, 4)

可变列表操作

  1. 获取元素(使用括号访问(索引值)
  2. 添加元素(+=
  3. 追加一个列表(++=
  4. 更改元素(使用括号获取元素,然后进行赋值
  5. 删除元素(-=
  6. 转换为List(toList
  7. 转换为Array(toArray

示例

  1. 定义一个可变列表包含以下元素:1,2,3
  2. 获取第一个元素
  3. 添加一个新的元素:4
  4. 追加一个列表,该列表包含以下元素:5,6,7
  5. 删除元素7
  6. 将可变列表转换为不可变列表
  7. 将可变列表转换为数组

参考代码

// 导入可变列表

scala> import scala.collection.mutable.ListBuffer

import scala.collection.mutable.ListBuffer

 

// 创建可变列表

scala> val a = ListBuffer(1,2,3)

a: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3)

 

// 获取第一个元素

scala> a(0)

res19: Int = 1

 

// 追加一个元素

scala> a += 4

res20: a.type = ListBuffer(1, 2, 3, 4)

 

// 追加一个列表

scala> a ++= List(5,6,7)

res21: a.type = ListBuffer(1, 2, 3, 4, 5, 6, 7)

 

// 删除元素

scala> a -= 7

res22: a.type = ListBuffer(1, 2, 3, 4, 5, 6)

 

// 转换为不可变列表

scala> a.toList

res23: List[Int] = List(1, 2, 3, 4, 5, 6)

 

// 转换为数组

scala> a.toArray

res24: Array[Int] = Array(1, 2, 3, 4, 5, 6)

列表常用操作

以下是列表常用的操作

  1. 判断列表是否为空(isEmpty
  2. 拼接两个列表(++
  3. 获取列表的首个元素(head)和剩余部分(tail)
  4. 反转列表(reverse
  5. 获取前缀(take)、获取后缀(drop
  6. 扁平化(flatten
  7. 拉链(zip)和拉开(unzip
  8. 转换字符串(toString
  9. 生成字符串(mkString
  10. 并集(union
  11. 交集(intersect
  12. 差集(diff

判断列表是否为空

参考代码

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

scala> a.isEmpty
res51: Boolean = false

拼接两个列表

参考代码

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

scala> val b = List(4,5,6)
b: List[Int] = List(4, 5, 6)

scala> a ++ b
res52: List[Int] = List(1, 2, 3, 4, 5, 6)

获取列表的首个元素和剩余部分

参考代码

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

scala> a.head
res4: Int = 1

scala> a.tail
res5: List[Int] = List(2, 3)

反转列表

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

scala> a.reverse
res6: List[Int] = List(3, 2, 1)

获取列表前缀和后缀

参考代码

scala> val a = List(1,2,3,4,5)

a: List[Int] = List(1, 2, 3, 4, 5)

 

scala> a.take(3)

res56: List[Int] = List(1, 2, 3)

 

scala> a.drop(3)

res60: List[Int] = List(4, 5)

扁平化(压平)

扁平化表示将列表中的     列表中的所有元素放到一个列表中。

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

参考代码

scala> val a = List(List(1,2), List(3), List(4,5))

a: List[List[Int]] = List(List(1, 2), List(3), List(4, 5))

 

scala> a.flatten

res0: List[Int] = List(1, 2, 3, 4, 5)

拉链与拉开

  1. 拉链:使用zip将两个列表,组合成一个元素为元组的列表
  2. 拉开:将一个包含元组的列表,解开成包含两个列表的元组

参考代码

scala> val a = List("zhangsan", "lisi", "wangwu")

a: List[String] = List(zhangsan, lisi, wangwu)

 

scala> val b = List(19, 20, 21)

b: List[Int] = List(19, 20, 21)

 

scala> a.zip(b)

res1: List[(String, Int)] = List((zhangsan,19), (lisi,20), (wangwu,21))

参考代码

scala> res1.unzip
res2: (List[String], List[Int]) = (List(zhangsan, lisi, wangwu),List(19, 20, 21))

转换字符串

toString方法可以返回List中的所有元素

参考代码

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

scala> println(a.toString)
List(1, 2, 3, 4)

生成字符串

mkString方法,可以将元素以分隔符拼接起来。默认没有分隔符

参考代码

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

scala> a.mkString
res7: String = 1234

scala> a.mkString(":")
res8: String = 1:2:3:4

并集

union表示对两个列表取并集,不去重

参考代码

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

scala> val a2 = List(3,4,5,6)
a2: List[Int] = List(3, 4, 5, 6)

// 并集操作
scala> a1.union(a2)
res17: List[Int] = List(1, 2, 3, 4, 3, 4, 5, 6)

// 可以调用distinct去重
scala> a1.union(a2).distinct
res18: List[Int] = List(1, 2, 3, 4, 5, 6)

交集

intersect表示对两个列表取交集

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

scala> val a2 = List(3,4,5,6)
a2: List[Int] = List(3, 4, 5, 6)

scala> a1.intersect(a2)
res19: List[Int] = List(3, 4)

差集

diff表示对两个列表取差集,例如: a1.diff(a2),表示获取a1在a2中不存在的元素

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

scala> val a2 = List(3,4,5,6)
a2: List[Int] = List(3, 4, 5, 6)

scala> a1.diff(a2)
res24: List[Int] = List(1, 2)

Set

Set(集)是代表没有重复元素的集合。Set具备以下性质:

  1. 元素不重复
  2. 不保证插入顺序

scala中的集也分为两种,一种是不可变集,另一种是可变集。

不可变集

语法

创建一个空的不可变集,语法格式:

val/var 变量名 = Set[类型]()

给定元素来创建一个不可变集,语法格式:

val/var 变量名 = Set(元素1, 元素2, 元素3...)

示例一

定义一个空的不可变集

参考代码

scala> val a = Set[Int]()
a: scala.collection.immutable.Set[Int] = Set()

示例二

定义一个不可变集,保存以下元素:1,1,1,1,1,3,2,4,8

参考代码

scala> val a = Set(1,1,1,1,1,3,2,4,8)
a: scala.collection.immutable.Set[Int] = Set(1, 2, 3, 8, 4)

基本操作

  1. 获取集的大小(size
  2. 遍历集(和遍历数组一致
  3. 添加一个元素,生成一个Set(+
  4. 拼接两个集,生成一个Set(++
  5. 拼接集和列表,生成一个Set(++

示例

 

参考代码

// 创建集
scala> val a = Set(1,1,2,3,4,5)
a: scala.collection.immutable.Set[Int] = Set(5, 1, 2, 3, 4)

// 获取集的大小
scala> a.size
res0: Int = 5

// 遍历集
scala> for(i <- a) println(i)

// 删除一个元素
scala> a - 1
res5: scala.collection.immutable.Set[Int] = Set(5, 2, 3, 4)

// 拼接两个集
scala> a ++ Set(6,7,8)
res2: scala.collection.immutable.Set[Int] = Set(5, 1, 6, 2, 7, 3, 8, 4)

// 拼接集和列表
scala> a ++ List(6,7,8,9)
res6: scala.collection.immutable.Set[Int] = Set(5, 1, 6, 9, 2, 7, 3, 8, 4)

可变集

定义

可变集合不可变集的创建方式一致,只不过需要提前导入一个可变集类。

手动导入:import scala.collection.mutable.Set

参考代码

scala> val a = Set(1,2,3,4)
a: scala.collection.mutable.Set[Int] = Set(1, 2, 3, 4)                          

// 添加元素
scala> a += 5
res25: a.type = Set(1, 5, 2, 3, 4)

// 删除元素s
scala> a -= 1
res26: a.type = Set(5, 2, 3, 4)

映射

Map可以称之为映射。它是由键值对组成的集合。在scala中,Map也分为不可变Map和可变Map。

不可变Map

语法

val/var map = Map(->, ->, ->...) // 推荐,可读性更好
val/var map = Map((, ), (, ), (, ), (, )...)

参考代码

scala> val map = Map("zhangsan"->30, "lisi"->40)
map: scala.collection.immutable.Map[String,Int] = Map(zhangsan -> 30, lisi -> 40)

scala> val map = Map(("zhangsan", 30), ("lisi", 30))
map: scala.collection.immutable.Map[String,Int] = Map(zhangsan -> 30, lisi -> 30)

// 根据key获取value
scala> map("zhangsan")
res10: Int = 30

//修改

scala> map("zhangsan")=33

<console>:11: error: value update is not a member of scala.collection.immutable.Map[String,Int]

              map("zhangsan")=33

可变Map

定义

定义语法与不可变Map一致。但定义可变Map需要手动导入import scala.collection.mutable.Map

scala> val map = Map("zhangsan"->30, "lisi"->40)
map: scala.collection.mutable.Map[String,Int] = Map(lisi -> 40, zhangsan -> 30)

// 修改value
scala> map("zhangsan") = 20

Map基本操作

基本操作

  1. 获取值(map(key))
  2. 获取所有key(map.keys
  3. 获取所有value(map.values
  4. 遍历map集合
  5. getOrElse
  6. 增加key,value对
  7. 删除key

示例

参考代码

scala> val map = Map("zhangsan"->30, "lisi"->40)
map: scala.collection.mutable.Map[String,Int] = Map(lisi -> 40, zhangsan -> 30)

// 获取zhagnsan的年龄
scala> map("zhangsan")
res10: Int = 30

// 获取所有的学生姓名
scala> map.keys
res13: Iterable[String] = Set(lisi, zhangsan)

// 获取所有的学生年龄
scala> map.values
res14: Iterable[Int] = HashMap(40, 30)

// 打印所有的学生姓名和年龄
scala> for((x,y) <- map) println(s"$x $y")
lisi 40
zhangsan 30

// 获取wangwu的年龄,如果wangwu不存在,则返回-1
scala> map.getOrElse("wangwu", -1)
res17: Int = -1

// 新增一个学生:wangwu, 35
scala> map += "wangwu"->35
res22: scala.collection.mutable.Map[String,Int] = Map(lisi -> 40, zhangsan -> 30, wangwu -> 35)

// 将lisi从可变映射中移除
scala> map -= "lisi"
res23: scala.collection.mutable.Map[String,Int] = Map(zhangsan -> 30)

iterator迭代器

scala针对每一类集合都提供了一个迭代器(iterator)用来迭代访问集合

使用迭代器遍历集合

  1. 使用iterator方法可以从集合获取一个迭代器
  2. 迭代器的两个基本操作
    1. hasNext——查询容器中是否有下一个元素
    2. next——返回迭代器的下一个元素,如果没有,抛出NoSuchElementException
  3. 每一个迭代器都是有状态的
  4. 迭代完后保留在最后一个元素的位置
  5. 再次使用则抛出NoSuchElementException
  6. 可以使用while或者for来逐个返回元素

示例

  1. 定义一个列表,包含以下元素:1,2,3,4,5
  2. 使用while循环和迭代器,遍历打印该列表

参考代码

scala> var a=List(1,2,3,4,5)

a: List[Int] = List(1, 2, 3, 4, 5)

 

scala> val ite = a.iterator
ite: Iterator[Int] = non-empty iterator

scala> while(ite.hasNext){

println(ite.next)

}

示例

  1. 定义一个列表,包含以下元素:1,2,3,4,5
  2. 使用for 表达式和迭代器,遍历打印该列表

参考代码

scala> val a = List(1,2,3,4,5)
a: List[Int] = List(1, 2, 3, 4, 5)

scala> for(i <- a) println(i)

 

 

类和对象

scala是支持面向对象的,也有类和对象的概念。我们依然可以基于scala语言来开发面向对象的应用程序。

创建类和对象

用法

使用class来定义一个类 new来创建对象

参考代码

object _01ClassDemo {
 // 创建类
 class Person{}
​ def main(args: Array[String]): Unit = {
   // 创建对象
   val p = new Person()
   println(p)
}
}

参考代码

object _02ClassDemo {

 // 创建类,省略花括号
 class Person

 def main(args: Array[String]): Unit = {
   // 创建对象,省略括号
   val person = new Person
}
}

定义和访问成员变量

一个类会有自己的属性,例如:人这样一个类,有自己的姓名和年龄。在类中定义、和访问成员变量

参考代码

object _03ClassDemo {
 class Person {
   // 定义成员变量
   var name = ""
   var age = 0
}

 def main(args: Array[String]): Unit = {
   // 创建Person对象
   val person = new Person
   person.name = "zhangsan"
   person.age = 20

   // 获取变量值
   println(person.name)
   println(person.age)
}
}

使用下划线初始化成员变量

scala中有一个更简洁的初始化成员变量的方式,可以让代码看起来更加简洁。

用法

在定义var类型的成员变量时,可以使用_来初始化成员变量

  1. String => null
  2. Int => 0
  3. Boolean => false
  4. Double => 0.0
  5. ...

val类型的成员变量,必须要自己手动初始化

参考代码

object _04ClassDemo {

 class Person{
   // 使用下划线进行初始化
   var name:String = _
   var age:Int = _
}

 def main(args: Array[String]): Unit = {
   val person = new Person
   
   println(person.name)
   println(person.age)
}
}

定义成员方法

类可以有自己的行为,scala中也可以通过定义成员方法来定义类的行为。

使用def来定义成员方法

示例

  1. 创建一个Customer类

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

  1. 创建一个该类的对象,并调用printHello方法

步骤

  1. 1、创建一个Object,添加main方法
  2. 2、创建Customer类,添加成员变量、成员方法
  3. 3、在main方法中创建Customer类对象,设置成员变量值(张三、男)
  4. 4、调用成员方法

参考代码

object _05ClassDemo {

 class Customer {
   var name:String = _
   var sex:String = _

   // 定义成员方法
   def sayHi(msg:String) = {
     println(msg)
  }
}

 def main(args: Array[String]): Unit = {
   val customer = new Customer
   customer.name = "张三"
   customer.sex = "男"
   customer.sayHi("你好")
}
}

访问修饰符

和Java一样,scala也可以通过访问修饰符,来控制成员变量和成员方法是否可以被访问。

定义

可以在成员前面添加private/protected(私有的/受保护的关键字来控制成员的可见性。

scala中,没有public关键字,任何没有被标为private或protected的成员都是公共的

案例

定义一个Person类

uploading.4e448015.gif转存失败重新上传取消 uploading.4e448015.gif转存失败重新上传取消

在main方法中创建该类的对象,测试是否能够访问到私有成员

参考代码

object _02AccessDemo {

 class Person {
   // 定义私有成员变量
   private var name:String = _
   private var age:Int = _

   def getName() = name
   def setName(name:String) = this.name = name
   def getAge() = age
   def setAge(age:Int) = this.age = age

   // 定义私有成员方法
   private def getNameAndAge = {
     name -> age
  }
}

 def main(args: Array[String]): Unit = {
   val person = new Person
   person.setName("张三")
   person.setAge(10)

   println(person.getName())
   println(person.getAge())
}
}

类的构造器

当创建类对象的时候,会自动调用类的构造器。之前使用的都是默认构造器。

主构造器

Java的构造器,有构造列表和构造代码块

class Person {
   // 成员变量
   private String name;
   private Integer age;

   // Java构造器
   public Person(String name, Integer age) {
       // 初始化成员变量
       this.name = name;
       this.age = age;
  }
}

在scala中, 可以使用更简洁的语法来实现。

语法

class 类名(var/val 参数名:类型 = 默认值, var/val 参数名:类型 = 默认值){
   // 构造代码块
}

  1. 主构造器的参数列表是直接定义在类名后面,添加了val/var表示直接通过主构造器定义成员变量
  2. 构造器参数列表可以指定默认值
  3. 创建实例,调用构造器可以指定字段进行初始化
  4. 整个class中除了字段定义和方法定义的代码都是构造代码

示例

  1. 定义一个Person类,通过主构造器参数列表定义姓名和年龄字段,并且设置它们的默认值
  2. 在主构造器中输出"调用主构造器"
  3. 创建"张三"对象(姓名为张三,年龄为20),打印对象的姓名和年龄
  4. 创建"空"对象,不给构造器传入任何的参数,打印对象的姓名和年龄
  5. 创建"man40"对象,不传入姓名参数,指定年龄为40,打印对象的姓名和年龄

参考代码

object _06ConstructorDemo {

 

  // 定义类的主构造器

  // 指定默认值

  class Person(var name:String = "", var age:Int = 0) {

    println("调用主构造器")

  }

 

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

    // 给构造器传入参数

    val zhangsan = new Person("张三", 20)

    println(zhangsan.name)

    println(zhangsan.age)

 

    println("---")

 

    // 不传入任何参数

    val empty = new Person

    println(empty.name)

    println(empty.age)

 

    println("---")

 

    // 指定字段进行初始化

    val man40 = new Person(age = 40)

    println(man40.name)

    println(man40.age)

  }

}

辅助构造器

除了主构造器之外的构造器称为辅助构造器

例如:允许通过多种方式,来创建对象,这时候就可以定义其他更多的构造器。

语法

  1. 定义辅助构造器与定义方法一样,也使用def关键字来定义
  2. 这个方法的名字为this

def this(参数名:类型, 参数名:类型) {

    // 第一行需要调用主构造器或者其他构造器

    // 构造器代码

}

辅助构造器的第一行代码,必须要调用主构造器或者其他辅助构造器

示例

示例说明

  1. 定义一个Customer类,包含一个姓名和地址字段
  2. 定义Customer类的主构造器(初始化姓名和地址)
  3. 定义Customer类的辅助构造器,该辅助构造器接收一个数组参数,使用数组参数来初始化成员变量
  4. 使用Person类的辅助构造器来创建一个"zhangsan"对象
  5. 姓名为张三
  6. 地址为北京
  7. 打印对象的姓名、地址

参考代码

object _07ConstructorDemo {

  class Customer(var name:String = "", var address:String = "") {

    // 定义辅助构造器

    def this(arr:Array[String]) = {

      // 辅助构造器必须要调用主构造器或者其他辅助构造器

      this(arr(0), arr(1))

    }

  }

 

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

    val zhangsan = new Customer(Array("张三", "北京"))

 

    println(zhangsan.name)

    println(zhangsan.address)

  }

}

单例对象

scala中没有Java中的静态成员,若想要定义类似于Java的static变量、static方法,就要使用到scala中的单例对象——object.

定义单例对象

单例对象表示全局仅有一个对象(类似于Java static概念)

  1. 定义单例对象和定义类很像,就是把class换成object
  2. 在object中定义的成员变量类似于Java的静态变量
  3. 可以使用object直接引用成员变量

示例

示例说明

  1. 定义一个Dog单例对象,保存狗有几条腿
  2. 在main方法中打印狗腿的数量

参考代码

object _08ObjectDemo {

 

  // 定义一个单例对象

  object Dog {

    // 定义腿的数量

    val LEG_NUM = 4

  }

 

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

    println(Dog.LEG_NUM)

  }

}

在单例对象中定义成员方法

  1. 在object中定义的成员方法类似于Java的静态方法

示例

示例说明

  1. 设计一个单例对象,定义一个能够打印分割线(15个减号)的方法
  2. 在main方法调用该方法,打印分割线

参考代码

object _09ObjectDemo {

 

  object PrintUtil {

    // 打印分割线

    def printSpliter() = {

      // 字符串乘法,表示返回多少个字符串

      println("-" * 10)

    }

  }

 

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

    PrintUtil.printSpliter()

  }

}

工具类案例

需求

  1. 编写一个DateUtil工具类专门用来格式化日期时间
  2. 定义一个方法,用于将日期(Date)转换为年月日字符串,例如:2030-10-05

步骤

  1. 定义一个DateUtil单例对象,定义日期格式化方法(format)
  2. 使用SimpleDateFormat将日期转换为字符串

参考代码

object _10ObjectDemo {

 

  object DateUtils {

    // 在object中定义的成员变量,相当于Java中定义一个静态变量

    // 定义一个SimpleDateFormat日期时间格式化对象

    val simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm")

 

    // 相当于Java中定义一个静态方法

    def format(date: Date) = simpleDateFormat.format(date)

  }

 

  // main是一个静态方法,所以必须要写在object中

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

    println(DateUtils.format(new Date()))

  }

}

main方法

scala和Java一样,如果要运行一个程序,必须有一个main方法。在Java中main方法是静态的,而在scala中没有静态方法。在scala中,这个main方法必须放在一个单例对象中。

定义main方法

main方法

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

    // 方法体

}

示例

示例说明

  1. 创建一个单例对象,在该单例对象中打印"hello, scala"

参考代码

object Main5 {

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

    println("hello, scala")

  }

}

实现App Trait来定义入口

创建一个object,继承自App Trait(特质),然后将需要编写在main方法中的代码,写在object的构造方法体内。

object 单例对象名 extends App {

    // 方法体

}

示例

示例说明

  1. 继承App特质,来实现一个入口。同样输出"hello, scala"

参考代码

object Main5 extends App {

  println("hello, scala")

}

伴生对象

在Java中,经常会有一些类,同时有实例成员又有静态成员。例如:

public class CustomerService {

 

    private static String SERVICE_NAME = "CustomerService";

 

    public void save() {

        // 保存客户

        System.out.println(SERVICE_NAME + ":保存客户");

    }

 

    public static void main(String[] args) {

        new CustomerService().save();

    }

}

在scala中,要实现类似的效果,可以使用伴生对象来实现。

定义伴生对象

一个class和object具有同样的名字。这个object称为伴生对象,这个class称为伴生类

  1. 伴生对象必须要和伴生类一样的名字
  2. 伴生对象和伴生类在同一个scala源文件中
  3. 伴生对象和伴生类可以互相访问private属性

示例

示例说明

  1. 编写一个CustomerService类,有一个save方法,打印
  2. 服务类名称:保存客户
  3. 编写一个CustomerService伴生对象,定义一个私有变量,用于保存服务类名称
  4. 创建CustomerService对象,调用save方法

参考代码

object _11ObjectDemo {

 

  class CustomerService {

    def save() = {

      println(s"${CustomerService.SERVICE_NAME}:保存客户")

    }

  }

 

  // CustomerService的伴生对象

  object CustomerService {

    private val SERVICE_NAME = "CustomerService"

  }

 

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

    val customerService = new CustomerService()

    customerService.save()

  }

}

private[this]访问权限

如果某个成员的权限设置为private[this],表示只能在当前类中访问。伴生对象也不可以访问

示例

示例说明

  1. 定义一个Person类,包含一个name字段
  2. 定义Person类的伴生对象,定义printPerson方法
  3. 测试伴生对象是否能访问private[this]权限的成员

示例代码

  class Person(private[this] var name:String)

  

  object Person {

    def printPerson(person:Person): Unit = {

      println(person.name)

    }

  }

  

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

    val person = new Person("张三")

    Person.printPerson(person)

  }

上述代码,会编译报错。但移除掉[this]就可以访问了

继承

scala语言是支持面向对象编程的, 可以使用scala来实现继承,通过继承来减少重复代码。

定义语法

  1. 使用extends关键字来实现继承
  2. 可以在子类中定义父类中没有的字段和方法,或者重写父类的方法
  3. 类和单例对象都可以从某个父类继承

语法

class/object 子类 extends 父类 {

    ..

}

示例 | 类继承

  1. 定义一个Person类,再定义一个Student类,继承自Person类

uploading.4e448015.gif转存失败重新上传取消

  1. 创建一个Student类对象实例,并设置name为“张三”
  2. 打印姓名

参考代码

class Person {

  var name = "super"

  def getName = this.name

}

 

class Student extends Person

object Main13 {

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

    val p1 = new Person()

    val p2 = new Student()

    p2.name = "张三"

 

    println(p2.getName)

  }

}

示例 | 单例对象继承

示例说明

  1. 创建一个Student单例对象,让单例对象继承示例1中的Person类
  2. 设置单例对象的名字为"张三",调用Student单例对象的getName方法

class Person {

  var name = "super"

  def getName = this.name

}

 

 

object Student extends Person

object Main13 {

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

    println(Student.getName)

  }

}

override和super

类似于Java语言, 在子类中使用override需要来重写父类的成员,可以使用super来引用父类

用法

  1. 子类要覆盖父类中的一个方法,必须要使用override关键字
  2. 使用override来重写一个val字段
  3. 使用super关键字来访问父类的成员方法

示例

示例说明

  1. 定义一个Person类,包含姓名字段(不可重新赋值)
  2. 获取姓名方法
  3. 定义一个Student类
  4. 重写姓名字段
  5. 重写获取姓名方法,返回"hello, " + 姓名
  6. 创建Student对象示例,调用它的getName方法

参考代码

class Person {

  val name = "super"

  def getName = name

}

 

class Student extends Person {

  // 重写val字段

  override val name: String = "child"

  // 重写getName方法

  override def getName: String = "hello, " + super.getName

}

 

object Main13 {

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

    println(new Student().getName)

  }

}

类型判断

有时候,我们设计的程序,要根据变量的类型来执行对应的逻辑。

uploading.4e448015.gif转存失败重新上传取消 uploading.4e448015.gif转存失败重新上传取消

在scala中,如何来进行类型判断呢?

有两种方式:

  1. isInstanceOf
  2. getClass/classOf

isInstanceOf/asInstanceOf

在Java中,可以使用instanceof关键字来判断类型、以及(类型)object来进行类型转换,在scala中如何实现?

scala中对象提供isInstanceOf和asInstanceOf方法。

  1. isInstanceOf判断对象是否为指定类以及其子类的对象
  2. asInstanceOf将对象转换为指定类型

用法

// 判断对象是否为指定类型

val trueOrFalse:Boolean = 对象.isInstanceOf[类型]

// 将对象转换为指定类型

val 变量 = 对象.asInstanceOf[类型]

示例

示例说明

  1. 定义一个Person类
  2. 定义一个Student类继承自Person类
  3. 创建一个Student类对象
  4. 判断该对象是否为Student类型,如果是,将其转换为Student类型并打印该对象

参考代码

class Person3

class Student3 extends Person3

 

object Main3 {

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

    val s1:Person3 = new Student3

    // 判断s1是否为Student3类型

    if(s1.isInstanceOf[Student3]) {

      // 将s1转换为Student3类型

      val s2 =  s1.asInstanceOf[Student3]

      println(s2)

    }

 

  }

}

getClass和classOf

isInstanceOf 只能判断对象是否为指定类以及其子类的对象,而不能精确的判断出,对象就是指定类的对象。如果要求精确地判断出对象就是指定类的对象,那么就只能使用 getClass 和 classOf 。

用法

  1. p.getClass可以精确获取对象的类型不是父类
  2. classOf[x] 可以指定精确类型
  3. 使用==操作符可以直接比较类型

示例

示例说明

  1. 定义一个Person类
  2. 定义一个Student类继承自Person类
  3. 创建一个Student类对象,并指定它的类型为Person类型
  4. 测试使用isInstance判断该对象是否为Person类型
  5. 测试使用getClass/classOf判断该对象是否为Person类型
  6. 测试使用getClass/classOf判断该对象是否为Student类型

参考代码

class Person4

class Student4 extends Person4

 

object Student4{

  def main(args: Array[String]) {

    val p:Person4=new Student4

    //判断p是否为Person4类的实例

    println(p.isInstanceOf[Person4])//true

    //判断p的类型是否为Person4类

    println(p.getClass == classOf[Person4])//false

 

    //判断p的类型是否为Student4类

    println(p.getClass == classOf[Student4])//true

  }

}

抽象类

和Java语言一样,scala中也可以定义抽象类

定义

如果类的某个成员在当前类中的定义是不包含完整的,它就是一个抽象类

不完整定义有两种情况:

  1. 方法没有方法体(抽象方法
  2. 变量没有初始化(抽象字段

定义抽象类和Java一样,在类前面加上abstract关键字

// 定义抽象类

abstract class 抽象类名 {

  // 定义抽象字段

  val 抽象字段名:类型

  // 定义抽象方法

  def 方法名(参数:参数类型,参数:参数类型...):返回类型

}

抽象方法

示例

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

  • 设计4个类,表示上述图中的继承关系
  • 每一个形状都有自己求面积的方法,但是不同的形状计算面积的方法不同

步骤

  1. 创建一个Shape抽象类,添加一个area抽象方法,用于计算面积
  2. 创建一个Square正方形类,继承自Shape,它有一个边长的主构造器,并实现计算面积方法
  3. 创建一个长方形类,继承自Shape,它有一个长、宽的主构造器,实现计算面积方法
  4. 创建一个圆形类,继承自Shape,它有一个半径的主构造器,并实现计算面积方法
  5. 编写main方法,分别创建正方形、长方形、圆形对象,并打印它们的面积

参考代码

// 创建形状抽象类

abstract class Shape {

  def area:Double

}

 

// 创建正方形类

class Square(var edge:Double /*边长*/) extends Shape {

  // 实现父类计算面积的方法

  override def area: Double = edge * edge

}

 

// 创建长方形类

class Rectangle(var length:Double /*长*/, var width:Double /*宽*/) extends Shape {

  override def area: Double = length * width

}

 

// 创建圆形类

class Cirle(var radius:Double /*半径*/) extends Shape {

  override def area: Double = Math.PI * radius * radius

}

 

object Main6 {

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

    val s1:Shape = new Square(2)

    val s2:Shape = new Rectangle(2,3)

    val s3:Shape = new Cirle(2)

 

    println(s1.area)

    println(s2.area)

    println(s3.area)

  }

}

抽象字段

在scala中,也可以定义抽象的字段。如果一个成员变量是没有初始化,我们就认为它是抽象的。

定义

语法

abstract class 抽象类 {

    val/var 抽象字段:类型

}

示例

示例说明

  1. 创建一个Person抽象类,它有一个String抽象字段WHO_AM_I
  2. 创建一个Student类,继承自Person类,重写WHO_AM_I字段,初始化为学生
  3. 创建一个Policeman类,继承自Person类,重写WHO_AM_I字段,初始化警察
  4. 添加main方法,分别创建Student/Policeman的实例,然后分别打印WHO_AM_I

参考代码

// 定义一个人的抽象类

abstract class Person6 {

  // 没有初始化的val字段就是抽象字段

  val WHO_AM_I:String

}

 

class Student6 extends Person6 {

  override val WHO_AM_I: String = "学生"

}

 

class Policeman6 extends Person6 {

  override val WHO_AM_I: String = "警察"

}

 

object Main6 {

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

    val p1 = new Student6

    val p2 = new Policeman6

 

    println(p1.WHO_AM_I)

    println(p2.WHO_AM_I)

  }

}

匿名内部类

匿名内部类是没有名称的子类,直接用来创建实例对象。Spark的源代码中有大量使用到匿名内部类。

scala中的匿名内部类使用与Java一致。

定义

语法

val/var 变量名 = new 类/抽象类 {

    // 重写方法

}

示例

示例说明

  1. 创建一个Person抽象类,并添加一个sayHello抽象方法
  2. 添加main方法,通过创建匿名内部类的方式来实现Person
  3. 调用匿名内部类对象的sayHello方法

参考代码

abstract class Person7 {

  def sayHello:Unit

}

 

object Main7 {

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

    // 直接用new来创建一个匿名内部类对象

    val p1 = new Person7 {

      override def sayHello: Unit = println("我是一个匿名内部类")

    }

    p1.sayHello

  }

}

特质(trait)

scala中没有Java中的接口(interface),替代的概念是——特质

定义

  • 特质是scala中代码复用的基础单元
  • 它可以将方法和字段定义封装起来,然后添加到类中
  • 与类继承不一样的是,类继承要求每个类都只能继承一个超类,而一个类可以添加任意数量的特质。
  • 特质的定义和抽象类的定义很像,但它是使用trait关键字

语法

定义特质

trait 名称 {

    // 抽象字段

    // 抽象方法

}

继承特质

class 类 extends 特质1 with 特质2 {

    // 字段实现

    // 方法实现

}

  • 使用extends来继承trait(scala不论是类还是特质,都是使用extends关键字)
  • 如果要继承多个trait,则使用with关键字

trait作为接口使用

trait作为接口使用,与java的接口使用方法一样。

示例 | 继承单个trait

 

示例说明

  1. 创建一个Logger特质,添加一个接受一个String类型参数的log抽象方法
  2. 创建一个ConsoleLogger类,继承Logger特质,实现log方法,打印消息
  3. 添加main方法,创建ConsoleLogger对象,调用log方法

参考代码

  trait Logger {

    // 抽象方法

    def log(message:String)

  }

 

  class ConsoleLogger extends Logger {

    override def log(message: String): Unit = println("控制台日志:" + message)

  }

 

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

    val logger = new ConsoleLogger

    logger.log("这是一条日志")

  }

示例 | 继承多个trait

 

示例说明

  1. 创建一个MessageSender特质,添加send方法
  2. 创建一个MessageReceiver特质,添加receive方法
  3. 创建一个MessageWorker实现这两个特质
  4. 在main中调用,分别调用send方法、receive方法

参考代码

trait MessageSender {

    def send(msg:String)

}

 

trait MessageReceive {

    def receive():String

}

 

class MessageWorker extends MessageSender with MessageReceive {

    override def send(msg: String): Unit = println(s"发送消息:${msg}")

 

    override def receive(): String = "你好!我叫一个好人!"

}

 

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

    val worker = new MessageWorker

    worker.send("hello")

    println(worker.receive())

}

示例 | object继承trait

 

示例说明

  1. 创建一个Logger特质,添加一个log抽象方法
  2. 创建一个ConsoleLogger的object,实现LoggerForObject特质,实现log方法,打印消息
  3. 编写main方法,调用ConsoleLogger的log方法

参考代码

trait Logger {

    def log(message:String)

}

 

object ConsoleLogger extends Logger {

    override def log(message: String): Unit = println("控制台消息:" + message)

}

 

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

    ConsoleLogger.log("程序退出!")

}

特质 | 定义具体的方法

和类一样,trait中还可以定义具体的方法

示例

示例说明

  1. 定义一个Logger特质,添加log实现方法
  2. 定义一个UserService类,实现Logger特质
  1. 添加add方法,打印"添加用户"

3. 添加main方法

  1. 创建UserService对象实例
  2. 调用add方法

参考代码

trait LoggerDetail {

  // 在trait中定义具体方法

  def log(msg:String) = println(msg)

}

 

class UserService extends LoggerDetail {

  def add() = log("添加用户")

}

 

object MethodInTrait {

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

    val userService = new UserService

    userService.add()

  }

}

trait中定义具体的字段和抽象的字段

定义

  1. 在trait中可以定义具体字段和抽象字段
  2. 继承trait的子类自动拥有trait中定义的字段
  3. 字段直接被添加到子类中

示例

示例说明

通过trait来实现一个日志输出工具,该日志工具可以自动添加日志的日期

步骤

  1. 创建Logger特质
  1. 定义一个SimpleDateFormat字段,用来格式化日期(显示到时间)
  2. 定义一个TYPE抽象字段,用于定义输出的信息
  3. 创建一个log抽象方法,用于输出日志
  1. 创建ConsoleLogger类,实现TYPE抽象字段和log方法
  2. 添加main方法
  1. 创建ConsoleLogger类对象
  2. 调用log方法

参考代码

  trait Logger {

    val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm")

    def log(msg:String)

  }

 

  class ConsoleLogger extends Logger {

    override def log(msg: String): Unit = {

      val info = s"${sdf.format(new Date())}:控制台消息:${msg}"

      println(info)

    }

  }

 

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

    val logger = new ConsoleLogger()

    logger.log("NullPointerException")

  }

使用trait实现模板模式

要实现以下需求:

  1. 实现一个输出日志的功能
  2. 目前要求输出到控制台
  3. 将来可能会输出到文件、输出到Redis、或者更多的需求

如何实现将来不修改之前的代码,来扩展现有功能呢?

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

定义

在一个特质中,具体方法依赖于抽象方法,而抽象方法可以放到继承trait的子类中实现,这种设计方式也称为模板模式

uploading.4e448015.gif转存失败重新上传取消 uploading.4e448015.gif转存失败重新上传取消

在scala中,trait是可以定义抽象方法,也可以定义具体方法的

  1. trait中定义了一个抽象方法
  2. trait中定义了其他的几个具体方法,会调用抽象方法
  3. 其他实现类可以来实现抽象方法
  4. 真正调用trait中具体方法的时候,其实会调用实现类的抽象方法实现

示例

示例说明

  1. 编写一个日志输出工具,分别有info、warn、error三个级别的日志输出
  2. 日志输出的方式要求设计为可扩展的,例如:可以输出到控制台、将来也可以扩展输出到文件、数据库等

实现步骤

  1. 添加一个Logger特质
  1. 添加一个log抽象方法
  2. 添加一个info、warn、error具体方法,这几个方法调用log抽象方法
  1. 创建ConsoleLogger类,实现Logger特质
  2. 添加main方法
  1. 创建ConsoleLogger类对象
  2. 分别调用info、warn、error方法输出日志

参考代码

  trait Logger {

    def log(msg:String)

    def info(msg:String) = log("INFO:" + msg)

    def warn(msg:String) = log("WARN:" + msg)

    def error(msg:String) = log("ERROR:" + msg)

  }

 

  class ConsoleLogger extends Logger {

    override def log(msg: String): Unit = {

      println(msg)

    }

  }

 

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

    val logger = new ConsoleLogger

    logger.info("信息日志")

    logger.warn("警告日志")

    logger.error("错误日志")

  }

对象混入trait

scala中可以将trait混入到对象中,就是将trait中定义的方法、字段添加到一个对象中

定义

语法

val/var 对象名 = new 类 with 特质

示例

  • 给一个对象添加一些额外的行为

步骤

  1. 创建一个Logger特质
  1. 添加一个log实现方法,打印参数
  1. 创建一个UserService类
  2. 添加main方法
  1. 创建UserService对象,混入Logger特质
  2. 调用log方法

参考代码

  trait Logger {

    def log(msg:String) = println(msg)

  }

 

  class UserService

 

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

    val service = new UserService with Logger

    service.log("混入的方法")

  }

trait实现调用链模式

我们如果要开发一个支付功能,往往需要执行一系列的验证才能完成支付。例如:

  1. 进行支付签名校验
  2. 数据合法性校验
  3. ...

如果将来因为第三方接口支付的调整,需要增加更多的校验规则,此时如何不修改之前的校验代码,来实现扩展呢?

责任链模式

uploading.4e448015.gif转存失败重新上传取消 uploading.4e448015.gif转存失败重新上传取消

trait调用链

uploading.4e448015.gif转存失败重新上传取消 uploading.4e448015.gif转存失败重新上传取消

类继承了多个trait后,可以依次调用多个trait中的同一个方法,只要让多个trait中的同一个方法在最后都依次执行super关键字即可。类中调用多个tait中都有这个方法时,首先会从最右边的trait方法开始执行,然后依次往左执行,形成一个调用链条。

示例

实现一个模拟支付过程的调用链

uploading.4e448015.gif转存失败重新上传取消 uploading.4e448015.gif转存失败重新上传取消

步骤

  1. 定义一个HandlerTrait特质
  1. 定义一个具体的handler方法,打印"处理数据..."
  1. 定义一个DataValidHandlerTrait,继承HandlerTrait特质
  1. 重写handler方法,打印"验证数据"
  2. 调用父特质的handler方法
  1. 定义一个SignatureValidHandlerTrait,继承HandlerTrait特质
  1. 重写Handler方法
  2. 打印"检查签名"
  3. 调用父特质的handler方法
  1. 创建一个PaymentService类
  1. 继承DataValidHandlerTrait
  2. 继承SignatureValidHandlerTrait
  3. 定义pay方法
      • 打印"准备支付"
      • 调用父特质的handler方法
  1. 添加main方法
  1. 创建PaymentService对象实例
  2. 调用pay方法

参考代码

trait HandlerTrait {
   def handle(data:String) = println("处理数据...")
}

trait DataValidHanlderTrait extends HandlerTrait {
   override def handle(data:String): Unit = {
       println("验证数据...")
       super.handle(data)
  }
}

trait SignatureValidHandlerTrait extends HandlerTrait {
   override def handle(data: String): Unit = {
       println("校验签名...")
       super.handle(data)
  }
}

class PayService extends DataValidHanlderTrait with SignatureValidHandlerTrait {
   override def handle(data: String): Unit = {
       println("准备支付...")
       super.handle(data)
  }
}

def main(args: Array[String]): Unit = {
   val service = new PayService
   service.handle("支付参数")
}

// 程序运行输出如下:
// 准备支付...
// 检查签名...
// 验证数据...
// 处理数据...

trait继承class

定义

trait也可以继承class的。特质会将class中的成员都继承下来。

示例

示例说明

  1. 定义一个特质,继承自一个class

步骤

  1. 创建一个MyUtils类,定义printMsg方法
  2. 创建一个Logger特质,继承自MyUtils,定义log方法
  3. 创建一个Person类,添加name字段
  1. 继承Logger特质
  2. 实现sayHello方法,调用log方法
  1. 添加main方法,创建一个Person对象,调用sayHello方法

参考代码

class MyUtil {
   def printMsg(msg:String) = println(msg)
}

trait Logger extends MyUtil {
   def log(msg:String) = printMsg("Logger:" + msg)
}

class Person extends Logger {
   def sayHello() = log("你好")
}

def main(args: Array[String]): Unit = {
   val person = new Person
   person.sayHello()
}

 

scala简介

scala是运行在JVM上的多范式(多种编程方法)编程语言,同时支持面向对象和面向函数编程

 

早期,scala刚出现的时候,并没有怎么引起重视,随着Spark和Kafka这样基于scala的大数据框架的兴起,scala逐步进入大数据开发者的眼帘。

为什么使用scala

  1. 开发大数据应用程序(Spark程序、Flink程序)
  2. 表达能力强,一行代码抵得上Java多行,开发速度快
  3. 兼容Java,可以访问庞大的Java类库,例如:操作mysql、redis、freemarker、activemq等等

scala对比Java

下面通过两个案例,分别使用java和scala实现的代码数量

案例

定义三个实体类(用户、订单、商品)

Java代码

/**
* 用户实体类
*/
public class User {
   private String name;
   private List<Order> orders;

   public String getName() {
  return name;
  }

   public void setName(String name) {
  this.name = name;
  }

   public List<Order> getOrders() {
  return orders;
  }

   public void setOrders(List<Order> orders) {
  this.orders = orders;
  }
}

/**
* 订单实体类
*/
public class Order {
   private int id;
   private List<Product> products;

   public int getId() {
  return id;
  }

   public void setId(int id) {
  this.id = id;
  }

   public List<Product> getProducts() {
  return products;
  }

   public void setProducts(List<Product> products) {
  this.products = products;
  }
}

/**
* 商品实体类
*/
public class Product {
   private int id;
   private String category;

   public int getId() {
  return id;
  }

   public void setId(int id) {
  this.id = id;
  }

   public String getCategory() {
  return category;
  }

   public void setCategory(String category) {
  this.category = category;
  }
}

scala代码

case class User(var name:String, var orders:List[Order]) // 用户实体类
case class Order(var id:Int, var products:List[Product]) // 订单实体类
case class Product(var id:Int, var category:String) // 商品实体类

开发环境安装

学习如何编写scala代码之前,需要先安装scala编译器以及开发工具

Java程序编译执行流程

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

Scala程序编译执行流程

uploading.4e448015.gif转存失败重新上传取消

uploading.4e448015.gif转存失败重新上传取消

scala程序运行需要依赖于Java类库,必须要有Java运行环境,scala才能正确执行

根据上述流程图,要编译运行scala程序,需要

  1. jdk(jvm)
  2. scala编译器(scala SDK)

接下来,需要依次安装以下内容:

  1. 安装JDK
  2. 安装scala SDK
  3. 安装IDEA插件

安装JDK

安装JDK 1.8  64位版本,并配置好环境变量

安装scala SDK

scala SDK是scala语言的编译器,要开发scala程序,必须要先安装SDK

本次安装的版本是: 2.11.12

步骤

  1. 下载、安装SDK
  2. 测试是否安装成功

具体操作

  1. 双击scala-2.11.12.msi,将scala安装在指定目录,例如:c:/opt
  2. 打开控制台,输入scala -version

安装IDEA scala插件

IDEA默认是不支持scala程序开发,所以需要来安装scala插件来支持scala语言。

步骤

  1. 下载指定版本IDEA scala插件
  2. IDEA配置scala插件
  3. 重新启动IDEA

具体操作

操作1:查看IDEA的版本号

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

操作2:到IDEA官网下载对应版本的IDEA scala插件

请务必下载IDEA版本一致的scala插件

操作3:选择配置 > 选择插件

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

操作4:点击小齿轮 > 选择从本地安装插件

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

操作5:找到下载的插件位置,点击OK

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

操作6:重新启动IDEA

操作7:查看scala插件

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

scala解释器

后续我们会使用scala解释器来学习scala基本语法,scala解释器像Linux命令一样,执行一条代码,马上就可以让我们看到执行结果,用来测试比较方便。

启动scala解释器

要启动scala解释器,只需要以下几步:

  1. 按住windows键 + r
  2. 输入scala即可

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

执行scala代码

在scala的命令提示窗口中输入println("hello, world"),回车执行

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

退出解释器

在scala命令提示窗口中执行:quit,即可退出解释器

声明变量

语法格式

Java变量定义

int a = 0;

在scala中,可以使用val或者var来定义变量,语法格式如下:

val/var 变量标识:变量类型 = 初始值

其中

  1. val定义的是不可重新赋值的变量
  2. var定义的是可重新赋值的变量
  3. scala中定义变量类型写在变量名后面
  4. scala的语句最后不需要添加分号

定义一个变量

示例:定义一个变量保存一个人的名字"tom"

参考代码

scala> val name:String = "tom"
name: String = tom

val和var变量

示例

给名字变量进行重新赋值为Jim,观察其运行结果

参考代码

scala> name = "Jim"
<console>:12: error: reassignment to val
      name = "Jim"

示例

使用var重新定义变量来保存名字"tom",并尝试重新赋值为Jim,观察其运行结果

参考代码

scala> var name:String = "tom"
name: String = tom

scala> name = "Jim"
name: String = Jim

类型推断定义变量

scala的语法要比Java简洁,我们可以使用一种更简洁的方式来定义变量。

示例

使用更简洁的语法定义一个变量保存一个人的名字"tom"

参考代码

scala> val name = "tom"
name: String = tom

scala可以自动根据变量的值来自动推断变量的类型,这样编写代码更加简洁。

惰性赋值

在企业的大数据开发中,有时候会编写非常复杂的SQL语句,这些SQL语句可能有几百行甚至上千行。这些SQL语句,如果直接加载到JVM中,会有很大的内存开销。如何解决?

当有一些变量保存的数据较大时,但是不需要马上加载到JVM内存。可以使用惰性赋值来提高效率。

语法格式:

lazy val  变量名 = 表达式

参考代码

scala> lazy val sql = """insert overwrite table adm.itcast_adm_personas
    |     select
    |     a.user_id,
....
    |     left join gdm.itcast_gdm_user_buy_category c on a.user_id=c.user_id
    |     left join gdm.itcast_gdm_user_visit d on a.user_id=d.user_id;"""
sql: String = <lazy>

字符串

scala提供多种定义字符串的方式,将来我们可以根据需要来选择最方便的定义方式。

  1. 使用双引号
  2. 使用插值表达式
  3. 使用三引号

使用双引号

语法

val/var 变量名 = “字符串”

参考代码

val name="hadoop6"
scala> println(name + name.length)
hadoop6

使用插值表达式

插值表达式可以有效避免大量字符串的拼接。

语法

val/var 变量名 = s"${变量/表达式}字符串"

在定义字符串之前添加s

在字符串中,可以使用${}来引用变量或者编写表达式

示例

若干个变量,分别保存:"zhangsan"、30、"male",定义一个字符串,保存这些信息。

打印输出:name=zhangsan, age=30, sex=male

 

参考代码

scala> val name = "zhangsan"
name: String = zhangsan

scala> val age = 30
age: Int = 30

scala> val sex = "male"
sex: String = male

scala> val info = s"name=${name}, age=${age}, sex=${sex}"
info: String = name=zhangsan, age=30, sex=male

scala> println(info)
name=zhangsan, age=30, sex=male

使用三引号

大段的文本需要保存,可以使用三引号来定义字符串。例如:保存一大段的SQL语句。三个引号中间的所有字符串都将作为字符串的值。

语法

val/var 变量名 = """字符串1
字符串2"""

参考代码

val sql = """select
    | *
    | from
    |     t_user
    | where
    |     name = "zhangsan""""

println(sql)

数据类型与操作符

scala中的类型以及操作符绝大多数和Java一样

数据类型

基础类型

类型说明

Byte

8位带符号整数

Short

16位带符号整数

Int

32位带符号整数

Long

64位带符号整数

Char

16位无符号Unicode字符

String

Char类型的序列(字符串)

Float

32位单精度浮点数

Double

64位双精度浮点数

Boolean

true或false

注意下 scala类型与Java的区别

  1. scala中所有的类型都使用大写字母开头
  2. 整形使用Int而不是Integer
  3. scala中定义变量可以不写类型,让scala编译器自动推断

运算符

类别

操作符

算术运算符

+、-、*、/

关系运算符

>、<、==、!=、>=、<=

逻辑运算符

&&、||、!

  1. scala中没有,++、--运算符
  2. 与Java不一样,在scala中,可以直接使用==、!=进行比较,它们与equals方法表示一致。而比较两个对象的引用值,使用eq

示例

有一个字符串"abc",再创建第二个字符串,值为:在第一个字符串后拼接一个空字符串。

然后使用比较这两个字符串是否相等、再查看它们的引用值是否相等。

参考代码

val str1 = "abc"
val str2 = str1 + ""
str1 == str2
str1.eq(str2)

scala类型层次结构

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

类型

说明

Any

所有类型的父类,,它有两个子类AnyRef与AnyVal

AnyVal

所有数值类型的父类

AnyRef

所有对象类型(引用类型)的父类

Unit

表示空,Unit是AnyVal的子类。它类似于Java中的void,但scala要比Java更加面向对象

Null

Null是AnyRef的子类,也就是说它是所有引用类型的子类。可以将null赋值给任何对象类型

Nothing

所有类型的子类 不能直接创建该类型实例,某个方法抛出异常时,返回的就是Nothing类型,因为Nothing是所有类的子类,那么它可以赋值为任何类型

nothing

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

    val c = m3(1,0)

}

 

def m3(x:Int, y:Int):Int = {

    if(y == 0) throw new Exception("这是一个异常")

    x / y

}

问题

以下代码是否有问题?

val b:Int = null

scala会解释报错:

Null类型并不能转换为Int类型,说明Null类型并不是Int类型的子类

条件表达式

条件表达式就是if表达式。scala条件表达式的语法和Java一样。

有返回值的if

参考代码

scala> val sex = "male"
sex: String = male

scala> val result = if(sex == "male") 1 else 0
result: Int = 1

与Java不一样的是,

  1. 在scala中,条件表达式也是有返回值的
  2. 在scala中,没有三元表达式,可以使用if表达式替代三元表达式

块表达式

  1. scala中,使用{}表示一个块表达式
  2. 和if表达式一样,块表达式也是有值的
  3. 值就是最后一个表达式的值

问题

请问以下代码,变量a的值是什么?

scala> val a = {
    | println("1 + 1")
    | 1 + 1
    | }

循环

在scala中,可以使用for和while,但一般推荐使用for表达式,因为for表达式语法更简洁

for表达式

语法

for(i <- 表达式/数组/集合) {

    // 表达式

}

简单循环

使用for表达式打印1-10的数字

参考代码1

scala> val nums = 1.to(10)                                                              nums: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)                                                                                  

scala> for(i <- nums) println(i)                                                                                                                        

参考代码2

// 中缀调用法

scala> for(i <- 1 to 10) println(i)

嵌套循环

使用for表达式,打印以下字符

*****

*****

*****

参考代码

for(i <- 1 to 3; j <- 1 to 5) {print("*");if(j == 5) println("")}

守卫

for表达式中,可以添加if判断语句,这个if判断就称之为守卫。我们可以使用守卫让for表达式更简洁。

语法

for(i <- 表达式/数组/集合 if 表达式) {

    // 表达式

}

示例

使用for表达式打印1-10之间能够整除3的数字

参考代码

// 添加守卫,打印能够整除3的数字

for(i <- 1 to 10 if i % 3 == 0) println(i)

for推导式

在for循环体中,可以使用yield表达式构建出一个集合,我们把使用yield的for表达式称之为推导式

示例

生成一个10、20、30...100的集合

参考代码

// for推导式:for表达式中以yield开始,该for表达式会构建出一个集合

val v = for(i <- 1 to 10) yield i * 10

while循环

scala中while循环和Java中是一致的

示例

打印1-10的数字

参考代码

scala> var i = 1

i: Int = 1

 

scala> while(i <= 10) {

     | println(i)

     | i = i+1

     | }

break和continue

  1. scala中,没有break/continue关键字
  2. 如果一定要使用break/continue,就需要使用scala.util.control包的Break类的breablebreak方法

实现break

用法

  1. 导入Breaks包import scala.util.control.Breaks._
  2. 使用breakable将for表达式包起来
  3. for表达式中需要退出循环的地方,添加break()方法调用

示例

使用for表达式打印1-100的数字,如果数字到达50,退出for表达式

参考代码

// 导入scala.util.control包下的Break

import scala.util.control.Breaks._

breakable{

    for(i <- 1 to 100) {

        if(i >= 50) break()

        else println(i)

    }

}

实现continue

用法

  1. continue的实现与break类似,但有一点不同:
  2. 实现continue是用breakable{}将for表达式的循环体包含起来

示例

打印1-100的数字,使用for表达式来遍历,如果数字能整除10,不打印

// 导入scala.util.control包下的Break    
import scala.util.control.Breaks._
for(i <- 1 to 100 ) {
   breakable{
       if(i % 10 == 0) break()
       else println(i)
  }
}

方法

一个类可以有自己的方法,scala中的方法和Java方法类似。但scala与Java定义方法的语法是不一样的。

定义方法

语法

def methodName (参数名:参数类型, 参数名:参数类型) : [return type] = {
   // 方法体:一系列的代码
}

  1. 参数列表的参数类型不能省略
  2. 返回值类型可以省略,由scala编译器自动推断
  3. 返回值可以不写return,默认就是{}块表达式的值

示例

  1. 定义一个方法,实现两个整形数值相加,返回相加后的结果
  2. 调用该方法

参考代码

scala> def add(a:Int, b:Int) = a + b
m1: (x: Int, y: Int)Int

scala> add(1,2)
res10: Int = 3

返回值类型推断

scala定义方法可以省略返回值,由scala自动推断返回值类型。

定义递归方法,不能省略返回值类型

示例

定义递归方法(求阶乘)

10 * 9 * 8 * 7 * 6 * ... * 1

参考代码

scala> def m2(x:Int) = {

     | if(x<=1) 1

     | else m2(x-1) * x

     | }

<console>:13: error: recursive method m2 needs result type

       else m2(x-1) * x

方法参数

scala中的方法参数,使用比较灵活。它支持以下几种类型的参数:

  • 默认参数
  • 带名参数
  • 变长参数

默认参数

在定义方法时可以给参数定义一个默认值。

参考代码

// x,y带有默认值为0

def add(x:Int = 0, y:Int = 0) = x + y

add()

带名参数

在调用方法时,可以指定参数的名称来进行调用。

参考代码

def add(x:Int = 0, y:Int = 0) = x + y

add(x=1)

变长参数

如果方法的参数是不固定的,可以定义一个方法的参数是变长参数。

语法格式:

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

    方法体

}

在参数类型后面加一个*号,表示参数可以是0个或者多个

参考代码

scala> def add(num:Int*) = num.sum

add: (num: Int*)Int

 

scala> add(1,2,3,4,5)

res1: Int = 15

方法调用方式

在scala中,有以下几种方法调用方式,

  • 后缀调用法
  • 中缀调用法
  • 花括号调用法
  • 无括号调用法

在后续编写spark、flink程序时,我们会使用到这些方法调用方式。

后缀调用法

这种方法与Java没有区别。

语法

对象名.方法名(参数)

示例

使用后缀法Math.abs求绝对值

参考代码

scala> Math.abs(-1)

res3: Int = 1

中缀调用法

语法 注意空格

对象名 方法名 参数

例如:1 to 10

如果有多个参数,使用括号括起来

示例

使用中缀法Math.abs求绝对值

scala> Math abs -1

res4: Int = 1

操作符即方法

scala中,+ - * / %等操作符都是方法,操作符是一个方法名字是符号的方法。

花括号调用法

语法

Math.abs{

    // 表达式1

    // 表达式2

}

方法只有一个参数,才能使用花括号调用法

示例

使用花括号调用法Math.abs求绝对值

参考代码

scala> Math.abs{-10}

res13: Int = 10

无括号调用法

如果方法没有参数,可以省略方法名后面的括号

示例

  1. 定义一个无参数的方法,打印"hello"
  2. 使用无括号调用法调用该方法

参考代码

def m3()=println("hello")

m3()

函数

scala支持函数式编程,将来编写Spark/Flink程序中,会大量使用到函数

定义函数

语法

val 函数变量名 = (参数名:参数类型, 参数名:参数类型....) => 函数体

  1. 函数是一个对象(变量)
  2. 类似于方法,函数也有输入参数和返回值
  3. 函数定义不需要使用def定义
  4. 无需指定返回值类型

参考代码

scala> val add = (x:Int, y:Int) => x + y

add: (Int, Int) => Int = <function2>

 

scala> add(1,2)

res3: Int = 3

方法和函数的区别

  1. 方法是隶属于类或者对象的,在运行时,它是加载到JVM的方法区中
  2. 可以将函数对象赋值给一个变量,在运行时,它是加载到JVM的堆内存中
  3. 函数是一个对象,继承自FunctionN,函数对象有apply,curried,toString,tupled这些方法。方法则没有

示例

方法无法赋值给变量

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

add: (x: Int, y: Int)Int

 

scala> val a = add

<console>:14: error: missing arguments for method add;

follow this method with `_' if you want to treat it as a partially applied function

       val a = add

方法转换为函数

  • 有时候需要将方法转换为函数,作为变量传递,就需要将方法转换为函数
  • 使用_即可将方法转换为函数

参考代码

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

add: (x: Int, y: Int)Int

 

scala> val a = add  _

a: (Int, Int) => Int = <function2>

数组

scala中,有两种数组,一种是定长数组,另一种是变长数组

定长数组

  1. 定长数组指的是数组的长度不允许改变
  2. 数组的元素可以改变

语法

// 通过指定长度定义数组
val/var 变量名 = new Array[元素类型](数组长度)

// 用元素直接初始化数组
val/var 变量名 = Array(元素1, 元素2, 元素3...)

  1. 在scala中,数组的泛型使用[]来指定
  2. 使用()来获取元素

参考代码

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

scala> a(0) = 110

scala> println(a(0))
110

变长数组

变长数组指的是数组的长度是可变的,可以往数组中添加、删除元素

变长数组

创建变长数组,需要提前导入ArrayBuffer类import scala.collection.mutable.ArrayBuffer

语法

创建空的ArrayBuffer变长数组,语法结构:

  • val/var a = new ArrayBuffer[元素类型]()

创建带有初始元素的ArrayBuffer

  • val/var a = ArrayBuffer(元素1,元素2,元素3....)

示例一

定义一个长度为0的整型变长数组

参考代码

val a = ArrayBuffer[Int]()

示例二

定义一个包含"hadoop", "storm", "spark"元素的变长数组

参考代码

scala> val a = ArrayBuffer("hadoop", "storm", "spark")

a: scala.collection.mutable.ArrayBuffer[String] = ArrayBuffer(hadoop, storm, spark)

添加/修改/删除元素

  1. 使用+=添加元素
  2. 使用-=删除元素
  3. 使用++=追加一个数组到变长数组

参考代码

// 定义变长数组

scala> val a = ArrayBuffer("hadoop", "spark", "flink")

a: scala.collection.mutable.ArrayBuffer[String] = ArrayBuffer(hadoop, spark, flink)

 

// 追加一个元素

scala> a += "flume"

res10: a.type = ArrayBuffer(hadoop, spark, flink, flume)

 

// 删除一个元素

scala> a -= "hadoop"

res11: a.type = ArrayBuffer(spark, flink, flume)

 

// 追加一个数组

scala> a ++= Array("hive", "sqoop")

res12: a.type = ArrayBuffer(spark, flink, flume, hive, sqoop)

遍历数组

可以使用以下两种方式来遍历数组:

  1. 使用for表达式直接遍历数组中的元素
  2. 使用索引遍历数组中的元素

参考代码

scala> val a = Array(1,2,3,4,5)

a: Array[Int] = Array(1, 2, 3, 4, 5)

 

scala> for(i<-a) println(i)

1

2

3

4

5

参考代码

scala> val a = Array(1,2,3,4,5)

a: Array[Int] = Array(1, 2, 3, 4, 5)

 

scala> for(i <- 0 to a.length - 1) println(a(i))

1

2

3

4

5

 

scala> for(i <- 0 until a.length) println(a(i))

1

2

3

4

5

0 until n——生成一系列的数字,包含0,不包含n

0 to n ——包含0,也包含n

数组常用算法

以下为常用的几个算法:

  1. 求和——sum方法
  2. 求最大值——max方法
  3. 求最小值——min方法
  4. 排序——sorted方法

// 升序排序

scala> a.sorted

res53: Array[Int] = Array(1, 2, 4, 4, 10)

 

// 降序

scala> a.sorted.reverse

res56: Array[Int] = Array(10, 4, 4, 2, 1)

元组

元组可以用来包含一组不同类型的值。例如:姓名,年龄,性别,出生年月。元组的元素是不可变的。

定义元组

使用括号来定义元组

val/var 元组 = (元素1, 元素2, 元素3....)

使用箭头来定义元组(元组只有两个元素)

val/var 元组 = 元素1->元素2

参考代码

scala> val a = ("zhangsan", 20)

a: (String, Int) = (zhangsan,20)

 

scala> val a = "zhangsan" -> 20

a: (String, Int) = (zhangsan,20)

访问元组

使用_1、_2、_3....来访问元组中的元素,_1表示访问第一个元素,依次类推

参考代码

scala> val a = "zhangsan" -> "male"

a: (String, String) = (zhangsan,male)

 

// 获取第一个元素

scala> a._1

res41: String = zhangsan

 

// 获取第二个元素

scala> a._2

res42: String = male

列表

列表是scala中最重要的、也是最常用的数据结构。List具备以下性质:

  1. 可以保存重复的值
  2. 有先后顺序

在scala中,也有两种列表,一种是不可变列表、另一种是可变列表

定义

不可变列表就是列表的元素、长度都是不可变的

语法

val/var 变量名 = List(元素1, 元素2, 元素3...)

使用Nil创建一个不可变的空列表

val/var 变量名 = Nil

使用::方法创建一个不可变列表

val/var 变量名 = 元素1 :: 元素2 :: Nil

使用::拼接方式来创建列表,必须在最后添加一个Nil

示例一

创建一个不可变列表,存放以下几个元素(1,2,3,4)

参考代码

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

示例二

使用Nil创建一个不可变的空列表

参考代码

scala> val a = Nil
a: scala.collection.immutable.Nil.type = List()

示例三

使用::方法创建列表,包含-2、-1两个元素

参考代码

scala> val a = -2 :: -1 :: Nil
a: List[Int] = List(-2, -1)

可变列表

可变列表就是列表的元素、长度都是可变的

要使用可变列表,先要导入import scala.collection.mutable.ListBuffer

  1. 可变集合都在mutable包中
  2. 不可变集合都在immutable包中(默认导入)

定义

使用ListBuffer[元素类型]()创建空的可变列表,语法结构:

val/var 变量名 = ListBuffer[Int]()

使用ListBuffer(元素1, 元素2, 元素3...)创建可变列表,语法结构:

val/var 变量名 = ListBuffer(元素1,元素2,元素3...)

示例一

创建空的整形可变列表

参考代码

  scala> val a = ListBuffer[Int]()

  a: scala.collection.mutable.ListBuffer[Int] = ListBuffer()

示例二

创建一个可变列表,包含以下元素:1,2,3,4

参考代码

scala> val a = ListBuffer(1,2,3,4)

a: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3, 4)

可变列表操作

  1. 获取元素(使用括号访问(索引值)
  2. 添加元素(+=
  3. 追加一个列表(++=
  4. 更改元素(使用括号获取元素,然后进行赋值
  5. 删除元素(-=
  6. 转换为List(toList
  7. 转换为Array(toArray

示例

  1. 定义一个可变列表包含以下元素:1,2,3
  2. 获取第一个元素
  3. 添加一个新的元素:4
  4. 追加一个列表,该列表包含以下元素:5,6,7
  5. 删除元素7
  6. 将可变列表转换为不可变列表
  7. 将可变列表转换为数组

参考代码

// 导入可变列表

scala> import scala.collection.mutable.ListBuffer

import scala.collection.mutable.ListBuffer

 

// 创建可变列表

scala> val a = ListBuffer(1,2,3)

a: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3)

 

// 获取第一个元素

scala> a(0)

res19: Int = 1

 

// 追加一个元素

scala> a += 4

res20: a.type = ListBuffer(1, 2, 3, 4)

 

// 追加一个列表

scala> a ++= List(5,6,7)

res21: a.type = ListBuffer(1, 2, 3, 4, 5, 6, 7)

 

// 删除元素

scala> a -= 7

res22: a.type = ListBuffer(1, 2, 3, 4, 5, 6)

 

// 转换为不可变列表

scala> a.toList

res23: List[Int] = List(1, 2, 3, 4, 5, 6)

 

// 转换为数组

scala> a.toArray

res24: Array[Int] = Array(1, 2, 3, 4, 5, 6)

列表常用操作

以下是列表常用的操作

  1. 判断列表是否为空(isEmpty
  2. 拼接两个列表(++
  3. 获取列表的首个元素(head)和剩余部分(tail)
  4. 反转列表(reverse
  5. 获取前缀(take)、获取后缀(drop
  6. 扁平化(flatten
  7. 拉链(zip)和拉开(unzip
  8. 转换字符串(toString
  9. 生成字符串(mkString
  10. 并集(union
  11. 交集(intersect
  12. 差集(diff

判断列表是否为空

参考代码

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

scala> a.isEmpty
res51: Boolean = false

拼接两个列表

参考代码

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

scala> val b = List(4,5,6)
b: List[Int] = List(4, 5, 6)

scala> a ++ b
res52: List[Int] = List(1, 2, 3, 4, 5, 6)

获取列表的首个元素和剩余部分

参考代码

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

scala> a.head
res4: Int = 1

scala> a.tail
res5: List[Int] = List(2, 3)

反转列表

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

scala> a.reverse
res6: List[Int] = List(3, 2, 1)

获取列表前缀和后缀

参考代码

scala> val a = List(1,2,3,4,5)

a: List[Int] = List(1, 2, 3, 4, 5)

 

scala> a.take(3)

res56: List[Int] = List(1, 2, 3)

 

scala> a.drop(3)

res60: List[Int] = List(4, 5)

扁平化(压平)

扁平化表示将列表中的     列表中的所有元素放到一个列表中。

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

参考代码

scala> val a = List(List(1,2), List(3), List(4,5))

a: List[List[Int]] = List(List(1, 2), List(3), List(4, 5))

 

scala> a.flatten

res0: List[Int] = List(1, 2, 3, 4, 5)

拉链与拉开

  1. 拉链:使用zip将两个列表,组合成一个元素为元组的列表
  2. 拉开:将一个包含元组的列表,解开成包含两个列表的元组

参考代码

scala> val a = List("zhangsan", "lisi", "wangwu")

a: List[String] = List(zhangsan, lisi, wangwu)

 

scala> val b = List(19, 20, 21)

b: List[Int] = List(19, 20, 21)

 

scala> a.zip(b)

res1: List[(String, Int)] = List((zhangsan,19), (lisi,20), (wangwu,21))

参考代码

scala> res1.unzip
res2: (List[String], List[Int]) = (List(zhangsan, lisi, wangwu),List(19, 20, 21))

转换字符串

toString方法可以返回List中的所有元素

参考代码

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

scala> println(a.toString)
List(1, 2, 3, 4)

生成字符串

mkString方法,可以将元素以分隔符拼接起来。默认没有分隔符

参考代码

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

scala> a.mkString
res7: String = 1234

scala> a.mkString(":")
res8: String = 1:2:3:4

并集

union表示对两个列表取并集,不去重

参考代码

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

scala> val a2 = List(3,4,5,6)
a2: List[Int] = List(3, 4, 5, 6)

// 并集操作
scala> a1.union(a2)
res17: List[Int] = List(1, 2, 3, 4, 3, 4, 5, 6)

// 可以调用distinct去重
scala> a1.union(a2).distinct
res18: List[Int] = List(1, 2, 3, 4, 5, 6)

交集

intersect表示对两个列表取交集

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

scala> val a2 = List(3,4,5,6)
a2: List[Int] = List(3, 4, 5, 6)

scala> a1.intersect(a2)
res19: List[Int] = List(3, 4)

差集

diff表示对两个列表取差集,例如: a1.diff(a2),表示获取a1在a2中不存在的元素

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

scala> val a2 = List(3,4,5,6)
a2: List[Int] = List(3, 4, 5, 6)

scala> a1.diff(a2)
res24: List[Int] = List(1, 2)

Set

Set(集)是代表没有重复元素的集合。Set具备以下性质:

  1. 元素不重复
  2. 不保证插入顺序

scala中的集也分为两种,一种是不可变集,另一种是可变集。

不可变集

语法

创建一个空的不可变集,语法格式:

val/var 变量名 = Set[类型]()

给定元素来创建一个不可变集,语法格式:

val/var 变量名 = Set(元素1, 元素2, 元素3...)

示例一

定义一个空的不可变集

参考代码

scala> val a = Set[Int]()
a: scala.collection.immutable.Set[Int] = Set()

示例二

定义一个不可变集,保存以下元素:1,1,1,1,1,3,2,4,8

参考代码

scala> val a = Set(1,1,1,1,1,3,2,4,8)
a: scala.collection.immutable.Set[Int] = Set(1, 2, 3, 8, 4)

基本操作

  1. 获取集的大小(size
  2. 遍历集(和遍历数组一致
  3. 添加一个元素,生成一个Set(+
  4. 拼接两个集,生成一个Set(++
  5. 拼接集和列表,生成一个Set(++

示例

 

参考代码

// 创建集
scala> val a = Set(1,1,2,3,4,5)
a: scala.collection.immutable.Set[Int] = Set(5, 1, 2, 3, 4)

// 获取集的大小
scala> a.size
res0: Int = 5

// 遍历集
scala> for(i <- a) println(i)

// 删除一个元素
scala> a - 1
res5: scala.collection.immutable.Set[Int] = Set(5, 2, 3, 4)

// 拼接两个集
scala> a ++ Set(6,7,8)
res2: scala.collection.immutable.Set[Int] = Set(5, 1, 6, 2, 7, 3, 8, 4)

// 拼接集和列表
scala> a ++ List(6,7,8,9)
res6: scala.collection.immutable.Set[Int] = Set(5, 1, 6, 9, 2, 7, 3, 8, 4)

可变集

定义

可变集合不可变集的创建方式一致,只不过需要提前导入一个可变集类。

手动导入:import scala.collection.mutable.Set

参考代码

scala> val a = Set(1,2,3,4)
a: scala.collection.mutable.Set[Int] = Set(1, 2, 3, 4)                          

// 添加元素
scala> a += 5
res25: a.type = Set(1, 5, 2, 3, 4)

// 删除元素s
scala> a -= 1
res26: a.type = Set(5, 2, 3, 4)

映射

Map可以称之为映射。它是由键值对组成的集合。在scala中,Map也分为不可变Map和可变Map。

不可变Map

语法

val/var map = Map(->, ->, ->...) // 推荐,可读性更好
val/var map = Map((, ), (, ), (, ), (, )...)

参考代码

scala> val map = Map("zhangsan"->30, "lisi"->40)
map: scala.collection.immutable.Map[String,Int] = Map(zhangsan -> 30, lisi -> 40)

scala> val map = Map(("zhangsan", 30), ("lisi", 30))
map: scala.collection.immutable.Map[String,Int] = Map(zhangsan -> 30, lisi -> 30)

// 根据key获取value
scala> map("zhangsan")
res10: Int = 30

//修改

scala> map("zhangsan")=33

<console>:11: error: value update is not a member of scala.collection.immutable.Map[String,Int]

              map("zhangsan")=33

可变Map

定义

定义语法与不可变Map一致。但定义可变Map需要手动导入import scala.collection.mutable.Map

scala> val map = Map("zhangsan"->30, "lisi"->40)
map: scala.collection.mutable.Map[String,Int] = Map(lisi -> 40, zhangsan -> 30)

// 修改value
scala> map("zhangsan") = 20

Map基本操作

基本操作

  1. 获取值(map(key))
  2. 获取所有key(map.keys
  3. 获取所有value(map.values
  4. 遍历map集合
  5. getOrElse
  6. 增加key,value对
  7. 删除key

示例

参考代码

scala> val map = Map("zhangsan"->30, "lisi"->40)
map: scala.collection.mutable.Map[String,Int] = Map(lisi -> 40, zhangsan -> 30)

// 获取zhagnsan的年龄
scala> map("zhangsan")
res10: Int = 30

// 获取所有的学生姓名
scala> map.keys
res13: Iterable[String] = Set(lisi, zhangsan)

// 获取所有的学生年龄
scala> map.values
res14: Iterable[Int] = HashMap(40, 30)

// 打印所有的学生姓名和年龄
scala> for((x,y) <- map) println(s"$x $y")
lisi 40
zhangsan 30

// 获取wangwu的年龄,如果wangwu不存在,则返回-1
scala> map.getOrElse("wangwu", -1)
res17: Int = -1

// 新增一个学生:wangwu, 35
scala> map += "wangwu"->35
res22: scala.collection.mutable.Map[String,Int] = Map(lisi -> 40, zhangsan -> 30, wangwu -> 35)

// 将lisi从可变映射中移除
scala> map -= "lisi"
res23: scala.collection.mutable.Map[String,Int] = Map(zhangsan -> 30)

iterator迭代器

scala针对每一类集合都提供了一个迭代器(iterator)用来迭代访问集合

使用迭代器遍历集合

  1. 使用iterator方法可以从集合获取一个迭代器
  2. 迭代器的两个基本操作
    1. hasNext——查询容器中是否有下一个元素
    2. next——返回迭代器的下一个元素,如果没有,抛出NoSuchElementException
  3. 每一个迭代器都是有状态的
  4. 迭代完后保留在最后一个元素的位置
  5. 再次使用则抛出NoSuchElementException
  6. 可以使用while或者for来逐个返回元素

示例

  1. 定义一个列表,包含以下元素:1,2,3,4,5
  2. 使用while循环和迭代器,遍历打印该列表

参考代码

scala> var a=List(1,2,3,4,5)

a: List[Int] = List(1, 2, 3, 4, 5)

 

scala> val ite = a.iterator
ite: Iterator[Int] = non-empty iterator

scala> while(ite.hasNext){

println(ite.next)

}

示例

  1. 定义一个列表,包含以下元素:1,2,3,4,5
  2. 使用for 表达式和迭代器,遍历打印该列表

参考代码

scala> val a = List(1,2,3,4,5)
a: List[Int] = List(1, 2, 3, 4, 5)

scala> for(i <- a) println(i)

 

 

类和对象

scala是支持面向对象的,也有类和对象的概念。我们依然可以基于scala语言来开发面向对象的应用程序。

创建类和对象

用法

使用class来定义一个类 new来创建对象

参考代码

object _01ClassDemo {
 // 创建类
 class Person{}
​ def main(args: Array[String]): Unit = {
   // 创建对象
   val p = new Person()
   println(p)
}
}

参考代码

object _02ClassDemo {

 // 创建类,省略花括号
 class Person

 def main(args: Array[String]): Unit = {
   // 创建对象,省略括号
   val person = new Person
}
}

定义和访问成员变量

一个类会有自己的属性,例如:人这样一个类,有自己的姓名和年龄。在类中定义、和访问成员变量

参考代码

object _03ClassDemo {
 class Person {
   // 定义成员变量
   var name = ""
   var age = 0
}

 def main(args: Array[String]): Unit = {
   // 创建Person对象
   val person = new Person
   person.name = "zhangsan"
   person.age = 20

   // 获取变量值
   println(person.name)
   println(person.age)
}
}

使用下划线初始化成员变量

scala中有一个更简洁的初始化成员变量的方式,可以让代码看起来更加简洁。

用法

在定义var类型的成员变量时,可以使用_来初始化成员变量

  1. String => null
  2. Int => 0
  3. Boolean => false
  4. Double => 0.0
  5. ...

val类型的成员变量,必须要自己手动初始化

参考代码

object _04ClassDemo {

 class Person{
   // 使用下划线进行初始化
   var name:String = _
   var age:Int = _
}

 def main(args: Array[String]): Unit = {
   val person = new Person
   
   println(person.name)
   println(person.age)
}
}

定义成员方法

类可以有自己的行为,scala中也可以通过定义成员方法来定义类的行为。

使用def来定义成员方法

示例

  1. 创建一个Customer类

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

  1. 创建一个该类的对象,并调用printHello方法

步骤

  1. 1、创建一个Object,添加main方法
  2. 2、创建Customer类,添加成员变量、成员方法
  3. 3、在main方法中创建Customer类对象,设置成员变量值(张三、男)
  4. 4、调用成员方法

参考代码

object _05ClassDemo {

 class Customer {
   var name:String = _
   var sex:String = _

   // 定义成员方法
   def sayHi(msg:String) = {
     println(msg)
  }
}

 def main(args: Array[String]): Unit = {
   val customer = new Customer
   customer.name = "张三"
   customer.sex = "男"
   customer.sayHi("你好")
}
}

访问修饰符

和Java一样,scala也可以通过访问修饰符,来控制成员变量和成员方法是否可以被访问。

定义

可以在成员前面添加private/protected(私有的/受保护的关键字来控制成员的可见性。

scala中,没有public关键字,任何没有被标为private或protected的成员都是公共的

案例

定义一个Person类

uploading.4e448015.gif转存失败重新上传取消 uploading.4e448015.gif转存失败重新上传取消

在main方法中创建该类的对象,测试是否能够访问到私有成员

参考代码

object _02AccessDemo {

 class Person {
   // 定义私有成员变量
   private var name:String = _
   private var age:Int = _

   def getName() = name
   def setName(name:String) = this.name = name
   def getAge() = age
   def setAge(age:Int) = this.age = age

   // 定义私有成员方法
   private def getNameAndAge = {
     name -> age
  }
}

 def main(args: Array[String]): Unit = {
   val person = new Person
   person.setName("张三")
   person.setAge(10)

   println(person.getName())
   println(person.getAge())
}
}

类的构造器

当创建类对象的时候,会自动调用类的构造器。之前使用的都是默认构造器。

主构造器

Java的构造器,有构造列表和构造代码块

class Person {
   // 成员变量
   private String name;
   private Integer age;

   // Java构造器
   public Person(String name, Integer age) {
       // 初始化成员变量
       this.name = name;
       this.age = age;
  }
}

在scala中, 可以使用更简洁的语法来实现。

语法

class 类名(var/val 参数名:类型 = 默认值, var/val 参数名:类型 = 默认值){
   // 构造代码块
}

  1. 主构造器的参数列表是直接定义在类名后面,添加了val/var表示直接通过主构造器定义成员变量
  2. 构造器参数列表可以指定默认值
  3. 创建实例,调用构造器可以指定字段进行初始化
  4. 整个class中除了字段定义和方法定义的代码都是构造代码

示例

  1. 定义一个Person类,通过主构造器参数列表定义姓名和年龄字段,并且设置它们的默认值
  2. 在主构造器中输出"调用主构造器"
  3. 创建"张三"对象(姓名为张三,年龄为20),打印对象的姓名和年龄
  4. 创建"空"对象,不给构造器传入任何的参数,打印对象的姓名和年龄
  5. 创建"man40"对象,不传入姓名参数,指定年龄为40,打印对象的姓名和年龄

参考代码

object _06ConstructorDemo {

 

  // 定义类的主构造器

  // 指定默认值

  class Person(var name:String = "", var age:Int = 0) {

    println("调用主构造器")

  }

 

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

    // 给构造器传入参数

    val zhangsan = new Person("张三", 20)

    println(zhangsan.name)

    println(zhangsan.age)

 

    println("---")

 

    // 不传入任何参数

    val empty = new Person

    println(empty.name)

    println(empty.age)

 

    println("---")

 

    // 指定字段进行初始化

    val man40 = new Person(age = 40)

    println(man40.name)

    println(man40.age)

  }

}

辅助构造器

除了主构造器之外的构造器称为辅助构造器

例如:允许通过多种方式,来创建对象,这时候就可以定义其他更多的构造器。

语法

  1. 定义辅助构造器与定义方法一样,也使用def关键字来定义
  2. 这个方法的名字为this

def this(参数名:类型, 参数名:类型) {

    // 第一行需要调用主构造器或者其他构造器

    // 构造器代码

}

辅助构造器的第一行代码,必须要调用主构造器或者其他辅助构造器

示例

示例说明

  1. 定义一个Customer类,包含一个姓名和地址字段
  2. 定义Customer类的主构造器(初始化姓名和地址)
  3. 定义Customer类的辅助构造器,该辅助构造器接收一个数组参数,使用数组参数来初始化成员变量
  4. 使用Person类的辅助构造器来创建一个"zhangsan"对象
  5. 姓名为张三
  6. 地址为北京
  7. 打印对象的姓名、地址

参考代码

object _07ConstructorDemo {

  class Customer(var name:String = "", var address:String = "") {

    // 定义辅助构造器

    def this(arr:Array[String]) = {

      // 辅助构造器必须要调用主构造器或者其他辅助构造器

      this(arr(0), arr(1))

    }

  }

 

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

    val zhangsan = new Customer(Array("张三", "北京"))

 

    println(zhangsan.name)

    println(zhangsan.address)

  }

}

单例对象

scala中没有Java中的静态成员,若想要定义类似于Java的static变量、static方法,就要使用到scala中的单例对象——object.

定义单例对象

单例对象表示全局仅有一个对象(类似于Java static概念)

  1. 定义单例对象和定义类很像,就是把class换成object
  2. 在object中定义的成员变量类似于Java的静态变量
  3. 可以使用object直接引用成员变量

示例

示例说明

  1. 定义一个Dog单例对象,保存狗有几条腿
  2. 在main方法中打印狗腿的数量

参考代码

object _08ObjectDemo {

 

  // 定义一个单例对象

  object Dog {

    // 定义腿的数量

    val LEG_NUM = 4

  }

 

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

    println(Dog.LEG_NUM)

  }

}

在单例对象中定义成员方法

  1. 在object中定义的成员方法类似于Java的静态方法

示例

示例说明

  1. 设计一个单例对象,定义一个能够打印分割线(15个减号)的方法
  2. 在main方法调用该方法,打印分割线

参考代码

object _09ObjectDemo {

 

  object PrintUtil {

    // 打印分割线

    def printSpliter() = {

      // 字符串乘法,表示返回多少个字符串

      println("-" * 10)

    }

  }

 

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

    PrintUtil.printSpliter()

  }

}

工具类案例

需求

  1. 编写一个DateUtil工具类专门用来格式化日期时间
  2. 定义一个方法,用于将日期(Date)转换为年月日字符串,例如:2030-10-05

步骤

  1. 定义一个DateUtil单例对象,定义日期格式化方法(format)
  2. 使用SimpleDateFormat将日期转换为字符串

参考代码

object _10ObjectDemo {

 

  object DateUtils {

    // 在object中定义的成员变量,相当于Java中定义一个静态变量

    // 定义一个SimpleDateFormat日期时间格式化对象

    val simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm")

 

    // 相当于Java中定义一个静态方法

    def format(date: Date) = simpleDateFormat.format(date)

  }

 

  // main是一个静态方法,所以必须要写在object中

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

    println(DateUtils.format(new Date()))

  }

}

main方法

scala和Java一样,如果要运行一个程序,必须有一个main方法。在Java中main方法是静态的,而在scala中没有静态方法。在scala中,这个main方法必须放在一个单例对象中。

定义main方法

main方法

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

    // 方法体

}

示例

示例说明

  1. 创建一个单例对象,在该单例对象中打印"hello, scala"

参考代码

object Main5 {

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

    println("hello, scala")

  }

}

实现App Trait来定义入口

创建一个object,继承自App Trait(特质),然后将需要编写在main方法中的代码,写在object的构造方法体内。

object 单例对象名 extends App {

    // 方法体

}

示例

示例说明

  1. 继承App特质,来实现一个入口。同样输出"hello, scala"

参考代码

object Main5 extends App {

  println("hello, scala")

}

伴生对象

在Java中,经常会有一些类,同时有实例成员又有静态成员。例如:

public class CustomerService {

 

    private static String SERVICE_NAME = "CustomerService";

 

    public void save() {

        // 保存客户

        System.out.println(SERVICE_NAME + ":保存客户");

    }

 

    public static void main(String[] args) {

        new CustomerService().save();

    }

}

在scala中,要实现类似的效果,可以使用伴生对象来实现。

定义伴生对象

一个class和object具有同样的名字。这个object称为伴生对象,这个class称为伴生类

  1. 伴生对象必须要和伴生类一样的名字
  2. 伴生对象和伴生类在同一个scala源文件中
  3. 伴生对象和伴生类可以互相访问private属性

示例

示例说明

  1. 编写一个CustomerService类,有一个save方法,打印
  2. 服务类名称:保存客户
  3. 编写一个CustomerService伴生对象,定义一个私有变量,用于保存服务类名称
  4. 创建CustomerService对象,调用save方法

参考代码

object _11ObjectDemo {

 

  class CustomerService {

    def save() = {

      println(s"${CustomerService.SERVICE_NAME}:保存客户")

    }

  }

 

  // CustomerService的伴生对象

  object CustomerService {

    private val SERVICE_NAME = "CustomerService"

  }

 

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

    val customerService = new CustomerService()

    customerService.save()

  }

}

private[this]访问权限

如果某个成员的权限设置为private[this],表示只能在当前类中访问。伴生对象也不可以访问

示例

示例说明

  1. 定义一个Person类,包含一个name字段
  2. 定义Person类的伴生对象,定义printPerson方法
  3. 测试伴生对象是否能访问private[this]权限的成员

示例代码

  class Person(private[this] var name:String)

  

  object Person {

    def printPerson(person:Person): Unit = {

      println(person.name)

    }

  }

  

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

    val person = new Person("张三")

    Person.printPerson(person)

  }

上述代码,会编译报错。但移除掉[this]就可以访问了

继承

scala语言是支持面向对象编程的, 可以使用scala来实现继承,通过继承来减少重复代码。

定义语法

  1. 使用extends关键字来实现继承
  2. 可以在子类中定义父类中没有的字段和方法,或者重写父类的方法
  3. 类和单例对象都可以从某个父类继承

语法

class/object 子类 extends 父类 {

    ..

}

示例 | 类继承

  1. 定义一个Person类,再定义一个Student类,继承自Person类

uploading.4e448015.gif转存失败重新上传取消

  1. 创建一个Student类对象实例,并设置name为“张三”
  2. 打印姓名

参考代码

class Person {

  var name = "super"

  def getName = this.name

}

 

class Student extends Person

object Main13 {

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

    val p1 = new Person()

    val p2 = new Student()

    p2.name = "张三"

 

    println(p2.getName)

  }

}

示例 | 单例对象继承

示例说明

  1. 创建一个Student单例对象,让单例对象继承示例1中的Person类
  2. 设置单例对象的名字为"张三",调用Student单例对象的getName方法

class Person {

  var name = "super"

  def getName = this.name

}

 

 

object Student extends Person

object Main13 {

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

    println(Student.getName)

  }

}

override和super

类似于Java语言, 在子类中使用override需要来重写父类的成员,可以使用super来引用父类

用法

  1. 子类要覆盖父类中的一个方法,必须要使用override关键字
  2. 使用override来重写一个val字段
  3. 使用super关键字来访问父类的成员方法

示例

示例说明

  1. 定义一个Person类,包含姓名字段(不可重新赋值)
  2. 获取姓名方法
  3. 定义一个Student类
  4. 重写姓名字段
  5. 重写获取姓名方法,返回"hello, " + 姓名
  6. 创建Student对象示例,调用它的getName方法

参考代码

class Person {

  val name = "super"

  def getName = name

}

 

class Student extends Person {

  // 重写val字段

  override val name: String = "child"

  // 重写getName方法

  override def getName: String = "hello, " + super.getName

}

 

object Main13 {

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

    println(new Student().getName)

  }

}

类型判断

有时候,我们设计的程序,要根据变量的类型来执行对应的逻辑。

uploading.4e448015.gif转存失败重新上传取消 uploading.4e448015.gif转存失败重新上传取消

在scala中,如何来进行类型判断呢?

有两种方式:

  1. isInstanceOf
  2. getClass/classOf

isInstanceOf/asInstanceOf

在Java中,可以使用instanceof关键字来判断类型、以及(类型)object来进行类型转换,在scala中如何实现?

scala中对象提供isInstanceOf和asInstanceOf方法。

  1. isInstanceOf判断对象是否为指定类以及其子类的对象
  2. asInstanceOf将对象转换为指定类型

用法

// 判断对象是否为指定类型

val trueOrFalse:Boolean = 对象.isInstanceOf[类型]

// 将对象转换为指定类型

val 变量 = 对象.asInstanceOf[类型]

示例

示例说明

  1. 定义一个Person类
  2. 定义一个Student类继承自Person类
  3. 创建一个Student类对象
  4. 判断该对象是否为Student类型,如果是,将其转换为Student类型并打印该对象

参考代码

class Person3

class Student3 extends Person3

 

object Main3 {

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

    val s1:Person3 = new Student3

    // 判断s1是否为Student3类型

    if(s1.isInstanceOf[Student3]) {

      // 将s1转换为Student3类型

      val s2 =  s1.asInstanceOf[Student3]

      println(s2)

    }

 

  }

}

getClass和classOf

isInstanceOf 只能判断对象是否为指定类以及其子类的对象,而不能精确的判断出,对象就是指定类的对象。如果要求精确地判断出对象就是指定类的对象,那么就只能使用 getClass 和 classOf 。

用法

  1. p.getClass可以精确获取对象的类型不是父类
  2. classOf[x] 可以指定精确类型
  3. 使用==操作符可以直接比较类型

示例

示例说明

  1. 定义一个Person类
  2. 定义一个Student类继承自Person类
  3. 创建一个Student类对象,并指定它的类型为Person类型
  4. 测试使用isInstance判断该对象是否为Person类型
  5. 测试使用getClass/classOf判断该对象是否为Person类型
  6. 测试使用getClass/classOf判断该对象是否为Student类型

参考代码

class Person4

class Student4 extends Person4

 

object Student4{

  def main(args: Array[String]) {

    val p:Person4=new Student4

    //判断p是否为Person4类的实例

    println(p.isInstanceOf[Person4])//true

    //判断p的类型是否为Person4类

    println(p.getClass == classOf[Person4])//false

 

    //判断p的类型是否为Student4类

    println(p.getClass == classOf[Student4])//true

  }

}

抽象类

和Java语言一样,scala中也可以定义抽象类

定义

如果类的某个成员在当前类中的定义是不包含完整的,它就是一个抽象类

不完整定义有两种情况:

  1. 方法没有方法体(抽象方法
  2. 变量没有初始化(抽象字段

定义抽象类和Java一样,在类前面加上abstract关键字

// 定义抽象类

abstract class 抽象类名 {

  // 定义抽象字段

  val 抽象字段名:类型

  // 定义抽象方法

  def 方法名(参数:参数类型,参数:参数类型...):返回类型

}

抽象方法

示例

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

  • 设计4个类,表示上述图中的继承关系
  • 每一个形状都有自己求面积的方法,但是不同的形状计算面积的方法不同

步骤

  1. 创建一个Shape抽象类,添加一个area抽象方法,用于计算面积
  2. 创建一个Square正方形类,继承自Shape,它有一个边长的主构造器,并实现计算面积方法
  3. 创建一个长方形类,继承自Shape,它有一个长、宽的主构造器,实现计算面积方法
  4. 创建一个圆形类,继承自Shape,它有一个半径的主构造器,并实现计算面积方法
  5. 编写main方法,分别创建正方形、长方形、圆形对象,并打印它们的面积

参考代码

// 创建形状抽象类

abstract class Shape {

  def area:Double

}

 

// 创建正方形类

class Square(var edge:Double /*边长*/) extends Shape {

  // 实现父类计算面积的方法

  override def area: Double = edge * edge

}

 

// 创建长方形类

class Rectangle(var length:Double /*长*/, var width:Double /*宽*/) extends Shape {

  override def area: Double = length * width

}

 

// 创建圆形类

class Cirle(var radius:Double /*半径*/) extends Shape {

  override def area: Double = Math.PI * radius * radius

}

 

object Main6 {

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

    val s1:Shape = new Square(2)

    val s2:Shape = new Rectangle(2,3)

    val s3:Shape = new Cirle(2)

 

    println(s1.area)

    println(s2.area)

    println(s3.area)

  }

}

抽象字段

在scala中,也可以定义抽象的字段。如果一个成员变量是没有初始化,我们就认为它是抽象的。

定义

语法

abstract class 抽象类 {

    val/var 抽象字段:类型

}

示例

示例说明

  1. 创建一个Person抽象类,它有一个String抽象字段WHO_AM_I
  2. 创建一个Student类,继承自Person类,重写WHO_AM_I字段,初始化为学生
  3. 创建一个Policeman类,继承自Person类,重写WHO_AM_I字段,初始化警察
  4. 添加main方法,分别创建Student/Policeman的实例,然后分别打印WHO_AM_I

参考代码

// 定义一个人的抽象类

abstract class Person6 {

  // 没有初始化的val字段就是抽象字段

  val WHO_AM_I:String

}

 

class Student6 extends Person6 {

  override val WHO_AM_I: String = "学生"

}

 

class Policeman6 extends Person6 {

  override val WHO_AM_I: String = "警察"

}

 

object Main6 {

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

    val p1 = new Student6

    val p2 = new Policeman6

 

    println(p1.WHO_AM_I)

    println(p2.WHO_AM_I)

  }

}

匿名内部类

匿名内部类是没有名称的子类,直接用来创建实例对象。Spark的源代码中有大量使用到匿名内部类。

scala中的匿名内部类使用与Java一致。

定义

语法

val/var 变量名 = new 类/抽象类 {

    // 重写方法

}

示例

示例说明

  1. 创建一个Person抽象类,并添加一个sayHello抽象方法
  2. 添加main方法,通过创建匿名内部类的方式来实现Person
  3. 调用匿名内部类对象的sayHello方法

参考代码

abstract class Person7 {

  def sayHello:Unit

}

 

object Main7 {

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

    // 直接用new来创建一个匿名内部类对象

    val p1 = new Person7 {

      override def sayHello: Unit = println("我是一个匿名内部类")

    }

    p1.sayHello

  }

}

特质(trait)

scala中没有Java中的接口(interface),替代的概念是——特质

定义

  • 特质是scala中代码复用的基础单元
  • 它可以将方法和字段定义封装起来,然后添加到类中
  • 与类继承不一样的是,类继承要求每个类都只能继承一个超类,而一个类可以添加任意数量的特质。
  • 特质的定义和抽象类的定义很像,但它是使用trait关键字

语法

定义特质

trait 名称 {

    // 抽象字段

    // 抽象方法

}

继承特质

class 类 extends 特质1 with 特质2 {

    // 字段实现

    // 方法实现

}

  • 使用extends来继承trait(scala不论是类还是特质,都是使用extends关键字)
  • 如果要继承多个trait,则使用with关键字

trait作为接口使用

trait作为接口使用,与java的接口使用方法一样。

示例 | 继承单个trait

 

示例说明

  1. 创建一个Logger特质,添加一个接受一个String类型参数的log抽象方法
  2. 创建一个ConsoleLogger类,继承Logger特质,实现log方法,打印消息
  3. 添加main方法,创建ConsoleLogger对象,调用log方法

参考代码

  trait Logger {

    // 抽象方法

    def log(message:String)

  }

 

  class ConsoleLogger extends Logger {

    override def log(message: String): Unit = println("控制台日志:" + message)

  }

 

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

    val logger = new ConsoleLogger

    logger.log("这是一条日志")

  }

示例 | 继承多个trait

 

示例说明

  1. 创建一个MessageSender特质,添加send方法
  2. 创建一个MessageReceiver特质,添加receive方法
  3. 创建一个MessageWorker实现这两个特质
  4. 在main中调用,分别调用send方法、receive方法

参考代码

trait MessageSender {

    def send(msg:String)

}

 

trait MessageReceive {

    def receive():String

}

 

class MessageWorker extends MessageSender with MessageReceive {

    override def send(msg: String): Unit = println(s"发送消息:${msg}")

 

    override def receive(): String = "你好!我叫一个好人!"

}

 

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

    val worker = new MessageWorker

    worker.send("hello")

    println(worker.receive())

}

示例 | object继承trait

 

示例说明

  1. 创建一个Logger特质,添加一个log抽象方法
  2. 创建一个ConsoleLogger的object,实现LoggerForObject特质,实现log方法,打印消息
  3. 编写main方法,调用ConsoleLogger的log方法

参考代码

trait Logger {

    def log(message:String)

}

 

object ConsoleLogger extends Logger {

    override def log(message: String): Unit = println("控制台消息:" + message)

}

 

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

    ConsoleLogger.log("程序退出!")

}

特质 | 定义具体的方法

和类一样,trait中还可以定义具体的方法

示例

示例说明

  1. 定义一个Logger特质,添加log实现方法
  2. 定义一个UserService类,实现Logger特质
  1. 添加add方法,打印"添加用户"

3. 添加main方法

  1. 创建UserService对象实例
  2. 调用add方法

参考代码

trait LoggerDetail {

  // 在trait中定义具体方法

  def log(msg:String) = println(msg)

}

 

class UserService extends LoggerDetail {

  def add() = log("添加用户")

}

 

object MethodInTrait {

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

    val userService = new UserService

    userService.add()

  }

}

trait中定义具体的字段和抽象的字段

定义

  1. 在trait中可以定义具体字段和抽象字段
  2. 继承trait的子类自动拥有trait中定义的字段
  3. 字段直接被添加到子类中

示例

示例说明

通过trait来实现一个日志输出工具,该日志工具可以自动添加日志的日期

步骤

  1. 创建Logger特质
  1. 定义一个SimpleDateFormat字段,用来格式化日期(显示到时间)
  2. 定义一个TYPE抽象字段,用于定义输出的信息
  3. 创建一个log抽象方法,用于输出日志
  1. 创建ConsoleLogger类,实现TYPE抽象字段和log方法
  2. 添加main方法
  1. 创建ConsoleLogger类对象
  2. 调用log方法

参考代码

  trait Logger {

    val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm")

    def log(msg:String)

  }

 

  class ConsoleLogger extends Logger {

    override def log(msg: String): Unit = {

      val info = s"${sdf.format(new Date())}:控制台消息:${msg}"

      println(info)

    }

  }

 

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

    val logger = new ConsoleLogger()

    logger.log("NullPointerException")

  }

使用trait实现模板模式

要实现以下需求:

  1. 实现一个输出日志的功能
  2. 目前要求输出到控制台
  3. 将来可能会输出到文件、输出到Redis、或者更多的需求

如何实现将来不修改之前的代码,来扩展现有功能呢?

uploading.4e448015.gif转存失败重新上传取消uploading.4e448015.gif转存失败重新上传取消

定义

在一个特质中,具体方法依赖于抽象方法,而抽象方法可以放到继承trait的子类中实现,这种设计方式也称为模板模式

uploading.4e448015.gif转存失败重新上传取消 uploading.4e448015.gif转存失败重新上传取消

在scala中,trait是可以定义抽象方法,也可以定义具体方法的

  1. trait中定义了一个抽象方法
  2. trait中定义了其他的几个具体方法,会调用抽象方法
  3. 其他实现类可以来实现抽象方法
  4. 真正调用trait中具体方法的时候,其实会调用实现类的抽象方法实现

示例

示例说明

  1. 编写一个日志输出工具,分别有info、warn、error三个级别的日志输出
  2. 日志输出的方式要求设计为可扩展的,例如:可以输出到控制台、将来也可以扩展输出到文件、数据库等

实现步骤

  1. 添加一个Logger特质
  1. 添加一个log抽象方法
  2. 添加一个info、warn、error具体方法,这几个方法调用log抽象方法
  1. 创建ConsoleLogger类,实现Logger特质
  2. 添加main方法
  1. 创建ConsoleLogger类对象
  2. 分别调用info、warn、error方法输出日志

参考代码

  trait Logger {

    def log(msg:String)

    def info(msg:String) = log("INFO:" + msg)

    def warn(msg:String) = log("WARN:" + msg)

    def error(msg:String) = log("ERROR:" + msg)

  }

 

  class ConsoleLogger extends Logger {

    override def log(msg: String): Unit = {

      println(msg)

    }

  }

 

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

    val logger = new ConsoleLogger

    logger.info("信息日志")

    logger.warn("警告日志")

    logger.error("错误日志")

  }

对象混入trait

scala中可以将trait混入到对象中,就是将trait中定义的方法、字段添加到一个对象中

定义

语法

val/var 对象名 = new 类 with 特质

示例

  • 给一个对象添加一些额外的行为

步骤

  1. 创建一个Logger特质
  1. 添加一个log实现方法,打印参数
  1. 创建一个UserService类
  2. 添加main方法
  1. 创建UserService对象,混入Logger特质
  2. 调用log方法

参考代码

  trait Logger {

    def log(msg:String) = println(msg)

  }

 

  class UserService

 

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

    val service = new UserService with Logger

    service.log("混入的方法")

  }

trait实现调用链模式

我们如果要开发一个支付功能,往往需要执行一系列的验证才能完成支付。例如:

  1. 进行支付签名校验
  2. 数据合法性校验
  3. ...

如果将来因为第三方接口支付的调整,需要增加更多的校验规则,此时如何不修改之前的校验代码,来实现扩展呢?

责任链模式

uploading.4e448015.gif转存失败重新上传取消 uploading.4e448015.gif转存失败重新上传取消

trait调用链

uploading.4e448015.gif转存失败重新上传取消 uploading.4e448015.gif转存失败重新上传取消

类继承了多个trait后,可以依次调用多个trait中的同一个方法,只要让多个trait中的同一个方法在最后都依次执行super关键字即可。类中调用多个tait中都有这个方法时,首先会从最右边的trait方法开始执行,然后依次往左执行,形成一个调用链条。

示例

实现一个模拟支付过程的调用链

uploading.4e448015.gif转存失败重新上传取消 uploading.4e448015.gif转存失败重新上传取消

步骤

  1. 定义一个HandlerTrait特质
  1. 定义一个具体的handler方法,打印"处理数据..."
  1. 定义一个DataValidHandlerTrait,继承HandlerTrait特质
  1. 重写handler方法,打印"验证数据"
  2. 调用父特质的handler方法
  1. 定义一个SignatureValidHandlerTrait,继承HandlerTrait特质
  1. 重写Handler方法
  2. 打印"检查签名"
  3. 调用父特质的handler方法
  1. 创建一个PaymentService类
  1. 继承DataValidHandlerTrait
  2. 继承SignatureValidHandlerTrait
  3. 定义pay方法
      • 打印"准备支付"
      • 调用父特质的handler方法
  1. 添加main方法
  1. 创建PaymentService对象实例
  2. 调用pay方法

参考代码

trait HandlerTrait {
   def handle(data:String) = println("处理数据...")
}

trait DataValidHanlderTrait extends HandlerTrait {
   override def handle(data:String): Unit = {
       println("验证数据...")
       super.handle(data)
  }
}

trait SignatureValidHandlerTrait extends HandlerTrait {
   override def handle(data: String): Unit = {
       println("校验签名...")
       super.handle(data)
  }
}

class PayService extends DataValidHanlderTrait with SignatureValidHandlerTrait {
   override def handle(data: String): Unit = {
       println("准备支付...")
       super.handle(data)
  }
}

def main(args: Array[String]): Unit = {
   val service = new PayService
   service.handle("支付参数")
}

// 程序运行输出如下:
// 准备支付...
// 检查签名...
// 验证数据...
// 处理数据...

trait继承class

定义

trait也可以继承class的。特质会将class中的成员都继承下来。

示例

示例说明

  1. 定义一个特质,继承自一个class

步骤

  1. 创建一个MyUtils类,定义printMsg方法
  2. 创建一个Logger特质,继承自MyUtils,定义log方法
  3. 创建一个Person类,添加name字段
  1. 继承Logger特质
  2. 实现sayHello方法,调用log方法
  1. 添加main方法,创建一个Person对象,调用sayHello方法

参考代码

class MyUtil {
   def printMsg(msg:String) = println(msg)
}

trait Logger extends MyUtil {
   def log(msg:String) = printMsg("Logger:" + msg)
}

class Person extends Logger {
   def sayHello() = log("你好")
}

def main(args: Array[String]): Unit = {
   val person = new Person
   person.sayHello()
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值