Scala中的特性类似于接口,但是功能更强大。 它们允许实现某些方法,字段,堆栈等。但是您是否想知道它们如何在JVM之上实现? 如何扩展多个特征以及实现的目的? 在本文中,基于我的StackOverflow答案 ,我将提供几个特征示例,并说明scalac
如何实现它们,有哪些缺点以及免费获得的东西。 通常,我们将研究编译的类并将其反编译为Java。 这不是必需的知识,但我希望您会喜欢它。 所有示例都是针对Scala 2.10.1编译的。
没有方法实现的简单特征
具有以下特征:
trait Solver {
def answer(question: String): Int
}
编译为最无聊的Java接口:
public interface Solver {
int answer(String);
}
Scala特性确实没有什么特别的。 这也意味着,如果将Solver.class
作为库的一部分提供,则用户可以从Java代码安全地实现此类接口。 就java
/ javac
而言,这是一个普通的Java编译接口。
特征已实现的一些方法
好的,但是如果特征实际上有一些方法主体怎么办?
trait Solver {
def answer(s: String): Int
def ultimateAnswer = answer("Answer to the Ultimate Question of Life, the Universe, and Everything")
}
在这里, ultimateAnswer
方法实际上有一些实现(它调用抽象的answer()
方法是无关紧要的),而answer()
仍未实现。 scalac
产生什么?
public interface Solver {
int answer(java.lang.String);
int ultimateAnswer();
}
好吧,它仍然是一个接口,实现已经消失了。 如果实现消失了,那么当我们扩展这种特性时会发生什么呢?
class DummySolver extends Solver {
override def answer(s: String) = 42
}
我们需要实现answer()
但ultimateAnswer
已经可以通过Solver
。 渴望看到它的内在外观吗? DummySolver.class
:
public class DummySolver implements Solver {
public DummySolver() {
Solver$class.$init$(this);
}
public int ultimateAnswer() {
return Solver$class.ultimateAnswer(this);
}
public int answer(String s) {
return 42;
}
}
那……是……很奇怪……显然我们错过了一个新的类文件Solver$class.class
。 是的,该类名为Solver$class
,即使在Java中也有效。 这不是返回Class[Solver]
Solver.class
表达式,它显然是一个名为Solver$class
的普通Java Solver$class
,带有一堆静态方法。 看起来是这样的:
public abstract class Solver$class {
public static int ultimateAnswer(Solver $this) {
return $this.answer("Answer to the Ultimate Question of Life, the Universe, and Everything");
}
public static void $init$(Solver solver) {}
}
你看到这个把戏了吗? Scala创建了一个辅助Solver$class
并且在trait中实现的方法实际上放置在该隐藏类中。 顺便说一句,这不是一个伴随对象,它只是您看不见的助手类。 但是真正的窍门是$this
参数,它作为Solver$class.ultimateAnswer(this)
调用。 由于我们在技术上处于另一个对象中,因此我们必须以某种方式获取真实 Solver
实例的句柄。 就像OOP,但手动完成。 因此,我们了解到,将特征的方法实现提取到特殊的帮助器类中。 每次我们调用此类方法时都会引用该类。 这样,我们就不会一遍又一遍地将方法主体复制到扩展给定特征的每个类中。
通过实现扩展多个特征
想象一下扩展多个特征,每个特征实现不同的方法集:
trait Foo {
def foo = "Foo"
}
trait Bar {
def bar = "Bar"
}
class Buzz extends Foo with Bar
通过类推,您可能已经知道Foo.class
和Bar.class
样子:
public interface Foo {
String foo();
}
public interface Bar {
String bar();
}
和以前一样,实现隐藏在神秘的...$class.class
文件中:
public abstract class Foo$class {
public static String foo(Foo) {
return "Foo";
}
public static void $init$(Foo) {}
}
public abstract class Bar$class {
public static String bar(Bar) {
return "Bar";
}
public static void $init$(Bar) {}
}
注意, Foo$class
中的静态方法接受Foo
实例,而Bar$class
需要引用Bar
。 之所以Buzz
是因为Buzz
实现了Foo
和Bar
:
public class Buzz implements Foo, Bar {
public Buzz() {
Foo.class.$init$(this);
Bar.class.$init$(this);
}
public String bar() {
return Bar$class.bar(this);
}
public String foo() {
return Foo$class.foo(this);
}
}
foo()
和bar()
通过this
引用,但这很好。
田间性状
田地是性状的另一个有趣特征。 这次让我们使用Spring Data项目中的真实示例。 如您所知,接口可能需要实现某些方法。 但是,他们不能强迫您提供某些字段(或自行提供字段)。 当使用Auditable<U, ID>
接口扩展Persistable<ID>
时,此限制变得很痛苦。 虽然仅仅存在这些接口,以确保某些领域存在你的@Entity
类,即createdBy
, createdDate
, lastModifiedBy
, lastModifiedDate
和id
,这不能被干净表达。 相反,您必须在每个扩展Auditable
实体中实现以下方法:
U getCreatedBy();
void setCreatedBy(final U createdBy);
DateTime getCreatedDate();
void setCreatedDate(final DateTime creationDate);
U getLastModifiedBy();
void setLastModifiedBy(final U lastModifiedBy);
DateTime getLastModifiedDate();
void setLastModifiedDate(final DateTime lastModifiedDate);
ID getId();
boolean isNew();
此外,每个类当然都必须定义上面突出显示的字段。 一点也不觉得干 。 幸运的是,特质可以帮助我们很多 。 在特征中,我们可以定义在扩展该特征的每个类中应创建哪些字段:
trait IAmAuditable[ID <: java.io.Serializable] extends Auditable[User, ID] {
var createdBy: User = _
def getCreatedBy = createdBy
def setCreatedBy(createdBy: User) {
this.createdBy = createdBy
}
var createdDate: DateTime = _
def getCreatedDate = createdDate
def setCreatedDate(creationDate: DateTime) {
this.createdDate = creationDate
}
var lastModifiedBy: User = _
def getLastModifiedBy = lastModifiedBy
def setLastModifiedBy(lastModifiedBy: User) {
this.lastModifiedBy = lastModifiedBy
}
var lastModifiedDate: DateTime = _
def getLastModifiedDate = lastModifiedDate
def setLastModifiedDate(lastModifiedDate: DateTime) {
this.lastModifiedDate = lastModifiedDate
}
var id: ID = _
def getId = id
def isNew = id == null
}
但是,等等,Scala内置了对POJO风格的getter / setter的支持! 因此,我们可以将其缩短为:
class IAmAuditable[ID <: java.io.Serializable] extends Auditable[User, ID] {
@BeanProperty var createdBy: User = _
@BeanProperty var createdDate: DateTime = _
@BeanProperty var lastModifiedBy: User = _
@BeanProperty var lastModifiedDate: DateTime = _
@BeanProperty var id: ID = _
def isNew = id == null
}
编译器生成的getter和setter方法会自动实现接口。 从现在开始,每个愿意提供审核功能的课程都可以扩展此特征:
@Entity
class Person extends IAmAuditable[String] {
//...
}
所有字段,获取器和设置器都在那里。 但是如何实现呢? 让我们看一下生成的Person.class
:
public class Person implements IAmAuditable<java.lang.String> {
private User createdBy;
private DateTime createdDate;
private User lastModifiedBy;
private DateTime lastModifiedDate;
private java.io.Serializable id;
public User createdBy() //...
public void createdBy_$eq(User) //...
public User getCreatedBy() //...
public void setCreatedBy(User) //...
public DateTime createdDate() //...
public void createdDate_$eq(DateTime) //...
public DateTime getCreatedDate() //...
public void setCreatedDate(DateTime) //...
public boolean isNew();
//...
}
实际上还有更多,但是您明白了。 因此,不会将字段重构为一个单独的类,但这是可以预料的。 取而代之的是将字段复制到扩展此特定特征的每个单个类中。 在Java中,我们可以为此使用抽象基类,但是最好保留继承给真实的is-a关系,而不要将其用于虚拟字段持有者。 在Scala中,trait是这样的持有者,它对可以在多个类中重用的公共字段进行分组。
Java互操作性如何? 嗯, IAmAuditable
被编译为Java接口,因此它根本没有字段。 如果从Java实现,则不会有什么特别的。 我们介绍了Scala中的特征及其实现的基本用例。 在下一篇文章中,我们将探讨mixin和可堆栈修改的工作方式。
参考: Scala特征实现和互操作性。 第一部分: Java和社区博客中JCG合作伙伴 Tomasz Nurkiewicz的基础知识 。