大数据之Scala简介第二部分

六、面向对象

Scala包

Scala的包命名规则,包的作用和包的写法跟Java基本是一样的。
Scala特有的包可以嵌套使用,并用花括号分开{},内层包可以直接使用外层包的属性和方法,但外层包无法直接使用内层包的对象和方法。

package com{ 
	// import com.mrlin.scala
	package mrlin{
		package scala{ }
	}
}

包对象:
scala中可以为每个包定义一个同名的包对象,定义在包对象的成员,作为对应包下面的所有class和object的共享变量,可以被直接访问。

package object mrlin{
	val shareValue = "share"
	def shareMethod() = {}
}

当包对象是在嵌套中使用时,需要确保包对象的定义跟对应的包在同一层级下面,否则无法正常使用。

package com{ 
	// import com.mrlin.scala
	package mrlin{
		package scala{ }
	}
	// 此处包对象跟包mrlin在同一层级,包mrlin中以及其内层的包scala都可以对shareValue,shareMethod对象进行访问
	package object mrlin{
	val shareValue = "share"
	def shareMethod() = {}
	}
}
// 此处定义的包对象跟com是同层级,跟mrlin不同层级,因此无法正常使用
package object mrlin{
	val shareValue = "share"
	def shareMethod() = {}
}

导包说明:
(1)和 Java 一样,可以在顶部使用 import 导入,在这个文件中的所有类都可以使用。
(2)局部导入:什么时候使用,什么时候导入。在其作用范围内都可以使用
(3)通配符导入:import java.util._
(4)给类起名:import java.util.{ArrayList=>JL}
(5)导入相同包的多个类:import java.util.{HashSet, ArrayList}
(6)屏蔽类:import java.util.{ArrayList =>,}
(7)导入包的绝对路径:new root.java.util.HashMap

Scala中的三个默认导入分别是:

import java.lang._ 
import scala._ 
import scala.Predef._

类和对象


scala的类和属性默认就是public的,所以不用再加public修饰符,加上反而会报错,因为没有public关键字。但是可以改成私有的属性,可以加上private,这里其实不用加,虽然scala默认是public但是scala的底层都是设置成私有的,属性私有,并且通过一个方法来访问和修改。同样,想实现Java那样的get和set方法,只需要在属性前面加上@BeanProperty注解即可,编译器会自动帮忙加上get和Set方法。
属性可以赋初始值,下划线_就是默认为空的初始值,它可以根据属性的类型自动推断。
注意:java中一个.java 只能有1个public类,但一个.scala中可以写多个类
类的语法如下:

[修饰符] class 类名 {
	类体
}
// 新建一个类
class Student {
	private var name:String = "alice" 		// 新建私有属性
	@BeanProperty
	var age:Int = _							// 加上注解会获得age的set和get方法
	var sex:String = _						// 下划线就是默认值为空,系统会自动根据类型判断
}

属性
属性是类的一个组成部分,属性的语法如下:

[修饰符] var|val 属性名称 [:类型] = 属性值
private var name:String = "alice"
protected val age:Int = _

封装
封装就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作。

访问权限
在 Java 中,访问权限分为:public,private,protected 和默认。在 Scala 中,你可以通过类似的修饰符达到同样的效果。但是使用上有区别。
(1)Scala 中属性和方法的默认访问权限为 public,但 Scala 中无 public 关键字。
(2)private 为私有权限,只在类的内部和伴生对象中可用。
(3)protected 为受保护权限,Scala 中受保护权限比 Java 中更严格,同类、子类可以访问,同包无法访问。
(4)private[包名]增加包访问权限,包名下的其他类也可以使用

方法
方法就是定义在类中的函数,基本语法如下:

def 方法名(参数列表) [:返回值类型] = {
	方法体
}

对象
创建对象基本语法:

val | var 对象名 [:类型]	= new 类型()

对象注意事项:
(1)val 修饰对象,不能改变对象的引用(即:内存地址),可以改变对象属性的值。
(2)var 修饰对象,可以修改对象的引用和修改对象的属性值
(3)自动推导变量类型不能多态,所以多态需要显示声明

构造器
Scala 类的构造器包括:主构造器和辅助构造器,基本语法如下:

class  类名(形参列表) {	// 主构造器
	// 类 体
	// 辅助构造器,辅助构造器可以有多个
	def	this(形参列表) {	
		// 同时,辅助构造器必须要调用主构造器
		this()
	}
}

Scala构造器的说明:
(1)辅助构造器,函数的名称必须是this,可以有多个,编译器通过参数的个数及类型来区分。
(2)辅助构造方法不能直接构建对象,必须直接或者间接调用主构造方法。
(3)构造器调用其他另外的构造器,要求被调用构造器必须提前声明,也就是后面的可以调用前面的,前面的不能调用后面的构造器

Scala构造器参数说明:
Scala 类的主构造器函数的形参包括三种类型:未用任何修饰、var 修饰、val 修饰,一般都用var修饰,因为val不能修改参数的值
(1)未用任何修饰符修饰,这个参数就是一个局部变量
(2)var 修饰参数,作为类的成员属性使用,可以修改
(3)val 修饰参数,作为类只读属性使用,不能修改

// 构造器常用用法:此处的花括号还可以再省略
class Student(var name:String,var age:Int){}
// 上面的定义跟下面的定义一样,但上面只需一行即可,即将属性直接定义
// 构造器定义全部直接定义在主构造器上面,这样更简洁
class Student{
	@BeanProperty
	var name:String = _
	@BeanProperty
	var age:Int = _
	
	def this(name:String,age:Int){
		this()
		this.name = name
		this.age = age
	}
}

继承和多态
继承:
继承父类的方法和属性,同时可以实现自己的方法和属性,也能重写父类的方法和属性。子类覆盖父类的方法和属性,需要添加 override 关键字。

多态:
同一个接口有多种不同的状态,当继承关系时,多继承时,同一个命名相同的方法可能有各种不同的实现。scala中方法和属性都是动态绑定,Java中方法是动态绑定,属性是静态绑定,例如:

