《快学Scala》心得体会
Val无法改变它的内容,实际上就是一个常量
Var 声明其值可变的变量
在scala中,与java不同的是,变量或函数的类型总是写在变量或函数名称的后面。
Scala有7种数值类型:Byte、Char、Short、Int、Long、Float、Double,以及一个Boolen类型。与java不同的是,在scala中不需要包装类型,编译器会自动完成基本类型和包装类型之间的转换。如 1.to(10) ;其实是Int值1首先被转换成RichInt,然后调用to方法。
在Scala中通常会使用类似函数调用的语法。
如”string”(4)其实是将()操作符进行了重载,”string”.apply(4)实际调用的apply方法,在StringOps类中。
与java,c/c++不同的是,scala中Everything is an object,特别是函数也可以做参数。
在scala中,任何语句都有返回值,语句返回Unit
object TimerAnonymous {
def oncePerSecond(callback: () => Unit) {
while(true) { callback(); Thread sleep 1000 }
}
def main(args: Array[String]) {
oncePerSecond(() => println("time flies like an arrow..."))
}
}
当val被声明为lazy时,它的初始化会被延迟,直到我们首次对它取值。
应用到Non-Strict中,以及Stream,视图等待,同时spark中的RDD
优点:懒性求值,没用上的参数不会浪费计算资源
对于大数据量或者无限长的数据,有着良好的支持(增量计算)
缺点:重复计算(可通过缓存计算过的值改善)
使用不当,将会使逻辑难以推断(不知道参数到底用没用,或者在什么时候用)
scala中一般都有可变与不可变的数组,集合等,分别在scala.collection.mutable和scala.collections.immutable中。
由于scala是兼容java的,所以代码中会出现,scala代码中调用java中的变量,这是就需要一些转换操作,不然会出现编译错误。
如在写RncKpiJoinHourTaskSpec这个DT时:
import scala.collection.JavaConversions._ //不加这句就会报编译错误,应为usccpchBasics 是java文件中定义的
cellPmCounters.usccpchBasics = List(new UsccpchBasic(111, "D2"))
public List<UsccpchBasic> usccpchBasics;
public List<RrcestCause> rrcestCauseList;
map[B](f: (A) ⇒ B): List[B]
定义一个变换,把该变换应用到列表的每个元素中,原列表不变,返回一个新的列表数据
求平方例子
val nums = List(1,2,3)
val square = (x: Int) => x*x
val squareNums1 = nums.map(num => num*num) //List(1,4,9)
val squareNums2 = nums.map(math.pow(_,2)) //List(1,4,9)
val squareNums3 = nums.map(square) //List(1,4,9)
val text = List("Homeway,25,Male","XSDYM,23,Female")
val usersList = text.map(_.split(",")(0)) // List[String] = List(Homeway, XSDYM)
val usersWithAgeList = text.map(line => {
val fields = line.split(",")
val user = fields(0)
val age = fields(1).toInt
(user,age)
})// List[(String, Int)] = List((Homeway,25), (XSDYM,23))
flatten: flatten[B]: List[B] 对列表的列表进行平坦化操作
flatMap: flatMap[B](f: (A) ⇒ GenTraversableOnce[B]): List[B] map之后对结果进行flatten
定义一个变换f, 把f应用列表的每个元素中,每个f返回一个列表,最终把所有列表连结起来。
val text = List("A,B,C","D,E,F")
val textMapped = text.map(_.split(",").toList) // List(List("A","B","C"),List("D","E","F"))
val textFlattened = textMapped.flatten // List("A","B","C","D","E","F")
val textFlatMapped = text.flatMap(_.split(",").toList) // List("A","B","C","D","E","F")
reduce[A1 >: A](op: (A1, A1) ⇒ A1): A1
使用 reduce 我们可以处理列表的每个元素并返回一个值。通过使用 reduceLeft 和 reduceRight 我们可以强制处理元素的方向
定义一个变换二元操作,并应用的所有元素。
列表求和
val nums = List(1,2,3)
val sum1 = nums.reduce((a,b) => a+b) //6
val sum2 = nums.reduce(_+_) //6
val sum3 = nums.sum //6
reduceLeft: reduceLeft[B >: A](f: (B, A) ⇒ B): B
reduceRight: reduceRight[B >: A](op: (A, B) ⇒ B): B
reduceLeft从列表的左边往右边应用reduce函数,reduceRight从列表的右边往左边应用reduce函数
val nums = List(2.0,2.0,3.0)
val resultLeftReduce = nums.reduceLeft(math.pow) // = pow( pow(2.0,2.0) , 3.0) = 64.0
val resultRightReduce = nums.reduceRight(math.pow) // = pow(2.0, pow(2.0,3.0)) = 256.0
fold: fold[A1 >: A](z: A1)(op: (A1, A1) ⇒ A1): A1 带有初始值的reduce,从一个初始值开始,从左向右将两个元素合并成一个,最终把列表合并成单一元素。
foldLeft: foldLeft[B](z: B)(f: (B, A) ⇒ B): B 带有初始值的reduceLeft
foldRight: foldRight[B](z: B)(op: (A, B) ⇒ B): B 带有初始值的reduceRight
val nums = List(2,3,4)
val sum = nums.fold(1)(_+_) // = 1+2+3+4 = 9
val nums = List(2.0,3.0)
val result1 = nums.foldLeft(4.0)(math.pow) // = pow(pow(4.0,2.0),3.0) = 4096
val result2 = nums.foldRight(1.0)(math.pow) // = pow(1.0,pow(2.0,3.0)) = 8.0
filter: filter(p: (A) ⇒ Boolean): List[A]
filterNot: filterNot(p: (A) ⇒ Boolean): List[A]
filter 保留列表中符合条件p的列表元素 , filterNot,保留列表中不符合条件p的列表元素
val nums = List(1,2,3,4)
val odd = nums.filter( _ % 2 != 0) // List(1,3)
val even = nums.filterNot( _ % 2 != 0) // List(2,4)
Trait有点类似于java中的interface,但也有不同之处。
①java的interface只定义方法名称和参数列表,不能定义方法体。而trait则可以定义方法体。 如
trait Friendly {
def greet() = "Hi"
}
②在java中实现接口用implement,而在scala中,实现trait用extends。
Scala中不再有implement这个关键词。但类似的,scala中的class可以继承0至多个traits。
class Dog extends Friendly {
override def greet() = "Woof"
}
此处,需要注意的一点是,与java不同,在scala中重写一个方法是需要指定override关键词的。如果重写一个方法时,没有加上override关键词,那么scala编译会无法通过。
③ java的interface和scala的trait的最大区别是,scala可以在一个class实例化的时候混合进一个trait。
trait Friendly {
def greet() = "Hi"
}
class Dog extends Friendly {
override def greet() = "Woof"
}
class HungryDog extends Dog {
override def greet() = "I'd like to eat my own dog food"
}
trait ExclamatoryGreeter extends Friendly {
override def greet() = super.greet() + "!"
}
var pet: Friendly = new Dog
println(pet.greet())
pet = new HungryDog
println(pet.greet())
pet = new Dog with ExclamatoryGreeter
println(pet.greet())
pet = new HungryDog with ExclamatoryGreeter
println(pet.greet())
由于Scala中没有静态方法一说,于是产生了一种新的特性,单例对象,object,里面存放的就相当于java里面的静态方法,字段。
为了得到既有实例方法和静态方法的类,可以通过创建与类同名的对象来达到目的。
在公司的以前的一些业务代码中,由于没有写DT,发现现在在写时,有些单例类型Oject的方法无法写测试
object A ===>
object A extends A
class A{
}
然后在测试用例中,新建A类,并将不关心的方法进行overwirte为空,这样就可以在不影响其他业务的情况下也能完成DT
应为对外界其实也有个单例类型object A。
但是在这样处理时,要注意,Oject里面的样例类case class应该继续放到oject里面,其他内容放到class A里;应为如果不这样处理,那么后面得到的样例类实例化对象就会不同,达不到我们想要的结果。
假如定义了一个类Outer,在里面定义了样本类Inner,并持有一个Int值
scala> class Outer {
| case class Inner(value:Int)
| }
defined class Outer
scala> val outer1 = new Outer; val outer2 = new Outer
outer1: Outer = Outer@560245
outer2: Outer = Outer@af0d85
scala> val inner1 = new outer1.Inner(1); val inner2 = new outer2.Inner(1)
inner1: outer1.Inner = Inner(1) // 内部类赋值一样
inner2: outer2.Inner = Inner(1) // 但是,注意他们的类型是不一样的,一个是outer1.Inner,另一个是outer2.Inner,它是依赖于具体对象的一个类型
scala> inner1 == inner2 // 在比较的时候返回false,虽然它们持有的值都是1
res5: Boolean = false
这样就在处理一些合并时,本来以为Key是相同的记录不会合并在一起。
已知String是Object的子类,从直觉上我们可能认为List<String>应该是List<Object>的子类型,但是看下面例子:
private static void printList(List<Object> list) {
for (Object obj : list) {
System.out.println(obj.toString());
}
}
public static void main(String[] args) { List<String> names = new ArrayList<String>(); names.add("aaa"); names.add("bbb"); printList(names); // compile error!!! }在调用printList时,传入的是一个List<String>,需要的是一个List<Object>,如果List<String>是List<Object>的子类,这个地方应该可以编译通过,结果不是,说明他们之间没什么关系。但是为了满足一些客观现实情况,我们在scala中映入了协变和逆变的概率。
1.不变: class Box[T]
2.协变: class Box[+T]
如果A是B的子类,那么Box [A]也是Box [B]的子类型
一般情况下,在定义协变类型时,主要用于返回,不要用于方法的参数,否则可能出现下面的结果:
class Box[+T](v: T) { def get: T = ??? def set(v: T) = ??? // 编译错误 } val stringBox = new Box[String]("abc") val anyBox: Box[Any] = stringBox anyBox.set(123) 如果编译器不做这样的限制,我们可能把一个int型的数据插入到需要String的Box中。3.逆变: class Box[-T]
如果A是B的超类,那么Box [A]是Box [B]的子类型 逆变
class Box[-T](v: T) { def get: T = ??? // compile error def set(v: T) = ??? }--
val anyBox = new Box[Any](123) val stringBox: Box[String] = anyBox stringBox.get 如果不做返回值的限制,那么我们可能就会从string的盒子拿出一个int值,有违常理。破坏了我们的类型系统。所以逆变的类型参数一般放在方法的参数,而不是返回值。 下面我们看一个协变的应用: 定义一个泛型类型Friend[T],表示希望与类型T的人成为朋友的人。 Trait Friend[T]{ def befriend(someone:T) }有这样一个函数
def makeFriendWith(s: Student,f: Friend[Student]){f.befriend(s)}
其中,
class Person extends Friend[Person]
class Student extends Person
val susan = new Student
val fred = new Person
客观上,fred可以和任何Person做朋友,那么就应该可以喝任何student做朋友,所以,我们要求函数调用makeFriendWith(susan,fred)是可以的,即Friend[Person]应该是Friend[Student]的子类型,于是我们将Friend定义为逆变的就可以达到要求:
Trait Friend[-T]{ def befriend(someone:T) }协变逆变与上下界的不同,上下界表明传入的参数类型是满足么个类型的超类或者子类,那么方法中就应该具备一些方法。
比如需要对一个Pair类型中的两个组件进行比较时:
class Pair[T](val first:T,val second: T){
def smaller = if(first.compareTo(second<0)first else second
}
但是这样会报错,因为T是个泛型,不知道他是否有compareTo这个方法。这个时候我们就可以采用上界的方法来解决这个问题:
class Pair[T<:Comparable[T]](val first:T,val second: T){
def smaller = if(first.compareTo(second<0)first else second
}
这样就限制了T必须是Comparable[T]的子类型,肯定具备compareTo方法。类似的下界T<:Comparable[T]要求T必须是Comparable[T]的超类型。
转载于:https://www.cnblogs.com/shaozhiqi/p/11535746.html
相关资源:SCALA 学习手册 2016.02