目录
一 Scala入门
1.1 概述
1.1.1 为什么学习Scala
1.1.2 Scala发展历史
1.1.3 Scala和Java关系
1.1.4 Scala语言特点
1.2 scala 运行环境准备
二 变量和数据类型
2.1 注释
1)基本语法
2)案例实操
3)代码规范
2.2 变量和常量(重点)
0)回顾:Java变量和常量语法
1)基本语法
2)案例实操
2.3 标识符的命名规范
1)命名规则
2)案例实操
2.4 字符串输出
1)基本语法
2)案例实操
2.5 键盘输入
1)基本语法
2)案例实操
2.6 数据类型(重点)
1)java的数据类型
2)Scala数据类型
3)Scala数据类型层次结构
2.7 整数类型(Byte、Short、Int、Long)
1)整型分类
2)案例实操
2.8 浮点类型(Float、Double)
1)浮点型分类
2)案例实操
2.9 字符类型(Char)
1)基本说明
2)案例实操
2.10 布尔类型:Boolean
1)基本说明
2)案例实操
2.11 Unit类型、Null类型和Nothing类型(重点)
1)基本说明
2)案例实操
2.12 类型转换
2.12.1 数值类型自动转换
1)基本说明
2)案例实操
2.12.2 强制类型转换
1)基本说明
2)案例实操
2.12.3 数值类型和String类型间转换
1)基本说明
2)案例实操
扩展面试题
第3章 运算符
3.1 算术运算符
1)基本语法
2)案例实操
3.2 关系运算符(比较运算符)
1)基本语法
2)案例实操
(1)需求1:应用各关系运算符
(2)需求2:Java和Scala中关于==的区别
3.3 逻辑运算符
1)基本语法
2)案例实操
3.4 赋值运算符
1)基本语法
2)案例实操
3.5 位运算符
1)基本语法
2)案例实操
3.6 Scala运算符本质
第4章 流程控制
4.1 分支控制if-else
4.1.1 单分支
1)基本语法
4.1.2 双分支
1)基本语法
2)案例实操
4.2 嵌套分支
1)基本语法
4.3 Switch分支结构
4.4 For循环控制
4.4.1 范围数据循环(To)
1)基本语法
2)案例实操
4.4.2 范围数据循环(Until)
1)基本语法
2)案例实操
4.4.3 循环守卫
1)基本语法
2)案例实操
4.4.4 循环步长
1)基本语法
2)案例实操
4.4.5 嵌套循环
1)基本语法
4.4.6 引入变量
1)基本语法
4.4.7 循环返回值
1)基本语法
2)案例实操
4.4.8 倒序打印
1)说明
2)案例实操:
4.5 While和do..While循环控制
4.5.1 While循环控制
4.5.2 do..while循环控制
1)基本语法
4.6 循环中断
1)基本说明
2)案例实操
第5章 函数式编程
5.1 函数基础
5.1.1 函数基本语法
1)基本语法
5.1.2 函数和方法的区别
1)核心概念
2)案例实操
5.1.3 函数定义
1)函数定义
2)案例实操
5.1.4 函数参数
1)案例实操
5.1.5 函数至简原则(重点)
1)至简原则细节
2)案例实操
5.2 函数高级
5.2.1 高阶函数
1)函数可以作为值进行传递
2)函数可以作为参数进行传递
3)函数可以作为函数返回值返回
5.2.2 匿名函数
1)说明
2)案例实操
5.2.3 函数柯里化&闭包
1)说明
2)案例实操
5.2.4 递归
1)说明
2)案例实操
5.2.5 控制抽象
1)值调用:把计算后的值传递过去
2)名调用:把代码传递过去
5.2.6 惰性函数
1)说明
2)案例实操
第6章 面向对象
6.1 Scala包
6.1.1 包的命名
6.1.2 包说明(包语句)
1)说明
2)案例实操
6.1.3 包对象
1)定义
2)说明
6.1.4 导包说明
6.2 类和对象
6.2.1 定义类
0)回顾:Java中的类
1)基本语法
2)案例实操
6.2.2 属性
1)基本语法
2)案例实操
6.3 封装
6.3.1 访问权限
1)说明
2)案例实操
6.3.2 方法
1)基本语法
2)案例实操
6.3.3 构造器
1)基本语法
2)案例实操
6.3.4 构造器参数
1)说明
2)案例实操
6.4 继承
6.5 抽象属性和抽象方法
6.5.1 抽象属性和抽象方法
1)基本语法
2)继承&重写
6.5.2 匿名子类
6.6 单例对象(伴生对象)
6.6.1 单例对象语法
1)基本语法
2)说明
3)案例实操
6.6.2 apply方法
1)说明
2)案例实操
6.7 特质(Trait)
6.7.1 特质声明
6.7.2 特质基本语法
6.7.3 特质叠加
6.7.5 特质自身类型
6.7.6特质和抽象类的区别
6.8 扩展
6.8.1 类型检查和转换
6.8.2 枚举类和应用类
6.8.3 Type定义新类型
一 Scala入门
1.1 概述
Scala将面向对象和函数式编程结合成一种简洁的高级语言。Scala的静态类型有助于避免复杂应用程序中的错误,它的JVM和JavaScript运行时让你可以轻松地访问庞大的库生态系统来构建高性能系统。
1.1.1 为什么学习Scala
1) Spark-新一代内存级大数据计算框架,是大数据的重要内容。
2) Spark就是使用Scala编写的。因此为了更好的学习Spark, 需要掌握Scala这门语言。
3) Spark的兴起,带动Scala语言的发展!
1.1.2 Scala发展历史
联邦理工学院的马丁•奥德斯基 (Martin Odersky)于2001年开始设计Scala。
马丁•奥德斯基是编译器及编程的狂热爱好者,长时间的编程之后,希望发明一种语言,能够让-写程序这样的基础工作变得高效,简单。所以当接触到JAVA语言后,对JAVA这门便携式,运行在网络,且存在垃圾回收的语言产生了极大的兴趣,所以决定将丽数式编程语言的特点融合到JAVA中,由此发明了两种语言 (Pizza & Scala)。
Pizza和Scala极大地推动了Java编程语言的发展。
•JDKS.0的泛型、增强for循环、自动类型转换等,都是从Pizza引1人的新特性。
• JDK8.0 的类型推断、Lambda表达式就是从Scala引人的特性。
JDK5.0和JDK8.0的编辑器就是马丁•奥德斯基写的,因此马丁•奥德斯基一个人的战斗力抵得上一个Java开发团队。
1.1.3 Scala和Java关系
一般来说,学Scala的人,都会Java,而Scala是基于Java的,因此我们需要将Scala和Java以及JVM之间的关系搞清楚,否则学习Scala你会蒙圈。
1.1.4 Scala语言特点
Scala是一门以Java虛拟机 (JVM)为运行环境并将面向对象和西数式编程的最佳特性结合在一起的静态类型编程语言(静态语言需要提前编译的如:Java、c、ct等,动态语言如:js) 。
1)Scala是一门多范式的编程语言,Scala支特面向对象和西数式编程。(多范式,就是多种编程方法的意思。有面向过程、面向对象、泛型、函数式四种程序设计方法。)
2)Scala源代码 (.scala) 会被编译成Java字节码(.class),然后运行于JVM之 上,并可以调用现有的Java类库,实现两种语言的无缝对接。
3) Scala单作为一门语言来看,非常的简洁高效。
4) Scala在设计时,马丁•奥德斯基是参考了Java的设计思想,可以说Scalla是源于Java,同时马丁•奥德斯基也加人了自己的恩想,将两数式编程语言的特点融合到JAVA中,因此,对于学习过Java的同学,只要在学习Scala的过程中,搞清楚Scala和Java相同点和不同点,就可以快速的掌握Scala这门语言。
1.2 scala 运行环境准备
scala 安装属于是傻瓜式安装了,这里提供了安装方法和安装 idea 插件的方法
window 的朋友,这虽然是 mac 的安装教程,但也就安装包不一样,其它没什么很大差别
mac安装 scala 详细教程(包含在 idea 上使用,以及scala插件安装)_超爱慢的博客-CSDN博客
二 变量和数据类型
2.1 注释
Scala注释使用和Java完全一样。
注释是一个程序员必须要具有的良好编程习惯。将自己的思想通过注释先整理出来,再用代码去体现。
1)基本语法
(1)单行注释://
(2)多行注释:/* */
(3)文档注释:/**
*
*/
2)案例实操
package cn.olo
object Test01 {
/**
* 重点在于养成写注释的好习惯
* @param args
*/
def main(args: Array[String]): Unit = {
// 单行注释 : //
// 多行注释
/*
*/
// 文档注释
/**
*
*
*/
}
}
3)代码规范
(1)使用一次tab操作,实现缩进,默认整体向右边移动,用shift+tab整体向左移
(2)或者使用ctrl + alt + L来进行格式化
(3)运算符两边习惯性各加一个空格。比如:2 + 4 * 5。
(4)一行最长不超过80个字符,超过的请使用换行展示,尽量保持格式优雅
2.2 变量和常量(重点)
常量:在程序执行的过程中,其值不会被改变的变量
0)回顾:Java变量和常量语法
变量类型 变量名称 = 初始值 int a = 10
final常量类型 常量名称 = 初始值 final int b = 20
注意:java中的final如果加static才会被存放在常量池中,否则作为不可修改的变量存在堆栈中。
1)基本语法
var 变量名 [: 变量类型] = 初始值 var i:Int = 10
val 常量名 [: 常量类型] = 初始值 val j:Int = 20
注意:能用常量的地方不用变量
2)案例实操
(1)声明变量时,类型可以省略,编译器自动推导,即类型推导
(2)类型确定后,就不能修改,说明Scala是强数据类型语言。
(3)变量声明时,必须要有初始值
(4)在声明/定义一个变量时,可以使用var或者val来修饰,var修饰的变量可改变,val修饰的变量不可改。
(5)var修饰的对象引用可以改变,val修饰的对象则不可改变,但对象的状态(值)却是可以改变的。(比如:自定义对象、数组、集合等等)
package cn.olo
object Test02 {
def main(args: Array[String]): Unit = {
// 声明变量和常量
val a: Int = 10
var b: Int = 20
// 常量值无法修改
// a = 20
b = 30
// (1)声明变量时,类型可以省略,编译器自动推导,即类型推导
val c = 30
// (2)类型确定后,就不能修改,说明Scala是强数据类型语言。
// b = "30"
// (3)变量声明时,必须要有初始值
val d: Int = 0
// var d1:Int = _
val test02_Var = new Test02_Var()
println(test02_Var.i)
// (4)var修饰的对象引用可以改变,val修饰的对象则不可改变,
// 但对象的状态(值)却是可以改变的。(比如:自定义对象、数组、集合等等)
val person0 = new Person02()
var person1 = new Person02()
// 引用数据类型的常量和变量能否替换成别的对象
// var 可以修改引用数据类型的地址值 val不行
person1 = new Person02()
// 引用数据类型中的属性值能否发生变化 取决于内部的属性在定义的时候是var还是val
// person0.name = "lisi"
person0.age = 11
}
}
class Test02_Var {
// scala中类的属性 如果是var变量也能使用默认值 但是必须要有等号
var i: Int = _
}
class Person02 {
val name: String = "zhangsan"
var age: Int = 10
}
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 = "" // ok
var Hello12: String = "" // ok
var 1hello: String = "" // error 数字不能开头
var h-b: String = "" // error 不能用-
var x h: String = "" // error 不能有空格
var h_4: String = "" // ok
var _ab: String = "" // ok
var Int: String = "" // ok 因为在Scala中Int是预定义的字符,不是关键字,但不推荐
var _: String = "hello" // ok 单独一个下划线不可以作为标识符,因为_被认为是一个方法
println(_) // IDEA不报错,运行报错
//(2)以操作符开头,且只包含操作符(+ - * / # !等)
var +*-/#! : String = "" // ok
var +*-/#!1 : String = "" // error 以操作符开头,必须都是操作符
//(3)用反引号`....`包括的任意字符串,即使是Scala关键字(39个)也可以
var if : String = "" // error 不能用关键字
var `if` : String = "" // ok 用反引号`....`包括的任意字符串,包括关键字
}
}
注意:正常使用不能只遵守规则,必须按照规范来写,即使用大小驼峰命名法。
2.4 字符串输出
1)基本语法
(1)字符串,通过+号连接
(2)重复字符串拼接
(3)printf用法:字符串,通过%传值。
(4)字符串模板(插值字符串):通过$获取变量值
2)案例实操
package cn.olo
object Test03 {
def main(args: Array[String]): Unit = {
// (1)字符串,通过+号连接
System.out.println()
println("hello" + "world")
// (2)重复字符串拼接
println("linhailinhai" * 200)
// (3)printf用法:字符串,通过%传值。
printf("name: %s age: %d\n", "linhai", 8)
// (4)字符串模板(插值字符串):通过$获取变量值
val name = "linhai"
val age = 8
val s1 = s"name: $name,age:${age}"
println(s1)
val s2 = s"name: ${name + 1},age:${age + 2}"
println(s2)
// (5)长字符串 原始字符串
println("我" +
"是" +
"一首" +
"诗")
//多行字符串,在Scala中,利用三个双引号包围多行字符串就可以实现。
// 输入的内容,带有空格、\t之类,导致每一行的开始位置不能整洁对齐。
//应用scala的stripMargin方法,在scala中stripMargin默认是“|”作为连接符,
// 在多行换行的行头前面加一个“|”符号即可。
println(
"""我
|是
|一首
|诗
|""".stripMargin)
"""
|select id,
| age
|from user_info
|""".stripMargin
s"""
|${name}
|${age}
|""".stripMargin
}
}
2.5 键盘输入
在编程中,需要接收用户输入的数据,就可以使用键盘输入语句来获取。
1)基本语法
StdIn.readLine()、StdIn.readShort()、StdIn.readDouble()
2)案例实操
需求:可以从控制台接收用户信息,【姓名,年龄】。
package cn.olo
import scala.io.StdIn
object Test04 {
def main(args: Array[String]): Unit = {
// 交互系统 输入姓名和年龄
println("欢迎来到your home!")
println("请输入您的姓名:")
val name: String = StdIn.readLine()
println("请输入您的年龄:")
val age: Int = StdIn.readInt()
println(s"欢迎${age}岁的${name}同学来our home玩")
}
}
2.6 数据类型(重点)
1)java的数据类型
Java基本类型:char、 byte 、short、int. long. float.double 、boolean
Java引用类型:(对象类型)
由于Java有基本类型,而且基本类型不是真正意义的对象,即使后面产生了基本类型的包装类,但是仍然存在基本数据类型,所以Java语言并不是真正意思的面向对象。
Java基本类型的包装类:Character、Byte、Short、Integer、Long. Float 、Double、Boolean
注意:Java中基本类型和引用类型没有共同的祖先。
2)Scala数据类型
Scala与Java有着相同的原始数据类型
3)Scala数据类型层次结构
- Any:所有类型的超类(顶级类型)
- AnyVal:表示值类型的超类
- AnyRef:表示引用类型的超类,对应java.lang.Object
- Unit:表示无值,类似Java中的void
- Nothing:所有类型的子类
- Null:表示null或空引用
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)编译器对于常量值的计算 能够直接使用结果进行编译,但是如果是变量值 编译器是不知道变量的值的 所以判断不能将大类型的值赋值给小的类型
(3)Scala程序中变量常声明为Int型,除非不足以表示大数,才使用Long
object Test05_Type {
def main(args: Array[String]): Unit = {
// 任何代码都会被当做代码块执行 最终将最后一行代码的返回值 返回
val i: Int = {
9
2 + 1
10
}
val unit: Unit = println(i)
// val value: Nothing = throw new RuntimeException()
// 整数类型
val i1 = 1
val l = 1L
// (1)Scala各整数类型有固定的表示范围和字段长度,不受具体操作的影响,以保证Scala程序的可移植性。
val b1: Byte = 2
// val b0: Byte = 128
val b2: Byte = 1 + 1
println(b2)
val i2 = 1
//(2)编译器对于常量值的计算 能够直接使用结果进行编译
// 但是如果是变量值 编译器是不知道变量的值的 所以判断不能将大类型的值赋值给小的类型
// val b3: Byte = i2 + 1
// println(b3)
// (3)Scala程序中变量常声明为Int型,除非不足以表示大数,才使用Long
val l1 = 2200000000L
}
}
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 Test05_Type{
def main(args: Array[String]): Unit = {
// 浮点数介绍
// 默认使用double
val d: Double = 3.14
// 如果使用float 在末尾添加f
val fl = 3.14f
// 浮点数计算有误差
println(0.1 / 3.3)
}
}
//运行的结果
//0.030303030303030307
2.9 字符类型(Char)
1)基本说明
字符类型可以表示单个字符,字符类型是Char。
2)案例实操
(1)字符常量是用单引号 ' ' 括起来的单个字符。
(2)\t :一个制表位,实现对齐的功能
(3)\n :换行符
(4)\\ :表示\
(5)\" :表示"
object Test05_Type{
def main(args: Array[String]): Unit = {
// (1)字符常量是用单引号 ' ' 括起来的单个字符。
val c1: Char = 'a'
val c2: Char = 65535
// (2)\t :一个制表位,实现对齐的功能
val c3: Char = '\t'
// (3)\n :换行符
val c4: Char = '\n'
println(c3 + 0)
println(c4 + 0)
// (4)\\ :表示\
val c5: Char = '\\'
println(c5 + 0)
// (5)\" :表示"
val c6: Char = '\"'
println(c6 + 0)
}
}
2.10 布尔类型:Boolean
1)基本说明
(1)布尔类型也叫Boolean类型,Booolean类型数据只允许取值true和false
(2)boolean类型占1个字节。
2)案例实操
object Test05_Type{
def main(args: Array[String]): Unit = {
val bo1: Boolean = true
val bo2: Boolean = false
}
}
2.11 Unit类型、Null类型和Nothing类型(重点)
1)基本说明
数据类型 | 描述 |
Unit | 表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。 |
Null | null , Null 类型只有一个实例值null |
Nothing | Nothing类型在Scala的类层级最低端;它是任何其他类型的子类型。 当一个函数,我们确定没有正常的返回值,可以用Nothing来指定返回类型,这样有一个好处,就是我们可以把返回的值(异常)赋给其它的函数或者变量(兼容性) |
2)案例实操
(1)Unit类型用来标识过程,也就是没有明确返回值的函数。
由此可见,Unit类似于Java里的void。Unit只有一个实例——( ),这个实例也没有实质意义
object Test05_Type{
def main(args: Array[String]): Unit = {
// unit
val unit1: Unit = {
10
println("1")
}
println(unit1)
// 如果标记对象的类型是unit的话 后面有返回值也没法接收
// unit虽然是数值类型 但是可以接收引用数据类型 因为都是表示不接收返回值
val i3: Unit = "aa"
println(i3)
}
}
(2)Null类只有一个实例对象,Null类似于Java中的null引用。Null可以赋值给任意引用类型(AnyRef),但是不能赋值给值类型(AnyVal)
object Test05_Type {
def main(args: Array[String]): Unit = {
// scala当中使用的字符串就是java中的string
val aa: String = "aa"
// null
var aa1: String = "aa"
aa1 = "bb"
aa1 = null
if (aa1 != null) {
val strings: Array[String] = aa1.split(",")
}
// 值类型不能等于null,idea不会识别报错 编译器会报错
var i4 = 10
// i4 = null
}
}
(3)Nothing,可以作为没有正常返回值的方法的返回类型,非常直观的告诉你这个方法不会正常返回,而且由于Nothing是其他任意类型的子类,他还能跟要求返回值的方法兼容。
object Test05_Type {
def main(args: Array[String]): Unit = {
val value: Nothing = {
println("hello")
1 + 1
throw new RuntimeException()
}
}
}
2.12 类型转换
2.12.1 数值类型自动转换
当Scala程序在进行赋值或者运算时,精度小的类型自动转换为精度大的数值类型,这个就是自动类型转换(隐式转换)。数据类型按精度(容量)大小排序为:
1)基本说明
(1)自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度大的那种数据类型,然后再进行计算。
(2)把精度大的数值类型赋值给精度小的数值类型时,就会报错,反之就会进行自动类型转换。
(3)(byte,short)和char之间不会相互自动转换。
(4)byte,short,char他们三者可以计算,在计算时首先转换为int类型
2)案例实操
object Test06_TypeCast {
def main(args: Array[String]): Unit = {
// (1)自动提升原则:有多种类型的数据混合运算时,
// 系统首先自动将所有数据转换成精度大的那种数据类型,然后再进行计算。
val fl: Float = 1 + 1L + 3.14f
val d: Double = 1 + 1L + 3.14f + 3.14
// (2)把精度大的数值类型赋值给精度小的数值类型时,就会报错,反之就会进行自动类型转换。
val i = 10
val b: Double = i
// (3)(byte,short)和char之间不会相互自动转换。
// 因为byte和short是有符号的数值,而char是无符号的
val b1: Byte = 10
val c1: Char = 20
// (4)byte,short,char他们三者可以计算,在计算时首先转换为int类型。
// 也就是说 byte+byte 返回的是一个 int 类型
val b2: Byte = 20
// val i1: Byte = b1 + b2
val i1: Int = 1100000000
val i2: Int = 1200000000
// 超出范围的int值计算会造成结果错误
val i3: Int = i1 + i2
println(i3)
}
}
注意:Scala还提供了非常强大的隐式转换机制(隐式函数,隐式类等),我们放在高级部分专门用一个章节来讲解。
2.12.2 强制类型转换
1)基本说明
自动类型转换的逆过程,将精度大的数值类型转换为精度小的数值类型。使用时要加上强制转函数,但可能造成精度降低或溢出,格外要注意。
Java : int num = (int)2.5
Scala : var num : Int = 2.7.toInt
2)案例实操
(1)将数据由高精度转换为低精度,就需要使用到强制转换
(2)强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级
object Test06_TypeCast {
def main(args: Array[String]): Unit = {
// 强制类型转换
val d1 = 2.999
// (1)将数据由高精度转换为低精度,就需要使用到强制转换
println((d1 + 0.5).toInt)
// (2)强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级
println((10 * 3.5 + 6 * 1.5).toInt)
}
}
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 Test06_TypeCast {
def main(args: Array[String]): Unit = {
// (1)基本类型转String类型(语法:将基本类型的值+"" 即可)
val string: String = 10.0.toString
println(string)
val str: String = 1 + ""
// (2)String类型转基本数值类型(语法:s1.toInt、s1.toFloat、s1.toDouble、s1.toByte、s1.toLong、s1.toShort)
val double: Double = "3.14".toDouble
println(double + 1)
println(double.toInt)
// 不能直接将小数类型的字符串转换为整数 需要先转换为double再转换int
// println("3.14".toInt)
// 标记为f的float数能够识别
// println("12.6f".toFloat)
}
}
扩展面试题
object Test06_TypeCast {
def main(args: Array[String]): Unit = {
// 将int值130强转为byte 值为多少
// 0000 0000 ..16.. 1000 0010 => 表示int的130
val i4 = 130
// 1000 0010
println(i4.toByte)
//输出结果是-126
//其实也好理解因为数值超过了其范围,溢出,究其原理的话可能得讲到计算机原理里的补码内容了
//有兴趣的朋友可以自行去研究一下
}
}
第3章 运算符
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 Test01_Operation{
def main(args: Array[String]): Unit = {
//(1)对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。
var r1: Int = 10 / 3 // 3
println("r1=" + r1)
var r2: Double = 10 / 3 // 3.0
println("r2=" + r2)
var r3: Double = 10.0 / 3 // 3.3333
println("r3=" + r3)
println("r3=" + r3.formatted("%.2f")) // 含义:保留小数点2位,使用四舍五入
//(2)对一个数取模a%b,和Java的取模规则一样。
var r4 = 10 % 3 // 1
println("r4=" + r4)
}
}
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 Test01_Operation{
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 static void main(String[] args) {
String s1 = "abc";
String s2 = new String("abc");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
输出结果:
false
true
Scala:==更加类似于Java中的equals,参照jd工具
def main(args: Array[String]): Unit = {
val s1 = "abc"
val s2 = new String("abc")
println(s1 == s2)
println(s1.eq(s2))
}
输出结果:
true
false
java 和 scala 在这个==上是反过来的
3.3 逻辑运算符
1)基本语法
用于连接多个条件(一般来讲就是关系表达式),最终的结果也是一个Boolean值。
假定:变量A为true,B为false
运算符 | 描述 | 实例 |
&& | 逻辑与 | (A && B) 运算结果为 false |
|| | 逻辑或 | (A || B) 运算结果为 true |
! | 逻辑非 | !(A && B) 运算结果为 true |
2)案例实操
object Test01_Operation {
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
}
}
// 扩展避免逻辑与空指针异常
def isNotEmpty(s : String):Boolean={
//如果按位与,s为空,会发生空指针
return s!=null && !"".equals(s.trim())
}
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 Test01_Operation {
def main(args: Array[String]): Unit = {
var r1 = 10
r1 += 1 // 没有++
r1 -= 2 // 没有--
}
}
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 Test01_Operation {
def main(args: Array[String]): Unit = {
// 测试:1000 << 1 =>10000
var n1 :Int =8
n1 = n1 << 1
println(n1)
}
}
3.6 Scala运算符本质
在Scala中其实是没有运算符的,所有运算符都是方法。
1)当调用对象的方法时,点.可以省略
2)如果函数参数只有一个,或者没有参数,()可以省略
object Test01_Operation {
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章 流程控制
4.1 分支控制if-else
让程序有选择的的执行,分支控制有三种:单分支、双分支、多分支
4.1.1 单分支
1)基本语法
if (条件表达式) {
执行代码块
}
说明:当条件表达式为ture时,就会执行{ }的代码。
4.1.2 双分支
1)基本语法
if (条件表达式1) {
执行代码块1
}
else if (条件表达式2) {
执行代码块2
}
……
else {
执行代码块n
}
2)案例实操
(1)需求1:需求:输入年龄,如果年龄小于18岁,则输出“童年”。如果年龄大于等于18且小于等于60,则输出“中年”,否则,输出“老年”。
object Test01_If {
def main(args: Array[String]): Unit = {
println("input age")
var age = StdIn.readInt()
if (age < 18){
println("童年")
}else if(age>=18 && age<60){
println("中年")
}else{
println("老年")
}
}
}
(2)需求2:Scala中if else表达式其实是有返回值的,具体返回值取决于满足条件的代码体的最后一行内容,用一个变量或常量去接受返回值。
object Test01_If {
def main(args: Array[String]): Unit = {
println("input age")
var age = StdIn.readInt()
val res :String = if (age < 18){
"童年"
}else if(age>=18 && age<60){
"中年"
}else{
"老年"
}
println(res)
}
}
(3)需求3:Scala中返回值类型不一致,取它们共同的祖先类型。
object Test01_If {
def main(args: Array[String]): Unit = {
println("input age")
var age = StdIn.readInt()
val res:Any = if (age < 18){
"童年"
}else if(age>=18 && age<60){
"中年"
}else{
100
}
println(res)
}
}
(4)需求4:Java中的三元运算符可以用if else实现
如果大括号{}内的逻辑代码只有一行,大括号可以省略。如果省略大括号,if只对最近的一行逻辑代码起作用。
object Test01_If {
def main(args: Array[String]): Unit = {
// Java
// int result = flag?1:0
// Scala
println("input age")
var age = StdIn.readInt()
val res:Any = if (age < 18) "童年" else "成年"
"不起作用"
println(res)
}
}
4.2 嵌套分支
在一个分支结构中又完整的嵌套了另一个完整的分支结构,里面的分支的结构称为内层。分支外面的分支结构称为外层分支。嵌套分支不要超过3层。
1)基本语法
if(){
if(){
}else{
}
}
4.3 Switch分支结构
在Scala中没有Switch,而是使用模式匹配来处理,具体章节在第八章哈。
模式匹配涉及到的知识点较为综合,因此我们放在后面讲解。
4.4 For循环控制
Scala也为for循环这一常见的控制结构提供了非常多的特性,这些for循环的特性被称为for推导式或for表达式。
4.4.1 范围数据循环(To)
1)基本语法
for(i <- 1 to 3){
print(i + " ")
}
println()
(1)i 表示循环的变量,<- 规定to
(2)i 将会从 1-3 循环,前后闭合
2)案例实操
需求:输出5句 "jojo——欧拉欧拉"
object TestFor {
def main(args: Array[String]): Unit = {
for(i <- 1 to 5){
println("jojo——欧拉欧拉"+i)
}
}
}
4.4.2 范围数据循环(Until)
1)基本语法
for(i <- 1 until 3) {
print(i + " ")
}
println()
(1)这种方式和前面的区别在于i是从1到3-1
(2)即使前闭合后开的范围
2)案例实操
输出数组内的元素,这样 的场景就比上面的 to要方便一点
val a : Array[Int] = Array(1,2,3)
for(i <- 0 until a.length){
println(a(i))
}
// for(re <- a){
// println(re)
// }
4.4.3 循环守卫
1)基本语法
for(i <- 1 to 3 if i != 2) {
print(i + " ")
}
println()
说明:
(1)循环守卫,即循环保护式(也称条件判断式,守卫)。保护式为true则进入循环体内部,为false则跳过,类似于continue。
(2)上面的代码等价
for (i <- 1 to 3){
if (i != 2) {
print(i + " ")
}
}
2)案例实操
需求:输出1到5中,不等于3的偶数
for (i <- 1 to 5 ; if i!=3 ; if i%2 == 0){
println(i)
}
4.4.4 循环步长
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)
}
说明:没有关键字,所以范围后一定要加;来隔断逻辑
上面的代码等价
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)
}
上面的代码等价于
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,并把数据返回到一个新的集合中。
object TestFor {
def main(args: Array[String]): Unit = {
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循环。
2)案例实操
需求:输出10句 "jojo——欧拉欧拉"
var i = 0
while (i < 10) {
println("jojo——欧拉欧拉" + i)
i += 1
}
4.5.2 do..while循环控制
1)基本语法
循环变量初始化;
do{
循环体(语句)
循环变量迭代
} while(循环条件)
说明
(1)循环条件是返回一个布尔值的表达式
(2)do..while循环是先执行,再判断
2)案例实操
需求:输出10句 "jojo——欧拉欧拉"
var i = 0
do{
println("jojo——欧拉欧拉" + i)
i += 1
}while (i < 10)
4.6 循环中断
1)基本说明
Scala内置控制结构特地去掉了break和continue,是为了更好的适应函数式编程,推荐使用函数式的风格解决break和continue的功能,而不是一个关键字。Scala中使用breakable控制结构来实现break和continue功能。
2)案例实操
需求1:采用异常的方式退出循环
def main(args: Array[String]): Unit = {
try {
for (elem <- 1 to 10) {
println(elem)
if (elem == 5) throw new RuntimeException
}
}catch {
case e =>
}
println("正常结束循环")
}
需求2:采用Scala自带的函数,退出循环
import scala.util.control.Breaks
def main(args: Array[String]): Unit = {
Breaks.breakable(
for (elem <- 1 to 10) {
println(elem)
if (elem == 5) Breaks.break()
}
)
println("正常结束循环")
}
//或者new 一个Breaks给常量
val b : Breaks = new Breaks()
b.breakable(
for (i <- 0 to 3){
println(i)
b.break()
}
)
需求3:对break进行省略
import scala.util.control.Breaks._
object TestBreak {
def main(args: Array[String]): Unit = {
breakable {
for (elem <- 1 to 10) {
println(elem)
if (elem == 5) break
}
}
println("正常结束循环")
}
}
需求4:循环遍历10以内的所有数据,奇数打印,偶数跳过(continue)
object TestBreak {
def main(args: Array[String]): Unit = {
for (elem <- 1 to 10) {
if (elem % 2 == 1) {
println(elem)
} else {
println("continue")
}
}
}
}
//或者
for (elem <- 1 to 10) {
Breaks.breakable {
if (elem % 2 == 1) {
// println(elem)
} else {
println("continue")
Breaks.break()
}
println(elem)
}
}
第5章 函数式编程
1)面向对象编程
解决问题,分解对象,行为,属性,然后通过对象的关系以及行为的调用来解决问题。
对象:用户
行为:登录、连接JDBC、读取数据库
属性:用户名、密码
Scala语言是一个完全面向对象编程语言。万物皆对象
对象的本质:对数据和行为的一个封装
2)函数式编程
解决问题时,将问题分解成一个一个的步骤,将每个步骤进行封装(函数),通过调用这些封装好的步骤,解决问题。
例如:请求->用户名、密码->连接JDBC->读取数据库
Scala语言是一个完全函数式编程语言。万物皆函数。
函数的本质:函数可以当做一个值进行传递
3)在Scala中函数式编程和面向对象编程完美融合在一起了。
5.1 函数基础
5.1.1 函数基本语法
1)基本语法
5.1.2 函数和方法的区别
1)核心概念
(1)为完成某一功能的程序语句的集合,称为函数。
(2)类中的函数称之方法。
2)案例实操
(1)Scala语言可以在任何的语法结构中声明任何的语法
(2)函数没有重载和重写的概念;方法可以进行重载和重写
(3)Scala中函数可以嵌套定义
object Test01_FuncAndMethod {
def main(args: Array[String]): Unit = {
// 定义了一个函数
// 函数不允许重载
def sayHi(name: String): Unit = {
println(s"hi ${name}")
}
// def sayHi(name: String, age: Int): Unit = {
// println(s"hi $age 岁的 $name")
// }
sayHi("linhai")
sayHi1("linhai", 28)
// 在函数当中也能够创建函数
def sayHi2(name: String): Unit = {
def changeName(): String ={
s"$name 大帅哥"
}
println(s"hi ${changeName()}")
}
sayHi2("linhai")
}
// 定义在类当中的叫做方法
def sayHi1(name:String):Unit = {
println(s"hi ${name}")
}
def sayHi1(name:String, age:Int) :Unit ={
// 在方法当中也可以定义函数
def changeName(): String ={
s"$name 大帅哥"
}
println(s"hi $age 岁的 ${changeName()}")
}
}
5.1.3 函数定义
1)函数定义
(1)函数1:无参,无返回值
(2)函数2:无参,有返回值
(3)函数3:有参,无返回值
(4)函数4:有参,有返回值
(5)函数5:多参,无返回值
(6)函数6:多参,有返回值
2)案例实操
object Test02_FuncDef {
def main(args: Array[String]): Unit = {
// (1)函数1:无参,无返回值
def sayHi(): Unit = {
println("hello world")
}
sayHi()
sayHi
// (2)函数2:无参,有返回值
def sayHi1(): String = {
"hi"
}
val str: String = sayHi1()
val str1: String = sayHi1
println(str)
println(str1)
// (3)函数3:有参,无返回值
def sayHi2(name: String): Unit = {
println(s"hi ${name}")
}
val linhai: Unit = sayHi2("linhai")
// (4)函数4:有参,有返回值
def sayHi3(name: String): String = {
s"hi ${name}"
}
val linhai1: String = sayHi3("linhai")
// (5)函数5:多参,无返回值
def sayHi4(name: String, age: Int): Unit = {
println(s"hi $age 的$name")
}
// (6)函数6:多参,有返回值
def sayHi5(name:String,age:Int) : String = {
s"hi $age 的 $name"
}
}
}
5.1.4 函数参数
1)案例实操
(1)可变参数
(2)如果参数列表中存在多个参数,那么可变参数一般放置在最后
(3)参数默认值,一般将有默认值的参数放置在参数列表的后面
(4)带名参数
object Test03_FunArgs {
def main(args: Array[String]): Unit = {
// (1)可变参数
def sayHi(names:String*):Unit = {
println(s"hi $names")
// 可变参数在函数值本质是一个数组
for (elem <- names) {
}
}
sayHi()
sayHi("linhai")
sayHi("linhai","jinlian")
// 可变参数使用:
// (2)可变参数必须在参数列表的最后
def sayHi1(sex: String,names:String*):Unit = {
println(s"hi $names")
}
// (3)参数默认值
def sayHi2(name:String = "linhai"):Unit = {
println(s"hi ${name}")
}
sayHi2("linhai")
sayHi2()
// 可变参数在使用的时候 可以不在最后
def sayHi3( name:String = "linhai" , age:Int):Unit = {
println(s"hi ${name}")
}
// (4)带名参数
sayHi3(age = 10)
}
}
5.1.5 函数至简原则(重点)
函数至简原则:能省则省
1)至简原则细节
(1)return可以省略,Scala会使用函数体的最后一行代码作为返回值
(2)如果函数体只有一行代码,可以省略花括号
(3)返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
(4)如果有return,则不能省略返回值类型,必须指定
(5)如果函数明确声明unit,那么即使函数体中使用return关键字也不起作用
(6)Scala如果期望是无返回值类型,可以省略等号
(7)如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
(8)如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
(9)如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
2)案例实操
object Test04_FuncSimply {
def main(args: Array[String]): Unit = {
// 定义一个原函数
def func0(x: Int, y: Int): Int = {
println("func0的调用")
if (x < 20) {
return x + y
}
2 * x + 3 * y
}
println(func0(10, 20))
// (1)return可以省略,Scala会使用函数体的最后一行代码作为返回值
def func1(x: Int, y: Int): Int = {
println("func1的调用")
x + y
}
val i: Int = func1(10, 20)
println(i)
// (2)如果函数体只有一行代码,可以省略花括号
// 如果不写大括号 默认有效范围只有一行
def func2(x: Int, y: Int): Int = x + y
// (3)返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
def func3(x: Int, y: Int) = x + y
// (4)如果有return,则不能省略返回值类型,必须指定
def func4(x: Int, y: Int): Int = {
if (x < 20) {
return x + y
}
2 * x + 3 * y
}
func4(10, 20)
// (5)如果函数明确声明unit,那么即使函数体中使用return关键字也不起作用
def func5(x: Int, y: Int): Unit = return x + y
val unit: Unit = func5(10, 20)
// (6)Scala如果期望是无返回值类型,可以省略等号
def func6(x: Int, y: Int) {
println(x + y)
}
// (7)如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
def func7(): Unit = {
println("hello")
}
func7()
func7
// (8)如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
def func8 {
println("hello")
}
func8
// (9)如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
def func9(x: Int, y: Int) = x + y
val function: (Int, Int) => Int = (x: Int, y: Int) => x + y
}
}
5.2 函数高级
5.2.1 高阶函数
在Scala中,函数是一等公民。怎么体现的呢?
对于一个函数我们可以:定义函数、调用函数
object Test06_HighFunc {
def main(args: Array[String]): Unit = {
def sayHi(name: String): String = {
println(s"hi $name")
s"hi $name"
}
sayHi("linhai")
}
但是其实函数还有更高阶的用法
1)函数可以作为值进行传递
2)函数可以作为参数进行传递
3)函数可以作为函数返回值返回
以下为演示:
1)函数可以作为值进行传递
object Test06_HighFunc{
def main(args: Array[String]): Unit = {
// 1)函数可以作为值进行传递
val func1: String = sayHi("linhai")
// 在被调用函数sayHi 后面加上 _,相当于把函数当成一个整体,传递给变量func2
val func2 = sayHi _
// 如果明确变量类型,那么不使用下划线也可以将函数作为整体传递给变量
val func3: String => String = sayHi
func2("jinlian")
func3("dalang")
}
}
2)函数可以作为参数进行传递
def main(args: Array[String]): Unit = {
// 2)函数可以作为参数进行传递
def sumAB(a: Int, b: Int): Int = a + b
def difAB(a: Int, b: Int): Int = a - b
// 给两个数 ,之后按照传入的公式进行计算
def funcAB(a: Int, b: Int, func: (Int, Int) => Int): Int = {
func(a, b)
}
val i: Int = funcAB(10, 20, difAB)
println(i)
funcAB(10, 20, 2 * _ / 4 *_)
}
3)函数可以作为函数返回值返回
def main(args: Array[String]): Unit = {
// 3)函数可以作为函数返回值返回
def sumByX(x:Int) = {
def sumXY(y: Int): Int = {
x + y
}
sumXY _
}
val function: Int => Int = sumByX(10)
println(function)
val i1: Int = function(20)
println(i1)
val i2: Int = sumByX(10)(20)
println(i2)
}
5.2.2 匿名函数
1)说明
没有名字的函数就是匿名函数。
(x:Int)=>{函数体}
x:表示输入参数类型;Int:表示输入参数类型;函数体:表示具体代码逻辑
2)案例实操
需求1:传递的函数有一个参数
传递匿名函数至简原则:
(1)参数的类型可以省略,会根据形参进行自动的推导
(2)类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过1的永远不能省略圆括号。
(3)匿名函数如果只有一行,则大括号也可以省略
(4)如果参数只出现一次,则参数省略且后面参数可以用_代替
def main(args: Array[String]): Unit = {
val f0: (Int, Int) => Int = (x: Int, y: Int) => x + y
// (1)参数的类型可以省略,会根据形参进行自动的推导
val f1: (Int, Int) => Int = (x, y) => x + y
// (2)类型省略之后,发现只有一个参数,则圆括号可以省略;
// 其他情况:没有参数和参数超过1的永远不能省略圆括号。
val f2: (Int, Int) => Int = (x, y) => x + y
val f3: Int => Int = x => x + 22
val f4: () => Int = () => 10
// (3)匿名函数如果只有一行,则大括号也可以省略
val f5: (Int, Int) => Int = (x, y) => {
println("匿名函数")
x + y
}
// (4)如果参数只出现一次,则参数省略且后面参数可以用_代替
val f6: (Int, Int) => Int = _ + _
// 化简为_的条件
// 1. 传入的参数类型可以推断 所以可以省略
val f7: (Int, Int) => Int = (x, y) => y - x
// 2. 参数必须只使用一次 使用的顺序必要和定义的顺序一样
val f8: (Int, Int) => Int = -_ + _
// 如果化简为匿名函数 只剩下一个_ 则不可以化简
val function: String => String = _ + ""
val str: String = function("linhai")
val function1: String => String = a => a
// 如果化简的下划线在函数里面 也会报错
// val function1: String => Unit = println(_ + "hi")
val function2: String => Unit = println
function2("linhai")
}
5.2.3 函数柯里化&闭包
闭包:函数式编程的标配
1)说明
闭包:如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境,称为闭包
函数柯里化:把一个参数列表的多个参数,变成多个参数列表。
2)案例实操
(1)闭包
object TestFunction {
def main(args: Array[String]): Unit = {
// 两个数相加 泛用性更强
def sumAB(a:Int,b:Int) :Int = a + b
// 确定一个数是4 适用性更强
def sumByFour(b:Int) :Int = 4 + b
def sumByFive(b:Int) :Int = 5 + b
// 定义一个函数 动态确定一个参数
def sumByA(a:Int): Int => Int = {
def sumAB(b:Int):Int = a + b
sumAB _
}
val sumByFour1: Int => Int = sumByA(4)
val sumByFive1: Int => Int = sumByA(5)
}
//函数柯里化
def fun(a:Int ,b:Int ,c:Int, d :Int): Int ={
a+b+c+d
}
def fun1(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
}
// 底层实现逻辑
// val intToIntToIntToInt : Int=>Int=>Int=>Int= fun2(2)
// val intToIntToInt : Int=>Int=>Int= intToIntToIntToInt(1)
// val intToInt : Int=>Int= intToIntToInt(1)
// val i : Int= intToInt(1)
fun(1,2,3,4)
fun1(1,2)(3,4)
fun2(1)(2)(3)(4)
5.2.4 递归
1)说明
一个函数/方法在函数/方法体内又调用了本身,我们称之为递归调用
2)案例实操
object Test08_Recursion {
def main(args: Array[String]): Unit = {
// 阶乘
var n = 5
var res = 1
for (i <- 1 to n) {
res *= i
}
println(res)
// 递归
// 1. 调用自身
// 2. 跳出条件
// 3. 填入的参数必须有规律
// 4. 递归必须声明函数返回值类型
def rec(n: Int): Int = {
if (n == 1) 1 else rec(n - 1) * n
}
println(rec(5))
// 尾递归优化
@tailrec
def rec1(n: Int, res: Int = 1): Int = {
rec1(n - 1,res * n)
}
println(rec1(5))
}
}
5.2.5 控制抽象
1)值调用:把计算后的值传递过去
在下面的例子里值传递,传递一个代码块过去后,先执行一遍代码块,调用参数调用的是代码块返回的值
2)名调用:把代码传递过去
在下面的例子里名传递,传递一个代码块过去后,调用参数时就会会执行一遍代码块,而后才会返回代码块的返回值
object Test09_ControlAbs {
def main(args: Array[String]): Unit = {
// 值调用
def sayHi(name:String):Unit = {
println("sayHi的调用")
println(s"hi $name")
println(s"hi $name")
}
sayHi({
println("代码块-字符串")
"linhai"
})
println("=======================")
// 名调用 => String
def sayHi1(name: => String):Unit = {
println("sayHi1的调用")
println(s"hi $name")
println(s"hi $name")
}
var n = 1
sayHi1({
println("代码块-字符串1")
n += 1
"linhai" + n
})
}
}
注意:Java只有值调用;Scala既有值调用,又有名调用。
案例:自定义一个While循环
object TestFunction {
def main(args: Array[String]): Unit = {
var i:Int = 1
myWhile(i <= 10){
println(i)
i +=1
}
}
//这里 condition 理解成判断条件即上面的 i<=10
//op 理解成代码块即上面的大括号括起来的语句
def myWhile(condition: =>Boolean)(op: =>Unit):Unit={
if (condition){
op
myWhile(condition)(op)
}
}
}
5.2.6 惰性函数
1)说明
当函数返回值被声明为lazy时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数。
2)案例实操
object Test10_Lazy {
def main(args: Array[String]): Unit = {
def sumAB(a:Int,b:Int):Int = {
println("sumAB调用")
a + b
}
lazy val n = sumAB(10,20)
println("分隔符===================")
println(n)
}
}
//输出结果:
分隔符===================
sumAB调用
30
注意:lazy不能修饰var类型的变量
第6章 面向对象
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.公司名.项目名.业务模块名
4)案例实操
com.atguigu.oa.model
com.atguigu.oa.controller
com.sohu.bank.order
6.1.2 包说明(包语句)
1)说明
Scala有两种包的管理风格,一种方式和Java的包管理风格相同,每个源文件一个包(包名和源文件所在路径不要求必须一致),包名用“.”进行分隔以表示包的层级关系,如com.atguigu.scala。另一种风格,通过嵌套的风格表示层级关系,如下
package com{
package atguigu{
package scala{
}
}
}
第二种风格有以下特点:
(1)一个源文件中可以声明多个package
(2)子包中的类可以直接访问父包中的内容,而无需导包
2)案例实操
package com {
import com.atguigu.Inner //父包访问子包需要导包
object Outer {
val out: String = "out"
def main(args: Array[String]): Unit = {
println(Inner.in)
}
}
package atguigu {
object Inner {
val in: String = "in"
def main(args: Array[String]): Unit = {
println(Outer.out) //子包访问父包无需导包
}
}
}
}
package other {
}
6.1.3 包对象
在Scala中可以为每个包定义一个同名的包对象,定义在包对象中的成员,作为其对应包下所有class和object的共享变量,可以被直接访问。
1)定义
package object com{
val shareValue="share"
def shareMethod()={}
}
2)说明
(1)若使用Java的包管理风格,则包对象一般定义在其对应包下的package.scala文件中,包对象名与包名保持一致。
(2)如采用嵌套方式管理包,则包对象可与包定义在同一文件中,但是要保证包对象与包声明在同一作用域中。
package com {
object Outer {
val out: String = "out"
def main(args: Array[String]): Unit = {
println(name)
}
}
}
package object com {
val name: String = "com"
}
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
package java {
package util {
class HashMap {
}
}
}
说明:
import com.atguigu.Fruit | 引入com.atguigu包下Fruit(class和object) |
import com.atguigu._ | 引入com.atguigu下的所有成员 |
import com.atguigu.Fruit._ | 引入Fruit(object)的所有成员 |
import com.atguigu.{Fruit,Vegetable} | 引入com.atguigu下的Fruit和Vegetable |
import com.atguigu.{Fruit=>Shuiguo} | 引入com.atguigu包下的Fruit并更名为Shuiguo |
import com.atguigu.{Fruit=>Shuiguo,_} | 引入com.atguigu包下的所有成员,并将Fruit更名为Shuiguo |
import com.atguigu.{Fruit=>_,_} | 引入com.atguigu包下屏蔽Fruit类 |
new _root_.java.util.HashMap | 引入的Java的绝对路径 |
注意:
Scala中的三个默认导入分别是
import java.lang._
import scala._
import scala.Predef._
6.2 类和对象
类:可以看成一个模板
对象:表示具体的事物
6.2.1 定义类
0)回顾:Java中的类
如果类是public的,则必须和文件名一致。
一般,一个.java有一个public类
注意:Scala中没有public,一个.scala中可以写多个类。
1)基本语法
[修饰符] class 类名 {
类体
}
说明
(1)Scala语法中,类并不声明为public,所有这些类都具有公有可见性(即默认就是public)
(2)一个Scala源文件可以包含多个类
2)案例实操
package cn.olo
//(1)Scala语法中,类并不声明为public,所有这些类都具有公有可见性(即默认就是public)
class Person {
}
//(2)一个Scala源文件可以包含多个类
class Teacher{
}
6.2.2 属性
属性是类的一个组成部分
1)基本语法
[修饰符] var|val 属性名称 [:类型] = 属性值
注:Bean属性(@BeanPropetry),可以自动生成规范的setXxx/getXxx方法
2)案例实操
package com.atguigu.scala.test
import scala.beans.BeanProperty
class Person {
var name: String = "bobo" //定义属性
var age: Int = _ // _表示给属性一个默认值
//Bean属性(@BeanProperty)
@BeanProperty var sex: String = "男"
//val修饰的属性不能赋默认值,必须显示指定
}
object Person {
def main(args: Array[String]): Unit = {
var person = new Person()
println(person.name)
person.setSex("女")
println(person.getSex)
}
}
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注解实现)。
package chapter06
object Test04_Access {
def main(args: Array[String]): Unit = {
// 同一个包都可以访问的到
Person04.name1
// 受保护的权限 同一个包也无法访问
// Person04.name2
// 访问公共的权限
Person04.name3
}
}
class Person04{
val nameClass = Person04.name
val name1Class = Person04.name1
// 受保护的权限
protected val name2:String = "受保护的权限"
}
object Person04{
// 私有的权限能够在当前类和当前伴生对象中调用
private val name:String = "私有权限"
// 包访问权限
private[chapter06] val name1:String = "包访问权限"
// public的权限
val name3:String = "公共的权限"
}
6.3.1 访问权限
1)说明
在Java中,访问权限分为:public,private,protected和默认。在Scala中,你可以通过类似的修饰符达到同样的效果。但是使用上有区别。
(1)Scala 中属性和方法的默认访问权限为public,但Scala中无public关键字。
(2)private为私有权限,只在类的内部和伴生对象中可用。
(3)protected为受保护权限,Scala中受保护权限比Java中更严格,同类、子类可以访问,同包无法访问。
(4)private[包名]增加包访问权限,包名下的其他类也可以使用
2)案例实操
package chapter06
object Test04_Access {
def main(args: Array[String]): Unit = {
// 同一个包都可以访问的到
Person04.name1
// 受保护的权限 同一个包也无法访问
// Person04.name2
// 访问公共的权限
Person04.name3
}
}
class Person04{
val nameClass = Person04.name
val name1Class = Person04.name1
// 受保护的权限
protected val name2:String = "受保护的权限"
}
object Person04{
// 私有的权限能够在当前类和当前伴生对象中调用
private val name:String = "私有权限"
// 包访问权限
private[chapter06] val name1:String = "包访问权限"
// public的权限
val name3:String = "公共的权限"
}
不同包的调用:
package chapter06Test
import chapter06.Person04
/**
* @author yhm
* @create 2021-09-15 16:11
*/
object Test04_Access {
def main(args: Array[String]): Unit = {
// 不同的包里面无法访问name1 包访问权限
// Person04.name1
// 不同的包也能访问到公共的权限
Person04.name3
}
}
class Student04 extends Person04{
// 即使不是一个包 继承的子类也能够访问到受保护的权限
val name2Class = name2
}
6.3.2 方法
1)基本语法
def 方法名(参数列表) [:返回值类型] = {
方法体
}
2)案例实操
class Person {
def sum(n1:Int, n2:Int) : Int = {
n1 + n2
}
}
object Person {
def main(args: Array[String]): Unit = {
val person = new Person()
println(person.sum(10, 20))
}
}
6.3.3 构造器
和Java一样,Scala构造对象也需要调用构造方法,并且可以有任意多个构造方法。
Scala类的构造器包括:主构造器和辅助构造器
1)基本语法
class 类名(形参列表) { // 主构造器
// 类体
def this(形参列表) { // 辅助构造器
}
def this(形参列表) { //辅助构造器可以有多个...
}
}
说明:
(1)辅助构造器,函数的名称this,可以有多个,编译器通过参数的个数及类型来区分。
(2)辅助构造方法不能直接构建对象,必须直接或者间接调用主构造方法。
(3)构造器调用其他另外的构造器,要求被调用构造器必须提前声明。
2)案例实操
(1)如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略。
object Test06_Constructor {
def main(args: Array[String]): Unit = {
val person0 = new Person06("zhangsan")
val person01 = new Person06()
println(person01.name1)
val person02 = new Person06("lisi", 18)
}
}
// 主构造器 直接写在类的定义后面 可以添加参数 可以使用权限修饰符
//class Person06 private(name:String){
// val name1 = name
//}
class Person06 (name:String){
println("调用主构造器")
val name1 = name
var age:Int = _
// 两个辅助构造器 再互相调用的时候 只能是下面的辅助构造器调用上面的辅助构造器
def this(){
// 辅助构造器的第一行 必须直接或简介的调用主构造器
// 直接调用主构造器
this("zhangsan")
println("调用辅助构造器1")
}
def this(name:String,age1:Int){
// 间接调用主构造器
this()
this.age = age1
println("调用辅助构造器2")
}
}
6.3.4 构造器参数
1)说明
Scala类的主构造器函数的形参包括三种类型:未用任何修饰、var修饰、val修饰
(1)未用任何修饰符修饰,这个参数就是一个局部变量
(2)var修饰参数,作为类的成员属性使用,可以修改
(3)val修饰参数,作为类只读属性使用,不能修改
2)案例实操
object Test07_ConstructorArgs {
def main(args: Array[String]): Unit = {
val person0 = new Person07("zhangsan",11,"男")
println(person0.name)
println(person0.age)
println(person0.sex)
}
}
// 主构造器参数 分为3类:
// 没有修饰符 : 作为构造方法中的传入参数使用
// val 修饰 : 会自动生产同名的属性 并且定义为val
// var 修饰 : 会自动生产同名的属性 并且定义为var
class Person07 (name1:String,val age:Int,var sex:String){
val name = name1
// val age = age
// var sex = sex
}
6.4 继承
1)基本语法
class 子类名 extends 父类名 { 类体 }
(1)子类继承父类的属性和方法
(2)scala是单继承
2)案例实操
(1)子类继承父类的属性和方法
(2)继承的调用顺序:父类构造器->子类构造器
object Test08_Inherit {
def main(args: Array[String]): Unit = {
// 子类继承父类的属性和方法
val student0 = new Student08
println(student0.name)
println(student0.age)
student0.sayHi
println("===========================")
val student01 = new Student08("lisi")
}
}
class Person08 () {
println("调用父类的主构造器")
var name = "person"
var age = 18
def sayHi: Unit ={
println("hi person")
}
def this(name:String){
this()
this.name = name
println("调用父类的辅助构造器")
}
}
// 子类继承
// scala中的继承本质上是继承一个父类的构造器
class Student08 (name:String) extends Person08 (name:String){
println("调用子类的主构造器")
def this(){
this("zhangsan")
// this.name = name
println("调用子类的辅助构造器")
}
}
6.5 抽象属性和抽象方法
6.5.1 抽象属性和抽象方法
1)基本语法
(1)定义抽象类:abstract class Person{} //通过abstract关键字标记抽象类
(2)定义抽象属性:val|var name:String //一个属性没有初始化,就是抽象属性
(3)定义抽象方法:def hello():String //只声明而没有实现的方法,就是抽象方法
案例实操
object Test09_AbstractClass {
def main(args: Array[String]): Unit = {
val person01:Person09 = new Student09
person01.sayHi()
}
}
// 抽象的属性只能存在于抽象的类中
// 抽象类的使用有两种方法 :
// 使用子类继承抽象类
// 使用匿名子类
abstract class Person09{
val name:String
var age:Int
def sayHi():Unit
}
class Student09 extends Person09{
override val name: String = "student"
override var age: Int = 18
override def sayHi(): Unit = {
println("hi student")
}
}
2)继承&重写
(1)如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类
(2)重写非抽象方法需要用override修饰,重写抽象方法则可以不加override。
(3)子类中调用父类的方法使用super关键字
(4)子类对抽象属性进行实现,父类抽象属性可以用var修饰;
子类对非抽象属性重写,父类非抽象属性只支持val类型,而不支持var。
因为var修饰的为可变变量,子类继承之后就可以直接使用,没有必要重写
(5)Scala中属性和方法都是动态绑定,而Java中只有方法为动态绑定。
案例实操(对比Java与Scala的重写)
Scala:
class Person {
val name: String = "person"
def hello(): Unit = {
println("hello person")
}
}
class Teacher extends Person {
override val name: String = "teacher"
override def hello(): Unit = {
println("hello teacher")
}
}
object Test {
def main(args: Array[String]): Unit = {
val teacher: Teacher = new Teacher()
println(teacher.name)
teacher.hello()
val teacher1:Person = new Teacher
println(teacher1.name)
teacher1.hello()
}
}
Java:
class Person {
public String name = "person";
public void hello() {
System.out.println("hello person");
}
}
class Teacher extends Person {
public String name = "teacher";
@Override
public void hello() {
System.out.println("hello teacher");
}
}
public class TestDynamic {
public static void main(String[] args) {
Teacher teacher = new Teacher();
Person teacher1 = new Teacher();
System.out.println(teacher.name);
teacher.hello();
System.out.println(teacher1.name);
teacher1.hello();
}
}
结果对比
Scala Java
6.5.2 匿名子类
1)说明
Java一样,可以通过包含带有定义或重写的代码块的方式创建一个匿名的子类。
2)案例实操
// 匿名必然是多态的用法
val person0: Person09 = new Person09 {
override val name: String = "匿名子类"
override var age: Int = 10
// 匿名子类中不要定义多余的东西 外部无法调用
val sex:String = "nan"
override def sayHi(): Unit = {
println("hi 匿名子类")
}
}
6.6 单例对象(伴生对象)
Scala语言是完全面向对象的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。但是为了能够和Java语言交互(因为Java中有静态概念),就产生了一种特殊的对象来模拟类对象,该对象为单例对象。若单例对象名与类名一致,则称该单例对象这个类的伴生对象,这个类的所有“静态”内容都可以放置在它的伴生对象中声明。
6.6.1 单例对象语法
1)基本语法
object Person{
val country:String="China"
}
2)说明
(1)单例对象采用object关键字声明
(2)单例对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。
(3)单例对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。
3)案例实操
//(1)伴生对象采用object关键字声明
object Person {
var country: String = "China"
}
//(2)伴生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。
class Person {
var name: String = "bobo"
}
object Test {
def main(args: Array[String]): Unit = {
//(3)伴生对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问。
println(Person.country)
}
}
6.6.2 apply方法
1)说明
(1)通过伴生对象的apply方法,实现不使用new方法创建对象。
(2)如果想让主构造器变成私有的,可以在()之前加上private。
(3)apply方法可以重载。
(4)Scala中obj(arg)的语句实际是在调用该对象的apply方法,即obj.apply(arg)。用以统一面向对象编程和函数式编程的风格。
(5)当使用new关键字构建对象时,调用的其实是类的构造方法,当直接使用类名构建对象时,调用的其实时伴生对象的apply方法。
2)案例实操
object Test11_Apply {
def main(args: Array[String]): Unit = {
// val person1 = new Person11
val person1: Person11 = Person11.getPerson11
// 如果调用的方法是apply的话 方法名apply可以不写
val person11: Person11 = Person11()
val zhangsan: Person11 = Person11("zhangsan")
// 类的apply方法调用
person11()
}
}
class Person11 private() {
var name:String = _
def this(name:String){
this()
this.name = name
}
def apply(): Unit = println("类的apply方法调用")
}
object Person11 {
// 使用伴生对象的方法来获取对象实例
def getPerson11: Person11 = new Person11
// 伴生对象的apply方法
def apply(): Person11 = new Person11()
// apply方法的重载
def apply(name: String): Person11 = new Person11(name)
}//注意:也可以创建其它类型对象,并不一定是伴生类对象
}
扩展:在Scala中实现单例模式:
object Test12_Singleton {
def main(args: Array[String]): Unit = {
val person: Person12 = Person12.getPerson
val person1: Person12 = Person12.getPerson
println(person.eq(person1))
val student1: Student12 = Student12()
val student2: Student12 = Student12()
println(student1.eq(student2))
}
}
// 单例模式
// 懒汉式: 优点是 开始的时候不占用内存 等到需要的时候再创建对象
class Person12 private(){
}
object Person12 {
var person:Person12 = null
def getPerson:Person12 = {
if (person == null) {
person = new Person12
}
person
}
}
// 饿汉式: 优点是 最开始的时候直接创建对象 没有线程安全问题
class Student12 private(){
}
object Student12{
val student:Student12 = new Student12
def apply():Student12 = student
}
6.7 特质(Trait)
Scala语言中,采用特质trait(特征)来代替接口的概念,也就是说,多个类具有相同的特质(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明。
Scala中的trait中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质。这种感觉类似于Java中的抽象类。
Scala引入trait特征,第一可以替代Java的接口,第二个也是对单继承机制的一种补充。
6.7.1 特质声明
1)基本语法
trait 特质名 {
trait主体
}
2)案例实操
trait Age13 {
// 允许出现抽象的属性和方法
val age :Int
def sayHi():Unit
// 允许出现非抽象的属性
val age1:Int = 10
def sayHi1()={
println("hi ")
}
}
通过查看字节码,可以看到特质=抽象类+接口
6.7.2 特质基本语法
一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了extends关键字,如果有多个特质或存在父类,那么需要采用with关键字连接。
1)基本语法:
没有父类:class 类名 extends 特质1 with 特质2 with 特质3 …
有父类:class 类名 extends 父类 with 特质1 with 特质2 with 特质3…
2)说明
(1)类和特质的关系:使用继承的关系。
(2)当一个类去继承特质时,第一个连接词是extends,后面是with。
(3)如果一个类在同时继承特质和父类时,应当把父类写在extends后。
3)案例实操
(1)特质可以同时拥有抽象方法和具体方法
(2)一个类可以混入(mixin)多个特质
(3)所有的Java接口都可以当做Scala特质使用
(4)动态混入:可灵活的扩展类的功能
(4.1)动态混入:创建对象时混入trait,而无需使类混入该trait
(4.2)如果混入的trait中有未实现的方法,则需要实现
object Test14_TraitMixin {
def main(args: Array[String]): Unit = {
val student1 = new Student14
println(student1.name1)
println(student1.age1)
println(student1.name)
println(student1.age)
//(4)动态混入:可灵活的扩展类的功能
//(4.1)动态混入:创建对象时混入trait,而无需使类混入该trait
//(4.2)如果混入的trait中有未实现的方法,则需要实现
// 只有当前创建的一个对象具有混入的特质 类是没有的
val teacher1: Teacher14 with Age14 = new Teacher14 with Age14 {
override val name = "teacher"
override var age = 18
}
}
}
trait Age14{
val name:String
var age:Int
val name1:String = "age"
var age1:Int = 10
}
abstract class Person14{
val name:String = "person"
var age:Int = 18
val name1:String = "person1"
// var age1:Int = 11
}
// 继承的时候 父类和特质不能有相同的具体属性 会发生冲突报错
// 报错的如果是val 常量 可以通过重写解决 如果是var 变量 只能去修改父类或者特质
// 如果继承的属性 一个是抽象的一个是非抽象的 不会发生冲突 需要注意var的属性不能重写
class Student14 extends Person14 with Age14{
override val name: String = "student"
age = 19
// 通过重写解决
override val name1: String = "student"
age1 = 18
}
//(3)所有的Java接口都可以当做Scala特质使用
class Teacher14 extends java.io.Serializable{
}
6.7.3 特质叠加
由于一个类可以混入(mixin)多个trait,且trait中可以有具体的属性和方法,若混入的特质中具有相同的方法(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。冲突分为以下两种:
第一种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法。
第二种,一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait继承自相同的trait(TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala采用了特质叠加的策略。
所谓的特质叠加,就是将混入的多个trait中的冲突方法叠加起来,案例如下,
trait Ball {
def describe(): String = {
"ball"
}
}
trait Color extends Ball {
override def describe(): String = {
"blue-" + super.describe()
}
}
trait Category extends Ball {
override def describe(): String = {
"foot-" + super.describe()
}
}
class MyBall extends Category with Color {
override def describe(): String = {
"my ball is a " + super.describe()
}
}
object TestTrait {
def main(args: Array[String]): Unit = {
println(new MyBall().describe())
}
}
结果如下:
思考:上述案例中的super.describe()调用的是父trait中的方法吗?
当一个类混入多个特质的时候,scala会对所有的特质及其父特质按照一定的顺序进行排序,而此案例中的super.describe()调用的实际上是排好序后的下一个特质中的describe()方法,排序规则如下:
结论:
(1)案例中的super,不是表示其父特质对象,而是表示上述叠加顺序中的下一个特质,即,MyClass中的super指代Color,Color中的super指代Category,Category中的super指代Ball。
(2)如果想要调用某个指定的混入特质中的方法,可以增加约束:super[],例如super[Category].describe()。
6.7.5 特质自身类型
1)说明
自身类型可实现依赖注入的功能。
2)案例实操
object Test16_TypeSelf {
def main(args: Array[String]): Unit = {
// val young1: Young16 = new Young16 {
// override val age: Int = 16
// }
}
}
trait Age16 {
val age: Int
}
//trait Young16 extends Age16
trait Young16 {
// 特质自身类型
_: Age16 =>
}
// 特质自身类型 要求必须同时实现两个依赖的特质
class Person16 extends Young16 with Age16{
override val age: Int = 17
}
6.7.6特质和抽象类的区别
1.优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类。
2.如果你需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数,而特质不行(有无参构造)。
6.8 扩展
6.8.1 类型检查和转换
1)说明
(1)obj.isInstanceOf[T]:判断obj是不是T类型。
(2)obj.asInstanceOf[T]:将obj强转成T类型。
(3)classOf获取类模板。
2)案例实操
class Person{
}
object Person {
def main(args: Array[String]): Unit = {
val person = new Person
//(1)判断对象是否为某个类型的实例
val bool: Boolean = person.isInstanceOf[Person]
if ( bool ) {
//(2)将对象转换为某个类型的实例
val p1: Person = person.asInstanceOf[Person]
println(p1)
}
//(3)获取类的模板
val pClass: Class[Person] = classOf[Person]
println(pClass)
}
}
6.8.2 枚举类和应用类
1)说明
枚举类:需要继承Enumeration
应用类:需要继承App
2)案例实操
object Test {
def main(args: Array[String]): Unit = {
println(Color.RED)
}
}
// 枚举类
object Color extends Enumeration {
val RED = Value(1, "red")
val YELLOW = Value(2, "yellow")
val BLUE = Value(3, "blue")
}
// 应用类
object Test20 extends App {
println("xxxxxxxxxxx");
}
6.8.3 Type定义新类型
1)说明
使用type关键字可以定义新的数据数据类型名称,本质上就是类型的一个别名
2)案例实操
object Test {
def main(args: Array[String]): Unit = {
type S=String
var v:S="abc"
def test():S="xyz"
}
}