Person person = new Worker();
// 静态绑定,在编译时就已经绑定了person类
System.out.println(person.name);
// 动态绑定,在执行时查看属于哪个类,再动态绑定那个接口,此处输出的应该worker下面的hello接口
person.hello();

抽象类
基本语法:
(1)定义抽象类:abstract class Person{} //通过 abstract 关键字标记抽象类
(2)定义抽象属性:val|var name:String //一个属性没有初始化,就是抽象属性
(3)定义抽象方法:def hello():String //只声明而没有实现的方法,就是抽象方法
继承和重写:
(1)如果父类为抽象类,那么子类需要将抽象的属性和方法实现,否则子类也需声明为抽象类
(2)重写非抽象方法需要用 override 修饰,重写抽象方法则可以不加 override。
(3)子类中调用父类的方法使用 super 关键字
(4)子类对抽象属性进行实现,父类抽象属性可以用 var 修饰,也可以用val修饰。
子类对非抽象属性重写,父类非抽象属性只支持val类型,而不支持var。因为 var修饰的为可变变量,子类继承之后就可以直接使用,没有必要重写。
匿名子类:
正常的调用是,创建父类,创建子类继承抽象类并实现抽象类中的抽象方法和属性,new一个子类,调用子类中的方法。和Java一样,scala可以通过包含带有定义或重写的代码的方式创建一个匿名的子类。

abstract class Person {
	val name: String 
	def hello(): Unit
}
object Test {
	def main(args: Array[String]): Unit = { 
		val person = new Person {
			override val name: String = "teacher"
			override def hello(): Unit = println("hello teacher")
		}
	}
}

伴生对象
Scala语言是完全面向对象的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。但是为了能够和Java语言交互(因为Java中有静态概念),就产生了一种特殊的对象来模拟类对象,该对象为单例对象。若单例对象名与类名一致,则称该单例对象为这个类的伴生对象,这个类的所有“静态”内容都
可以放置在它的伴生对象中声明。调用的时候有可以通过类名+方法类名+属性的方式来调用,而且伴生类里面的所有方法和属性对伴生对象都是共享的。

class Person {}			// 伴生类
object Person {}		// 伴生对象

伴生类和伴生对象简单说明:
(1)单例对象采用object 关键字声明
(2)单例对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。
(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)
	}
}

apply方法
(1)通过伴生对象的 apply 方法,实现不使用 new 方法创建对象。
(2)如果想让主构造器变成私有的,可以在参数()之前加上 private。
(3)apply 方法可以重载。
(4)Scala 中 obj(arg)的语句实际是在调用该对象的 apply 方法,即 obj.apply(arg)。用以统一面向对象编程和函数式编程的风格。
(5)当使用 new 关键字构建对象时,调用的其实是类的构造方法,当直接使用类名构建对象时,调用的其实时伴生对象的 apply 方法。

object Test {
	def main(args: Array[String]): Unit = {
		// 正常创建类的方法,但这里主构造器添加了private属性,无法直接调用
		// val p0 = new Person("alice")
		// 通过伴生类来创建对象
		val newP = Person.newPerson("alice")
		//(1)通过伴生对象的 apply 方法,实现不使用 new 关键字创建对象
		// 这里直接调用Person对象object Person 里面的apply方法
		val p11 = Person.apply()
		// 在伴生类中,已经对apply方法进行了优化,可以省略apply不写
		val p1 = Person()				
		println("p1.name=" + p1.name)
		val p2 = Person("bobo") 
		println("p2.name=" + p2.name)
		}
	}
	//(2)如果想让主构造器变成私有的,可以在()之前加上 private 
	class Person private(cName: String) {
		var name: String = cName
	}
	object Person {
		// 当伴生对象的构造器私有化时,可以通过这里new一个对象,来创建
		def newPerson(name:String):Person = new Person(name);
		def apply(): Person = { 
			println("apply 空参被调用") 
			new Person("xx")
		}
		def apply(name: String): Person = { 
			println("apply 有参被调用")
			new Person(name)
		}
	}

特质
特质,也叫特征(Trait)。在Scala 语言中,采用特质 trait(特征)来代替接口的概念,也就是说,多个类具有相同的特质(特征)时,就可以将这个特质(特征)独立出来,采用关键字 trait 声明。
Scala 中的 trait 中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质。这种感觉类似于 Java 中的抽象类。
(1)基本语法
一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素, 所以在使用时,也采用了extends 关键字,如果有多个特质或存在父类,那么需要采用with 关键字连接。
没有父类时语法:

class 类名 extends 特质1 with 特质2	with 特质3 …

有父类时语法:

class 类名 extends 父类 with 特质1 with 特质2 with 特质3…  //继承父类时,父类必须放在第一个

(2)说明
类和特质的关系,使用的是继承关系;当一个类去继承特质时,第一个连接词是 extends,后面都是with;如果一个类在同时继承特质和父类时,应当把父类写在 extends 后,即所有特质的最前面。

(3)特质属性
(1)特质跟抽象类一样,可以同时拥有抽象方法和具体方法;一个类可以混入(mixin)或继承多个特质;所有的 Java 接口都可以当做Scala 特质使用 (这点很重要);通过动态混入,可以灵活扩展类的功能。创建对象时混入 trait,而无需使类混入该 trait,如果混入的 trait 中有未实现的方法,则需要实现。

(4)特质叠加
(1)当一个类objAB混入了TraitA和TraitB中具有相同的具体方法,就会在两个特质中产生冲突,解法的方式是在objAB中重写该方法。
(2)一个类(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())
	}
}

执行的结果为:my ball is a blue-foot-ball。执行的顺序是按照Color,Category,descibe的顺序依次执行的。
特质和抽象类:优先使用特质,一个类扩展多个特质是很方便的,但却只能扩展一个抽象类。如果需要构造函数参数,则使用抽象类。因为抽象类可以定义带参数的构造函数, 而特质不行(有无参构造)。

