Scala(二) 基础语法

一:创建Main类

右键src/main/scala -> New -> Scala Class -> 输入名称,Kind选择Object(注意不是Class),在类中输入mian会有提示回车就定义了一个main方法的签名
在这里插入图片描述
Scala对比与一下Java,给人的感觉就是换了一种书写形式而已,Scala使用def关键字来定义方法,就像Javascript使用function关键字来定义函数一样,都是用一个关键字来定义函数,具体用什么关键字都无所谓。Scala方法的参数列表中的参数名在前,参数的数据类型在后面,参数名和参数数据类型用冒号分隔,这和Swift中的语法是完全一致的。对于数组的定义形式两者只是在书写上不一致,Scala使用Array类来定义数组,将数组元素的数据类型放到中括号里面。Scala将返回值写在参数列表后面,并通过冒号将参数列表和参数返回值分割开,在Scala中使用Unit来代表方法没有返回值,就像java中使用void来代表没有返回值一样。

像这种书写形式上的不同,一般在别的语言中都能找到相似的语法,特别是新兴的现代语言中如Swift、Kotlin、Go等语言都会出现一些新的语法,新的书写形式,如果你了解的语言多了,这些语法一看就懂,不懂也能猜的差不多。
在这里插入图片描述

main方法的两种入口

main方法的入口可以直接在class中定义main方法,也可以直接继承App,两种方式都可以运行程序。

class Test {
  def main(args: Array[String]): Unit = {
    println("class main method")
  }
}

object Test extends App {
  println("App类中定义了main方法, 继承自App中的主构造器代码会自动加到main函数中来执行")
}

二:基础语法

1. 变量声明

var用于定于变量,val用于定义常量语法为关键字(var|val) 变量名: 数据类型 = 值, 在Scala中推荐多使用val来定义,其中数据类型可以根据赋值来类型推断出来,因此数据类型是可以省略的,类型推断已经不是什么新概念了,在Swift等其它语言中就有。在Scala中的每行的结尾的分号也不是必须的,可以省略,省略分号这种语法在现代编程语言中几乎是标配。在Scala中数据类型都是对象类型,没有基本类型,即所有的数据类型的首字母都是大写,如Double,在scala中是没有double这种基本数据类型的。在Scala中有一种数据类型叫Any表示任意一种数据类型,类型Java中的Object类型。

// 语法   

var foo: String = "hello world"
var foobar = "hello world"
// Any代表任意类型,类型与Java中的Object类型,所以将一个Boolean类型的值赋值给Any类型也是可以的
var foobaz: Any = true

val pi: Double = 3.14

2. 数据类型

在Scala中没有基本数据类型,所有类型都是类似于Java中的对基本类型的包装类型。如Int、Double、String等,具体都有哪些数据类型,差不多和Java中有的包装类型Scala中都有。

在Scala中有一种前缀为Rich的类型,如RichInt,RichInt是对Int类型的增强,提供了一些比较常用的方法,如to方法就是产生一个连续的数字集合。每种类型都对应一种Rich类型,Rich类型和对象类型可以自动装箱和拆箱,即可以自动隐式的相互转换。

var age: RichInt = 18

// 1.to(10) 产生一个连续的区间范围集合:[1, 2, 3, 4, 5, ,6 7, 8, 9, 10]
// foreach 用于循环一个集合,println用于打印循环的每一个元素
1.to(10).foreach(println)
Option可选类型

在传统开发中当获取一个变量的值,如果值为null,此时就不知道这个值究竟是没有赋值时返回的默认值null,还是有值这个值就是用户显式赋值的null; 这两种情况是分不开的,为了解决这种问题,在现代编程语言中基本上都引进了一种叫做"可选类型"的数据类型(JDK1.8中的Optional和Swift中的Optional等都引进了),在Scala中使用Option来表示,支持泛型,这种类型有两种状态:

  • 有值状态,当有值时使用Some(T)类来表示有值状态,可以通过调用Some对象的get方法获取具体的值
  • 无值状态,当没有值时使用None类来表示一个空对象
var opt: Option[String] = None
println(opt)
println(opt.getOrElse("default value"))
// 检查值是否None,是返回true
println(opt.isEmpty)

