`
zhangziyangup
  • 浏览: 1083571 次
文章分类
社区版块
存档分类
最新评论

面向 Java 开发人员的 Scala 指南: 关于特征和行为,使用 Scala 版本的 Java 接口

 
阅读更多

著名科学家、研究学者艾萨克.牛顿爵士有这样一句名言:“如果说我看得比别人远一些,那是因为我站在巨人的肩膀上”。作为一名热心的历史和政治学家,我想对这位伟人的名言略加修改:“如果说我看得比别人远一些,那是因为我站在历史的肩膀上”。而这句话又体现出另一位历史学家 George Santayana 的名言:“忘记历史必将重蹈覆辙”。换句话说,如果我们不能回顾历史,从过去的错误(包括我们自己过去的经验)中吸取教训,就没有机会做出改进。

您可能会疑惑,这样的哲学与 Scala 有什么关系?继承就是我们要讨论的内容之一。考虑这样一个事实,Java 语言的创建已经是近 20 年前的事情,当时是 “面向对象” 的全盛时期。它设计用于模仿当时的主流语言 C++,尝试将使用这种语言的开发人员吸引到 Java 平台上来。毫无疑问,在当时看来,这样的决策是明智而且必要的,但回顾一下,就会发现其中有些地方并不像创建者设想的那样有益。

例如,在二十年前,对于 Java 语言的创建者来说,反映 C++ 风格的私有继承和多重继承是必要的。自那之后,许多 Java 开发人开始为这些决策而后悔。在这一期的 Scala 指南中,我回顾了 Java 语言中多重继承和私有继承的历史。随后,您将看到 Scala 是怎样改写了历史,为所有人带来更大收益。

C++ 和 Java 语言中的继承

历史是人们愿意记录下来的事实。
—拿破仑.波拿巴

从事 C++ 工作的人们能够回忆起,私有继承是从基类中获取行为的一种方法,不必显式地接受 IS-A 关系。将基类标记为 “私有” 允许派生类从该基类继承而来,而无需实际成为 一个基类。但对自身的私有继承是未得到广泛应用的特性之一。继承一个基类而无法将它向下或向上转换到基类的理念是不明智的。

另一方面,多重继承往往被视为面向对象编程的必备要素。在建模交通工具的层次结构时, SeaPlane 无疑需要继承 Boat(使用其 startEngine()sail() 方法)以及 Plane(使用其 startEngine()fly() 方法)。SeaPlane 既是 Boat,也是 Plane,难道不是吗?

无论如何,这是在 C++ 鼎盛时期的想法。在快速转向 Java 语言时,我们认为多重继承与私有继承一样存在缺陷。所有 Java 开发人员都会告诉您,SeaPlane 应该继承 FloatableFlyable 接口(或许还包括 EnginePowered 接口或基类)。继承接口意味着能够实现该类需要的所有方法,而不会遇到 虚拟多重继承 的难题(遇到这种难题时,要弄清楚在调用 SeaPlanestartEngine() 方法时应调用哪个基类的 startEngine())。

遗憾的是,彻底放弃私有继承和多重继承会使我们在代码重用方面付出昂贵的代价。Java 开发人员可能会因从虚拟多重继承中解放出来而高兴,但代价是程序员往往要完成辛苦而易于出错的工作。

回顾可重用行为

事情大致可以分为可能永远不会发生的和不重要的。
—William Ralph Inge

JavaBeans 规范是 Java 平台的基础,它带来了众多 Java 生态系统作为依据的 POJO。我们都明白一点,Java 代码中的属性由 get()/set() 对管理,如清单 1 所示:


清单 1. Person POJO

  1. //ThisisJava
  2. publicclassPerson
  3. {
  4. privateStringlastName;
  5. privateStringfirstName;
  6. privateintage;
  7. publicPerson(Stringfn,Stringln,inta)
  8. {
  9. lastName=ln;firstName=fn;age=a;
  10. }
  11. publicStringgetFirstName(){returnfirstName;}
  12. publicvoidsetFirstName(Stringv){firstName=v;}
  13. publicStringgetLastName(){returnlastName;}
  14. publicvoidsetLastName(Stringv){lastName=v;}
  15. publicintgetAge(){returnage;}
  16. publicvoidsetAge(intv){age=v;}
  17. }

这些代码看起来非常简单,编写起来也不难。但如果您希望提供通知支持 — 使第三方能够使用 POJO 注册并在变更属性时接收回调,事情会怎样?根据 JavaBeans 规范,必须实现 PropertyChangeListener 接口以及它的一个方法 propertyChange()。如果您希望允许任何 POJO 的 PropertyChangeListener 都能够对属性更改 “投票”,那么 POJO 就需要实现 VetoableChangeListener 接口,该接口的实现又依赖于 vetoableChange() 方法的实现。

至少,事情应该是这样运作的。

实际上,希望成为属性变更通知接收者的用户必须实现 PropertyChangeListener 接口,发送者(本例中的 Person 类)必须提供接收该接口实例的公共方法和监听器需要监听的属性名称。最终得到更加复杂的 Person,如清单 2 所示:


清单 2. Person POJO,第 2 种形式

  1. //ThisisJava
  2. publicclassPerson
  3. {
  4. //restasbefore,exceptthatinsideeachsetterwehavetodosomething
  5. //like:
  6. //publicsetFoo(TnewValue)
  7. //{
  8. //ToldValue=foo;
  9. //foo=newValue;
  10. //pcs.firePropertyChange("foo",oldValue,newValue);
  11. //}
  12. publicvoidaddPropertyChangeListener(PropertyChangeListenerpcl)
  13. {
  14. //keepareferencetopcl
  15. }
  16. publicvoidremovePropertyChangeListener(PropertyChangeListenerpcl)
  17. {
  18. //findthereferencetopclandremoveit
  19. }
  20. }

保持引用属性变更监听器意味着 Person POJO 必须保留某种类型的集合类(例如 ArrayList)来包含所有引用。然后必须实例化、插入并移除 POJO — 由于这些操作不是原子操作,因此还必须包含恰当的同步保护。

最后,如果某个属性发生变化,属性监听器列表必须得到通知,通常通过遍历 PropertyChangeListener 的集合并对各元素调用 propertyChange() 来实现。此过程包括传入新的 PropertyChangeEvent 描述属性、原有值和新值,这是 PropertyChangeEvent 类和 JavaBeans 规范的要求。

在我们编写的 POJO 中,只有少数支持监听器通知,这并不意外。在这里要完成大量工作,必须手动地重复处理所创建的每一个 JavaBean/POJO。

除了工作还是工作 — 变通方法在哪里?

有趣的是,C++ 对于私有继承的支持在 Java 语言中得到了延续,今天,我们用它来解决 JavaBeans 规范的难题。一个基类为 POJO 提供了基本 add()remove() 方法、集合类以及 “firePropertyChanged()” 方法,用于通知监听器属性变更。

我们仍然可以通过 Java 类完成,但由于 Java 缺乏私有继承,Person 类必须继承 Bean 基类,从而可向上转换Bean。这妨碍了 Person 继承其他类。多重继承可能使我们不必处理后续的问题,但它也重新将我们引向了虚拟继承,而这是绝对要避免的。

针对这个问题的 Java 语言解决方案是运用众所周知的支持 类,在本例中是 PropertyChangeSupport:实例化 POJO 中的一个类,为 POJO 本身使用必要的公共方法,各公共方法都调用 Support 类来完成艰难的工作。更新后的 Person POJO 可以使用 PropertyChangeSupport,如下所示:


清单 3. Person POJO,第 3 种形式

  1. //ThisisJava
  2. importjava.beans.*;
  3. publicclassPerson
  4. {
  5. privateStringlastName;
  6. privateStringfirstName;
  7. privateintage;
  8. privatePropertyChangeSupportpropChgSupport=
  9. newPropertyChangeSupport(this);
  10. publicPerson(Stringfn,Stringln,inta)
  11. {
  12. lastName=ln;firstName=fn;age=a;
  13. }
  14. publicStringgetFirstName(){returnfirstName;}
  15. publicvoidsetFirstName(StringnewValue)
  16. {
  17. Stringold=firstName;
  18. firstName=newValue;
  19. propChgSupport.firePropertyChange("firstName",old,newValue);
  20. }
  21. publicStringgetLastName(){returnlastName;}
  22. publicvoidsetLastName(StringnewValue)
  23. {
  24. Stringold=lastName;
  25. lastName=newValue;
  26. propChgSupport.firePropertyChange("lastName",old,newValue);
  27. }
  28. publicintgetAge(){returnage;}
  29. publicvoidsetAge(intnewValue)
  30. {
  31. intold=age;
  32. age=newValue;
  33. propChgSupport.firePropertyChange("age",old,newValue);
  34. }
  35. publicvoidaddPropertyChangeListener(PropertyChangeListenerpcl)
  36. {
  37. propChgSupport.addPropertyChangeListener(pcl);
  38. }
  39. publicvoidremovePropertyChangeListener(PropertyChangeListenerpcl)
  40. {
  41. propChgSupport.removePropertyChangeListener(pcl);
  42. }
  43. }

不知道您有何感想,但这段代码的复杂得让我想去重拾汇编语言。最糟糕的是,您要对所编写的每一个 POJO 重复这样的代码序列。清单 3 中的半数工作都是在 POJO 本身中完成的,因此无法被重用 — 除非是通过传统的 “复制粘贴” 编程方法。

现在,让我们来看看 Scala 提供什么样内容来实现更好的变通方法。

Scala 中的特征和行为重用

所有人都有义务考虑自己的性格特征。必须合理控制这些特征,而不去质疑他人的性格特征是否更适合自己。
—西塞罗

Scala 使您能够定义处于接口和类之间的新型结构,称为特征(trait)。特征很奇特,因为一个类可以按照需要整合许多特征,这与接口相似,但它们还可包含行为,这又与类相似。同样,与类和接口类似,特征可以引入新方法。但与类和接口不同之处在于,在特征作为类的一部分整合之前,不会检查行为的定义。或者换句话说,您可以定义出这样的方法,在整合到使用特征的类定义之前,不会检查其正确性。

特征听起来十分复杂,但一个实例就可以非常轻松地理解它们。首先,下面是在 Scala 中重定义的 Person POJO:


清单 4. Scala 的 Person POJO

                
//This is Scala
class Person(var firstName:String, var lastName:String, var age:Int)
{
}

您还可以确认 Scala POJO 具备基于 Java POJO 的环境中需要的 get()/set() 方法,只需在类参数 firstNamelastNameage 上使用 scala.reflect.BeanProperty 注释即可。现在,为简单起见,我们暂时不考虑这些方法。

如果 Person 类需要能够接收 PropertyChangeListener,可以使用如清单 5 所示的方式来完成此任务:


清单 5. Scala 的 Person POJO 与监听器

  1. //ThisisScala
  2. objectPCL
  3. extendsjava.beans.PropertyChangeListener
  4. {
  5. overridedefpropertyChange(pce:java.beans.PropertyChangeEvent):Unit=
  6. {
  7. System.out.println("Beanchangedits"+pce.getPropertyName()+
  8. "from"+pce.getOldValue()+
  9. "to"+pce.getNewValue())
  10. }
  11. }
  12. objectApp
  13. {
  14. defmain(args:Array[String]):Unit=
  15. {
  16. valp=newPerson("Jennifer","Aloi",28)
  17. p.addPropertyChangeListener(PCL)
  18. p.setFirstName("Jenni")
  19. p.setAge(29)
  20. System.out.println(p)
  21. }
  22. }

注意,如何使用清单 5 中的 object 实现将静态方法注册为监听器 — 而在 Java 代码中,除非显式创建并实例化 Singleton 类,否则永远无法实现。这进一步证明了一个理论:Scala 从 Java 开发的历史 痛苦 中吸取了教训。

Person 的下一步是提供 addPropertyChangeListener() 方法,并在属性更改时对各监听器触发 propertyChange() 方法调用。在 Scala 中,以可重用的方式完成此任务与定义和使用特征一样简单,如清单 6 所示。我将此特征称为 BoundPropertyBean,因为在 JavaBeans 规范中,“已通知” 的属性称为绑定属性


清单 6. 神圣的行为重用!

  1. //ThisisScala
  2. traitBoundPropertyBean
  3. {
  4. importjava.beans._
  5. valpcs=newPropertyChangeSupport(this)
  6. defaddPropertyChangeListener(pcl:PropertyChangeListener)=
  7. pcs.addPropertyChangeListener(pcl)
  8. defremovePropertyChangeListener(pcl:PropertyChangeListener)=
  9. pcs.removePropertyChangeListener(pcl)
  10. deffirePropertyChange(name:String,oldVal:_,newVal:_):Unit=
  11. pcs.firePropertyChange(newPropertyChangeEvent(this,name,oldVal,newVal))
  12. }

同样,我依然要使用 java.beans 包的 PropertyChangeSupport 类,不仅因为它提供了约 60% 的实现细节,还因为我所具备的行为与直接使用它的 JavaBean/POJO 相同。对 “Support” 类的其他任何增强都将传播到我的特征。不同之处在于 Person POJO 不需要再直接使用 PropertyChangeSupport,如清单 7 所示:


清单 7. Scala 的 Person POJO,第 2 种形式
                
//This is Scala
class Person(var firstName:String, var lastName:String, var age:Int)
    extends Object
    with BoundPropertyBean
{
    override def toString = "[Person: firstName=" + firstName +
        " lastName=" + lastName + " age=" + age + "]"
}

在编译后,简单查看 Person 定义即可发现它有公共方法 addPropertyChangeListener()removePropertyChangeListener()firePropertyChange(),就像 Java 版本的 Person 一样。实际上,Scala 的 Person 版本仅通过一行附加的代码即获得了这些新方法:类声明中的 with 子句将 Person 类标记为继承 BoundPropertyBean 特征。

遗憾的是,我还没有完全实现;Person 类现在支持接收、移除和通知监听器,但 Scala 为 firstName 成员生成的默认方法并没有利用它们。同样遗憾的是,这样编写的 Scala 没有很好的注释以自动地 生成利用 PropertyChangeSupport 实例的 get/set 方法,因此我必须自行编写,如清单 8 所示:


清单 8. Scala 的 Person POJO,第 3 种形式

  1. //ThisisScala
  2. classPerson(varfirstName:String,varlastName:String,varage:Int)
  3. extendsObject
  4. withBoundPropertyBean
  5. {
  6. defsetFirstName(newvalue:String)=
  7. {
  8. valoldvalue=firstName
  9. firstName=newvalue
  10. firePropertyChange("firstName",oldvalue,newvalue)
  11. }
  12. defsetLastName(newvalue:String)=
  13. {
  14. valoldvalue=lastName
  15. lastName=newvalue
  16. firePropertyChange("lastName",oldvalue,newvalue)
  17. }
  18. defsetAge(newvalue:Int)=
  19. {
  20. valoldvalue=age
  21. age=newvalue
  22. firePropertyChange("age",oldvalue,newvalue)
  23. }
  24. overridedeftoString="[Person:firstName="+firstName+
  25. "lastName="+lastName+"age="+age+"]"
  26. }

应该具备的出色特征

特征不是一种函数编程 概念,而是十多年来反思对象编程的结果。实际上,您很有可能正在简单的 Scala 程序中使用以下特征,只是没有意识到而已:


清单 9. 再见,糟糕的 main()!
                //This is Scala
object App extends Application
{
    val p = new Person("Jennifer", "Aloi", 29)

    p.addPropertyChangeListener(PCL)
    
    p.setFirstName("Jenni")
    p.setAge(30)
    
    System.out.println(p)
}

Application 特征定义了一直都是手动定义的 main() 的方法。实际上,它包含一个有用的小工具:计时器,如果系统属性 scala.time 传递给了 Application 实现代码,它将为应用程序的执行计时,如清单 10 所示:


清单 10. 时间就是一切
                
$ scala -Dscala.time App
Bean changed its firstName from Jennifer to Jenni
Bean changed its age from 29 to 30
[Person: firstName=Jenni lastName=Aloi age=30]
[total 15ms]

JVM 中的特征

任何足够高级的技术都近乎魔术。
— Arthur C Clarke

在这个时候,有必要提出这样一个问题,这种看似魔术的接口与方法结构( 特征)是如何映射到 JVM 的。在清单 11 中,我们的好朋友 javap 展示了魔术背后发生了什么:


清单 11. Person 内幕

  1. $javap-classpathC:/Prg/scala-2.7.0-final/lib/scala-library.jar;classesPerson
  2. Compiledfrom"Person.scala"
  3. publicclassPersonextendsjava.lang.ObjectimplementsBoundPropertyBean,scala.
  4. ScalaObject{
  5. publicPerson(java.lang.String,java.lang.String,int);
  6. publicjava.lang.StringtoString();
  7. publicvoidsetAge(int);
  8. publicvoidsetLastName(java.lang.String);
  9. publicvoidsetFirstName(java.lang.String);
  10. publicvoidage_$eq(int);
  11. publicintage();
  12. publicvoidlastName_$eq(java.lang.String);
  13. publicjava.lang.StringlastName();
  14. publicvoidfirstName_$eq(java.lang.String);
  15. publicjava.lang.StringfirstName();
  16. publicint$tag();
  17. publicvoidfirePropertyChange(java.lang.String,java.lang.Object,java.lang
  18. .Object);
  19. publicvoidremovePropertyChangeListener(java.beans.PropertyChangeListener);
  20. publicvoidaddPropertyChangeListener(java.beans.PropertyChangeListener);
  21. publicfinalvoidpcs_$eq(java.beans.PropertyChangeSupport);
  22. publicfinaljava.beans.PropertyChangeSupportpcs();
  23. }

请注意 Person 的类声明。该 POJO 实现了一个名为 BoundPropertyBean 的接口,这就是特征作为接口映射到 JVM 本身的方法。但特征方法的实现又是什么样的呢?请记住,编译器可以容纳所有技巧,只要最终结果符合 Scala 语言的语义含义即可。在这种情况下,它会将特征中定义的方法实现和字段声明纳入实现特征的类 Person 中。使用 -private 运行 javap 会使这更加显著 — 如果 javap 输出的最后两行体现的还不够明显(引用特征中定义的 pcs 值):


清单 12. Person 内幕,第 2 种形式

  1. $javap-private-classpathC:/Prg/scala-2.7.0-final/lib/scala-library.jar;classesPerson
  2. Compiledfrom"Person.scala"
  3. publicclassPersonextendsjava.lang.ObjectimplementsBoundPropertyBean,scala.
  4. ScalaObject{
  5. privatefinaljava.beans.PropertyChangeSupportpcs;
  6. privateintage;
  7. privatejava.lang.StringlastName;
  8. privatejava.lang.StringfirstName;
  9. publicPerson(java.lang.String,java.lang.String,int);
  10. publicjava.lang.StringtoString();
  11. publicvoidsetAge(int);
  12. publicvoidsetLastName(java.lang.String);
  13. publicvoidsetFirstName(java.lang.String);
  14. publicvoidage_$eq(int);
  15. publicintage();
  16. publicvoidlastName_$eq(java.lang.String);
  17. publicjava.lang.StringlastName();
  18. publicvoidfirstName_$eq(java.lang.String);
  19. publicjava.lang.StringfirstName();
  20. publicint$tag();
  21. publicvoidfirePropertyChange(java.lang.String,java.lang.Object,java.lang.Object);
  22. publicvoidremovePropertyChangeListener(java.beans.PropertyChangeListener);
  23. publicvoidaddPropertyChangeListener(java.beans.PropertyChangeListener);
  24. publicfinalvoidpcs_$eq(java.beans.PropertyChangeSupport);
  25. publicfinaljava.beans.PropertyChangeSupportpcs();
  26. }

实际上,这个解释也回答了为何可以推迟特征方法的执行,直至用该检查的时候。因为在类实现特征的方法之前,它实际上并不是任何类的一 “部分”,因此编译器可将方法的某些逻辑方面留到以后再处理。这非常有用,因为它允许特征在不了解实现特征的实际基类将是什么的情况下调用 super()

关于特征的备注

BoundPropertyBean 中,我在 PropertyChangeSupport 实例的构建中使用了特征功能。其构造方法需要属性得到通知的 bean,在早先定义的特征中,我传入了 “this”。由于在 Person 上实现之前并不会真正定义特征,“this” 将引用 Person 实例,而不是 BoundPropertyBean 特征本身。特征的这个具体方面 — 定义的推迟解析 — 非常微妙,但对于此类的 “迟绑定” 来说可能非常强大。

对于 Application 特征的情况,有两部分很有魔力;Application 特征的 main() 方法为 Java 应用程序提供普适入口点,还会检查 -Dscala.time 系统属性,查看是否应该跟踪执行时间。但由于 Application 是一个特征,方法实际上会在子类上出现(App)。要执行此方法,必须创建 App 单体,也就是说构造 App 的一个实例,“处理” 类的主体,这将有效地执行应用程序。只有在这种处理完成之后,特征的 main() 才会被调用并显示执行所耗费的时间。

虽然有些落后,但它仍然有效,尽管应用程序无权访问任何传入 main() 的命令行参数。它还表明特征的行为如何 “下放到” 实现类。

特征和集合

不是解决方法的一部分,就注定被淘汰。
— Henry J Tillman

在将具体行为与抽象声明相结合以便为实现者提供便捷时,特征非常强大。例如,考虑经典的 Java 集合接口/类 ListArrayListList 接口保证此集合的内容能够按照插入时的次序被遍历,用更正规的术语来说,“位置语义得到了保证”。

ArrayListList 的具体类型,在分配好的数组中存储内容,而 LinkedList 使用的是链表实现。ArrayList 更适合列表内容的随机访问,而 LinkedList 更适合在除了列表末尾以外的位置进行插入和删除操作。无论如何,这两种类之间存在大量相同的行为,它们继承了公共基类 AbstractList

如果 Java 编程支持特征,它们应已成为出色的结构,能够解决 “可重用行为,而无需诉诸于继承公共基类” 之类的问题。特征可以作为 C++ “私有继承” 机制,避免出现新 List 子类型是否应直接实现 List(还有可能忘记实现 RandomAccess 接口)或者扩展基类 AbstractList 的迷惑。这有时在 C++ 中称为 “混合”,与 Ruby 的混合(或后文中探讨的 Scala 混合)有所不同。

在 Scala 文档集中,经典的示例就是 Ordered 特征,它定义了名字很有趣的方法,以提供比较(以及排序)功能,如清单 13 所示:


清单 13. 顺序、顺序

                
//This is Scala
trait Ordered[A] {
  def compare(that: A): Int
  
  def <  (that: A): Boolean = (this compare that) <  0
  def >  (that: A): Boolean = (this compare that) >  0
  def <= (that: A): Boolean = (this compare that) <= 0
  def >= (that: A): Boolean = (this compare that) >= 0
  def compareTo(that: A): Int = compare(that)
}

在这里,Ordered 特征(具有参数化类型,采用 Java 5 泛型方式)定义了一个抽象方法 compare,它应获得一个 A 作为参数,并需要在 “小于” 的情况下返回小于 1 的值,在 “大于” 的情况下返回大于 1 的值,在相等的情况下返回 0。然后它继续使用 compare() 方法和更加熟悉的 compareTo() 方法(java.util.Comparable 接口也使用该方法)定义关系运算符(<> 等)。

Scala 和 Java 兼容性

一张图片胜过千言万语。一个界面胜过上千图片。
—Ben Shneiderman

实际上,伪实现继承并不是 Scala 内特征的最常见应用或最强大用法,与此不同,特征在 Scala 内作为 Java 接口的基本替代项。希望使用 Scala 的 Java 程序员也应熟悉特征,将其作为使用 Scala 的一种机制。

我在本系列的文章中一直强调,编译后的 Scala 代码并非总是能够保证 Java 语言的特色。例如,回忆一下,Scala 的 “名字很有趣的方法”(例如 “+” 或 “/”),这些方法往往会使用 Java 语言语法中不直接可用的字符编码(“$” 就是一个需要考虑的严重问题)。出于这方面的原因,创建 “Java 可调用” 的接口往往要求深入研究 Scala 代码。

这个特殊示例有些憋足,Scala 主义者 通常并不需要特征提供的间接层(假设我并未使用 “名字很有趣的方法”),但概念在这里十分重要。在清单 14 中,我希望获得一个传统的 Java 风格工厂,生成 Student 实例,就像您经常在各种 Java 对象模型中可以看到的那样。最初,我需要一个兼容 Java 的接口,接合到 Student


清单 14. 我,学生

  1. //ThisisScala
  2. traitStudent
  3. {
  4. defgetFirstName:String;
  5. defgetLastName:String;
  6. defsetFirstName(fn:String):Unit;
  7. defsetLastName(fn:String):Unit;
  8. defteach(subject:String)
  9. }

在编译时,它会转换成 POJI:Plain Old Java Interface,查看 javap 会看到这样的内容:


清单 15. 这是一个 POJI!

  1. $javapStudent
  2. Compiledfrom"Student.scala"
  3. publicinterfaceStudentextendsscala.ScalaObject{
  4. publicabstractvoidsetLastName(java.lang.String);
  5. publicabstractvoidsetFirstName(java.lang.String);
  6. publicabstractjava.lang.StringgetLastName();
  7. publicabstractjava.lang.StringgetFirstName();
  8. publicabstractvoidteach(java.lang.String);
  9. }

接下来,我需要一个类成为工厂本身。通常,在 Java 代码中,这应该是类上的一个静态方法(名称类似于 “StudentFactory”),但回忆一下,Scala 并没有此类的实例方法。我认为这就是我在这里希望得到的结论,因此,我创建了一个 StudentFactory 对象,将我的 Factory 方法放在那里:


清单 16. 我构造 Students

  1. //ThisisJava
  2. objectStudentFactory
  3. {
  4. classStudentImpl(varfirst:String,varlast:String,varsubject:String)
  5. extendsStudent
  6. {
  7. defgetFirstName:String=first
  8. defsetFirstName(fn:String):Unit=first=fn
  9. defgetLastName:String=last
  10. defsetLastName(ln:String):Unit=last=ln
  11. defteach(subject:String)=
  12. System.out.println("Iknow"+subject)
  13. }
  14. defgetStudent(firstName:String,lastName:String):Student=
  15. {
  16. newStudentImpl(firstName,lastName,"Scala")
  17. }
  18. }

嵌套类 StudentImplStudent 特征的实现,因而提供了必需的 get()/set() 方法对。切记,尽管特征可以具有行为,但它根据 JVM 作为接口建模这一事实意味着尝试实例化特征将产生错误 —— 表明 Student 是抽象的。

当然,这个简单示例的目的在于编写出一个 Java 应用程序,使之可以利用这些由 Scala 创建的新对象:


清单 17. 学生 Neo
                
//This is Java
public class App
{
    public static void main(String[] args)
    {
        Student s = StudentFactory.getStudent("Neo", "Anderson");
        s.teach("Kung fu");
    }
}

运行此代码,您将看到:“I know Kung fu”。(我知道,我们经过了漫长的设置过程,只是得到了一部廉价电影的推介)。

结束语

人们不喜欢思考。思考总是要得出结论。而结论并非总是令人愉快。
— Helen Keller

特征提供了在 Scala 中分类和定义的强大机制,目的在于定义一种接口,供客户端使用,按照 传统 Java 接口的形式定义;同时提供一种机制,根据特征内定义的其他行为来继承行为。或许我们需要的是一种全新的继承术语,用于 描述特征和实现类之间的关系。

除了本文所述内容之外,还有很多种方法可以使用特征,但本系列文章的部分目的就在于提供关于这种语言的足够信息,鼓励您在家中进一步开展实验;下载 Scala 实现,亲自试用,查看 Scala 可插入当前 Java 系统的什么位置。此外,如果您发现 Scala 非常有用,如果您对本文有任何意见建议,或者(叹息)您发现代码、行文中存在 bug,请 给我留言,让我知道您的意见。

分享到:
评论

相关推荐

    面向 Java 开发人员的 Scala 指南

    面向 Java 开发人员的 Scala 指南 Scala是一个结合面向对象与函数编程的新兴语言。

    面向Java开发人员的Scala指南

    面向Java开发人员的Scala指南 面向Java开发人员的Scala指南

    面向Java开发人员Scala指南,Scala和servlet的比较

    面向Java开发人员Scala指南,Scala和servlet的比较 以及一些网上资料的整理,给大家分享分享!!!

    IBM:面向Java开发人员的Scala指南-p178.7z

    IBM:面向Java开发人员的Scala指南-p178.7z,IBM:面向Java开发人员的Scala指南-p178.7z

    面向 Java 开发人员的 Scala 指南系列.rar

    面向 Java 开发人员的 Scala 指南系列.rar,面向 Java 开发人员的 Scala 指南系列.rar,面向 Java 开发人员的 Scala 指南系列.rar

    面向Java开发人员的Scala指南1

    此文摘自developerWorks中国的Javatechnology系列中国 [选择]使用条款文档选项打印本页将此页作为电子邮件发送英文原文关于本系列Ted

    Scala实用指南

    本书共分为 4 个部分:第一部分详细介绍 Scala 的一些基础知识,并和 Java 中的相关概 念进行了参照,方便读者快速上手 Scala;第二部分进一步介绍 Scala 的一些中级知识,以及 与 Java 的一些差异点,方便读者编写...

    Scala程序设计(第2版)

    3.14 Trait:Scala语言的接口和“混入” 83 3.15 本章回顾与下一章提要 85 第4章 模式匹配 86 4.1 简单匹配 86 4.2 match中的值、变量和类型 87 4.3 序列的匹配 90 4.4 元组的匹配 94 4.5 ...

    Scala实用指南-高清带目录

    很高兴见到你对 Scala 感兴趣。...Java 生态系统是目前用于开发和部署企业级应用最强大的平台之一。Java 平台几乎无所不在并且用途广泛;它类库丰富,可以在多种硬件上运行,并且衍生出了 200 多种基于此平台的编程语言

    Scala快速入门

    Cay S.Horstmann所著的《快学Scala》内容简介:Scala是一门以Java 虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的最佳特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用...

    scala-2.13.1.tgz

    Scala是一门以Java虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的最佳特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用并发的威力。由于Scala运行于JVM之上,因此它可以...

    Scala函数式编程

     “Scala和Java8开发者的函数式编程指南!”  ——William E. Wheeler, TekSystems  “本书向你展示了提升Scala技能的方法和理念,它已超过‘更好的Java’。”  ——Fernando Dobladez, Code54  “里面的...

    快学Scala 电子书 pdf

    Scala是一门以Java虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的最佳特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用并发的威力。由于Scala运行于JVM之上,因此它可以...

    快学Scala.中文完整版

    《快学Scala》是一门以Java虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用并发的威力。由于Scala运行于JVM之上,因此它...

    快学 Scala.pdf

    Cay S.Horstmann所著的《快学Scala》内容简介:Scala是一门以Java 虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的**特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用并发...

    快学scala第二版中英文.zip

    Scala是一门主要以Java虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的*佳特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用并发的威力。由于Scala默认运行于JVM之上,因此...

    Scala入门必看

    本书以十分精简的文字向开发人员展示了Scala的能力和使用方法。在本书中,国际畅销书《Java核心技术》的主要作者Cay S . Horstmann完全从实用角度出发,给出了一份快速的、基于代码的入门指南。 Horstmann以“博客...

    快学Scala PDF扫描版

    Scala是一门以Java虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的最佳特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用并发的威力。由于Scala运行于JVM之上,因此它可以...

    《快学Scala》PDF中文版

    Scala是一门以Java虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的最佳特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用并发的威力。由于Scala运行于JVM之上,因此它可以...

    快学Scala(中文完整版)

    Scala是一门以Java虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的最佳特性结合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用并发的威力。由于Scala运行于JVM之上,因此它可以...

Global site tag (gtag.js) - Google Analytics