Scala Day02
第一部分
scala编程语言介绍
-
如何设计
提炼共同点和不同点变量的定义
常用的编程规范
数据类型
流程控制 -
为什么要学习
内部原因:优雅,简单,编程体验好,基于JVM的,与java程序无缝衔接
外部原因:spark
环境安装
SDK + IDEA
windows + linux
入门程序
scalac scala xxx.scala
object Test11{
def main(args:Array())
}
变量定义
var
val
lazy
类似于java中的自动类型提升 == 隐式提升
自动类型推断
数据类型
Any
AnyVal
AnyRef
Nothing
Null
Unit
scala中所有和java同名的类型都和java一样,只不过是首字母大写;
数组
定长 + 变长
Array + ArrayBuffer
定长数组 和 变长数组 的相互转换
val array = Array(1,2,3)
val ab: ArrayBuffer[Int] = new ArrayBuffer()
// 定长数据 转换为 变长数组
array.toBuffer()
// 变长数组 转换为 定长数组
ab.toArray()
集合
可变 + 不可变
注意要点:
对于一个不可变的集合如果进行集合的修改操作,那么其实是生成了一个新的集合返回
List
List: 列表
注意要点:
List是由一个头元素+一个尾列表组成
val list = 1 :: 2 :: 3 :: Nil
Nil != Null
set
作用:
- 去重
- 关系判断
val set1 = Set(1,2,3,4,4,3,3,2,4)
val set2 = Set(1,3,4,5,7,4)
// 并集
set1 union set2
set1 ++ set2
// 交集
set1 intersect set2
// 差集
set1 diff set2
元组
元组中的元素都是不可变;如果元组中某个值是引用对象,那么引用对象的值是可以变的;
元组的访问,下标从1开始的;取出数据是使用_
例如
val t1= (1,2f,3.3,true,"huangbo")
t1._1
t1._2
将元组的每个元素赋值给某个变量
val t1,(a,b,c,d,e)= (1,2f,3.3,true,"huangbo")
t1._1
a
映射
访问方式有三种:
map.get(key)
map(key) // 如果key不存在,则会报错;
map.getorEles(key,默认值) // 如果不存在,则返回默认值
遍历
for((key,value) <- map1){println(key,value)}
for(key <- map1.keys){println(key)}
for(value <- map1.values){println(value)}
压缩/拉链
zip
,unzip
val list1 = list("a","b","c","d")
val list2 = list(1,2,3,4,5)
val listResult= list1 zip list2
val listResult = list1.zip(list2)
val map = listResult.zip
map.unzip
排序
sorted
: 直接按照元素的字典顺序排序
sortBy
:按照元素的部分字段的组合的字典顺序来排序
sortWith
: 根据自定义的比较规则来排序
scala> val list = List(3,2,6,1,5,4,7,8)
list: List[Int] = List(3, 2, 6, 1, 5, 4, 7, 8)
scala> list.sort
sortBy sortWith sorted
scala> list.sortWith((x, y) => x > y)
res192: List[Int] = List(8, 7, 6, 5, 4, 3, 2, 1)
scala> list.sortWith((x, y) => x < y)
res193: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8)
scala> list.sorted
res194: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8)
scala> list.sortBy(x => x)
res195: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8)
更完整的测试:
scala> case class Student(id:Int, name:String)
defined class Student
scala> var s1 = Student(1, "huangbo")
s1: Student = Student(1,huangbo)
scala> var s2 = Student(2, "xuzheng")
s2: Student = Student(2,xuzheng)
scala> var s3 = Student(3, "wang")
s3: Student = Student(3,wang)
scala> val list1 = List(s2,s3,s1)
list1: List[Student] = List(Student(2,xuzheng), Student(3,wang), Student(1,huangbo))
scala> list1.sortBy(s => s.id)
res199: List[Student] = List(Student(1,huangbo), Student(2,xuzheng), Student(3,wang))
使用java语言具备map,reduce,filter,count的功能
- java中的List或者Array等等都没有map,filter,reduce ,count,。。。。
- 如果定义好了这个方法,那么如何给这些定义好的方法,再传入一个方法作为参数;
实现思路
-
解决第一个问题
继承,组合,包装
class Array implements List
class MyList Implements xxx
// 包装(装饰器设计模式) --> 代理class MyList{ private List list;// 被代理类 public List map(xx){ } public Object reduce(xxx){ } }
-
第二个问题;
传递的参数是一个接口,这个接口是一个函数式接口;```java private List list; public object reduce(ReduceOperation o){ Object x = null; // x: 状态值 // y: 从list中遍历出来的值 for(Object y: list){ x = o.operate(x, y) // (x, y) => x + y // (x, y) => if(x > y) x else y } return x; } ```
整个思路就是lambd函数的设计逻辑
可以出参考collections.sort方法
具体实现
99乘法表,wordcount
99乘法表
for( i <- 1 to 9; j<- 1 to i ){printf("%d*%d=%d%s",i,j,(i*j),if(j==i)"\n"else "\t")
wordcount
两种方式:
- 依据mapreduce逻辑
- 根据scala语言的特性
val array = Array("hi a","hi a b","hi c")
// 先切割,再压平
scala> array.map(x => x.split(" ")).flatten
res225: Array[String] = Array(hi, a, hi, b, a, hi, c)
// 其实就是一种一对多的映射,flatMap,就相当于先map在压平
// "hi a" ===> "hi", "a"
scala> array.flatMap(x => x.split(" "))
res226: Array[String] = Array(hi, a, hi, b, a, hi, c)
scala> array.flatMap(x => x.split(" ")).map(word => (word, 1)).groupBy(x => x._1)
res229: scala.collection.immutable.Map[String,Array[(String, Int)]] =
Map(
b -> Array((b,1)),
a -> Array((a,1), (a,1)),
c -> Array((c,1)),
hi -> Array((hi,1), (hi,1), (hi,1))
)
reduce(key, values, context)
// 依据mapreduce逻辑
scala> array.flatMap(x => x.split(" ")).map(word => (word, 1)).groupBy(x => x._1).map(x => (x._1, x._2.length)).toList.sortBy(x => x._2).reverse
res235: List[(String, Int)] = List((hi,3), (a,2), (c,1), (b,1))
// 简化方法
scala> array.flatMap(x=>x.split(" ")).groupBy(x=>x).toList.map(x=>(x._1,x._2.length)).sortBy(x=>x._2)
res7: List[(String, Int)] = List((b,1), (c,1), (a,2), (hi,3))
sortBy groupBy ... 按照指定的字段按照默认的规则进行
面向对象
类的定义
class Student{
}
object Student{
}
class
与object
都是定义类的关键字;
- 当这两个关键字定义的类名相同时,
class
定义的类叫 伴生类;object
定义的类叫 伴生对象。 - 如果定义的类名不同;那么
class
定义的类就是一个普通的类;object
定义的类就是一个普通的单例对象;
单例对象中的所有方法和属性都是静态(static修饰的)的; - 如果这两个关键字定义的类名相同,那么所有使用
private
修饰的属性或者方法,都能互相访问;但是使用private[this]
修饰的属性或者方式,就不能互相访问了;
属性和方法
- 使用
var
修饰的是普通的成员变量;底层会给这个属性自动生成getter方法和setter方法; - 使用
val
修饰的成员变量,表示有getter方法,而没有setter方法 - 如果一个成员变量使用
private
修饰:- 如果一个
object
类和这个普通类的类名一样,这个object
或者这个普通类,能够访问对方使用private
修饰的变量 - 如果是在不相同的类名的范围中;则不能访问使用
private
修饰的变量
- 如果一个
- 如果一个成员变量使用
private[this]
修饰,name只有当前类能访问,就算是和这个类同名的其他类也不能访问;
package com.aura.scala.day02.oop
/**
* 伴生类
*
* 如果 object Dog 不存在,,那么class D og 就是一个普通的类
*
*
*
*/
class Dog {
private var age:Int = 3
private def print(): Unit ={
// 4
// 使用类名,直接调用某个方法,那么这个方法,一定是使用static修饰的方法
Dog.eat("汉堡")
}
}
// 互为伴生关系的类和对象之间,可以访问对方的私有成员
// 补充:
// 使用private修饰,互相都能访问
// 但是
// 使用private[this]修饰,计算是伴生关系的类或者对象,也不能访问
/**
* 伴生对象
*
* 如果 class Dog 不存在,,那么object Dog 就是一个普通的单例对象
*
*/
object Dog{
private def eat(what:String): Unit ={
// 5
println("eat : " + what)
}
def main(args: Array[String]): Unit = {
// 1
var dog = new Dog()
// 2
println(dog.age)
// 3
println(dog.print())
}
}
构造器
class
修饰的类有一个主构造器,和若干个辅构造器
- 主构造器中只有使用
val
或者var
修饰的形参,会自动提升为当前这个类的 成员变量 - 如果主构造器中的形参,不使用任何修饰符;但是这个变量在某个方法中调用了;那么次变量也会提升为成员变量;只不过修饰符是
private[this]
- private修饰的形参;伴生对象是能够访问的
- 如果把主构造器使用
private
修饰了,那么只有伴生关系的类或对象才能访问这个构造器;作用就是不随便让其他的组件去访问和使用这个构造器,只有它的伴生对象,才能够访问
如果主构造器被私有了,怎么创建对象
- 使用
object
类中的apply
方法创建对象; - 使用辅助构造器
辅助构造器的注意事项:
- 辅助构造器的方法的名称,必须是this,并且的有参数列表
- 这个方法的{}中的代码块中的第一句,一定是调用其他的辅助构造器,或者主构造器
- 辅助构造器在调用的时候,会执行类定义中的{}中的所有代码
伴生对象的apply
方法
package com.aura.scala.day02.oop
object ApplyTest {
def main(args: Array[String]): Unit = {
/**
* 如果以后看见创建对象的时候, 有没有new
*
* 有new, 表示调用普通class类的构造器创建对象
* 没有new,其实就是调用object对象的,如果传入了参数,其实就是调用
* 这个object对象中的静态apply方法
*/
val o1 = ApplyOO
val o2 = ApplyOO
val o3 = new ApplyOO()
val o4 = new ApplyOO()
println(o1 eq o2)
println(o3 eq o4)
println("-----------------------------------")
val o5 = ApplyOO("huangbo")
val o6 = ApplyOO("huangbo", 19)
}
}
object ApplyOO{
/**
* 给我一堆零散的属性值,我帮你返回一个某个类型的实例对象!!!
*
* 返回的对象,一定是当前的object对象的伴生类的实例对象
*
* 你就把这个东西,看做是java的有参构造的另外一种方式
* WebLogBean wlb = new WebLogBean();
* wlb.set(1,2,3,4,...)
*/
// apply是可以重载的
def apply(name:String): ApplyOO = {
println("203742093848203804802 : ", name)
val oo = new ApplyOO()
// 模拟做各种操作
oo
}
def apply(name:String, age:Int): ApplyOO = {
println("203742093848203804802 : ", name)
new ApplyOO()
}
}
class ApplyOO{
}
辅助构造器
package com.aura.scala.day02.oop
// () 就是主构造器
// 当一个代码在调用某个class类的主构造器构建对象的时候,
// {} 中的所有能执行的代码全执行了。
// 哪些是不能执行的?
// 内部类,一个没有被调用的方法的定义代码,
// 哪些是能执行的代码呢?
// 单句能执行的代码,调用了的方法,已经属性的初始化
/**
* SparkContext {}
*
* 429 - 2100
*/
/**
* 主构造器中的 只使用val或者var修饰的形参,会自动提升为当前这个类的 成员变量
*
*
* 如果一个主构造器中的形参,不适用var和val修饰,也么有其他的修饰, 但是在某个方法中被使用了的话
* 那么这个属性也会提升为成员变量,只不过这个成员变量的修饰符是:
* private[this] val money:Int
*
*
* private修饰的形参, 伴生对象能不能在伴生关系的类和对象中访问, 能访问,能访问
*
*
* 如果使用private把主构造器给私有了, 那么只有 伴生 关系的类和对象中,才能访问 这个构造器
* 私有的目的:
* 就是为了不随便让其他的组件去访问和使用这个构造器,只有它的伴生对象,才能够访问
*
*
* val cat = new Cat();
*
* 第一种解决方案: val cat = Cat("huangbo", 19, 10000, "百亿影帝")
*
* 第二种解决方案: 主构造器被私有了。但是可以提供辅助构造器
* 三个要点:
* 1、辅助构造器的方法的名称,必须是this
* 2、这个方法的{}中的代码块中的第一句,一定是调用其他的辅助构造器,或者主构造器
* 3、辅助构造器在调用的时候,会不会执行类定义中的{}中的所有代码呢? 会
*/
class Cat private (val name:String, var age:Int, money:Int, private var nickname:String) {
var aa:Int = _ // 表示默认值
// 辅助构造器
def this(name:String, age:Int, money:Int, nickname:String, abc:Int){
this(name, age, money, nickname)
println("调用了辅助构造器")
this.aa = abc
}
{
println("hello scala")
}
def abc(): Unit ={
println(money)
println(nickname)
println("hello spark")
}
private val abcd:Int = {println("hello hadoop"); 3 + 3 + 3 + 3}
abc()
}
object Cat{
def apply(name:String,age:Int,money:Int,nickname:String): Cat = {
new Cat("huangbo", 19, 10000, "百亿影帝")
}
def apply(name:String,age:Int,money:Int,nickname:String, abc:Int): Cat = {
println("执行了apply方法,调用了辅助构造器")
new Cat(name,age,money,nickname, abc)
}
def main(args: Array[String]): Unit = {
//
// val cat = new Cat("huangbo", 19, 10000, "百亿影帝")
// println(cat.name)
// println(cat.age)
// println(cat.nickname)
// println(cat.)
// cat.name = ""
// cat.age = 20
val cat1 = Cat("huangbo", 19, 10000, "百亿影帝", 100)
}
}
面向对象的特性
封装、继承、多态
abstract |
抽象类 | interface/trait |
接口/特质 |
---|---|---|---|
extends |
继承/实现 | ||
override |
override |
override |
抽象类
scala的抽象类 和 java的抽象类只有一个不同点
抽象类: abstract class Student
可以有抽象的方法和属性,也可以有已实现的方法和属性
抽象:未实现的
具体:已实现的
一个抽象类中,可以定义抽象或者具体的方法和属性;抽象的属性:没有赋初始值的属性
接口 interface:
-
不能有未赋值的字段, 因为接口中的字段,都是常量
-
不能有已实现的方法,所以所有的方法,都应该是抽象的
特质:trait
抽象和具体的方法属性等在trait里面都可以定义
使用什么抽象类
-
在普通的用法上,可以任何
trait
是java中的抽象类和接口的统一 -
在java中,只能单继承,多实现,不能多继承
class A extends B, C, D xxxxx
class A implements B,C,D √√√√√而在scala中,继承依然只能继承一个,但是可以
mixin
(混入)其他的特质
class A extends B with C with DA : 普通的类
B : 可以是抽象类,也可以是特质
C,D: 必须是特质- 特质 因为 有 mixin 的语法, 所以功能要比 抽象类 强大
- 在使用过程中,一定是优先使用特质:基本上,只有一种情况使用抽象类:
如果需要给这个类定义构造器的话,那么只能在抽象类上定义构造器;不能在特质上定义构造器
-
trait的底层实现,就是java的抽象类
继承
关键问题:
- 如果继承了某个抽象类,要实现父类中的抽象的属性和方法,根据IDE会自动提示添加一个override关键字;如果这个属性和方法是抽象的,那么这个关键字 加 或者 不加 都可以
- 如果你的子类要重写父类中的已实现的方法:必须在重写的方法的前面加上 override
package com.aura.scala.day02.oop
abstract class AbstractTest {
// 抽象字段
val abc:Int
// 具体字段
val cba:Int = 1
// 抽象方法
def abcd1(name:String):Int
// 具体方法
def abcd2(name:String):Int = {
println(name);
println("调用了父类中的已实现的方法")
2
}
}
/**
* 编写一个类:AbstractTestImpl
* 扩展:AbstractTest
*
* 然后要重写 父类 AbstractTest 中的所有的抽象 属性和方法
*
* 关键问题:
*
* 1、如果继承了某个抽象类,要实现父类中的抽象的属性和方法,根据IDE会自动提示添加一个override关键字
* 如果这个属性和方法是抽象的,那么这个关键字 加 或者 不加 都可以
*
* 2、如果你的子类要重写父类中的已实现的方法:
* 必须在重写的方法的前面加上 override
*/
class AbstractTestImpl extends AbstractTest{
val abc: Int = 11
override def abcd1(name: String): Int = {println(name); 33}
override def abcd2(name: String): Int = {
println("子类中,重写了父类中的已实现的方法")
44
}
def ceshi(): Unit ={
this.abcd2("huangbo")
println("-----------------------------------")
super.abcd2("xuzheng")
}
}
object AbstractTest2222{
def main(args: Array[String]): Unit = {
val oo = new AbstractTestImpl()
oo.ceshi()
}
}
trait调用链
Scala 中支持让类继承多个 Trait 后,依次调用多个 Trait 中的同一个方法,只要让多个 Trait
的同一个方法中,在最后都执行 super.方法 即可
类中调用多个 Trait 中都有的这个方法时,首先会从最右边的 Trait 的方法开始执行,然后依
次往左执行,形成一个调用链条
这种特性非常强大,其实就相当于设计模式中的责任链模式的一种具体实现依赖
package com.mazh.scala.day2.oop
trait Handler {
def handler(data:String){}
}
trait Handler_A extends Handler{
override def handler(data:String): Unit = {
println("Handler_A :"+data)
super.handler(data)
}
}
trait Handler_B extends Handler{
override def handler(data:String): Unit = {
println("Handler_B :"+data)
super.handler(data)
}
}
trait Handler_C extends Handler{
override def handler(data:String): Unit = {
println("Handler_C :"+data)
super.handler(data)
}
}
class Person_TraitChain(val name:String) extends Handler_C with Handler_B with Handler_A{
def sayHello={
println("Hello "+name)
handler(name)
}
}
object TraitChain_Test{
def main(args: Array[String]) {
val p=new Person_TraitChain("zhangxiaolong");
p.sayHello
}
}
执行结果
Hello lixiaolong
Handler_A :zhangxiaolong
Handler_B :zhangxiaolong
Handler_C :zhangxiaolong
类型检查和转换
操作 | Scala | Java |
---|---|---|
判断 | obj.isInstanceOf[C] | obj instanceof C |
转换 | obj.asInstanceOf[C] | (C)obj |
获取 | classOf[C] | C.class |
应用程序对象App
Scala 程序都必须从一个对象的 main 方法开始,可以通过扩展 App 特质,不写 main 方法。
package com.aura.scala.day02.oop
/**
* 要去思考, 为什么 extends App 之后, {} 当中的代码就跟 main方法中的代码一样就能执行了呢?
*
*/
object AppTest extends App{
println(args.mkString(","))
println("abc")
}
/**
* Access a specific command line argument:
* Since the args variable which we inherited from
* the App trait is an Array of type String, we can
* access say the first argument only using the
* following syntax: args(0)
*
* hadoop jar xxx.jar com.aura.WordCount /input /output
*/
object AppTest1{
def main(args: Array[String]): Unit = {
println(args.mkString(","))
var input = args(0)
var output = args(1)
println("abc")
}
}
昨天作业难点回顾
求平均数
三种方法
最简单的方法
list.sum.toDouble / list.count(x => true)
使用map+reduce的方法
scala> val mapList = list.map(x => (x, 1))
mapList: List[(Int, Int)] = List((1,1), (4,1), (-2,1), (-3,1), (5,1), (6,1), (-7,1), (-8,1))
scala> mapList.reduce( (x:(Int,Int), y:(Int,Int)) => (x._1 + y._1, x._2 + y._2))
res183: (Int, Int) = (-4,8)
scala> val avg = res183._1.toDouble / res183._2
avg: Double = -0.5
使用fold的方式
// 使用fold的方式
val tResult: (Int, Int) = list.map(x => (x, 1)).fold((0, 0))((x, y) => (x._1 + y._1, x._2 + y._2))
println(tResult._1.toDouble / tResult._2)
// 使用foldleft
val tResult1: (Int, Int) = list.foldLeft((0, 0))((x:(Int, Int), y:Int) => (x._1 + y, x._2 + 1))
println(tResult1._1.toDouble / tResult1._2)
// 使用foldRight
val tResult2: (Int, Int) = list.foldRight((0, 0))((y:Int, x:(Int, Int)) => (x._1 + y, x._2 + 1))
println(tResult2._1.toDouble / tResult2._2)
根据已有列表val list = List(1,4,-2,-3,5,6,-7,-8)
,求出结果
保留所有的正数
scala> list.filter((x:Int) => if(x > 0) true else false)
res165: List[Int] = List(1, 4, 5, 6)
保留所有的除了第一个负数之外的所有数
scala> var flag = 0
flag: Int = 0
scala> list.filter((x:Int) => {if(x>0) () else {flag += 1}; if(flag == 1 && x < 0) false else true})
res167: List[Int] = List(1, 4, -3, 5, 6, -7, -8)
保留所有正数+第一个负数的所有数
scala> var flag = 0
flag: Int = 0
scala> list.filter((x:Int) => {if(x>0) () else {flag += 1}; if(flag > 1 && x < 0) false else true})
res170: List[Int] = List(1, 4, -2, 5, 6)
val tResult = list.foldLeft((0,0))((x:(Int,Int),y:Int)=>(if(y>0)x._1+1 else x._1,if(y<0) x._2+1 else x._2))
list.filter((x:Int)=>{if(x<0 && flaga < flag) {flaga = flaga+1;true}else if (x>0 && flagb < flag) {flagb = flagb+1;true }else false})