分享

spark开发基础之从关键字、函数入门Scala

pig2 发表于 2016-11-8 16:35:38 [显示全部楼层] 只看大图 回帖奖励 阅读模式 关闭右栏 8 24479
问题导读

1.apply的作用是什么?
2.yield什么情况下使用?
3.partition如何使用?




上一篇:
spark开发基础之从Scala符号入门Scala

这一篇总结一些关键字及函数,with,apply,yield,map,partition,filter。
关键字函数很多,这里不会全部总结,主要针对一些难以理解的内容
spark开发基础之Scala入门常用操作符及函数介绍
http://www.aboutyun.com/forum.php?mod=viewthread&tid=20133


Scala有39个关键字:package, import, class, object, trait, extends, with, type, forSome
private, protected, abstract, sealed, final, implicit, lazy, override
try, catch, finally, throw
if, else, match, case, do, while, for, return, yield
def, val, var
this, super
new
true, false, null
函数当然也很多,不在列举





with用法
复合类型的定义,需要使用with。
T1with T2 with T3 …,这种形式的类型称为复合类型(compoundtype)或者也叫交集类型(intersection type)。

如下面

[mw_shl_code=scala,true]// 定义特征(CompoundType1、CompoundType2)
trait CompoundType1;  
trait CompoundType2;  

class CompoundType extends CompoundType1 with CompoundType2;  

object CompoundType {  
    // 定义方法compoundType,该方法需要传递即参数满足CompoundType1类型且又要满CompoundType2类型的,复合类型。  
    def compoundType(x:CompoundType1 with CompoundType2) = {println("Compound Type in global method!!!")}   

    def main(args: Array[String]): Unit = {  
        // new出即符合CompoundType1类切也符合CompoundType2类型,执行结果:Compound Type in global method!!!   
        compoundType(new CompoundType1 with CompoundType2)  

        // object 混入代码中,将object对象当参数传递方法,执行结果:Compound Type in global method!!!  
        object compoundTypeObject extends CompoundType1 with CompoundType2  
        compoundType(compoundTypeObject);  

        // 使用type关键字,起别名   
        type compoundTypeAlias = CompoundType1 with CompoundType2;  
        // 定义函数,参数使用别名compoundTypeAlias类型  
        def compoundTypeLocal(x:compoundTypeAlias) = println("Compound Type in local method!!!");  
        // 别名的函数调用,执行结果:Compound Type in local method!!!  
        var compoundTypeClass = new CompoundType();  
        compoundTypeLocal(compoundTypeClass);  

        // 定义复合类型:在CompoundType1并且CompoundType2类,同时必须实现在init方法。  
        type Scala = CompoundType1 with CompoundType2 {def init():Unit}  
    }  
}[/mw_shl_code]



trait用法

这里的trait字面意思是特质或者特征,这个词翻译成特征比较合适。它的意义和java,c#中接口很类似。但是trait支持部分实现,也就是说可以在scala的trait中可以实现部分方法。

trait的使用方法就是这样子了,它很强大,抽象类能做的事情,trait都可以做。它的长处在于可以多继承。

trait和抽象类的区别在于抽象类是对一个继承链的,类和类之前确实有父子类的继承关系,而trait则如其名字,表示一种特征,可以多继承。


trait我们该如何理解,下面使用trait的例子加深我们的理解

[mw_shl_code=scala,true]abstract class Plant {
  def photosynthesis = println("Oh, the sunlight!")
}

class Rose extends Plant {
  def smell = println("Good!")

  def makePeopleHappy = println("People like me")
}

class Ruderal extends Plant {
  def grow = println("I take up all the space!")
}

abstract class Animal {
  def move = println("I can move!")
}

class Dog extends Animal {
  def bark = println("Woof!")

  def makePeopleHappy = println("People like me")
}

class Snake extends Animal {
  def bite = println("I am poisonous!")
}[/mw_shl_code]