类型检查和转换
(1)obj.isInstanceOf[T]:判断 obj 是不是T 类型。
(2)obj.asInstanceOf[T]:将 obj 强转成 T 类型。
(3)classOf 获取对象的类名。

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)
	}
}

枚举类
枚举类需要继承 Enumeration。

object Test {
	def main(args: Array[String]): Unit = {
		println(Weeks.Monday)
	}
}

// 枚举类
object Weeks extends Enumeration { 
	val Monday = Value(1, "mon")
	val Tuesday = Value(2, "tus") 
	val Wednesday = Value(3, "wed")
}

Type定义新类型
使用 type 关键字可以定义新的数据数据类型名称,本质上就是类型的一个别名。

type S=String
var v:S="abc"

七、集合

1、集合简介

(1)Scala 的集合有三大类:序列Seq、集Set、映射 Map,所有的集合都扩展自 Iterable 特质。
(2)对于几乎所有的集合类,Scala 都同时提供了可变和不可变的版本,分别位于以下两个包

不可变集合:scala.collection.immutable 
可变集合:scala.collection.mutable

(3)Scala 不可变集合,就是指该集合对象不可修改,每次修改就会返回一个新对象, 而不会对原对象进行修改,类似于 java 中的 String 对象。不可变指的是指向该对象的地址不变,对象不可变,但是对象里面的内容可以修改,针对对象的修改指的是添加新元素,删除新元素,合并对象等。
(4)可变集合,就是这个集合可以直接对原对象进行修改,而不会返回新的对象。类似于 java 中 StringBuilder 对象
建议:在操作集合的时候,针对不可变的集合时,采用符号的方法,针对可变的集合时,采用命名方法,这样可以便于区分。因为部分可变和不可变的集合是同名的,区分的方式主要看前面加载的包名。

2、数组

不可变数组

创建方式1:
泛型需要用中括号括起来[],不可变数组需要一开始就确定数据类型和数组长度,如果希望存放任意数据类型,则泛型指定Any。

val arr1 = new Array[Int](10)

创建方式2:
采用伴生对象的apply方法创建,直接输入元素即可,不用使用new,默认不加泛型,可以输入多种类型的数据。

val arr2 = Array(1,5,8,6,9,8,10)

数据操作:

// 查看数组
println(arr2.mkString(","))

// 修改数组元素的值,两种方式,前面是索引后面是值
arr1(0) = 10
arr1.update(1,11)

// 数组遍历
for (i <- 0 until arr1.length) println(arr1(i))	// 不建议使用这种方式
for (i <- arr1.indices) println(arr1(i))		// 下标直接取 indices
for (elem <- arr1) println(elem)				// 直接取数值,不用通过下标来取,效率更高
arr1.foreach(println)							// 直接通过foreach取值,后面直接接处理函数
// 获取迭代器的方式,迭代输出
var iter = arr1.iterator
while(iter.hasNext)	println(iter.next())			

// 修改数组,因为是不可变数组,每次修改都返回一个新的数组,所以需要使用新的数组来接收
var arr3 = arr1 :+ 15				// 在后面添加元素
var arr4 = 10 +: arr1				// 在前面添加元素,当冒号在后面时,数值需要放到前面
// 执行的顺序是从后往前 arr4 = 12 +: 10 +: arr1 也是合法的
var arr5 = arr3 ++: arr4			// 两个数组相加时,需要添加两个+号
println(arr5.mkString(","))

可变数组

需先导包:scala.collection.mutable.ArrayBuffer
创建方式1:
可变数组可以不用输入数组的长度,泛型和数量都可以不用带,默认是Any,这里创建Int型的可变数组。

val arr1 = new ArrayBuffer[Int]()

创建方式2:
使用伴生对象的apply方法创建,apply可以省略,会根据参数类型自动判断调用。

val arr2 = ArrayBuffer(1,5,7,8)

数据操作:

// 查看数组,默认是调用了toString的方法
println(arr2)

// 修改值,跟不可变数组一样,arr1未添加任何值之前的访问都会导致数组越界,切记
arr2(0) = 10
arr2.update(1,11)

// 数组的遍历方式也是跟不可变数组一样
// 修改数组,可变数组可以直接修改,不建议使用修改后再用新的数组接收,因为对象一样,
// 其中一个修改都会导致另一个对象的值也修改,实际开发会带来很多不可预测的问题
arr2 += 7			// 可变数组的符号使用 += ,跟+=的使用逻辑一样,加上后面的值后再赋给前面的值
8 +=: arr2			// 在前面添加元素时,需要加冒号,还记得数值在前面的规则是,冒号在后面时,数值在前面
arr2.append(9)		// 使用命名方法添加,建议使用
arr2.insert(1,56)	// 使用命名方法插入,建议使用,在索引1后面添加数值56
arr2.remove(1)		// 1 个参数时,删除该索引的值
arr2.remove(1,3)	// 2 个参数时,删除该索引后面的3个元素

// 批量添加
var arr3 = ArrayBuffer(9,10)
arr1 ++= arr3				// 将arr3添加到arr1后面
arr1 ++=: arr3				// 将arr1添加到arr3前面
arr1.insertAll(1,arr3)		// 将arr3插入到arr1索引1的后面
arr1.appendAll(arr3)		// 将arr3添加到arr1后面
arr1.prependAll(arr3)		// 将arr3添加到arr1前面			

可变数组虽然也是有符号的调用来修改元素,但可变数组建议都用方法名调用,可以明细看出最后修改的是哪个对象,不会搞混;而是跟不可变数组可以做区分。

不可变数组和可变数组的转换

可变转为不可变,直接调用toArray方法:

val arr1 = new ArrayBuffer(1,2,3)
val arr2 = arr1.toArray

不可变转为可变,调用buffer方法:

val arr3:mutable.Buffer[Int] = arr2.toBuffer

多维数组