var opt2: Option[String] = Some("Hello")
println(opt2)
println(opt2.get)

var opt3: Option[String] = Option("Hello")
println(opt3)
println(opt3.get)

3. 基本操作符

在Scala中基本操作符都会被定义为一个方法,可以通过查看Int源码得知,这一点和Java不一样。1+1也可以写成1.+(1) 其中点号用于方法调用,+字符是方法的名称,小括号是实参。在其它编程语言中有的可以重写操作符的实现,就是通过重写操作符方法来实现的。

在Scala中没有++等此类的运算符,但是有+=这种方式,如 i++ 写成 i += 1

在这里插入图片描述

4. if else 语句有返回值

在Scala中if else是有返回值的,默认代码块的最后一句代码的结果作为返回值,在代码块中不用使用return关键字。如果if else中没有返回值则默认为Unit, 用()来表示,在Scala中如果代码块中只有一句代码可以省略大括号,这在很多语言中都允许的,Java也是允许的,只是Java规范不推荐使用。

// helloworld
val reuslt = if (true) {
	  "hello " + "world"
	} else {
	  666
	}

// 返回值类型可以不一样
val reuslt = if (true) "a" else 666

// 没有返回值,result的结果为 ()
val reuslt = if (true) {
    println("if")
  } else {
    println("else")
  }

// 如果if else 代码块没有返回值,Scala就使用Unit作为默认返回值,用() 来表示没有返回值
val reuslt = if (true) {
    println("if")
    ()
  } else {
    println("else")
    ()
  }

5. for 循环

在循环中使用左箭头 <- 来分割集合元素和集合,for循环支持if守卫条件,每执行一次就判断一下元素是否满足条件。在for循环中是没有break关键字的,必须通过调用scala.util.control.Breaks下的break方法,而且for循环还必须写在breakable方法中。


for (i <- 0.to(10)) {
  println(i)
}

// 0.to(10) 也可以简写成0 to 10
for (i <- 0 to 10) {
  println(i)
}

// 循环字符串
for (i <- "abc") {
  println(i)
}

// 在for中加if条件这种书写格式自己感觉还不如平常使用的java那种格式简洁
for (i <- 0 to 10 if (i > 5)) {
  println(i)
}

// 循环数组
val arr = Array(1, true, "Scala", 66.6)
for (ele <- arr) {
  println(ele)
}

// 在Scala中导入可以具体到类下面的方法,如果导入某个类的所有方法使用下划线表示 _
import scala.util.control.Breaks._
// 要想使for循环具有break的能力必须将循环代码写在breakable()方法中,在循环的内部调用break()方法
// 不是Scala的一行代码抵Java多行代码吗?怎么书写这么麻烦,搞不懂为啥这么做,自己也不喜欢这种语法,第一次遇到这种语法
breakable({
  for (i <- 0 until 10) {
    println(i)

    if (i > 5) {
      break()
    }
  }
})

三:方法

在Scala中使用def关键字来表示方法,方法的返回值不需要使用return关键字,最后一行代码的执行结果即为返回值, 如果没有返回值则返回Unit,表示不返回值,相当于Java中的void。返回值类型不是必须的,也可以省略,Scala会自动推断出方法的返回值类型。

关于方法的名称:在其它语言中方法名称一般是不包含特殊字符的(如 : +: -: :\ &~ /:),但是在Scala中方法名称是允许特殊字符的,特别是在操作集合时,系统还提供了一些只有特殊字符的方法,这虽然简洁,个人感觉可读性不好,有点另类,有点排斥。

// [ ] 表示可选,非必须的 
def 方法名称(参数名:参数类型, 参数名:参数类型)[: 返回值] = {
	// 方法体
}

// 方法定义的另外一种方式,将参数列表中的参数名和参数类型分开定义,和Objective-C中的匿名代码块、Java中的Lambda表达式类似
def 方法名称:(参数类型列表) => 返回值 = {
	(参数名称列表) => {
		// 方法体
	}
}
// 函数定义:方式一
def sum(x: Int, y: Int): Int = {
  x + y
}
val result = sum(1, 1)


// 函数定义:方式二(此种方式也很常用)
def sum: (Int, Int) => Int = {
  (x, y) => {
    x + y
  }
}