植物家族有玫瑰和杂草。
动物家族有狗和毒蛇。
仔细观察可以发现,玫瑰和狗有一个共同的行为,它们都可以取悦人类,这个行为是用完全一样的代码实现的。
如何把Rose和Dog中的重复代码消除掉呢?有一种潜在的解决方案: 把makePeopleHappy提取到一个类中去,让植物和动物都继承自它。
这么做虽然消除了重复代码但有两个明显的缺点:
  • 植物和动物继承自同一个类,不太合理
  • 杂草和毒蛇也具有了取悦于人的能力,也不太合理
这时我们就可以使用trait,它没有上面提到的两个缺点。

[mw_shl_code=scala,true]trait PeoplePleaser {
  def makePeopleHappy = println("People like me")
}

class Rose extends Plant with PeoplePleaser {
  def smell = println("Good!")
}

class Dog extends Animal with PeoplePleaser {
  def bark = println("Woof!")
}[/mw_shl_code]

我们定义一个trait,把makePeopleHappy置于其中,让Rose和Dog都with这个trait。然后就可以写这样的代码来调用它们了:
[mw_shl_code=scala,true] new Rose().makePeopleHappy
  new Dog().makePeopleHappy[/mw_shl_code]

这样我们就解决了重复代码的问题,而且没有触及已存在的继承关系。

现在看看trait的实现机制吧,我们开始反编译!

[mw_shl_code=scala,true]public abstract interface PeoplePleaser
{
  public abstract void makePeopleHappy();
}

public abstract class PeoplePleaser$class
{
  public static void makePeopleHappy(PeoplePleaser $this)
  {
    Predef..MODULE$.println("People like me");
  }

  public static void $init$(PeoplePleaser $this)
  {
  }
}

public class Rose extends Plant
  implements PeoplePleaser
{
  public void makePeopleHappy()
  {
    PeoplePleaser$class.makePeopleHappy(this);
  }

  public void smell() { Predef..MODULE$.println("Good!"); }

  public Rose()
  {
    PeoplePleaser.class.$init$(this);
  }
}

public class Dog extends Animal
  implements PeoplePleaser
{
  public void makePeopleHappy()
  {
    PeoplePleaser$class.makePeopleHappy(this);
  }

  public void bark() { Predef..MODULE$.println("Woof!"); }

  public Dog()
  {
    PeoplePleaser.class.$init$(this);
  }
}[/mw_shl_code]

真相大白了,PeoplePleaser被编译成了一个接口加一个抽象类。Rose和Dog实现这个接口,并通过调用抽象类中的静态方法来实现了makePeopleHappy。

很有趣的一点是Rose和Dog在调用静态方法时都把this传了进去,为什么呢?我们把原来的代码改成这样来看:

[mw_shl_code=scala,true]trait PeoplePleaser {
  val moreMessage = ""

  def makePeopleHappy = println("People like me. " + moreMessage)
}

class Rose extends Plant with PeoplePleaser {
  override val moreMessage = "Because I smell nice."

  def smell = println("Good!")
}

class Dog extends Animal with PeoplePleaser {
  override val moreMessage = "Because I fetch balls."

  def bark = println("Woof!")
}[/mw_shl_code]

我们给makePeopleHappy加上一段额外的信息。 现在再次反编译。
[mw_shl_code=scala,true]public abstract interface PeoplePleaser
{
  public abstract void objsets$PeoplePleaser$_setter_$moreMessage_$eq(String paramString);

  public abstract String moreMessage();

  public abstract void makePeopleHappy();
}

public abstract class PeoplePleaser$class
{
  public static void makePeopleHappy(PeoplePleaser $this)
  {
    Predef..MODULE$.println(new StringBuilder()
    .append("People like me. ")
    .append($this.moreMessage()).toString());
  }

  public static void $init$(PeoplePleaser $this)
  {
    $this.objsets$PeoplePleaser$_setter_$moreMessage_$eq("");
  }
}

public class Rose extends Plant
  implements PeoplePleaser
{
  private final String moreMessage;

  public void objsets$PeoplePleaser$_setter_$moreMessage_$eq(String x$1)
  {
  }

  public void makePeopleHappy()
  {
    PeoplePleaser$class.makePeopleHappy(this);
  }

  public String moreMessage() { return this.moreMessage; }

  public void smell() {
    Predef..MODULE$.println("Good!");
  }

  public Rose()
  {
    PeoplePleaser.class.$init$(this);
    this.moreMessage = "Because I smell nice.";
  }
}

