Abstract Members
这一章主要讲抽象成员。在Scala中,you can make everything that is not yet known into an abstract member, it does not matter whether the unknown is a type, method, variable or value。
首先,定义什么是抽象: A member of a class or trait is abstract if the member does not have a complete definition in the class. Abstract members are intended to be implemented in subclasses of the class in which they are declared.
这一章主要介绍四种类型的抽象成员: vals, vars, methods, and types
Abstract types
- 可以用 type 关键字来定义一个抽象类型,使用抽象类型的原因是: declare abstract types that must be defined in subclasses.
Abstract vals
定义一个 val 而不赋初值,那么这个 val 就是抽象val。抽象val的一个用处是: when you do not know the correct value in the class, but yo do know that the variable will have an unchangeable value in each instance of the class.
One difference between abstract methods and abstract vals is that you can override a ‘def’ with a ‘val’ but you cannot override a ‘val’ with a ‘def’
抽象 vals 的角色有时候和超类参数有些类似: They let you provide details in a subclass that are missing in a superclass.
关于抽象 vals 的使用,一个要注意的地方是:当在一个 trait 中定义了抽象 val,那么使用这个 trait实例化一个匿名类时,抽象val的值是在实例化这个trait后才能使用。也就是说: The initialization order is not the same for class parameters and abstract fields. **A class parameter argument is evaluated before it is passed to the class constructor (unless the parameter is by-name). An implementing val definition in a subclass, by contrast, is evaluated only after the superclass has been initialized.
但是,如果我们想在超类还没被初始化之前就使用 val的值怎么办? Scala提供了两种解决办法: pre-initialized fields and lazy vals
Pre-initialized fields lets you initialize a field of a subclass before the superclass is called: To do this, simply place the field definition in braces before the superclass constructor call.
使用pre-initialized fields也有一个要注意的地方: Because **pre-initialized fields are initialized before the superclass constructor is called, their initializers cannot refer to the object that’s being constructed. **Consequently, if such an initializer refer to this, the reference goes to the object containing the class or object that’s being constructed, not the constructed object itself.
Lazy vals就像名字暗示一样:If you prefix a val definition with a lazy modifier, the initializing expression on the right-hand side will only be evaluated the first time the val is used. But remember, a lazy def will be evaluated as many as you want, a lazy val is never evaluated more than once. In fact, after the first evaluation of a lazy val the result of the evaluation is stored, to be reused when the same val is used subsequently.
Abstract vars
- 抽象 var 的性质并没有很多,定义一个 abstract var 其实就是定义没有初始化的变量。同时还定义了一个 getter method 以及一个 setter method
Abstract types
An abstract type defins a type that is at yet unknown at the point where it is declared and will be defined further down the class hierarchy.
使用 abstract type 可以定义类型的 upper bound以及 lower bound
实现在子类中的具体类型实际上 path-dependent type – The “path” here means a reference to an object. As the term “path-dependent type” says, the type depends on the path: in general, different paths give rise to different types.
A path-dependent type resembles the syntax for an inner class type in Java, but there is a crucial difference: a path-dependent type names an outer object, whereas an inner class type names an outer class.
使用 path-dependent type 还可以实现枚举。在Scala,没有提供内置枚举类型,但是我们可以利用 Enumeration 这个类以及 path-dependent type来实现。因为不同的枚举定义是在不同的object,那么根据 path-dependent type,那么我们实际上是访问不同 object中的枚举值
Implicit Conversions and Parameters
这一章主要讲隐式转换和隐式参数。隐式转换可以让你实现自己的隐式转换函数,然后Scala编译器会在合适的时候调用这个隐式转换函数;隐式参数则可以让你略去已经标记为隐式类型的参数,而Scala编译器也会自动帮你补上
使用隐式转换只需要定义一个函数,将已有类型转换为目标类型,同时标记为 implicit,那么Scala编译器就会在合适的时候调用这个函数而不用自己显示调用
Rules for implicits
Marking Rule
- Only definitions marked implicit are available
Scope Rule
An inserted implicit conversion must be in scope as a single identifier, or be associated with the source or target type of the conversion.
也就是说这里有两种规则: 1. 第一种是 single identifier,不可以是嵌套的标识符,即带有星号( . ) 2. 第二种是 be associated with the source or target type of the conversion – The compiler will also look for implicit definitions in the companion object of the source or expected target types of the conversion.
Non-Ambiguity Rule
- An implicit conversion is only inserted if there is no other possible conversion to insert
One-at-a-time Rule
- Only one implicit is tried, which means that the compiler does not insert further implicit conversions when it is already in the middle of trying another implicit
Explicits-First Rule
Whenever code type checks as it is written, no implicits are attempted, which means that the compiler will not change code that already works.
A corollary of this rule is that you can always replace implicit identifiers by explicit ones
Where implicits are tried
There are three places implicits are used in the language:
conversions to an expected type
conversions of the receiver of a selection
implicit parameters
Implicit conversion to an expected type
- This rule is very simple: Whenever the compiler sees an X, but needs a Y, it will look for an implicit function that converts X to Y.
Converting the receiver
This kind of implicit conversion has two main uses:
First, receiver conversions allow smoother integration of a new class into an existing class hierarchy
Second, they support writing domain-specific languages (DSLs) within the language.
最重要的一句话是这样: Whenever you see someone calling methods that appear not to exist in the receiver class, they are probably using implicits.
Implicit parameters
the implicit keyword applies to an entire parameter list, not to individual parameters.
The compiler selects implicit parameters by matching types of parameters against types of values in scope, implicit parameters usually have “rare” or “special” enough types that accidental matches are unlikely.
Implicit parameters are perhaps most often used to provide information about a type mentioned explicitly in an earlier parameter list.
A style rule for implicit parameters is that: use at least one role-determining name within the type of an implicit parameter
另外,使用隐式参数还有一个需要注意的是: When you use implicit on a parameter, then not only will the compiler try to supply that parameter with an implicit value, but the compiler will also use that parameter as an available implicit in the body of the method.
View bound
因为Scala编译器在方法体也会使用到隐式参数,这样我们可以使用 view bound
View bound 与 uppe bound 的区别是:如,对于“T <% Ordered[T]” , 相当于: I can use any T, so long as T can be treated as an Ordered[T],而如果是 upper bound,则是说 T is an Ordered[T]
Debugging implicits
Debugging implicits有两种方法:
write the conversion out explicitly
编译时使用 -Xprint:typer 选项,将打印出 the post-typing source code it uses internally,不过这信息有时是非常庞大的
结语
- 虽然隐式转换和隐式参数非常方便,但是有时也会让代码难以理解,所以一般不要轻易使用,而是考虑其他的实现方式,如: inheritance, mixin composition, method overloading。只有当这些都不能满足需要时,才考虑使用隐式转换和隐式参数