多维数组的定义,使用ofDim设置维数,最高5维,这里设置3行4列的二维数组。

val arr = Array.ofDim[Int](3,4)

3、列表

列表

创建方式:
List 是一个抽象类,只能通过调用伴生对象的方式调用创建,其中类型:List[Int]可以省略。列表有一个空列表 Nil。

val list1:List[Int] = List(1,5,8,8,9,6)

数据操作:

// 修改值,修改后返回新的列表,第一个表示下标,第二个表示值
val list2 = list1.updated(0,3)

// 添加值
val list3 = list1 :+ 4
val list4 = 5 +: list1
val list5 = 0::10:100::list1		// :: 的运算规则是从右到左

// 列表合并
// :: 只是将list3整体作为一个元素加入到list4中,并不能实现list3的元素跟list4的元素合并
val list6 = list3 :: list4
// ::: 的原理是将list3和list4的元素拆开来,再一个一个添加进list7,称为扁平化
val list7 = list3 ::: list4
val list8 = list3 ++ list4			// ++ 跟 ::: 效果是一样的

// 查看某个值
println(list8(3))

// 遍历值
list8.foreach(println)

// 新的创建方式,采用::加上空列表的形式创建
val list9 = 32 :: Nil
val list10 = 14 :: 15 :: 18 :: Nil

可变列表

创建方式:
同样也是通过apply的方式创建。

val list1 = ListBuffer(1,2,3,4)

数据操作:

// 更新,更新可以直接使用update
list1.update(0,5)
list1(0) = 8

// 添加和插入
list1 += 5
list1.append(6)
list1.insert(0,0)

// 删除
list1 -= 5				// 删除的是从前往后,找到的第一个为5的值
list1.remove(0)			// 删除的是从前往后,找到的第一个为0的值

// 遍历
list1.foreach(println)
// 其他大部分操作跟List一样

4、集合Set

默认情况下, Scala 使用的是不可变集合, 如果你想使用可变集合, 需要导入scala.collection.mutable.Set包,可变和不可变的集合名字是一样的。集合数据无序,且数据不可重复,数据添加前会检查,重复数据会直接返回,不会执行添加操作。

不可变集合

创建方式:
直接使用apply的方式创建。

val set1 = set(1,5,8,6)

数据操作:

// 添加/删除元素
val set2 = set1 + 10
val set3 = set3 - 5

// 合并集合,会去重
val set4 = set1 ++ set2

// 遍历集合
for (x <- set1) println(x)

可变集合

创建方式:
创建可变集合,因为可变不可变的Set名称是一样的,这里需要添加可变集合的包。同样也是用apply的方式创建。

val set = mutable.Set(1,2,3,4,5,6)

数据操作:

// 集合添加元素
set += 8
val flag1 = set.add(8)  // add 有返回值,添加成功返回true
val flag2 = set.add(8)	// add 有返回值,添加失败返回false

// 向集合中添加元素,返回一个新的Set 
val ints = set.+(9)			// 等价于 val ints = set + 9
println(ints) 
println("set2=" + set)

//(5)删除数据
set -= 5
val flag3 = set.remove(8)	//remove 有返回值,删除成功返回true
val flag4 = set.remove(8)	//remove 有返回值,删除失败返回false

// (6)合并集合
set1 ++= set2			// 将set2合并到set1

5、Map集合

Map集合,Scala 中的 Map 和 Java 类似,也是一个散列表,它存储的内容也是键值对(key-value)的映射。

不可变Map

创建方式:

val map = Map( "a"->1, "b"->2, "c"->3)

数据操作:

// 访问数据
for (elem <- map.keys) {
	// 使用get访问map集合的数据,会返回特殊类型Option(选项): 有值(Some),无值(None),需要再添加get
	println(elem + "=" + map.get(elem).get)  
	// 更简洁的方式跟上面等价
	println(elem + "=" + map(elem))
}

// 获取健值
println(map.get("a").get)		// 单纯get方法不够安全,因为当健不存在时,会返回异常
println(map.get("d").get)		// 健不存在时,会返回异常
// 等价于
println(map("d"))

// 优化get,如果key不存在,则返回0,就不会报错
println(map.get("d").getOrElse(0)) 
// 等价于
println(map.getOrElse("d", 0))

//(2)循环打印
map.foreach( (kv)=>{println(kv)} )	// 简写可以写成下面的形式
map.foreach( println )

可变Map

创建方式:
同理,可变map因为同名也是需要事先导入可变map的包。

val map = mutable.Map( "a"->1, "b"->2, "c"->3 )

数据操作:

// 向集合添加数据
map += ("d"->4)
map.put("a", 4) 		// 建议使用

// 使用put可以修改键值对 a->1 中的值为 4,并返回修改前的值
val maybeInt: Option[Int] = map.put("a", 4) 
println(maybeInt.getOrElse(0))

// 修改数据
map.update("d",5) 		// 建议使用
map("d") = 5

// 删除数据,直接删除key就可以了
map -= ("b", "c")
map.remove("b", "c")	// 建议使用

// 集合合并
map1 ++= map2			// 将map2的健值添加进map1,如果是相同的key,则map2的值会覆盖掉map1的值

//(2)打印集合
map.foreach((kv)=>{println(kv)})

// 上面说的集合合并,会导致map2的值会覆盖map1的值,但一般情况下,这个不是我们想要的效果
// 我们想要的效果有可能是健值相同的,实现后面值的相加,如("a",2),("a",2)=》("a",4)
// 如下,实现健相同的,值相加
val map1 = Map(("a",2),("b",4),("c",8))
val map2 = mutable.Map(("a",2),("b",4),("c",8),("d",5))

val map3 = map1.foldLeft(map2)(
	(mergerMap, kv) => {
		val key = kv._1
		val value = kv._2
		mergerMap(key).mergerMap.getOrElse(key,0) + value 
		mergerMap
	}
)
println(map3)

6、元祖