// 定义了一个sum方法接收一个参数,方法的返回值类型是一个方法,该方法接收一个Int参数并且返回值类型为Int,即Int => Int
def sum(x: Int): Int => Int = {
  (y: Int) => {
    x + y
  }
}
val result = sum(1)(2)


// 对上面方法的定义省略返回值,看起来更简洁一些
def sum(x: Int) = {
  (y: Int) => {
    x + y
  }
}


// 当方法体只有一行代码时也可以省略掉方法体的大括号,这样看起来更加简洁一些
def sum(x: Int) = (y: Int) => { x + y }
// 继续省略方法返回值中的方法体的大括号,再简洁一下
def sum(x: Int) = (y: Int) =>  x + y


// 柯理化函数:将参数列表中的多个参数分成单个参数
// 此种方式是上面书写的另一种方式,效果都一样,只是书写形式不同而已
def sum(x: Int)(y: Int): Int = {
  x + y
}
// 调用的时候也要分别传递实参
val result = sum(1)(2)


def foo() = {
  println("无参方法在调用时可以使用小括号,也可以省略")
}
foo()
// 调用时也可以省略小括号直接使用方法名称来调用
foo

def foo = {
 println("没有参数列表在调用时只能通过方法名称来调用,不能使用小括号来调用")
}
foo

def foo {
    println("方法没有参数可以直接省略参数列表,没有返回值可以省略掉=返回值语法,后面直接跟大括号")
}
foo

// 递归函数:函数体内调用自己,求n + n-1 + n-2 + ... + 1 的和
def sum(n: Int): Int = {
  if (n <= 0) 0
  else n + sum(n - 1)
}   
// sum(3) = 3 + sum(2) = 3 + (2 + sum(1)) + 3 + 2 + (1 + sum(0)) = 3 + 2 + 1 + 0 = 6
sum(3) 

// *号表示可变参数,可变参数放到参数列表的最后面,当做数组使用
def sum(values: Int*) :Int = {
  var result = 0;
  for (i <- values) {
    result = result + i
  }
  result
}
sum(1, 2, 3)

// 参数默认值,一般放到后面,把没有默认值的放到参数列表前面
def foo(name: String, gender: String = "保密"): Unit = {
  println(name + " " + gender)
}
// 按照参数定义的顺序传值
foo("mengday")
// 带参数名调用:显式指定参数名对应的参数值,这种方式可以不按照参数列表中参数的定义,可以是任意顺序的
foo(gender = "mengday", name = "male")


// 方法嵌套
def foo(): Unit = {
	println("foo method")
	def bar(): Unit = {
	  println("bar")
	}

	bar
}
foo()

四:函数

一般在面向过程的语言中如C语言将“方法”称之为函数,一般在面向对象的语言中如Java将“方法”称之为方法,在Scala中明确了两者的概念,在Scala中方法使用def定义,一般方法体和方法的返回值使用=号分割。想Java语言中的Lambda表达式和Objective-C中的代码块就是Scala语言中的函数,函数使用箭头分割方法的签名和方法体。

函数也是一种数据类型,因此也可以作为值赋值给变量,也可以赋值为常量。

// 匿名函数
(参数列表) => {
	函数体
}

// 方式一
var sum = (x: Int, y: Int) => {
  x + y
}

// 方式二:(Int, Int) => Int 表示变量的数据类型,=号是赋值运算符,{ } 表示一个匿名的函数
// 方式二和方式一的区别在于参数名的位置不同
var sum2: (Int, Int) => Int = {
  (x, y) => {
    x + y
  }
}

// 可以通过 方法名 _ 语法可以显式将方法转化为函数
// 在scala中也会自动将方法隐式转换为函数
def sum(x: Int, y:Int): Int = {
  x + y
}
val calc = sum _
// Main$$$Lambda$1/764977973@1fbc7afb
// 从打印结果可以看到Lambda了,因此可以看出Scala中的函数就是对Java中的Lambda进行包装了一下而已
println(calc)