public class Dog extends Animal
  implements PeoplePleaser
{
  private final String moreMessage;

  public void objsets$PeoplePleaser$_setter_$moreMessage_$eq(String x$1)
  {
  }

  public void makePeopleHappy()
  {
    PeoplePleaser$class.makePeopleHappy(this);
  }

  public String moreMessage() { return this.moreMessage; }

  public void bark() {
    Predef..MODULE$.println("Woof!");
  }

  public Dog()
  {
    PeoplePleaser.class.$init$(this);
    this.moreMessage = "Because I fetch balls.";
  }
}[/mw_shl_code]


现在就清楚了,抽象类中的静态方法可能会依赖于各个实例不同的状态,所以需要把this传递进去。 这样我们才能够给makePeopleHappy加上一段额外的信息。
基于trait和with总结

什么情况下使用with

这就需要明白,什么情况下使用extends,我们知道Java中,子类可以继承父类。二者是有相同的地方。比如:
动物--猫
植物--玫瑰
上面情况可以使用extends
那么对于动物,植物取悦于人,这个人跟动物和植物是非继承关系的,这时候我们就可以使用with。


在实例化对象的时候,有时候我们也需要使用with。



super用法

属性只能是本类的属性,方法可以是父类的方法。也就是调用父类用super

super:  1、super.方法   2、super(参数1,参数2,……)

1、新建父类:AnimalInfo.Java

public class AnimalInfo {
private String color;
private String type;

public AnimalInfo(String color, String type) {
this.color = color;
this.type = type;
System.out.println("父类的有参构造方法结果:打印颜色:"+color+";类型:"+type);
}

/**
* 重载:要求返回值类型、方法名称一致而参数列表必须不同,访问修饰符不限制
*
*/
public void eat(){
System.out.println("父类的吃方法!");
}
protected void eat(String width){}
void eat(String width,String height){}
/*动物叫方法*/
public void song(){
System.out.println("父类的叫方法!");
}
}

2、新建子类:DogInfo.java

public class DogInfo extends AnimalInfo {
    String name;

    /**

      *覆盖:要求方法名称必须一致,访问修饰符限制必须一致或者范围更宽
     */

    /*如果构造方法不添加访问修饰符,那么就是默认(default)*/
    DogInfo() {
    this("狗二");
    System.out.println("子类无参构造方法");
    super.eat();//调用父类方法
   this.eat();
    }
    /**覆盖构造方法,覆盖只发生在构造方法中*/
    DogInfo(String input) {
    super("blue","dog");//调用父类构造方法
     // super();
       name = input;
   }
    private  DogInfo(String out,String in){ }
    DogInfo(String out,String in,String error){  }
}

更多super内容Scala中super总结

type用法:

scala 中也可以定义类型成员,跟class,trait,object类似,类型成员以关键字type 声明。通过使用类型成员,你可以为类型定义别名。
type相当于声明一个类型别名:
[mw_shl_code=bash,true]scala> type S = String
defined type alias S[/mw_shl_code]

上面把String类型用S代替,通常type用于声明某种复杂类型,或用于定义一个抽象类型。

场景1 用于声明一些复杂类型,比如下面声明一个结构类型

[mw_shl_code=scala,true]scala> type T = Serializable {
|          type X
|          def foo():Unit
|     }
defined type alias T[/mw_shl_code]
这个结构类型内部也允许通过type来定义类型,这里对X没有赋值表示X是一个抽象类型,需要子类型在实现时提供X具体的类型。下面是一个T类型的具体实例:
[mw_shl_code=scala,true]scala> object A extends Serializable{ type X=String; def foo(){} }

scala> typeOf[A.type] <:< typeOf[T]
res19: Boolean = true[/mw_shl_code]

场景2 用于抽象类型
[mw_shl_code=scala,true]scala> trait A { type T ; def foo(i:T) = print(i) }

scala> class B extends A { type T = Int }

scala> val b = new B

scala> b.foo(200)
200

scala> class C extends A { type T = String }

scala> val c = new C

scala> c.foo("hello")
hello[/mw_shl_code]

forSome用法:

forSome是Scala的一个关键字,不是函数。

forSome用于下面的场景:
我们想对一些对象做一些操作,但是不关心对象内部的具体类型。或者说我们指向知道对象可以进行哪些操作,但是不关心它是什么以及可以进行哪些其他操作。比如我们想取List的第一个元素或者List的长度,但是不关心List是什么类型。因此我们需要知道该对象可以进行获取长度的操作,但是不关心其元素是什么类型:


即Existential type做到了隐藏我们不关心的对象结构,同时暴露我们想要进行的操作,恰如其分地对外暴露协议。
[mw_shl_code=bash,true]def printFirst(x : Array[T] forSome {type T}) = println(x(0))
[/mw_shl_code]

我们也可以使用泛型:
[mw_shl_code=bash,true]def printFirst[T](x : Array[T]) = println(x(0))
[/mw_shl_code]

但是有些情况下我们可能不想使用方法泛型。

来自scala的术语表:

[mw_shl_code=bash,true]An existential type includes references to type variables that are unknown. For example, Array[T] forSome { type T } is an existential type. It is an array of T, where T is some completely unknown type. All that is assumed about T is that it exists at all. This assumption is weak, but it means at least that an Array[T] forSome { type T } is indeed an array and not a banana.[/mw_shl_code]

也就是说forSome只是表面类型存在,至于它是什么类型,不关心,Java中我们这样定义泛型:
[mw_shl_code=bash,true]class MyClass<?> {
    ...
}[/mw_shl_code]

也就是我们不关心泛型的类型,任何类型都可以。

另外可以指定某些类型,类似于Java中的

[mw_shl_code=bash,true]def addToFirst(x : Array[T] forSome {type T <: Integer}) = x(0) + 1
[/mw_shl_code]
[mw_shl_code=bash,true]class MyClass<? extends Integer> {
    ...
}[/mw_shl_code]

一种更简洁的写法:
[mw_shl_code=bash,true]def addToFirst(x: Array[_ <: Integer ]) = x(0) + 1
[/mw_shl_code]

对于下面两个例子:
[mw_shl_code=bash,true]Array[T] forSome { type T; }
Array[T forSome { type T; }][/mw_shl_code]

他们之间的区别非常大,第一个代表元素类型为任意类型的Array,第二个代表Array[Any]。即:T forSome {type T;} 等同于Any。
[mw_shl_code=bash,true]Type Any is equivalent to a for_some { type a; }
[/mw_shl_code]


再看:
[mw_shl_code=scala,true]Map[Class[T forSome { type T}], String]  // Map[Class[Any],String]
Map[Class[T] forSome { type T}, String]  // key为任意类型的Class
Map[Class[T], String] forSome { type T}[/mw_shl_code]

使用通配符:
[mw_shl_code=scala,true]Array[_]               // Array[T] forSome {type T}
Map[Class[_] , String] //Map[Class[T] ,String] forSome {type T}[/mw_shl_code]


lazy用法:

Scala中使用关键字lazy来定义惰性变量,实现延迟加载(懒加载)。
惰性变量只能是不可变变量,并且只有在调用惰性变量时,才会去实例化这个变量。

在Java中,要实现延迟加载(懒加载),需要自己手动实现。一般的做法是这样的:
[mw_shl_code=scala,true]public class LazyDemo {

  private String property;

public String getProperty() {
  if (property == null) {//如果没有初始化过,那么进行初始化
    property = initProperty();
  }
  return property;
}

  private String initProperty() {
    return "property";
  }
}[/mw_shl_code]


比如常用的单例模式懒汉式实现时就使用了上面类似的思路实现。

而在Scala中对延迟加载这一特性提供了语法级别的支持:

[mw_shl_code=scala,true]lazy val property = initProperty()
[/mw_shl_code]

使用lazy关键字修饰变量后,只有在使用该变量时,才会调用其实例化方法。也就是说在定义property=initProperty()时并不会调用initProperty()方法,只有在后面的代码中使用变量property时才会调用initProperty()方法。

如果不使用lazy关键字对变量修饰,那么变量property是立即实例化的:


[mw_shl_code=scala,true]object LazyOps {

    def init(): String = {
        println("call init()")
        return ""
    }

    def main(args: Array[String]) {
        val property = init();//没有使用lazy修饰
        println("after init()")
        println(property)
    }

}
[/mw_shl_code]

上面的property没有使用lazy关键字进行修饰,所以property是立即实例化的,如果观察程序的输出:
[mw_shl_code=scala,true]call init()
after init()
[/mw_shl_code]


可以发现,property声明时,立即进行实例化,调用了`init()“实例化方法

而如果使用lazy关键字进行修饰:


[mw_shl_code=scala,true]object LazyOps {

    def init(): String = {
        println("call init()")
        return ""
    }

    def main(args: Array[String]) {
        lazy val property = init();//使用lazy修饰
        println("after init()")
        println(property)
        println(property)
    }

}
[/mw_shl_code]

观察输出:
[mw_shl_code=scala,true]after init()
call init()
[/mw_shl_code]


在声明property时,并没有立即调用实例化方法intit(),而是在使用property时,才会调用实例化方法,并且无论缩少次调用,实例化方法只会执行一次。

与Java相比起来,实现懒加载确实比较方便了。那么Scala是如何实现这个语法糖的呢?反编译看下Scala生成的class:


[mw_shl_code=scala,true]private final String property$lzycompute$1(ObjectRef property$lzy$1, VolatileByteRef bitmap$0$1)
  {
    synchronized (this)//加锁
    {
      if ((byte)(bitmap$0$1.elem & 0x1) == 0)//如果属性不为null
      {//那么进行初始化
        property$lzy$1.elem = init();bitmap$0$1.elem = ((byte)(bitmap$0$1.elem | 0x1));
      }
      return (String)property$lzy$1.elem;
    }
  }
[/mw_shl_code]


Scala同样使用了Java中常用的懒加载的方式自动帮助我们实现了延迟加载,并且还加锁避免多个线程同时调用初始化方法可能导致的不一致问题。

小结

对于这样一个表达式: lazy val t:T = expr 无论expr是什么东西,字面量也好,方法调用也好。Scala的编译器都会把这个expr包在一个方法中,并且生成一个flag来决定只在t第一次被访问时才调用该方法。

另外一篇:ScalaLazy用法




apply用法:

apply网上很多资料,但是总感觉总讲的不是很透彻。这里自己总结下:包括摘录及个人经验,希望可以加深大家的理解


什么情况下会调用apply方法


当遇到下面表达式时,apply方法会被调用:Object(参数1,参数2。。。。参数N)
通常这样一个方法返回的是伴生对象。
举例来说:Array对象定义了apply,我们可以用下面形式来数组
Array("about”,"yun","com")
为什么不使用构造器?对于嵌套式表达式而言,省去new关键字方便很多,例如
Array(Array("about","yun"),Array("www","com"))

我们比较容易搞混
Array("about","yun")与new Array("about","yun")
表面是多了一个new,他们的原理是不同的。
第一个调用的apply方法
第二个调用的this构造器

理解apply方法:实例化作用
这里一个定义apply方法的示例
class Account private (val id: Int, initaBalance: Double){
private var balance = initaBalance

}



Object Account//伴生对象
{
def apply( initaBalance: Double)=new Account (newUniqueNumber(),initalBalance)
}
这样就可以通过apply创建对象了。
val acct=Account (1000.0)

我们在来看一个例子

package aboutyun.com

class ApplyTest{
  def apply()=println("I want to speak good english !")

  def haveATry: Unit ={
    println("Have a try on apply")
  }
}
object ApplyTest{
  def apply()={
    println("I am a student of KMUST")
    new ApplyTest
  }
}
//驱动函数
object ApplyOperation {
  def main(args:Array[String]): Unit ={
//调用伴生对象的apply方法,实例化
    val a=ApplyTest()
    a.haveATry
  }
}

上面我们可以先忽略类的apply方法,只看伴生对象的apply方法。也就是说伴生对象的apply方法简化了对象的实例,一般对象的实例化是
A a=new A();
有了apply,我们可以这样
A a=A();
同样对应起来
val a=new ApplyTest()
有了apply,可以这样
val a=ApplyTest(),其实它是是val a=ApplyTest.apply()的简化。
从这里我们再次得出,只要是我们使用apply实例化的类,必定有伴生对象。
(补充:Scala中同名和类和对象分别称之为伴生类和伴生对象)
为了加深理解,我们在换另外一个说法

半生对象中有一个apply方法  
构造类的时候一般不用new A()

上面不知你是否理解,这样我们在反过来,假如一个类,没有伴生对象,能否使用使用A a=A();的方式。
显然这样是不行的。但是在Scala中,很多类默认都是由伴生对象的,所以我们可以简化类的实例。这样apply方法你是否理解了。
对象调用apply方法

apply除了实例化作用,我们看到实例化的对象,还可以a();这样,这时候调用的是类的apply方法,而非伴生对象的。
package aboutyun.com

class ApplyTest{
  def apply()=println("I want to speak good english !")

  def haveATry: Unit ={
    println("Have a try on apply")
  }
}
object ApplyTest{
  def apply()={
    println("I am a student of KMUST")
    new ApplyTest
  }
}
//驱动函数
object ApplyOperation {
  def main(args:Array[String]): Unit ={
//调用伴生对象的apply方法,实例化
    val a=ApplyTest()
    a.haveATry
    a();
  }
}





##############################

通过上面我们的理解,我们来看下下面的内容:

[mw_shl_code=scala,true]scala> val f = (x: Int) => x + 1
f: Int => Int = <function1>[/mw_shl_code]

改如何调用函数对象的方法[mw_shl_code=scala,true]scala> f.apply(3)[/mw_shl_code]

上面为何能这么做,这是因为函数也是对象,, 每一个对象都是scala.FunctionN(1-22)的实例。每次调用方法对象都要通过FunctionN.apply(x, y...), 就会略显啰嗦, Scala提供一种模仿函数调用的格式来调用函数对象

[mw_shl_code=scala,true]scala> scala> f(3)
res3: Int = 4[/mw_shl_code]

上面其实就是scala.FunctionN的对象。f(3),就是 f.apply(3)


#############################################
apply方法具有查找属性

集合类
在Scala集合一文中提到过Iterator迭代器的几个子trait包括Seq, Set, Map都继承PartialFunction并实现了apply方法, 不同的是实现的方式不一样,


也就是说如果前面是集合,后面使用apply,则具有查找功能

[mw_shl_code=scala,true]scala> Seq(1, 2, 3).apply(1) // 检索
res6: Int = 2
  
scala> Set(1, 2, 3).apply(2) // 判断是否存在
res7: Boolean = true
  
scala> Map("china" -> "beijing", "US" -> "Washington").apply("US") // 根据键查找值
res8: String = Washington
  
scala> Set(1, 2, 3)(2)
res9: Boolean = true
  
scala> Set(1, 2, 3)(2)
res10: Boolean = true
  
scala> Map("china" -> "beijing", "US" -> "Washington")("US")
res11: String = Washington[/mw_shl_code]

总结:

从上面总结:
apply方法具有三个作用
1.具有实例化的作用
2.实例化对象可以再次调用apply方法
3.apply在集合中具有查找作用

yield用法

scala里面的for...yield循环:

下面那段话的意义就是,for 循环中的 yield 会把当前的元素记下来,保存在集合中,循环结束后将返回该集合。Scala 中 for 循环是有返回值的。如果被循环的是 Map,返回的就是  Map,被循环的是 List,返回的就是 List,以此类推。


1.png

返回:Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25)
还可以增加表达式,例如if:




2.png

返回:

Vector(6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25)

每循环一次,会自动添加一个yield的1*x表达式算出来的值,循环结束后,会返回所有yield的值组成的集合。返回的集合类型和遍历的类型是一样的。


map用法

scala对map的操作:

3.png

打印:

1---->str1
2---->str2
3---->str3


partition用法


partition根据断言函数的返回值对列表进行拆分。


4.png

经过partition之后,会返回元组形式。

filter用法

有时候需要对一个集合进行判断,比如,从1到10里面去查找5这个元素:

5.png

以上代码可以改成这样:


6.png

是不是简单很多?
以上的:

7.png

实际上就是:



8.png
只是代码方便了很多。


update用法

在Scala中,名字叫做update的方法是有特殊作用的。
比如:
[mw_shl_code=scala,true]val scores = new scala.collection.mutable.HashMap[String, Int]
scores("Bob") = 100
val bobsScore = scores("Bob")[/mw_shl_code]

以上三行代码,我们创建了一个可变的map来存储得分情况,然后我们记录了Bob的得分是100分,最后我们又把Bob的分数取出来了。

这三行代码看似平淡无奇,实则暗藏了一点点玄机。

第二行实际是调用了HashMap的update方法。

第三行实际是调用了HashMap的apply方法。

我们可以把上面的代码改写成下面的等价形式:

[mw_shl_code=scala,true]val scores = new scala.collection.mutable.HashMap[String, Int]
scores.update("Bob", 100)
val bobsScore = scores.apply("Bob”)[/mw_shl_code]

虽然等价,但是可读性却降低了一些。

apply方法我们之前讲过,就不再赘述。

update方法也不太复杂,它的规则就是:

[mw_shl_code=scala,true]x(y) = z[/mw_shl_code]
这样的代码会被编译为:
[mw_shl_code=scala,true]x.update(y, z)
[/mw_shl_code]


这次的目的主要是介绍一个update方法的适用场景。

我们来看用来修改某个人地址的一段代码:

[mw_shl_code=scala,true]class AddressChanger {

  def update(name: String, age: Int, newAddress: String) = {
    println(s"changing address of $name, whose age is $age to $newAddress")
    //actually change the address
  }

}[/mw_shl_code]

我们可以这样来调用它:
[mw_shl_code=scala,true]val changer = new AddressChanger()
changer.update("xiao ming", 23, "beijing")[/mw_shl_code]
或者,我们也可以这样来调用它:
[mw_shl_code=scala,true]val addressOf = new AddressChanger()
addressOf(name = "xiao ming", age = 23) = "beijing"[/mw_shl_code]


这两段代码是等价的。

比较一下,前一种用法显得中规中矩,没什么特别好的,也没啥特大的毛病。

可是后一种用法就不同了,读起来很通顺,有读英语语句的感觉:把名字叫做小明,年龄23岁的人的地址改为北京。

如果再给AddressChanger加上一个apply方法,我们还可以写这样的代码:

[mw_shl_code=scala,true]val currentAddress = addressOf(name = "xiao ming", age = 23)
[/mw_shl_code]
这样,读取和更新的代码都看起来非常自然。

如果我们把这两段代码连起来看:

[mw_shl_code=scala,true]val currentAddress = addressOf(name = "xiao ming", age = 23)
addressOf(name = "xiao ming", age = 23) = "beijing"[/mw_shl_code]
感觉甚好。

addressOf(name = “xiao ming”, age = 23)可以看做一个整体,它就如同一个可读可写的属性。

我们把它放到赋值语句的右侧,就能取到小明的当前住址。

我们把它放到赋值语句的左侧,就能修改小明的住址。

apply和update都是蛮简单的语言特性,但是加以合适的应用,却能得到可读性极强的代码。
关于update也可参考Scala 中区别 apply 和 update 方法


相关篇


spark开发基础之从Scala快餐序言
http://www.aboutyun.com/forum.php?mod=viewthread&tid=20335

spark开发基础之Scala资源汇总
http://www.aboutyun.com/forum.php?mod=viewthread&tid=20233




参考:
csdn严小超
cnblogs Ayning
cnblogs cuipengfei
csdn 崔鹏飞









已有(8)人评论

跳转到指定楼层
地球仪 发表于 2016-11-9 09:39:13
spark开发基础之从关键字入门Scala
回复

使用道具 举报

sdtm1016 发表于 2016-11-9 16:34:02
非常 不错的文章
回复

使用道具 举报

1691335036 发表于 2016-11-9 19:47:13
很不错的文章顶起
回复

使用道具 举报

lixvrui1103 发表于 2016-12-22 16:48:02
这一章感觉有点跟不上了,前几章看来没有消化好
回复

使用道具 举报

heraleign 发表于 2018-9-2 18:16:21
哈哈,非常好,scala真的有好多语法非常简捷非常赞
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条