元组也是可以理解为一个容器,可以存放各种相同或不同类型的数据。说的简单点,就是将多个无关的数据封装为一个整体,称为元组。注意:元组中最大只能有 22 个元素。
创建方式:
声明元组的方式:(元素 1,元素 2,元素 3…)

val tuple: (Int, String, Boolean) = (40,"bobo",true)

数据操作:

// 访问元组,通过元素的顺序进行访问,调用方式:_顺序号
println(tuple._1)
println(tuple._2)
println(tuple._3)
// 通过索引访问数据
println(tuple.productElement(0))
//通过迭代器访问数据
for (elem <- tuple.productIterator) { 
	println(elem)
}

// Map中的键值对其实就是元组,只不过元组的元素个数为 2,称之为对偶
val map = Map("a"->1, "b"->2, "c"->3) 
// 相当于
val map1 = Map(("a",1), ("b",2), ("c",3))

map.foreach(tuple=>{println(tuple._1 + "=" + tuple._2)

7、常用函数的集合

基本属性

val list = List(1,2,3,4,5,6,7,8,9)
val list2 = List(8,9,10,11,12,13)
//(1)获取集合长度
println(list.length)
//(2)获取集合大小
println(list.size)
//(3)循环遍历
list.foreach(println)
//(4)迭代器
for (elem <- list.iterator) println(elem)
//(5)生成字符串
println(list.mkString("-"))
//(6)是否包含
println(list.contains(3))

衍生集合

val list = List(1,2,3,4,5,6,7,8,9)
val list2 = List(8,9,10,11,12,13)
//(1)获取集合的头
println(list.head)
//(2)获取集合的尾(不是列表的最后一个,而是除了第一个之外的所有)
println(list.tail)
//(3)集合最后一个数据
println(list.last)
//(4)集合初始数据(不包含最后一个剩下的所有,称为初始数据)
println(list.init)
//(5)数据反转
println(list.reverse)
//(6)取前(后)n 个元素
println(list.take(3))
println(list.takeRight(3))
//(7)去掉前(后)n 个元素
println(list.drop(2))
println(list.dropRight(2))
//(8)并集,列表不会去重,直接相加
println(list.union(list2))
//(9)交集
println(list.intersect(list2))
//(10)差集
println(list.diff(list2))
//(11)拉链,前面两个数值合成健值对,多余的直接丢弃,返回元祖列表
println(list.zip(list2))
//(12)滑窗
for (elem <- list.sliding(3)) 
println(elem)
list.sliding(2,5).foreach(println)

简单计算函数

val list = List(1,2,3,4,5,6,7,8,9)
list2 = List(("a", 3),("b", 2),("c", 1))
//(1)求和
println(list.sum)
//(2)求乘积
println(list.product)
//(3)最大值
println(list.max)
println(list2.maxBy((tuple:(String, Int)) => tuple._2))	//指定用来判断大小的字段
println(list2.maxBy(_._2))	//简化后为
//(4)最小值
println(list.min)
//(5)排序
println(list.sorted)
// 正常排序
println(list.sortBy(x => x))
// 按绝对值排序
println(list.sortBy(x => x.abs))
// 小于排前面,升序排序
println(list.sortWith((x,y) => x < y))
println(list.sortWith( _ < _ ))   // 简化
// 大的排前面,降序排序
println(list.sortWith((x,y) => x > y))
println(list.sortWith( _ > _ ))   // 简化

排序规则:
(1)sorted 对一个集合进行自然排序,通过传递隐式的Ordering
(2)sortBy 对一个属性或多个属性进行排序,通过它的类型。
(3)sortWith 基于函数的排序,通过一个 comparator 函数,实现自定义排序的逻辑。

高级计算函数

相当于MapReduce的Map阶段:
(1)过滤(filter)
遍历一个集合并从中获取满足指定条件的元素组成一个新的集合
(2)转化/映射(map)
将集合中的每一个元素映射到某一个函数
(3)扁平化(flat)
将集合的元素拆开来,在按照一个一个元素来处理
(4)扁平化+映射 (flatMap )
flatMap 相当于先进行 map 操作,在进行 flatten 操作集合中的每个元素的子元素映射到某个函数并返回新集合
(5)分组(group)
按照指定的规则对集合的元素进行分组
相当于MapReduce的reduce阶段:
(6)简化(归约) reduce
(7)折叠 fold,就是在reduce上面的基础上,再加上一个初始值
Map操作:

val list1: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
val nestedList: List[List[Int]] = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9))
val	wordList: List[String] = List("hello world","hello mrlin", "hello scala")

// 过滤,取偶数部分
println(list1.filter(x => x % 2 == 0))
println(list1.filter( _ % 2 == 0 ))

// 转化/映射,这个就属于狭义上面的map操作
println(list.map(x => x * x))	// 返回平方
println(list.map( _ * 2 ))		// 返回两倍

// 扁平化,就是将列表里面的数据一个一个取出来
println(nestedList.flatten)

// 扁平化+映射 flatMap 相当于先进行 map 操作,在进行 flatten操作
// 将单词按照空格分开,再单独一个一个取出来
println(wordList.flatMap(x => x.split(" ")))

// 分组 groupBy
println(list.groupBy(x => x % 2))
val groupMap:Map[Int, List[Int]] = list.groupBy(_%2)
val groupMap:Map[String, List[Int]] = list.groupBy(data => if(data % 2 == 0) "偶数" else "奇数")

val wordList = List("china","merci","bob","limei")
println(wordList.groupBy(_.charAt(0)))		// 按首字母进行分组

Reduce操作:

val list = List(1,2,3,4)
// reduce
// 数据两两结合,实现运算规则,底层就是递归调用(((1+2)+3)+4),第一步1+2,然后将1+2的结果作为第一个参数,(1+2)+3,以此类推
println(list.reduce((x,y)=>x+y)
println(list.reduce(_+_))			// 简化效果

// 源码的角度看reduce的底层就是调用了reduceLeft来实现
println(list.reduceLeft(_+_))

// reduceRight 就是调用了 reverse 再调用reduceLeft,List(1,2,3,4) 反转后就是 List(4,3,2,1),再(((4+3)+2)+1)
println(list.reduceRight((x,y)=>x+y)
println(list.reduceRight(_+_))

// reduceRight 就是调用了 reverse 再调用reduceLeft,List(1,2,3,4) 反转后就是 List(4,3,2,1),再(((4-3)-2)-1),等于 -2
println(list.reduceRight(_-_))

// 折叠fold,就是在reduce上面的基础上,再加上一个初始值
println(list.fold(10)(_+_))			// 10+(((1+2)+3)+4)
println(list.fold(10)(_-_))
println(list.forldRight(11)(_-_))   // (11-4)-3-2-1  = 1

fold的参数fold((A1)(op:(A1,A1) => A1)),其中op函数中的A1是当前的聚合状态,另一个A1是我们要遍历的元素,这里的类型是要相同的。foldLeft的参数是foldLeft((B)(op:(B,A) => B)),其中op函数中的B是当前的聚合状态,另一个A是我们要遍历的元素,这里的类型是可以不同的。此处我们想要的是聚合状态为map,遍历的元素为键值对,也就是2维元祖。

简单的示例:

// 实例1 两个Map的数据合并
val map1 = Map("a"->1, "b"->2, "c"->3)
val map2 = mutable.Map("a"->4, "b"->5, "d"->6)
// 这里传入的两个参数,mergeMap是指可变的map2,kv指的是不可变的map1中的元祖
val map3 = map1.foldLeft(map2)(
  (mergeMap, kv) => {
    val key = kv._1
    val value = kv._2
    mergeMap(key) = mergeMap.getOrElse(key,0) + value
    mergeMap
  }
)
println(map3)
	
// 实例2 简单WordCount
val list1 = List("Hello Scala Hbase kafka","Hello Scala Hbase","Hello Scala","Hello")
// 1) 将每一个字符串转换成一个一个单词,空格分开,再扁平化
val list2 = list1.flatMap(_.split(" "))
// 2) 将相同的单词放置在一起,就是分组,按照本身进行分组就是形同的key分为一组
val map1 = list2.groupBy(x => x)
// 3) 对相同的单词进行计数,就是Map转化,后面的转化成长度
val map2 = map1.map(x=>(x._1,x._2.size))
// 4) 对计数完成后的结果进行排序(降序),先转成列表,再按照列表的第二个值最为降序的标准
val sortList = map2.toList.sortWith(_._2 > _._2)
// 5) 取前3名的数据,直接取出列表的前面3个元素
val resultList = sortList.take(3)
// 打印输出结果
println(resultList)

队列

Scala 也提供了队列(Queue)的数据结构,队列的特点就是先进先出。进队和出队的方法分别为 enqueue 和dequeue。队列也是分为可变和不可变队列,此处演示的是可变的队列,不可变的队列需要调用object里面的方法。

val que = new mutable.Queue[String]()
// 进队
que.enqueue("a","b","c")
// 出队
println(que.dequeue())

并行集合

Scala 为了充分使用多核 CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算。直接在调用方法前面加上 .par

// 只在主线程里面运行,打印的线程ID只有一个1
val result1 = (0 to 100).map{
	case _ => Thread.currentThread().getId
}
// 真正的多CPU运行,打印多个的线程ID
val result2 = (0 to 100).par.map{
	case _ => Thread.currentThread().getId
}

八、模式匹配

Scala中的模式匹配类似于Java 中的 switch 语法,但功能更加强大。模式匹配语法中,采用 match 关键字声明,每个分支采用 case 关键字进行声明。当需要匹配时,会从第一个 case 分支开始。如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有case都不匹配,那么会执行case _分支, 类似于Java中 default 语句。

1、基本语法

基本语法:

val result = op match {
	case 变量 => 表达式
	case _ => "illegal"
}

简单说明:
(1)如果所有case都不匹配,那么会执行case _分支,类似于Java中default 语句,若此时没有case _ 分支,那么会抛出MatchError异常;
(2)每个case中,不需要单独使用break语句,匹配成功case自动中断;
(3)match case 语句可以匹配任何类型,而不只是字面变量;
(4)=> 后面的代码块,直到下一个 case 语句之前的代码是作为一个整体执行,可以使用{}括起来,也可以不括。

2、模式守卫

如果想要表达匹配某个范围的数据,或者过滤掉部分内容,就需要在模式匹配中增加条件守卫。
示例如下:

def abs(x: Int) = x match {
	case i: Int if i >= 0 => i
	case j: Int if j < 0 => -j
	case _ => "type illegal"
}

3、模式匹配类型

匹配常量

Scala中,模式匹配可以匹配所有的字面变量,包括字符串,字符,数字,布尔值等等。

def describe(x: Any) = x match {
	case 5 => "Int five"
	case "hello" => "String hello"
	case true => "Boolean true"
	case '+' => "Char +"
	case _ => "type illegal"
}

匹配类型

需要进行类型判断时,可以使用前文所学的 isInstanceOf[T]和 asInstanceOf[T],也可使用模式匹配实现同样的功能。就是直接判断类型,但是只有Array会判断[]里面的类型,其他的类型只会判断外面的那层类型,list或者Map等其他的类型在底层的泛型被擦除了。

def describe2(x: Any) = x match {
	case i: Int => "Int"
	case s: String => "String hello"
	case m: List[_] => "List"					// 泛型擦除,写成List[String]传入List(1,2,3)还是会判断成list的
	case c: Array[Int] => "Array[Int]"			// 不会泛型擦除,Array("a","b")不会判断成Array
	case someThing => "something else " + someThing
}

匹配数组

scala 模式匹配可以对集合进行精确的匹配,例如匹配只有两个元素的、且第一个元素为 0 的数组。

val result = arr match {
	case Array(0) => "0" 					//匹配 Array(0) 这个数组
	case Array(x, y) => x + "," + y 		//匹配有两个元素的数组,然后将将元素值赋给对应的 x,y
	case Array(0, _*) => "以 0 开头的数组" 	//匹配以0开头的数组
	case _ => "something else"
}

匹配列表

Scala 模式匹配列表,也是可以精确匹配,同时列表还能有另外一种方式的匹配规则。
方式一:

val result = list match {
	case List(0) => "0" 				//匹配 List(0)
	case List(x, y) => x + "," + y 		//匹配有两个元素的List
	case List(0, _*) => "0 ..."			//匹配有0开头的list列表
	case _ => "something else"
}

方式二就是使用first ,second 等来指代元素。
方式二:

val list1: List[Int] = List(1, 2, 5, 6, 7)
list1 match {
	// 匹配有2个元素以上的列表,first为第一个,second为第二个,rest为后面所有
	case first :: second :: rest => println(first + "-" + second + "-" + rest)
	// 可有可没有
	case _ => println("something else")
}

匹配元祖

匹配元祖的方式跟上面的数组和列表类似。

val result = tuple match {
	case (0, _) => "0 ..."			 		// 第一个元素是 0 的元组
	case (y, 0) => "" + y + "0" 			// 匹配后一个元素是 0 的对偶元组
	case (a, b) => "" + a + " " + b 		// 匹配2元祖
	case _ => "something else" 				//默认
}

for推导式中的模式匹配

// 有列表定义如下
val list:List[(String,Int)] = List(("a",12),("b",6),("d",90))

// 原本的遍历方式
for (elem <- list){ println(elem._1 + " " + elem._2) }

// 将list中的元素直接定义成元祖,对变量赋值
for ((word,count) <- list) println(word + " " + count)

// 也能取部分元素的数据,下划线的部分就直接省略,不取
for ((word,_) <- list) println(word)

// 可以指定某个位置的值必须是多少,就是取第一个元祖为a的值
for (("a",count) <- list) println(count)

匹配对象及样例类

匹配对象的时候,一般匹配的是对象的地址,但那样的话对象基本都是不相同的。如果只想匹配对象的参数是否一致,就需要将对象的参数或者属性拆开来,然后再返回对象的属性以供对比。
方式一:

class Student(val name:String, val age:Int)
object Student{
	// 创建apply方法,可以直接在外面调用类名创建对象
	def apply(name:String,age:Int) = new Student(name,age)
	// 创建unapply方法,可以将对象的属性拆开,并使用 Option 传递参数类型
	def unapply(student:Student):Option[(String,Int)] = {
		if (student == null) None					// 对象为空时,直接返回None
		else Some(student.name,student.age)			// 否则返回对象的属性,用Some包装
	}
}

方式二:
这里更简单的方式是采用样例类的方式,样例类里面会自动帮忙创建apply和unapply方法。

object MatchCaseClass {
	def main(args: Array[String]): Unit = {
		val student1 = Student("alice",18)			// 创建对象
		val result = student1 match {
			case Student("alice",18) => "yes"
			case _ => "No"
		}
	println(result)
	}
}
// 直接定义成样例类,里面会自动帮忙创建apply和unapply方法
case class Student1(name:String, age:Int)

样例类说明:
(1)样例类仍然是类,和普通类相比,只是其自动生成了伴生对象,并且伴生对象中自动提供了一些常用的方法,如 apply、unapply、toString、equals、hashCode 和 copy 等。
(2)样例类是为模式匹配而优化的类,因为其默认提供了 unapply 方法,因此,样例类可以直接使用模式匹配,而无需自己实现 unapply 方法。
(3)构造器中的每一个参数都成为 val,除非它被显式地声明为 var(但不建议这样做)

变量中的模式匹配

在Scala中模式匹配是很强大的,强大到一般的变量中也是都可以使用模式匹配的规则,如下示例:

def main(args: Array[String]): Unit = {
	val (x, y) = (1, 2) 
	println(s"x=$x,y=$y")
	
	val Array(first, second, _*) = Array(1, 7, 2, 9) 
	println(s"first=$first,second=$second")
	
	val Person(name, age) = Person1("zhangsan", 16) 
	println(s"name=$name,age=$age")
}

偏函数中的模式匹配

偏函数的定义:

val second: PartialFunction[List[Int], Option[Int]] = { 
		case x :: y :: _ => Some(y)
}

其中second偏函数名,PartialFunction为偏函数类型,List[Int]为参数类型,Option[Int]为返回值类型。偏函数的函数体为case语句。就是通过模式匹配实现的,每个偏函数实现部分功能,然后多个偏函数组合起来形成一个完整的功能。偏函数的调用使用 applyOrElse 方法调用,多个可以实现多个连接。

// 定义偏函数,对输入的数据分不同的情况,求绝对值
val positiveAbs:PartialFunction[Int,Int] = { case x if x > 0 => x }
val negativeAbs:PartialFunction[Int,Int] = { case x if x < 0 => -x }
val zeorAbs:PartialFunction[Int,Int] = { case 0 => 0 }

// 偏函数调用
def abs(x:Int):Int = (positiveAbs orElse negativeAbs orElse zeorAbs)(x)

九、异常

Scala的异常跟Java类似,但又不全一样,基本使用可以按照Java的异常处理机制来处理。不同的是:
(1)Scala没有编译期异常,所有的异常都是在运行时捕获的;
(2)Scala捕获异常时,具体的异常写在普遍的异常后面也是不会报错的,但不建议这样做。
(3)Scala所有的函数都是有返回值的,throw也是有返回值,返回值是Nothing,因为Nothing是所有的类型的子类型。
Java的异常捕获机制:
(1)Java 语言按照 try—catch—finally 的方式来处理异常
(2)不管有没有异常捕获,都会执行 finally,因此通常可以在 finally 代码块中释放资源。
(3)可以有多个 catch,分别捕获对应的异常,这时需要把范围小的异常类写在前面, 把范围大的异常类写在后面,否则编译报错。
Scala中的异常捕获语法如下:

try {
		var n= 10 / 0
}catch {
	case ex: ArithmeticException=>{
		// 发生算术异常
		println("发生算术异常")
	}
	case ex: Exception=>{
		// 对异常处理
		println("发生了异常 1")
		println("发生了异常 2")
	}
}finally {
	println("finally")
}

十、隐式转换

当编译器第一次编译失败的时候,会在当前的环境中查找能让代码编译通过的方法,用于将类型进行转换,实现二次编译。

1、隐式函数

隐式转换可以在不需改任何代码的情况下,扩展某个类的功能。

class MyRichInt(val self: Int) { 
	def myMax(i: Int): Int = {
		if (self < i) i else self
	}
	def myMin(i: Int): Int = { 
		if (self < i) self else i
	}
}
object TestImplicitFunction {
	// 使用 implicit 关键字声明的函数称之为隐式函数
	implicit def convert(arg: Int): MyRichInt = { 
		new MyRichInt(arg)
	}
	def main(args: Array[String]): Unit = {
		// 当想调用对象功能时,如果编译错误,那么编译器会尝试在当前作用域范围内查找能调用对应功能的转换规则,
		// 这个调用过程是由编译器完成的,所以称之为隐式转换。也称之为自动转换
		println(2.myMax(6))
	}
}

2、隐式参数

普通方法或者函数中的参数可以通过 implicit 关键字声明为隐式参数,调用该方法时, 就可以传入该参数,编译器会在相应的作用域寻找符合条件的隐式值。
说明:
(1)同一个作用域中,相同类型的隐式值只能有一个
(2)编译器按照隐式参数的类型去寻找对应类型的隐式值,与隐式值的名称无关
(3)隐式参数优先于默认参数

object TestImplicitParameter {
	implicit val str: String = "hello world!"
	def hello(implicit arg: String="good bey world!"): Unit = { 
		println(arg)
	}
	def main(args: Array[String]): Unit = { 
		hello
	}
}

3、隐式类

在 Scala2.10 后提供了隐式类,可以使用 implicit 声明类,隐式类的非常强大,同样可以扩展类的功能,在集合中隐式类会发挥重要的作用。
隐式类说明:
(1)其所带的构造参数有且只能有一个
(2)隐式类必须被定义在“类”或“伴生对象”或“包对象”里,即隐式类不能是顶级的。

object TestImplicitClass {
	implicit class MyRichInt(arg: Int) { 
		def myMax(i: Int): Int = {
			if (arg < i) i else arg
		}
		def myMin(i: Int) = {
			if (arg < i) arg else i
		}
	}
	def main(args: Array[String]): Unit = { 
		println(1.myMax(3))
	}
}

4、隐式机制

简单说明
(1)首先会在当前代码作用域下查找隐式实体(隐式方法、隐式类、隐式对象);
(2)如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。类型的作用域是指与该类型相关联的全部伴生对象以及该类型所在包的包对象。

十一、泛型

1、协变和逆变

//协变
class MyList[+T]{ }
//逆变
class MyList[-T]{ }
//逆变
class MyList[T] 

简单说明:
协变:Son 是 Father 的子类,则 MyList[Son] 也作为 MyList[Father]的“子类”。
逆变:Son 是 Father 的子类,则 MyList[Son]作为 MyList[Father]的“父类”。
不变:Son 是 Father 的子类,则 MyList[Father]与 MyList[Son]“无父子关系”。
简单示例:

//协变
class MyList[+T]{}
//逆变
class MyList[-T]{}

class Parent{}
class Child extends Parent{} 
class SubChild extends Child{}

object Scala_TestGeneric {
	def main(args: Array[String]): Unit = {
	var s:MyList[Child] = new MyList[SubChild]	// 协变时能正常运行
	var s:MyList[Child] = new MyList[Parent]	// 逆变是能正常运行
	}
}

十二、IDEA的部分快捷键

(1)快速生成程序入口:main

 输入 main->回车	
 def main(args: Array[String]): Unit = {
}

(2)自动补全变量:.var

输入 1.var->回车
val i: Int = 2

(3)快速打印:.sout

输入 1.sout->回车
println(1)

(4)快速生成 for 循环:遍历对象.for

输 入 1 to 3.for
for (elem <- 1 to 3) {
}

(5)查看当前文件的结构:Ctrl + F12
(6) 格式化当前代码:Ctrl + Shift + L
(7) 自动为当前代码补全变量声明:Ctrl + Shift + V

其他:

Ctrl+Shift + Enter,语句完成
”!”,否定完成,输入表达式时按“!“键
Ctrl+E,最近的文件
Ctrl+shift+E,最近更改的文件
shift+Click,可以关闭文件
Ctrl+[ OR ],可以跑到大括号的开头与结尾
Ctrl+F12,可以显示当前文件的结构
Ctrl+F7,可以查询当前元素在当前文件中的引用,然后按 F3 可以选择
ctrl+N,可以快速打开类
Ctrl+shift+N,可以快速打开文件
Alt+Q,可以看到当前方法的声明
Ctrl+P,可以显示参数信息
Ctrl+shift+Inset,可以选择剪贴板内容并插入
Alt+Insert,可以生成构造器/Getter/Setter等
Ctrl+Alt+V,可以引入变量。例如: new string0); 自动导入变量定义
Ctrl+Alt+T,可以把代码包在一个块内,例: try/catch
Ctrl+Enter,导入包,自动修正
Ctrl+Alt+L,格式化代码
Ctrl+Alt+I,将选中的代码进行自动缩进编排,这个功能在编辑 JSP 文件时也可以工作
Ctrl+Alt+o,优化导入的类和包
Ctrl+R,替换文本
Ctrl+F,查找文本
Ctrl+Shift+Space,自动补全代码
ctrl+空格,代码提示(与系统输入法快捷键冲实)
Ctrl+shift+Alt+N,查找类中的方法或变量
Alt+Shift+C,最近的更改
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值