Scala
Scala简介
Scala 是一种基于 JVM 的多范式编程语言,这里的范式可以理解为一种编程 风格,比如面向对象编程就是一种范式。常见范式包括:面向过程、面向对象、 泛型以及函数式编程。
dos窗口下测试 类似java编译机制
创建一个文件名为.scala的文件
object Test01 {
def main(args:Array[String]) = {
println("Hello,World")
}
}
一、Scala特点
1.1 静态类型
Scala的变量声明后不允许改变类型
,换句话说所有的变量和表达式在编译时就已经完全确定。这一点和Java是一样的。- 可以增强IDE的类型感知能力,适大型复杂系统开发
感知就是在使用的时候,就能够去获取它的类型 - 可读性好,可以声明时指定类型,且使用前必须确定类型
1.2 强类型
Scala是强类型语言
,一个变量被指定了某个人数据类型,如果不经过强制转换,name它就永远是这个数据类型,也就是说不允许隐式类型转换。意味着 Scala 是类型安全的语言,这点和 Java 也相同,但 Scala 在声明变量时一般无需显示指 定类型,Scala 编译器会自动推断出类型
Scala没有提供Java中的强制类型转换,取代方法有:
①、对象名.asInstanceOf[XXX]
②、对象名.toXXX 方法
③、隐式转换(implicit 关键字)
1.3 面向对象编程(OOP)
Scala 中一切值都是对象,可以说 Scala 比 Java OOP 更纯粹。比如,Java 中类 对象(包含静态字段和方法)便不是纯粹的对象,因为类是一个抽象的概念,对 象是类的实例,是一个具体的概念。所以 Scala 中去除了 static 关键字,使用一 种特殊的对象来模拟类对象(伴生对象),使得类的所有静态内容都可以放置在 它的伴生对象中声明和调用。
二、伴生对象
伴生对象
package one
// 构造函数创建:变量在前,类型在后,自动映射到类里变成属性
class Scala_Basic(name:String,age:Int){
// 方法 变量写在类里
def show():Unit={
println(s"my name is $name, I'm $age") // 格式化
}
}
// 伴生对象(有同名类)
object Scala_Basic {
def main(args: Array[String]): Unit = {
// 为了把常量区分开,放在Object中,也体现了动静分离
// 创建变量的方式
var a = new Test("henrry",18)
a.show() // 调用方法
}
}
=> my name is henrry, I'm 18
三、Scala关键字
Java 与 Scala 关键字对比:
四、Scala变量和常量
val:定义常量(value)
var:定义变量(variable)
区别:val定义的参数是,不能被再次赋值,而var定义的参数可以被再次赋值。一般不严格说明时,变量和常量也统称为变量。变量是可变变量,而常量是 不可变变量。
示例代码:
var
val
package one
object Review {
def main(args: Array[String]): Unit = {
val a = 18
println(a)
var b = 10
println(b)
println("--------")
//这里如果写上a=10重新赋值会直接报错,执行不了
//a=10 这里是写写不了的,直接会报错
println(a)
b = 20
println(b)
}
}
可以看出val定义的常量a不能被改变,而var定义的变量b重新赋值后值改变了
变量的创建方式
在scala中,数值类型为一类类型(包括八大类型)
// 可以不指定类型,但是一定要赋值
var a = 1
var b:Int = 2
// 正确声明变量的方式
var a = 1
var b:Int = 2
var c:String = "henrry"
var g:Char = '男'
var s:Double = 80.0
var d:Int = _ // 没有值得时候可以给个下划线:_
val e:Int = _ // 这个时候就会报错,常量必须初始化
定义一个变量
var 变量名称:类型 = xxx
一般无需显示指定类型,Scala编译器会自动推断出类型
常量:
val 常量名称:类型=xxx
使用类型别名定义变量
type 类型别名 = 类型
type T = String
val name:T = "Json"
var m:String = _
m: String = null
自动判断值为null,如果不给值又不给类型,无法判断
五、Scala数据类型
- 整体上分为两类:
值类型(AnyVal)
和引用类型(AnyRef)
,超级父类为Any
,超级子类为Nothing
。 - 值类型和Java八大基本类型一致(byte、short、int、long、float、double、char、boolean),
注意在Scala中所有类型首字母都大写
,还有一个Unit类型,类似于Java中的void,除 了 scala.Unit 和 scala.Boolean 外其余都是数字类型。 - 所有的非值类型都是引用类型,,如果 Scala 运行中 Java 环境中,AnyRef 相当于 java.lang.Object。特别地,Scala 中的 String 类型等同于 java.lang.String。
- 值类型:直接存储值,在栈上存储其值。
- 引用类型:存储对其值得引用,在栈上存储地址,在堆上存储值。
- ==是值比较,eq(b)为地址比较
- AnyRef是所有引用类型的超类
- AnyVal是所有值类型的超类
- Nothing是所有值得子类
- Null是空引用
5.1 Unit
Unit 代表没有任何意义的值类型,他是 AnyVal 的子类型,仅有一个实例对 象“()”,叫做无用占位符,类似于 Java 中的 void 类型。
Null
- 类型Null只有一个对象(Java中null不是对象)
- 所有引用类型的子类
Nothing
- 所有类型子类,也是Null的子类
Unit
- 无意义的值类型
- 类似于Java中的void
- 在scala中有返回值(),scala中一切表达式都有值
结构图
一般来说用map较多
package two
object Test {
def main(args: Array[String]): Unit = {
// 声明了一个map
var map = Map("henrry" -> 18, "areiel" -> 20, "pola" -> 14)
map.keys.foreach(println)
}
}
=> henrry
areiel
pola
数组
package two
object Test {
def main(args: Array[String]): Unit = {
var arr = Array(11,2,33,42,14)
// 模拟数组下标,用下标做一个数组
var ixArr = Range.apply(0,arr.length)
arr.foreach(x=>print(s"$x\t"))
println()
ixArr.foreach(x=>print(s"$x\t"))
}
}
=>输出结果
11 2 33 42 14
0 1 2 3 4
Range
Range 区间类型。本质上就是一种特殊的Array
可以使用 range() 方法来生成一个区间范围内的数组。range() 方法最后一个参数为步长,默认为 1,可以参照上面的代码。
var arr = Range.apply(1,5)
for (elem <- arr) {
print(elem+"\t")
}
=>输出1 2 3 4
可以理解为1 until 5
测试一下
var arr = Range.apply(1,5)
print(arr)
=>输出Range 1 until 5
六、Scala字符串插值(拼接/插值器)
Scala 为我们提供了三种字符串插值的方式,分别是 s, f 和 raw
。其本质都 是定义在 StringContext 中的方法
。
① # s 利用之后学习的隐式转换实现
IDE下测试
// $符号可以理解为引用值
val name = "henrry"
println(s"my name is $name")
=> my name is henrry
println(s"1 and 1 eq ${1+1}")
=> 1 and 1 eq 2
或者可以这么写,声明了变量类型(可写可不写)
在Scala中声明变量和常量不一定要指明数据类型,在没有指明数据类型的情况下,其数据类型是通过变量或常量的初始值推断出来的。但是一定要赋初值
var x:Int = 2
println(x)
=> 2
② # f 简单格式化
var name = "henrry";var age = 19;var score=88.123;var gender = '男'
println(f"name=$name%s,age=$age%d,score=$score%.2f,gender=$gender%c")
=> name=henrry,age=19,score=88.12,gender=男
③ # raw 即所见所得,不转义
println(s"a\tb\nc\td")
println("------")
println(raw"a\tb\nc\td")
=> a b
c d
------
a\tb\nc\td
④、模式匹配
类似于switch case
var score = 88
var rst = score.match {
// i就是score,是隐式参数传进来的
case i if i>=80 =>"A"
case i if i>=70 =>"B"
case i if i>=60 =>"C"
case i =>"D" // 相当于default
println(rst)
}
=> 输出结果 A
字符串
var line:String = """"""
var s = 'Abc // 符号变量
println(s.isInstanceOf[scala.Symbol])
=>true
DOS窗口下测试
scala> val a = 20
a: Int = 20
scala> a
res1: Int = 20
scala> print(s"a的变量为:$a")
a的变量为:20
scala> print(s"a+a的变量为:${a+a}")
a+a的变量为:40
scala> val height = 1.8
height: Double = 1.8
scala> val name = "zhangsan"
name: String = zhangsan
scala> print(f"$name%s的身高为$height%2.2f")
zhangsan的身高为1.80
scala> print(f"$name%s的身高为$height%2.3f")
zhangsan的身高为1.800
scala> print(raw"$name \n $height")
zhangsan \n 1.8
scala> val x = 10
x: Int = 10
scala> if(x>0){ print("x大于0")}
x大于0
scala> print(f"$name%s的身高为$height%.3f")
zhangsan的身高为1.800
scala> print(f"$name%s的身高为$height%5.3f")
zhangsan的身高为1.800
针对5.3f前面的5是什么意思
scala> print(f"$height%10.3f")
1.800
scala> print(f"$height%1.3f")
1.800
指的是前面的空格数
七、函数的定义
- 函数的定义以def开始,然后是函数名,类似于 def main
- 跟着的是括号里使用逗号分隔的参数类型键值对,类似x:Int,y:Int
- 下面的代码表示maxInt函数带有两个参数:x和y,都是Int类型,在函数列表后,会使用"返回值类型="的形式声明函数的返回值类型,我的理解是,
一切函数都是值
,最后的肯定是一个值,如果需要返回值,则需要用返回类型接收确认返回值。
定义格式:
def functionName ([参数列表]) : [return type] = {
function body
}
def maxInt(x:Int) = {
if(x > y) x else y
}
// 调用函数
val m = maxInt(6,9)
println(m)
=> 9
函数
函数有23种,由trait定义
package one
object Process2 {
// 简洁的写函数
val func = (x: Int, y: Int) => {
x + y
// 最后一句话是什么,返回值就是什么,没有return
}
// 完整的去写一个函数
val fun2 = new Function2[Int, Double, Double] { // 泛型 ,输出类型为Double型
override def apply(v1: Int, v2: Double): Double = v1 + v2
}
def main(args: Array[String]): Unit = {
println(func(1,2))
println(fun2(1,2))
}
}
=> 输出都为3
类型转换
Boolean a = obj.instanceOf[Xxx]
Xxx a = Obj.asInstanceOf[Xxx]
obj.toXxx
隐式转换(implicit Xxx)
package one
object Process2 {
}
def main(args: Array[String]): Unit = {
var a : String = "henrry"
println(a.isInstanceOf[String]) // isInstance:判断是不是指定类型
=>返回true
println(a.isInstanceOf[Int])
=>返回false
// 尝试转换类型
var a : String = "henrry"
println(a.isInstanceOf[String])
if(a.asInstanceOf[Boolean]){
println("ok")
}
//这里运行会直接报错,不给转,也因此证明了scala是强类型
}
// 创建一个数组
var arr:Array[Int] = Array(1,5,3,2)
println(arr.toList)
=> List(1, 5, 3, 2)
println(arr.toString)
=> [I@6bdf28bb
println(arr.toSet)
=> Set(1, 5, 3, 2)
// 万物皆可to,直接转换
}
定义一个函数
一切函数都为值
可以将函数作为参数和返回值
object Process2 {
val fun2 = new Function2[Int, Double, Double] {
override def apply(v1: Int, v2: Double): Double = v1 + v2
}
def main(args: Array[String]): Unit = {
// 定义一个方法
def add(x:Doubele,y:Int):Double = {
println(s"$x + $y = ${x+y}")
x + y
}
println(add(fun2(1,2)),3) 先调用fun2的方法,得到一个结果,再将这个结果作为add方法的输入最终得到一个结果
=> 输出6.0
3.0 + 3 = 6.0
6.0
// 验证了一切函数都为值
}
}
方法和函数的区别
- 方法有返回值,函数不写返回值
- 函数归根结底就是值,可以作为参数传进方法里
// 方法有返回值,函数不写返回值
// 函数归根结底就是值,可以作为参数传进方法里
val add = (x:Int,y:Int)=>{ // 如果这里写了返回值会报错
x+y
}
// 方法一般这么写
// 方法标准书写是 def 方法名(参数)[泛型]
def m1(x:Int,y:Int):Int = {
x+y
}
println(add(1,2)) => 3
m1(1,2) => 3
println(m1(add(1,2),3)):将函数作为参数传进去,其实就是传值
// add(1,2)--> m1(3,3)
// 函数作为参数传递
def m2(f:(Int,Int)=>Int,x:Int,y:Int):Int = f(x,y)
println(m2(add, 1, 2))
}
// 函数作为返回值
def m3():(Int,Int)=>Int=(x:Int,y:Int)=>x+y
println(m3()(2,3))
andThen && compose
// 写两个简单函数
val f1 = (x: Int) => x + 3
val f2 = (x: Int) => x * 3
val f3 = (x: Int) => x-1
// 柯里化 :一个函数的输出作为下一个函数的输入
def f = f1 andThen f2 andThen f3
println(f(2)) // 这两个函数先后调用 f1函数的输出是f2函数的输入
=>输出14
def f3 = f1 compose f2
println(f3(2)) // 先执行f2再执行f1
=>输出9 2*3+3
先后执行顺序的不同
八、程序控制
8.1 条件控制(重点if)
var b : Int = 5
var a = if(b>5) "a" else 2.0
=> 2.0
val x = 9
val y = if(x==10) x+1
println(y )
=> ()
如果说if后面没有else,则默认else部分的值是Unit,返回()
8.2 块表达式{}
也是一个结果,{}中最后一行的结构就是快表达式的结果
var a = {
var a =6;var b = 5;
a*b // 把想要的值写在最后一行就行,包括函数,包括方法
}
println(a)
=> 30
#赋值语句的结果为Unit
var x = 10
var y = {
x = x * 2
//x*2
}
println()
=> ()
// 创建代码块时即创建了一个作用域,在代码块内可以访问代码块外的实体 (变量、函数、类),反之不成立。因为最后一个是表达式,不是值,输出为Unit,但是当我加上了x*2后,一切都是值,这个时候就可以打印值40
九、for循环
9.1 单循环
循环
for (i <- 表达式) {
statements
}
#Range min to/until max by step
to 包含max,until不包含max
// 循环
// 像数组但不是数组,如果要转为数组,直接在后面.toArray
var arr = Range.apply(1,10,2) // 2代表的是步幅为2
for (elem <- arr) {
println(elem)
}
// 更为简洁的一种方式
for (elem <- 1 until 10 by 2) { // 直接 1 to 10 by 2.for 就k可以出来
print(elem)
}
=>输出结果为 1 3 5 7 9
- 首先,
变量 i 不需要声明
。严格来说是 i 前面不允许有 val 或 var。i 可以是作 用域内的现有变量。
- 其次“<-”是关键字,非操作符,类似 Java 中的“:”。
- 最后表达式可以是数组、元组、集合。在 Scala 中 Range 是常用的集合类型, 表示范围区间。
Range min to/until max by step :指的是从最小值到最大值,步长为多少
to与until的区别:to是到多少,可以取到最大值,until是直到多少,取不到最大值
to:[]
until:[)
案例:
对这个值更进一步处理
// if和前一个语句之间可加分号,可不加分号,验证都没有错误
for (i <- 0 to 10 by 2 if i % 3 == 0) {
println(i)
}
// 首先从0 2 4 6 8 10中选择,可以理解为这是第一个函数的值,然后作为下一个函数的输入,筛选符合取模3等于0的数,最后筛选出 0 6
=> 0 6
for (i <- 0 to 10 by 2 if i % 3 == 0 if i > 5) {
println(i)
}
=> 6
// 首先根据上面的代码可以知道,筛选出0,6 再作为最后一个函数的入口参数,最后可以得到6
for (i <- 1 to 20 if i % 2 == 0; j <- 1 to i if j % 3 == 0) {
println(s"$i\t$j")
}
=>4 3 部分结果
6 3
6 6
8 3
for (elem <- 1 to 100;if elem%7==0;if elem%3==0) { // ;两个条件都要满足
println(elem)
}
=> 21 42 63 84
9.2 二重循环
for (i <- 表达式1;j <- 表达式2;....){
statements
}
案例:
// 小九九
for (i <- 1 until 10;j<-1 to i) {
print(s"$j * $i = ${j * i}\t")
if (j==i){
println()
=>输出结果
1 * 1 = 1
1 * 2 = 2 2 * 2 = 4
1 * 3 = 3 2 * 3 = 6 3 * 3 = 9
1 * 4 = 4 2 * 4 = 8 3 * 4 = 12 4 * 4 = 16
1 * 5 = 5 2 * 5 = 10 3 * 5 = 15 4 * 5 = 20 5 * 5 = 25
1 * 6 = 6 2 * 6 = 12 3 * 6 = 18 4 * 6 = 24 5 * 6 = 30 6 * 6 = 36
1 * 7 = 7 2 * 7 = 14 3 * 7 = 21 4 * 7 = 28 5 * 7 = 35 6 * 7 = 42 7 * 7 = 49
1 * 8 = 8 2 * 8 = 16 3 * 8 = 24 4 * 8 = 32 5 * 8 = 40 6 * 8 = 48 7 * 8 = 56 8 * 8 = 64
1 * 9 = 9 2 * 9 = 18 3 * 9 = 27 4 * 9 = 36 5 * 9 = 45 6 * 9 = 54 7 * 9 = 63 8 * 9 = 72 9 * 9 = 81
#注意:如果()中有多个表达式,可以使用{}代替,换行写多条语句
for {i <- 1 to 20 if i % 2 == 0
j<-1 to 20 if j%3==0}{
println(s"$i\t$j")
}
把for循环所有的结果保存到一个集合中,也称for推导式
#推导式:yield循环的结果输出生成集合Vector
var r = for (i <- 1 to 15 if i % 3 == 0 || i % 7 == 0) yield i
r.foreach(println)
=> 3679121415
yield的作用:把i一个一个取出来放到r中
println执行的是r中的元素
print(r)
=> Vector(3, 6, 7, 9, 12, 14, 15)
如果这么写
r.foreach(x=>println(x+2)) // x就相当于形参,所有值都加2
5
8
9
11
14
16
17
十、循环跳转语句
scala中没有break和continue,需要手动导包
//scala中没有break,continue 需要手动导包
import scala.util.control.Breaks._ // ._相当于.*
breakable {
for (elem <- 1 to 100) {
println(elem)
if (elem%17==0){
break()
}
}
}
十一、数据结构
在 Scala 中,对象一般都有两种方式创建,一种使用类创建,一种使用其对 应的伴生对象创建,区别在于是否使用 new 关键字。本章暂不涉及 OOP 的内容, 了解伴生对象创建的方式即可。
Scala数据结构是非常重要的知识点,并且Scala中的数据结构远比Java丰富、 复杂。但是基本原理都是相同的,比如说数组、栈、队列、链表、树、散列表。 这里主要关注线性结构,包括 Array、Seq、List、Map 及 Set 等。
对 比 Java , Scala 中 的 数 据 结 构 都 强 调 了 是 可 变 还 是 不 可 变 , 即 mutable/immutable,默认情况下,Scala 会为用户选择不可变的数据结构。两者 的差别在于:
不可变:初始化后内容不再发生变化。
可变:初始化后内容可以发生变化。
#动态内容
Class A… (变量和方法)
var a:A = new A();
#静态内容
object A… (常量)
十二 、数组
线性结构 Array、Seq、List、Map、Set
scala中有mutable和immutable两种类型,而java中的结构容量都是可变的
内容可变
数组(容量不可变)
1、创建数组的标准方式
var arr: Array[Int] = new Array[Int](5) // 定义数组length=5
for (i <- 0 until arr.length) {
arr(i) = Random.nextInt(100) // 随机数100以内
}
=>遍历所有值: arr.foreach(println)
=>遍历某一个值: println(arr(2))
可以发现,5加到了最后,原来的数组没有变,返回了一个新数组
var a2 = arr:+5
a2.foreach(println)
=> 输出
71
90
40
69
32
5
var a3 = 7+:arr
a3.foreach(println)
2、维度
// 二维
var arr = Array.ofDim[Int](2, 3)
for (i <- 9 until arr.length; j <- 0 until arr(i).length){
arr(i)(j) = Random.nextInt(100)
}
arr.foreach(x => {
x.foreach(x => print(s"$x\t"))
println()
})
=>输出结果
50 71 35
83 2 2
3、可变数组
需要导包
import scala.collection.mutable.ArrayBuffer
var buffer = new ArrayBuffer[Int]
println(s"size = ${buffer.size}")
for (i <- 1 to 10) {
// buffer.append(Random.nextInt(100)) 可以写成
buffer+=Random.nextInt(100)
}
println(s"size = ${buffer.size}")
buffer.foreach(println)
/**
* append是往后加
* prepend是往前加
*/