一、概述
1.1 Scala语言特点
Scala是一门以Java虚拟机(JVM)为运行环境并将面向对象和函数式编程的最佳特性结合在一起的静态类型编程语言(静态语言需要提前编译的如: Java、 c、 c++等, 动态语言如: js)。
1)Scala是一门多范式的编程语言,Scala支持面向对象和函数式编程。(多范式,就是多种编程方法的意思。有面向过程、面向对象、泛型、函数式四种程序设计方法。)
2)Scala源代码(.scala)会被编译成Java字节码(.class) ,然后运行于JVM之上,并可以调用现有的Java类库,实现两种语言的无缝对接。
3)Scala单作为一门语言来看,非常的简洁高效。
4)Scala在设计时,马丁奥德斯基是参考了Java的设计思想,可以说Scala是源于Jaa,同时马丁奥德斯基也加入了自己的思想,将函数式编程语言的特点融合到JAVA中,因此,对于学习过Java的同学,只要在学习Scala的过程中,搞清楚Scala和Java相同点和不同点,就可以快速的掌握Scala这门语言。
1.2 入门代码
代码详解:
1.3 伴生对象详解(Java中static在Scala中的实现)
下列Java代码:
Scala中实现:
二、变量和数据类型
2.1 变量和常量(重点)
常量:在程序执行的过程中,其值不会被改变的变量。
0)回顾:Java变量和常量语法
变量类型 变量名称=初始值 int a= 10
final常量类型 常量名称=初始值 final intb= 20
1)基本语法
var 变星名 [: 变量类型]=初始值 var i:Int= 10
val 常量名 [: 常量类型]=初始值 val j:Int= 20
注意:能用常量的地方不用变量
示例:
def main(args: Array[String]): Unit = {
//声明一个变量的通用语法
var a:Int=10
// (1)声明变量时,类型可以省略,编译器自动推导,即类型推导
var a1=10
val b1=23
// (2)类型确定后,就不能修改,说明Scala是强数据类型语言。
var a2 = 15 // a2类型为Int
//a2 = "helLo" : String
// (3)变量声明时,必须要有初始值
//var a3: Int
// (4)在声明/定义一个变量时,可以使用var或者val来修饰,var修饰的变量可改变,val修饰的变量不可改。
a1=12
//b1=25
var alice = new Student( name = "alice", age = 20)
alice = new Student( name = "Alice", age = 20)
alice = null
val bob = new Student( name = "bob", age = 23)
bob.age = 24
bob.printInfo ( )
2.2 标识符
(1)以字母或者下划线开头,后接字母、数字、下划线。
(2) 以操作符开头,且只包含操作符(+ - * / # ! 等)。
(3)用反引号 ‘…’ 包括的任意宇符串,即使是Scala关键宁(39个)也可以。
def main(args: Array[String]): Unit = {
// (1)以字母或者下划线开头,后接字母、数字、下划线
val hello: String = ""
var Hello123 = ""
val _abc = 123
//val h-b = ""
//val 123abc = 234
// (2) 以操作符开头,且只包含操作符(+ - * / # ! 等)
val -+/% = "hello"
println(-+/%)
// (3)用反引号'....'包括的任意宇符串,即使是Scala关键宁(39个)也可以
val 'if' = "if"
printIn('if')
}
2.3 字符串输出
(1) 字符串,通过+号连接。
(2) printf用法: 字符串,通过%传值。
(3) 字符串模板(插值字符串):通过$获取变量值。
def main(args: Array[String]): Unit = {
// (1) 字符串,通过+号连接
val name: String = "alice"
val age: Int = 18
println(age + "岁的" + name + "在NIIT学习")
// * 用于将一个字符串复刺多次并拼接
println(name * 3)
// (2) printf用法: 字符串,通过%传值。
printf("%d岁的%s在NIIT学习",age, name)
// (3) 字符串模板(插值字符串):通过$获取变量值。(s"",f"",raw""为模版)
println(s"${age}岁的${name}在NIIT学习")
val num: Double = 2.3456
println(f"The num is ${num}%2.2f")//格式化模板字符串,输出:The num is 2.35
println(raw"The num is ${num}%2.2f")//输出:The num is 2.3456%2.2f
//三引号表示字符串,保持多行字符串的原格式输出
s"""
|select *
|from
| student
|where
| name = ${name}
|and
| age > ${age}
|""".stripMargin
}
三引号表示字符串输出:
2.4 键盘输入
在编程中,需要接收用户输入的数据,就可以使用键盘输入语句来获取。
1)基本语法
StdIn.readLine()、StdIn.readShort()、StdIn.readDouble()
def main(args: Array[String]): Unit = {
//输入信息
println("请输入您的姓名: ")
val name: String = StdIn.readLine()
println("请输入您的年龄: ")
val age: Int = StdIn.readInt()
//控制台打印输出
println(s"欢迎${age}岁的${name}")
}
2.5 读写文件
import java.io.{File, PrintWriter}
import scala.io.Source
object test {
def main(args: Array[String]): Unit = {
// 1.从文件中读收数据
Source.fromFile("src/main/resources/test.txt").foreach(print)
// 2.将数据写入文件
val writer = new PrintWriter(new File("src/main/resources/output.txt"))
writer.write("heLlo scala from Java writer" )
writer.close()
}
}
2.6 数据类型
回顾: Java数据类型:
Java基本类型:char、byte、short、int、long、float、double、boolean
Java引用类型:(对象类型)
由于Java有基本类型,而且基本类型不是真正意义的对象,即使后面产生了基本类型的包装类,但是仍然存在基本数据类型,所以Java语言并不是真正意思的面向对象。
Java基本类型的包装类: Character、Byte、Short、Integer、Long、Float、Double、Boolean
注意:Java中基本类型和引用类型没有共同的祖先。
Scala数据类型:
1)Scala中一切数据都是对象,都是Any的子类。
2)Scala中数据类型分为两大类:数值类型( AnyVal )、引用类型( AnyRef),不管是值类型还是引用类型都是对象。
3)Scala数据类型仍然遵守,低精度的值类型向高精度值类型,自动转换(隐式转换)。
4)Scala中的StringOps是对Java中的Sting增强。
5)Unit:对应Java中的void,用于方法返回值的位置,表示方法没有返回值。Unit是一个数据类型,只有一个对象就是()。Void不是数据类型,只是一个关键字。
6)Null是一个类型, 只有一个对象就是null。它是所有引用类型(AnyRef) 的子类。
7)Nothing,是所有数据类型的子类,主要用在一个函数没有明确返回值时使用,因为这样我们可以把抛出的返回值,返回给任何的变量或者函数。
2.7 整数类型(Byte、Short、Int、Long)
Scala的整数类型就是用于存放整数值的,比如12, 30, 3456 等等。
1)整型分类
示例:
object test {
def main(args: Array[String]): Unit = {
// 1.整数类型
val a1: Byte = 127
val a2: Byte = -128
// val a2: Byte = 128 // error
val a3 = 12 //整数默认类型为Int
val a4: Long = 1324135436436L // 长整型数值定文
val b1: Byte = 10
// val b2: Byte = (10 + 20): Int
// println(b2)
// val b3:Byte=b1+20
val b3: Byte = (b1 + 20).toByte
println(b3)//30
}
}
2.8 浮点类型
def main(args: Array[String]): Unit = {
println("long 的最大值" + Long.MaxValue + "~" + Long.MinValue)
var i = 10 //i Int
var j = 10l //j Long
var e = 9223372036854775807l //说 9223372036854775807 超过 int
//2.2345678912f , 2.2345678912
var num1:Float = 2.2345678912f
var num2:Double = 2.2345678912
println("num1=" + num1 + " num2=" + num2)
//十进制数形式:如:5.12 512.0f .512 (必须有小数点)
var num3:Double=.512
var num4:Float=512.0f
println("num3=" + num3 + " num4=" + num4)
//科学计数法形式:如:5.12e2=5.12乘以10的2次方 5.12E-2=5.12除以10的2次方
var num5:Double=5.12e2
var num6:Double=5.12E-2
println("num5=" + num5 + " num6=" + num6)
}
2.9 字符类型(Char)和布尔类型
def main(args: Array[String]): Unit = {
var char1: Char = 97
//当我们输出一个 char 类型是,他会输出该数字对应的字符(码值表 unicode)//unicode 码值表包括 ascii
println("char1=" + char1) // a
//char 可以当做数字进行运行
var char2: Char = 'a'
var num = 10 + char2
println("num=" + num) // 107
//原因和分析
//1.当把一个计算的结果赋值一个变量,则编译器会进行类型转换及判断(即会看范围+类型)
//2.当把一个字面量赋值一个变量,则编译器会进行范围的判定
// var c2: Char = 'a' + 1
// var c3: Char = 97 + 1
// var c4: Char = char1+1
var c5: Char = 98
// 布尔类型
val isTrue: Boolean = true
println(isTrue)
}
2.10 空类型(重点)
1)基本说明:
def main(args: Array[String]): Unit = {
//空类型
//1.空值Unit
def m1(): Unit = {
println("m1被调用执行")
}
val a: Unit = m1()
println("a:"+ a)
// 2.空引用Null
// val n: Int = null // error
var student: Student = new Student(name = "alice", age = 20)
student = null
println(student)
// 3.Nothing
/*
def m2(n: Int): Nothing = {
throw new NullPointerException
}
val b = m2(0)
println(b)//不会输出具体值,会抛异常报错。
*/
//一般像下述代码一样使用
def m2(n: Int): Int = {
if (n == 0)
throw new NullPointerException
else
return n
}
val b = m2(0)
println(b)
}
2.11 值类型隐式转换
当 Scala 程序在进行赋值或者运算时,精度小的类型自动转换为精度大的数据类型,这个就是自动类型转换(隐式转换)。
数据类型按精度(容量)大小排序为:
1)基本说明:
(1)自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度大的那种数据类型,然后再进行计算。
(2)把精度大的数值类型赋值给精度小的数值类型时,就会报错,反之就会进行自动类型转换。
(3) (byte, short) 和char之间不会相互自动转换。
(4) byte, short, char 他们三者可以计算,在计算时首先转换为动类型。
def main(args: Array[String]): Unit = {
// (1)自动提升原则:有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度大的那种数据类型,然后再进行计算。
val a1: Byte = 10
val b1: Long = 2353
val result1: Long = a1 + b1
val result11: Int = (a1 + b1. toInt)//强转
// (2)精度大的数值类型赋值给精度小的数值类型时,就会报错
val a2: Byte = 10
val b2: Int = a2
// val c2: Byte = b2 // error
// (3) (byte, short) 和char之间不会相互自动转换。
val a3: Byte = 10
val b3: Char = 'b'
// val c3: Byte = b3 // error
val c3: Int = b3
println(c3) // 98
// (4) byte, short, char 他们三者可以计算,在计算时首先转换为int 类型。
val a4: Byte = 12
val b4: Short = 25
val c4: Char = 'c'
val result4: Int = a4 + b4
val result5: Int = a4 + b4 + c4
println(result5)
}
2.12 强制类型转换
自动类型转换的逆过程,将精度大的数据类型转换为精度小的数据类型。使用时要加上强制转函数,但可能造成精度降低或溢出,格外要注意。
def main(args: Array[String]): Unit = {
// (1)将数据由高精度转换为低精度,就需要使用到强制转换
val n1: Int = 2.9.toInt
println("n1:"+ n1) //n1:2
val n2: Int = -2.9.toInt
println("n2:"+ n2) //n2:-2
// (2)强转符号只针对于最近的操作数有效,往往会使用小括号提升优先级
val n3: Int = 2.6.toInt + 3.7.toInt
val n4: Int = (2.6 + 3.7).toInt
println("n3:"+ n3) //n3:5
println("n4:"+ n4) //n4:6
// (3)数值类型和String类型的转换
// 数值转String
val n:Int=27
val s:String = n + ""
println(s)
// String转数值
val m: Int = "12".toInt
val f1: Float = "12.3".toFloat
// val f2: Int = "12.3".toInt //error
val f2: Int = "12.3".toDouble.toInt
println(f2)
}
2.13 强制溢出面试题
/*
128: Int类型,占据4个字节,32位
原码 0000 0000 0000 0000 0000 0000 1000 0000
补码 0000 0000 0000 0000 0000 0000 1000 0000
截取最后一个字节,Byte
得到补码 1000 0000
对应源码 1000 0000
表示最大负数 -128
130: Int类型,占据4个字节,32位
原码 0000 0000 0000 0000 0000 0000 1000 0010
补码 0000 0000 0000 0000 0000 0000 1000 0010
截取最后一个字节,Byte
得到补码 1000 0010
对应源码 1111 1110
表示最大负数 -126
*/
object test {
def main(args: Array[String]): Unit = {
val n: Int= 130
val b: Byte = n.toByte
println(b) //-126
}
}
三、运算符
3.1 算术运算符
3.2 关系运算符(比较运算符)
3.3 逻辑运算符
3.4 位运算符
四、流程控制
4.1 分支控制if-else
让程序有选择的的执行,分支控制有三种:单分支、双分支、多分支
用法与Java相同,不同的是有返回值。
if-else的返回值:
import scala.io.StdIn
object IfElse {
def main(args: Array[String]): Unit = {
println("请输入您的年龄: ")
val age: Int = StdIn.readInt()
//1.单分支
if (age >= 18){
println("成年")
}
println("=================")
// 2.双分支
if (age >= 18){
println("成年")
} else {
println("未成年")
}
println("=================")
// 3.多分支
if (age <= 6){
println("童年")
} else if(age < 18){
println("青少年")
} else if(age < 35){
println("青年")
} else if(age < 60){
println("中年")
} else {
println("老年")
}
println("=================")
// 4.分区语句返回值
// 分支语句的返回值-String
val res1:String=if (age <= 6){
println("童年")
"童年"
} else if(age < 18){
println("青少年")
"青少年"
} else if(age < 35){
println("青年")
"青年"
} else if(age < 60){
println("中年")
"中年"
} else {
println("老年")
"老年"
}
println("res="+res1)
// 分支语句的返回值-每个分支不同类型返回值。"童年"父类型->AnyRef,age父类型->AnyVal,取共同父类型->Any
val res2:Any=if (age <= 6){
println("童年")
"童年"
} else if(age < 18){
println("青少年")
"青少年"
} else if(age < 35){
println("青年")
age
} else if(age < 60){
println("中年")
age
} else {
println("老年")
age
}
println("res="+res2)
}
}
4.2 for循环
object ForLoop {
def main(args: Array[String]): Unit = {
// 范围遍历
for(i<-1 to 5){
print(i+". hello world | ")
}
for(i<-1.to(5)){
print(i+". hello world | ")
}
println()
// 不包含边界的范围遍历
for(i <- Range(1, 5)){
print(i + ". hello world | ")
}
for(i <- 1 until 5){
print(i + ". hello world | ")
}
println()
// 集合遍历
for(i<-Array(1,2,3)){
print(i+" | ")
}
println()
for(i<-List(1,2,3)){
print(i+" | ")
}
println()
for(i<-Set(1,2,3)){
print(i+" | ")
}
println()
// 循环守卫,即循环保护式(也称条件判断式,守卫)。保护式为true则进入循环体内部,为false则跳过,类似于continue。
for(i<-1 to 5 if i != 5){
print(i+" | ")
}
println()
// 循环步长
for(i <- 1 to 5 by 2){
print(i+" | ")
}
println()
for(i <- 5 to 1 by -2){
print(i+" | ")
}
println()
for(i <- 1 to 5 by 2 reverse){
print(i+" | ")
}
println()
for(i <- 1.0 to 3.0 by 0.5 reverse){
print(i+" | ")
}
println()
// 嵌套循环
for(i <- 1 to 2){
for(j <- 1 to 3){
print("i = "+ i + ", j = "+ j + " | ");
}
}
println()
for(i <- 1 to 2; j<- 1 to 3){
print("i = " + i + ", j = " + j + " | ")
}
}
}
4.3 示例1:九九乘法表
def main(args: Array[String]): Unit = {
for(i <- 1 to 9){
for(j<- 1 to i){
print(s"$i * $j = ${i * j} \t")
}
println()
}
}
def main(args: Array[String]): Unit = {
for(i <- 1 to 9; j<- 1 to i){
print(s"$i * $j = ${i * j} \t")
if(j==i){
println();
}
}
}
4.4 示例2:输出九层妖塔
def main(args: Array[String]): Unit = {
for(i <- 1 to 9){
val stars = 2 * i - 1
val spaces = 9 - i
println(" " * spaces + "*" * stars)
}
}
def main(args: Array[String]): Unit = {
for(i <- 1 to 9; stars = 2 * i - 1; spaces = 9 - i){
println(" " * spaces + "*" * stars)
}
}
def main(args: Array[String]): Unit = {
for(stars <- 1 to 17 by 2; spaces = (17 - stars) / 2){
println(" " * spaces + "*" * stars)
}
}
4.5 for循环的返回值
def main(args: Array[String]): Unit = {
val a = for(i <- 1 to 10){
i
}
println("a = " + a)
val b: immutable.IndexedSeq[Int] = for (i <- 1 to 10) yield i * i
println("b = " + b)
}
4.6 while循环
object Broadcast {
def main(args: Array[String]): Unit = {
var a: Int = 10
while (a >= 1){
println("this is a while loop: " + a)
a -= 1
}
var b: Int = 0
do{
println("this is a do-while loop: " + b)
b -= 1
}while (b > 0)
}
}
4.7 循环中断
Scala内置控制结构特地去掉了break 和continue,是为了更好的适应函数式编程,推荐使用函数式的风格解决break和continue的功能,而不是一个关键字。 Scala中使用breakable控制结构来实现break和continue功能。
1)Java中通过抛出异常来实现break的功能
2)scala中通过抛出异常来实现break的功能
3)scala中另一种实现循环中断的方式
若导入的是包:import scala.util.control.Breaks._ ,可继续简化如下:
五、函数式编程
5.1 函数和方法
1)基本语法:
2)函数和方法的区别
核心概念:
(1)为完成某一功能的程序语句的集合,称为函数。
(2)类中的函数称之方法。
Scala函数:
(1) Scala 语言可以在任何的语法结构中声明任何的语法
(2) 函数没有重载和重写的概念;方法可以进行重载和重写
(3) Scala 中函数可以嵌套定义
5.2 函数参数的特殊用法
def main(args: Array[String]): Unit = {
// (1) 可变参数
def f1(str: String*): Unit = {
println(str)
}
f1("alice")
f1("aaa", "bbb", "ccc")
// (2) 如果参数列表中存在多个参数,那么可变参数一般放置在最后
def f2(str1: String, str2: String*): Unit = {
println("str1: " + str1 + " str2: " + str2)
}
f2("alice")
f2("aaa", "bbb", "ccc")
// (3) 参数默认值,一般将有默认值的参数放置在参数列表的后面
def f3(name: String = "niit"): Unit = {
println("My school is " + name)
}
f3("school")
f3()
// (4) 带名参数
def f4(name: String, age: Int): Unit = {
println(s"${age}岁的${name}在niit学习")
f4("alice", age = 20)
f4(age = 23, name = "bob")
}
}
5.3 函数至简原则
至简原则细节:
(1) return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
(2) 如果函数体只有一行代码,可以省略花括号
(3) 返回值类型如果能够推断出来。那么可以省略(和返回值类型一起省略)
(4) 如果有return,则不能省略返回值类型,必须指定
(5) 如果函数明确声明unit,那么即使函数体中使用return关键字也不起作用
(6) Scala 如果期望是无返回值类型,可以省略等号
(7) 如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
(8) 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
(9) 如果不关心名称,只关心逻辑处理,那么函数名(def) 可以省略
object Simplify {
def main(args: Array[String]): Unit = {
def f0(name: String): String = {
return name
}
println(f0("niit"))
println("==============================")
// (1) return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
def f1(name: String): String = {
name
}
println(f1("niit"))
println("==============================")
// (2) 如果函数体只有一行代码,可以省略花括号
def f2(name: String): String = name
println(f2("niit"))
println("==============================")
// (3) 返回值类型如果能够推断出来。那么可以省略(和返回值类型一起省略)
def f3(name: String) = name
println(f3("niit"))
println("==============================")
// (4) 如果有return,则不能省略返回值类型,必须指定
// def f4(name: String) = {
// return name
// }
// println(f4("niit"))
// println("==============================")
// (5) 如果函数明确声明unit,那么即使函数体中使用return关键字也不起作用
def f5(name: String): Unit = {
return name
}
println(f5("niit")) //打印为()
println("==============================")
// (6) Scala 如果期望是无返回值类型,可以省略等号以及返回值类型
def f6(name: String){
println(name)
}
println(f6("niit"))
println("==============================")
// (7) 如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
def f7(){
println("hello")
}
println(f7())
println(f7)
println("==============================")
// (8) 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
def f8{
println("hello")
}
// println(f8())
println(f8)
println("==============================")
// (9) 如果不关心名称,只关心逻辑处理,那么函数名(def) 可以省略
// 匿名函数,lambda表达式
(name: String) => { println(name) }
println("==============================")
}
}
5.4 匿名函数
说明:
没有名字的函数就是匿名函数。
(x:Int)=>{函数体}
x:表示输入参数类型;Int:表示输入参数类型;函数体:表示具体代码逻辑。
def main(args: Array[String]): Unit = {
val fun = (name: String) => { println(name) }
fun("niit")
// 定义一个函数,以函数作为参数传入
def f(func: String => Unit): Unit = {
func("niit")
}
f(fun)
f( (name: String) => { println(name) } )
// 匿名函数的简化原则
// (1) 参数的类型可以省略,会根据形参进行自动的推导
f( (name) => {
println(name)
} )
// (2) 类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过1的永远不能省略圆括号。
f( name => {
println(name)
} )
// (3) 匿名函数如果只有一行,则大括号也可以省略
f( name => println(name) )
// (4) 如果参数只出现一次,则参数省略且后面爹数可以用_代替
f( println(_) )
// (5) 如果可以推断出,当前传入的println是一个函数体,而不是调用语句,可以直接省略下划线
f( println )
println("==============================================")
// 实际示例,定义一个“二元运算”函数,只操作1和2两个数,但是具体运算通过参数传入
def dualFunctionOneAndTwo(fun: (Int, Int)=>Int): Int ={
fun(1, 2)
}
val add = (a: Int, b: Int) => a + b
val minus = (a: Int, b: Int) => a - b
// 匿名函数简化
println(dualFunctionOneAndTwo((a: Int, b: Int) => a + b))
println(dualFunctionOneAndTwo((a: Int, b: Int) => a - b))
println(dualFunctionOneAndTwo((a, b) => a + b))
println(dualFunctionOneAndTwo(_ + _))
println(dualFunctionOneAndTwo(_ - _))
println(dualFunctionOneAndTwo((a, b) => b - a))
println(dualFunctionOneAndTwo( -_ + _))
}
5.5 高阶用法
object HighOrderFunction {
def main(args: Array[String]): Unit = {
// 最简单的函数声明和调用过程
def f(n: Int): Int = {
println("f调用")
n + 1
}
val result: Int = f(123)
println(result)
println("=============================")
// 高阶用法1: 函数作为值进行传递
val f1: Int => Int = f
val f2 = f _
println(f1)
println(f1(12))
println(f2)
println(f2(35))
println("=============================")
// 高阶用法2: 函数作为参数进行传递
// 定义二元计算函数
def dualEval(op: (Int, Int)=>Int, a: Int,b: Int): Int = {
op(a, b)
}
def add(a:Int, b:Int): Int = {
a + b
}
println(dualEval(add, 12, 35))
println(dualEval((a, b) => a + b, 12, 35))
println(dualEval(_ + _, 12, 35))
println("=============================")
// 高阶用法2: 函数作为参数进行传递
def f3(): Int => Unit = {
def f4(a: Int): Unit = {
println("f4调用 " + a)
}
f4
}
println(f3())
val f5 = f3()
println(f5)
println(f5(25))
println(f3()(25))
}
}
5.6 高阶函数应用案例
object CollectionOperation {
def main(args: Array[String]): Unit = {
val arr: Array[Int] = Array(12, 45, 75, 98)
// 对数组进行处理,将操作抽象出来,处理完毕之后的结果返回一个新的数组
def arrayOperation(array: Array[Int], op: Int=>Int): Array[Int] ={
for (elem <- array) yield op(elem)
}
// 定义一个加一操作
def addOne(elem: Int): Int = {
elem + 1
}
val newArray1: Array[Int] = arrayOperation(arr, addOne)
println(newArray1.mkString(","))
// 传入匿名函数,实现元素翻倍
val newArray2 = arrayOperation(arr, elem => elem * 2)
// 继续简化
val newArray3 = arrayOperation(arr, _ * 2)
println(newArray2.mkString(","))
println(newArray3.mkString(","))
}
}
5.7 匿名函数-拓展练习
练习1:定义一个匿名函数,并将它作为值赋给变量fun。函数有3三个参数,类型分别为Int, String, Char, 返回值类型为Boolean。
要求调用函数fun(0,"", ‘0’)得到返回值为false,其它情况均返回true。
def main(args: Array[String]): Unit = {
val fun = (i: Int, s: String, c: Char) => { if(i == 0&& s == ""&& c == '0') false else true }
println(fun(0, "", '0'))
println(fun(0, "", '1'))
println(fun(23, "", '0'))
println(fun(0, "hello", '0'))
}
练习2:定义一个函数func,它接收一个Int类型的参数,返回一个函数(记作f1)。它返回的函数f1,接收一个String类型的参数,同样返回一个函数(记作f2)。函数f2接收一个Char类型的参数,返回一个Boolean的值。
要求调用函数func(0) ("") (‘0’)得到返回值为false,其它情况均返回true。
def main(args: Array[String]): Unit = {
def func(i: Int): String=>(Char=>Boolean) ={
def f1(s: String): Char=>Boolean ={
def f2(c: Char): Boolean ={
if(i == 0&& s == ""&& c == '0') false else true
}
f2
}
f1
}
println(func(0)("")('0'))
println(func(0)("")('1'))
println(func(23)("")('0'))
println(func(0)("hello")('0'))
println("==================================")
// 匿名函数简写
def func1(i: Int): String=>(Char=>Boolean) = {
s => c => if(i == 0 && s == "" && c == '0') false else true
}
println(func(0)("")('0'))
println(func(0)("")('1'))
println(func(23)("")('0'))
println(func(0)("hello")('0'))
println("==================================")
// 柯里化
def func2(i: Int)(s: String)(c: Char): Boolean = {
if(i == 0 && s == "" && c == '0') false else true
}
println(func(0)("")('0'))
println(func(0)("")('1'))
println(func(23)("")('0'))
println(func(0)("hello")('0'))
}
5.8 函数柯里化&闭包
闭包:如果一个函数,访问到了它的外部(同部)变量的值,那么这个函数和他所处的环境,称为闭包。
函数柯里化:把一个参数列表的多个参数,变成多个参数列表。
5.9 递归
object Recursion {
def main(args: Array[String]): Unit = {
println(fact(5))
println(taiFact(5))
}
// 递归实现计算阶乘
def fact(n: Int): Int = {
if(n == 0) return 1
fact(n - 1) * n
}
// 尾递归实现
def taiFact(n: Int): Int = {
def loop(n: Int, currRes: Int): Int = {
if(n == 0) return currRes
loop(n - 1, currRes * n)
}
loop(n, 1)
}
}
5.10 控制抽象
object ControlAbstraction {
def main(args: Array[String]): Unit = {
// 1.传值参数
def f0(a: Int): Unit = {
println("a: " + a)
println("a: " + a)
}
f0(23)
def f1(): Int = {
println("f1调用")
12
}
f0(f1)
println("============================")
// 2.传名参数,传递的不再是具体的值,而是代码块
def f2(a: =>Int): Unit = {
println("a: " + a)
println("a: " + a)
}
f2(23)
f2(f1())
}
}
结果分析:
- 传值参数是将12这个值赋值给a,因此f1只调用一次来取出值12。
- 传名阐述是将f1赋值给a,即有几个a,f1就会被调用几次。
六、面向对象
6.1 包对象
在scala中可以为每个包定义一个同名的包对象,定义在包对象中的成员,作为其对应包下所有class和object的共享变量,可以被直接访问。
6.2 封装
封装就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作。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的属性设置gexXXX和setXXX方法(通过@BeanProperty注解实现) 。
6.3 访问权限
6.4 构造器
和Java一样,Scala 构造对象也需要调用构造方法,并且可以有任意多个构造方法。
Scala类的构造器包括:主构造器和辅助构造器
6.5 构造器参数
object ConstructorParams {
def main(args: Array[String]): Unit = {
val student2 = new Student2
student2.name = "alice"
student2.age = 18
println(s"student2: name = ${student2 .name}, age = ${student2. age}")
val student3 = new Student3(name = "bob", age = 20)
println(s"student3: name = ${student3.name}, age = ${student3.age}")
val student4 = new Student4(name = "cary", age = 20)
// println(s"student4: name = ${student4.name}, age = ${student4. age}") //error
student4.printInfo()
val student5 = new Student5(name = "bob", age = 20, school = "niit")
println(s"student5: name = ${student5.name}, age = ${student5.age}")
student5.printInfo()
}
}
// 定义类
// 无参构造器
class Student2 {
//单独定义属性
var name: String = _
var age: Int = _
}
//上面定义等价于
class Student3(var name: String, var age: Int)
//主构造器参数无修饰
class Student4(name: String, age: Int){
def printInfo(){
println(s"student4: name = $name, age = $age")
}
}
class Student5(var name: String, var age: Int){
var school: String = _
def this(name: String, age: Int, school: String){
this(name, age)
this.school = school
}
def printInfo() {
println(s"student5: name = ${name}, age = $age, school = $school")
}
}
6.6 继承
基本语法(与Java相同):
class 子类名 extends 父类名 { 类体 }
(1) 子类继承父类的属性和方法
(2) scala 是单继承
继承的调用顺序:父类构造器->子类构造器。示例:
object Inherit {
def main(args: Array[String]): Unit = {
val student1 = new Student1(name = "alice", age = 18)
println("================================")
val student2 = new Student1(name = "bob" , age= 20, stdNo = "std001")
println("================================")
val student3 = new Student11(name = "bob" , age= 20, stdNo = "std001")
}
}
class Person1() {
var name: String = _
var age: Int = _
println("1.父类的主构造器调用")
def this(name: String, age: Int) {
this()
println("2.父类的辅助构造器调用")
this.name = name
this.age = age
}
def printInfo(): Unit = {
println(s"Person: $name $age")
}
}
//定义子类
class Student1(name: String, age: Int) extends Person1 {
var stdNo: String = _
println("3.子类的主构造器调用")
def this(name: String, age: Int, stdNo: String) {
this(name, age)
println("4.子类的辅助构造器调用")
this.stdNo = stdNo
}
override def printInfo(): Unit = {
println(s"Student: $name $age $stdNo")
}
}
//定义子类
class Student11(name: String, age: Int) extends Person1(name, age) {
var stdNo: String = _
println("3.子类的主构造器调用")
def this(name: String, age: Int, stdNo: String) {
this(name, age)
println("4.子类的辅助构造器调用")
this.stdNo = stdNo
}
override def printInfo(): Unit = {
println(s"Student: $name $age $stdNo")
}
}
运行结果:
6.7 多态
在Java中,属性是静态绑定的,而方法是动态绑定的。示例:
public class TestDynamicBind {
public static void main(String[] args) {
// 多态
Person person = new Worker();
System.out.println(person.name);// 静态绑定属性
person.hello();// 动态绑定方法
// person.hi(); // error
}
}
class Person {
String name = "person";
public void hello(){
System.out.println("hello person");
}
}
class Worker extends Person{
String name = "worker";
public void hello() {
System.out.println("hello worker");
}
public void hi(){
System.out.println("hi worker");
}
}
运行结果:
在scala中,属性和方法都是动态绑定的。示例:
object DynamicBind {
def main(args: Array[String]): Unit = {
val student: Person = new Student
println(student.name)
student.hello()
}
}
class Person{
val name: String = "person"
def hello(): Unit = {
println("hello person")
}
}
class Student extends Person{
override val name: String = "student"
override def hello(): Unit = {
println("hello student")
}
}
运行结果:
6.8 抽象属性和抽象方法
1)基本语法:
(1)定义抽象类: abstract class Person{} //通过abstract关键字标记抽象类。
(2)定义抽象属性: val|var name :String //一个属性没有初始化,就是抽象属性。
(3)定义抽象方法def hello():string //只声明而没有实现的方法,就是抽象方法。
2)继承&重写:
(1)如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类。
(2)重写非抽象方法需要用override修饰,重写抽象方法则可以不加override.
(3)子类中调用父类的方法使用super关键字
(4)子类对抽象属性进行实现,父类抽象属性可以用var修饰;
子类对非抽象属性重写,父类非抽象属性只支持val类型,而不支持var.
因为var修饰的为可变变量,子类继承之后就可以直接使用,没有必要重写。
object AbstractClass {
def main(args: Array[String]): Unit = {
val stu = new Student
stu.eat()
stu.sleep()
}
}
abstract class Person{
//非抽象属性
val name: String = "person"
//抽象局性
var age: Int
//非抽象方法
def eat(): Unit = {
println("person eat")
}
//抽象方法
def sleep(): Unit
}
//定义具体的实现子类
class Student extends Person {
//实现抽象同性和方法
var age: Int = 18
def sleep(): Unit = {
println("student sleep")
}
//重写非抽象属性和方法
override val name: String = "student"
override def eat(): Unit = {
super.eat()
println("student eat")
}
}
运行结果:
6.9 匿名子类
object AnnoymousClass {
def main(args: Array[String]): Unit = {
val person: P = new P {
override var name: String = "alice"
override def eat(): Unit = println("person eat")
}
println(person.name)
person.eat()
}
}
//定义抽象类
abstract class P{
var name: String
def eat(): Unit
}
6.10 单例对象(伴生对象)
Scala语言是完全面向对象的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。但是为了能够和Java语言交互(因为Java中有静态概念), 就产生了一种特殊的对象来模拟类对象,该对象为单例对象。若单例对象名与类名一致,则称该单例对象这个类的伴生对象,这个类的所有"静态"内容都可以放置在它的伴生对象中声明。
例1:
object Object {
def main(args: Array[String]): Unit = {
val student = new Student(name= "alice", age= 18)
student.printInfo()
}
}
// 定义类
class Student(val name: String, val age: Int){
def printInfo(){
println(s"student: name = ${name}, age = $age, school = ${Student. school}")
}
}
//件生对象
object Student{
val school: String = "niit"
}
运行结果:
例2:(类私有的情况)
object Object {
def main(args: Array[String]): Unit = {
// val student = new Student(name= "alice", age= 18)
// student.printInfo()
val student1 = Student.newStudent("bob", 19)
student1.printInfo()
val student2 = Student.apply("bob", 19)
student2.printInfo()
// 会默认调用apply方法
val student3 = Student("bob",19)
student3.printInfo()
}
}
// 定义私有类
class Student private(val name: String, val age: Int){
def printInfo(){
println(s"student: name = ${name}, age = $age, school = ${Student. school}")
}
}
// 伴生对象
object Student{
val school: String = "niit"
// 可以定义一个私有类的对象实例的创建方法
def newStudent(name: String, age: Int): Student= new Student(name, age)
def apply(name: String, age: Int): Student = new Student(name, age)
}
6.11 特质(Trait)
Scala语言中,采用特质trait (特征)来代替接口的概念,也就是说,多个类具有相同的特质(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明。
Scala中的trait中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin) 多个特质。这种感觉类似于Java中的抽象类。
Scala引入trait特征,第一可以替代Java的接口,第二个也是对单继承机制的一种补充。
1)特质基本语法:
一个类具有某种特质(特征) ,就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了extends 关键字,如果有多个特质或存在父类,那么需要采用with关键字连接。
没有父类:class 类名extends 特质1 with 特质2 with 特质3 …
有父类:class类名extends 父类 with 特质1 with 特质2 with 特质3…
2)说明:
(1)类和特质的关系:使用继承的关系。
(2)当一个类去继承特质时,第一个连接词是extends,后面是with。
(3)如果一个类在同时继承特质和父类时,应当把父类写在extends后。
3)示例代码:(存在问题)
object Trait {
def main(args: Array[String]): Unit = {
val student: Student = new Student
student.sayHello()
student.study()
student.dating()
student.play()
}
}
// 定义一个父类
class Person {
val name: String = "person"
var age: Int = 18
def sayHello(): Unit = {
println("hello from:"+ name)
}
}
// 定义一个特质
trait Young {
// 声明抽象和非抽象属性
var age: Int
val name: String = "young"
// 声明抽象和非抽象的方法
def play(): Unit = {
println("young people is playing")
}
def dating(): Unit
}
class Student extends Person with Young {
// 实现抽象方法
def dating(): Unit = println(s"student $name is dating")
def study(): Unit = println(s"student $name is studying")
// 重写父类方法
override def sayHello(): Unit = {
super.sayHello()
println(s"hello from: student $name")
}
}
运行结果:
因为在Person类以及特质Young中均定义了name属性,因此不知道应该取哪个属性值,会报错,因此需要重写冲突的属性。
object Trait {
def main(args: Array[String]): Unit = {
val student: Student = new Student
student.sayHello()
student.study()
student.dating()
student.play()
}
}
// 定义一个父类
class Person {
val name: String = "person"
var age: Int = 18
def sayHello(): Unit = {
println("hello from:"+ name)
}
}
// 定义一个特质
trait Young {
// 声明抽象和非抽象属性
var age: Int
val name: String = "young"
// 声明抽象和非抽象的方法
def play(): Unit = {
println(s"young people $name is playing")
}
def dating(): Unit
}
class Student extends Person with Young {
// 重写冲突的属性
override val name: String = "student"
// 实现抽象方法
def dating(): Unit = println(s"student $name is dating")
def study(): Unit = println(s"student $name is studying")
// 重写父类方法
override def sayHello(): Unit = {
super.sayHello()
println(s"hello from: student $name")
}
}
运行结果:
4)示例二,动态添加特质(只针对当前new的对象的特质)
object Trait {
def main(args: Array[String]): Unit = {
//动态混入
val studentWithTalent = new Student with Talent {
override def dancing(): Unit = println("student 1s good at dancing")
override def singing(): Unit = println("student is good at singing")
}
studentWithTalent.sayHello()
studentWithTalent.play()
studentWithTalent.study()
studentWithTalent.dating()
studentWithTalent.dancing()
studentWithTalent.singing()
}
}
// 定义一个父类
class Person {
val name: String = "person"
var age: Int = 18
def sayHello(): Unit = {
println("hello from:"+ name)
}
}
// 定义一个特质
trait Young {
// 声明抽象和非抽象属性
var age: Int
val name: String = "young"
// 声明抽象和非抽象的方法
def play(): Unit = {
println(s"young people $name is playing")
}
def dating(): Unit
}
trait Talent {
def singing(): Unit
def dancing(): Unit
}
class Student extends Person with Young {
// 重写冲突的属性
override val name: String = "student"
// 实现抽象方法
def dating(): Unit = println(s"student $name is dating")
def study(): Unit = println(s"student $name is studying")
// 重写父类方法
override def sayHello(): Unit = {
super.sayHello()
println(s"hello from: student $name")
}
}
6.12 特质自身类型
1)说明
自身类型可实现依赖注入的功能
2)案例
object TraitSelfType {
def main(args: Array[String]): Unit = {
val user = new RegisterUser("alice" ,"123456")
user.insert()
}
}
// 用户类
class User(val name: String, val password: String)
trait UserDao {
// 自身类型
_: User =>
// 向数据库插入数据
def insert(): Unit = {
println(s"insert into db: ${this.name}")
}
}
// 定义注册用户类
class RegisterUser(name: String, password: String) extends User(name, password) with UserDao
6.13 类型的检查和转换
(1) obj isInstanceOf[T]:判断obj是不是T类型。
(2) obj asInstanceOf[T]:将obj强转成T类型。
(3) classOf 获取对象的类名。
6.14 枚举类和应用类
object test {
def main(args: Array[String]): Unit = {
// 测试枚举类
println(Workday.MONDAY)
}
}
// 定义枚举类
object Workday extends Enumeration {
val MONDAY = Value(1, "Monday")
val TUESDAY = Value(2, "TuesDay")
}
// 定义应用类对象
object TestApp extends App {
println("app start")
type MyString = String
val a: MyString = "abc"
println(a)
}