def sum(x: Int, y:Int): Int = {
  x + y
}
// 将函数作为方法的参数,参数名为func, 参数的数据类型为(Int, Int) => Int函数
def calc(func: (Int, Int) => Int, x: Int, y:Int) :Int = {
  func(x, y)
}
// sum虽然是函数,但是Scala会自动隐式的将方法转为函数
calc(sum, 1, 2)

// Scala提供的方法中有很多参数为函数类型,如foreach方法
val arr = Array(1, 2, 3)
arr.foreach((i: Int) => {
  println(i)
})
// 函数体只有一行代码可以省略大括号
arr.foreach((i: Int) => println(i))
// i的类型可以推断出来,所以可以省略
arr.foreach((i) => println(i))
// 只有一个参数时参数列表中的小括号也可以省略
arr.foreach(i => println(i))
// 使用下划线代表循环的每一个元素
arr.foreach(println(_))
// 最终也可以简写成这种形式, 在Java中可以写成list.forEach(System.out::println);
arr.foreach(println)

// filter也接收一个函数参数用于过滤数据
arr.filter(i => i > 2).foreach(println)
// 方法中的参数可以是函数类型
def func(f: () => String) = {
	println("方法的参数类型为函数类型")
	println(f())
}
func(() => "hi")


def myFunc(): (Int, Int) => Int = {
	println("方法的返回值类型为函数类型")
	return (x: Int, y: Int) => {
	  x + y
	}
}
val myFun = myFunc
val mySum = myFun(1, 1)
println(mySum)
      
      

// 方法的返回值为函数
def foobar(): Int => Int = {
	x => x + 1
}
val func = foobar()
println(func(10))

def closure(r: Double) :() => Double = {
	val pi = 3.12
	// 闭包:匿名函数可以访问外部变量
	return () => 2 * pi * r
}
val result = closure(1)()
println(result)

偏函数

偏函数: 一种特殊的函数,接收一个参数,返回一个值。一般参数的值是可以分类的(满足某些条件可以归为一类)或者可以穷尽的(如枚举值)。在偏函数中可以使用case来具体匹配输入的参数, 也可以通过下划线_来匹配所有值,也可以使用case定义一个变量,在变量后面可以使用if来判断变量值是否满足条件。

// 偏函数
val func: PartialFunction[Int, String] = {
	case 6 => "星期六"
	case 7 => "星期日"
	case weekday if weekday >=1 && weekday <= 5 => "工作日"
	case _ => "--"
}

// 星期六
println(func(6))
// 工作日:星期4
println(func(4))
// --
println(func(-1))

五:集合

1. 数组

数组分为:

  • 不可变长度数组Array
  • 可变长度数组ArrayBuffer
// 定义数组时指定类型
val arr = Array[Int](1, 2, 3)
// 定义数组时也可以不指定类型,默认为Any类型,即数组中可以存储任意类型的值
val arr2: Array[Any] = Array(1, 6.6, true, "Scala")
// 通过小括号指定下标这和其语言通过中括号[]不一样
arr2(1) = 8.8

// 指定数组的长度, 并且每个元素都有值,值为数组元素类型默认的值,Int默认为0,arr3 = [0, 0, 0]
var arr3 = new Array[Int](3)
arr3.foreach(println)
// 可以通过 ++ 将集合中的元素合并到一个新的数组中,注意:并不改变原有的数组
var result = arr ++ arr2


var arr = Array(1, 2, 3)
// 最大值,最小值
println(arr.max)
println(arr.min)

// 拼接字符串,可以指定start、step、end
println(arr.mkString("[", ",", "]"))
// 数组反转reverse
println(arr.reverse.toBuffer)
// map对数组中每个元素进行映射
arr.map(i => i * 2).foreach(println)


// 可变长度数组可以通过 += 来添加元素, 通过++= 添加不可变数组, 通过-=来移除指定的元素
val ab = ArrayBuffer[Int]()
ab += 1
ab += 2
ab ++= Array(3, 4)
ab -= 1
ab.append(5)
// remove的参数是下标
ab.remove(1)

val ab2: ArrayBuffer[Int] = ArrayBuffer(1,2, 3)

2. 序列

  • 不可变序列 List
  • 可变序列 ListBuffer
val list = List(1, 2, 3)
val newList = list ++ List(4, 5)
// 求和
println(list.sum)
// 最大值
println(list.max)
// 最小值
println(list.min)
// 第一个元素
println(list.head)
// 最后一个元素
println(list.last)
// 反转序列
println(list.reverse)
// 拼接序列中的元素
println(list.mkString(","))
// distinct 去重复
list.distinct.foreach(println(_))

val listBuffer = ListBuffer(1, 2)
listBuffer += 3
listBuffer ++= List(4, 5)
listBuffer ++= ListBuffer(6, 7, 9)
listBuffer -= 3
listBuffer.append(8)
// 根据下标来移除元素
listBuffer.remove(0)
// 从某个下标开始移除指定数量的元素
listBuffer.remove(0, 2)
// ArrayBuffer(5, 6, 7, 9, 8)
println(listBuffer.toBuffer)

var list = List(4, 5, 6)
// 向List的头部添加元素,不修改原来的List集合数据, 整体作为一个List集合作为元素添加到List中
// ArrayBuffer((1,2,3), 4, 5, 6)
var newList = list.::(1, 2, 3)
// ArrayBuffer((1,2,3), 4, 5, 6)
var newList2 = list.+:(1, 2, 3)
// ArrayBuffer((1,2,3), 4, 5, 6)
var newList3 = (1, 2, 3) :: list
// ArrayBuffer((1,2,3), 4, 5, 6)
var newList4 = (1, 2, 3) +: list
// ArrayBuffer(1, 2, 3, 4, 5, 6)
var newList5 = List(1, 2, 3) ++ list

// 尾部添加
// ArrayBuffer(4, 5, 6, (1,2,3))
var newList6 = list.:+(1, 2, 3)
// ArrayBuffer(4, 5, 6, 1, 2, 3)
var newList7 = list ++ List(1, 2, 3)

3. Set

var set = Set(1, 2, 3, 4, 5)
var set2 = Set(2, 4, 5)

// 元素个数
println(set.size)
println(set.head)
println(set.last)
println(set.sum)
println(set.max)
println(set.min)

// 并集: 可以通过 union、 ++、| 三种方式实现
val unionSet = set.union(set2)
val unionSet2 = set ++ set2
val unionSet3 = set | set2
println(unionSet.toBuffer)
println(unionSet2.toBuffer)
println(unionSet3.toBuffer)

// 差集
val diffJoin = set.diff(set2)
println(diffJoin.toBuffer)

// 交集: 可以通过intersect或者& 方法都可以实现交集
val interSet = set.intersect(set2)
var interSet2 = set & set2
println(interSet.toBuffer)
println(interSet2.toBuffer)

// 是否子集
val bool = set2.subsetOf(set)
println(bool)

// 添加集合
var set3 = set + (1, 6)
// 删除集合
var set4 = set - (1, 5)

// 默认使用的Set是不可变的,可变Set在mutable包下
var mutableSet = scala.collection.mutable.Set(1, 2, 3)
mutableSet += 4
mutableSet -= 2

mutableSet ++= Set(4, 5)
mutableSet --= Set(4, 2)

4. Tuple 元组

val tuple1: Tuple4[Int, Double, String, Boolean] = (1, 1.1, "Scala", true)
// 只有2个元素的被称之为"对偶元组"
val tuple2 = new Tuple2(1, "Tuple")
val tuple3 = (1, "Scale Tuple")

// 通过调用下划线下标的方式来获取指定下标的值
val value1 = tuple1._1
val value2 = tuple1._2
// 元组转为字符串(1,1.1,Scala,true)
val str = tuple1.toString()

// 使用productIterator来迭代元组
tuple1.productIterator.foreach(println(_))

// 交换元组中两个值的顺序 只有Tuple2有该方法
println(tuple2.swap)

// 元组模式匹配
var (name, age, gender) = ("mengday", 28, "male")
println(name)
println(age)
println(gender)

// 数组的拉链操作:将相同下标的元组组成元组放到数组中 (0,星期一), (1,星期二), (2,星期三)
val arr1 = Array("0", "1", "2")
val arr2 = Array("星期一", "星期二", "星期三")
val result:Array[(String, String)] = arr1.zip(arr2)
println(result.toBuffer)

5. Map

val map: Map[Char, Int] = Map()
// 通过元组方式创建Map
val map2 = Map(("key1", "value1"), ("key2", "value2"))
// 通过箭头方式创建Map
var map3: Map[String, String] = Map("red" -> "#FF0000", "azure" -> "#F0FFFF")
// 箭头和元组混合方式
var map4 = Map("red" -> "#FF0000", ("key3", "value3"))

map4 += ("blue" -> "#0000FF")
var map5 = map2 ++ map3

var mutableMap = scala.collection.mutable.Map("a" -> "A")
mutableMap.put("b", "B")
mutableMap += ("c" -> "C", "d" -> "D")
mutableMap += (("e" -> "E"), ("f" -> "F"))
mutableMap ++= scala.collection.mutable.Map("g" -> "G")
mutableMap -= "e"
mutableMap --= Set("b", "c")
mutableMap.remove("f")


val keys = map5.keys
val values = map5.values
// key不存在报错
val value1 = map5("key1")
// key存在返回Some 和 key不存在返回None
val valueOpt: Option[String] = map5.get("key1")
println(valueOpt.get)
println(map5.getOrElse("key11", "defalut value11"))

val isContain = map5.contains("key1")
val empty = map.isEmpty

// 插入时是有序的
val linkedHashMap = scala.collection.mutable.LinkedHashMap[String, String]()
linkedHashMap += ("d" -> "D")
linkedHashMap += ("a" -> "A")
linkedHashMap += ("c" -> "C")
linkedHashMap += ("b" -> "B")
// Map(d -> D, a -> A, c -> C, b -> B)
println(linkedHashMap)

// 自动对Key进行排序
val sortedMap = scala.collection.mutable.SortedMap[String, String]()
sortedMap("c") = "C"
sortedMap("a") = "A"
sortedMap("b") = "B"
// TreeMap(a -> A, b -> B, c -> C)
println(sortedMap)

6. Iterator 迭代器

迭代器不是一个集合而是访问集合的一种方式,可以调用next()返回下一个元素和调用hasNext()检查是否还有元素

def main(args: Array[String]): Unit = {
	val it = Iterator("a", "b", "c")
	
	while (it.hasNext) {
	  // 注意:next()会从迭代器中删除
	  println(it.next())
	}
	
	// 注意:toList会清空Iterator中的所有元素
	// val list = it.toList
	// println(list.toBuffer)
	// println(it.length)
}

六:模式匹配 match case

模式匹配一种特殊的方法,在方法签名和方法体之间使用match关键字,在方法体内可以使用case关键字来匹配任意情况,类似于Java中的switch case吧,只不过Scala中的match case 和方法的定义融合在一块了, 只不过case的情况更多(Swift也支持复杂匹配,这种语法在现代编程语言中不算什么稀奇了),只是一种语法糖而已吧

object Main {
  def main(args: Array[String]): Unit = {
    println(func(3))
    println(func(Array(1, 2, 3, 4)))
    println(func(List("v1", "v2")))
    println(func((1, "mengday", 28)))
    println(func(Person("mengday", 28)))
    println(func(true))
  }

  def func(x: Any): Any = x match {
    case 1 => "one"
    case "two" => 2
    case y: Int if y > 3 => "Int:" + y
    case Array(1, x, y) => "数组"
    case Array(1, _*) => "_* 表示或略后面的所有元素"
    case List("v1", _) => "List 1"
    case List(ele1, ele2) => ele1 + "," + ele2
    case (1, username, age) => username + "=" + age
    case Person("mengday", age) => "你好鸭mengday:" + age
    case _ => "最后的匹配:" + x
  }

  case class Person(name: String, age: Int) {
    var _name = name
    var _age = age
  }
}

七: 异常处理

使用 throw new 抛出异常

object Main {
  def main(args: Array[String]): Unit = {
    try {
        throw new NullPointerException()
    } catch {
      case ex: NullPointerException => {
        println(ex)
      }
      case ex: IndexOutOfBoundsException => {
        println(ex)
      }
    } finally {
      println("finally")
    }
  }
}
发布了308 篇原创文章 · 获赞 936 · 访问量 133万+

猜你喜欢

转载自blog.csdn.net/vbirdbest/article/details/88769586
今日推荐