1、类
class Counter{
private var value = 0 // 必须初始化字段
def increment() { value += 1} //方法默认是公有的
def corrent() = value
}
//类无需声明为public , 源文件可以包含多各类,所有类都具有公有可见性。
val myCounter = new Counter // 或 new Counter()
myCounter.increment
//或 myCounter.increment() ,对于不会改变对象状态的方法,去掉()是不错的风格
//可以通过以不带()的方式声明来强制这种风格
class Counter {
...
def current = value // 定义中不带()
}
//这样 使用者必须用 myCounter.current
getter 和 setter
class Person{
var age = 0 // 公有,Scala 默认生成 get和set, 分别为 age 和 age_=
//如果定义为私有,那么 get 和 set 也将变为私有
}
println(fred.age) // 调用了fred.age()
fred.age = 21 //调用了 fred.age_(21)
自定义getter 和 setter
class Person{
private var privateAge = 0 // 设为私有并改名
def age = privateAge
def age_=(new Value:Int){
if(new Value > privateAge) privateAge = newValue; //不能变年轻
}
}
只带getter
class Message{
val timeStamp = new Java.util.Date // 用 val
}
总结:, 四个选择
1、var foo // getter 和 setter
2、val foo // getter
3、自定义 foo 和 foo_= 方法
4、自定义 foo 方法
对象私有字段
class Counter{
private var value = 0
def increment() { value += 1}
def isLess(other: Counter) = value < other.valule // 可以访问另一个对象的 私有字段
} // 之所以访问other.value是合法的,是因为other也同样是Counter对象
更严格的访问限制, 通过private[this] 修饰来实现
private [this] var value = 0 //如此一来 Counter类的方法只能访问到当前对象的value字段,而不能访问同样是Counter类型的其他对象的该字段。
类私有字段 生成私有getter 和 setter, 但对象私有的字段,不会生成。
Bean属性
虽然Scala会提供getter和setter方法,但这不是java工具所预期的,,如果要更具JavaBeans规范生成getFoo/setFoo方法,参照如下:
import scala.reflect.BeanProperty
class Person{
@BeanProperty var name:String = _
}
将生成四个方法:
1. name:String
2. name_=(newValue:String):Unit
3. getName() :String
4. setName(newValue:String):Unit
如果使用主构造器参数的方式定义了某字段,并且需要JavaBean版的getter 和 setter 的方法:
class Person(@BeanProperty var name:String)
辅助构造器
class Person{
private var name = ""
private var age = 0
def this(name: String){
this() //调用主构造器
this.name = name
}
def this(name: String, age:Int){
this(name) //调用前一个辅助构造器
this.age = age
}
}
val p1 = new Person //主构造器
val p2 = new Person("Fred") // 第一个辅助构造器
val p3 = new Person("Fred", 42) // 第二个辅助构造器
主构造器
主构造器的参数直接放置在类名之后
import scala.collection.mutable.ArrayBuffer
class Networ{
class Member(val name: String){
val contacts = new ArrayBuffer[Member]
}
private val members = new ArrayBuffer[Member]
def join(name:String) = {
val m = new Member(name)
members += m
m
}
}
val chatter = new Network
val myFace = new Network
chatter.Member 和 myFace.Member 是不同的两个类
val fred = chatter.join("Fred")
val wilm = chatter.join("Wilma")
fred.contacts += wilma // ok
val barney = myFace.join("Barney") // 类型为myFace.Member
fred.contacts += barney //错误,不能将一个myFace.Member 添加到chatter.Member元素缓冲中
如果希望突破这个限制,可以将嵌套类移到其他地方,一个不错的位置是 伴生对象
object Network{
class Member(val name:String){} //类似java的静态对象,挂在类上,而不是 对象上。
}
class Network{
private val members = new ArrayBuffer[Network.Member]
}
或者是投影
class Network {
class Member(val name: String) {
val contacts = new ArrayBuffer[Network#Member]
}
}
class Person(val name:String , val age:Int){}主构造器会执行类定义中的所有语句
class Person(val name:String, val age: Int){
println("Just constructed another person")
def description = name + " is " + age + " years old"
}
class MyProg{
private val props = new Properties
props.load(new FileRecoder("myprog.properties"))
}
class Person(val name:Stirng , private var age:Int)
class Person(name:String, age:Int) { // 这里没有给val 或 var修饰,但是在它们至少被一个方法使用,所以升格为字段,并对象私有,同等于private[this] val字段
def description = name + " is " + age + " years old"
} //如果它们没被使用,将不保存为字段,仅仅是主构造器中的代码访问的普通参数。
主构造器参数生成的字段和方法
name: String 对象私有字段。如果没有方法使用name,则没有该字段
private val/var name: Sting 私有字段,私有的getter/setter方法
val/var name:String 私有字段,公有的getter/setter方法
@BeanProperty val/var name:String 私有字段,公有的Scala版和JavaBeans版的getter/setter方法
私有的主构造器
class Person private(val id:Int) {} //这样 用户必须通过辅助构造器了
嵌套类
import scala.collection.mutable.ArrayBuffer
class Networ{
class Member(val name: String){
val contacts = new ArrayBuffer[Member]
}
private val members = new ArrayBuffer[Member]
def join(name:String) = {
val m = new Member(name)
members += m
m
}
}
val chatter = new Network
val myFace = new Network
chatter.Member 和 myFace.Member 是不同的两个类
val fred = chatter.join("Fred")
val wilm = chatter.join("Wilma")
fred.contacts += wilma // ok
val barney = myFace.join("Barney") // 类型为myFace.Member
fred.contacts += barney //错误,不能将一个myFace.Member 添加到chatter.Member元素缓冲中
如果要突破这个先知,可以将嵌套类移到别处,一个不错的选择是 伴生对象
object Network{
class Member(val name:String){}
}
class Network{
private val members = new ArrayBuffer[Network.Member]
}
或者是使用 类型投影Network#Member,其含义是“任何Network的Member”
class Network{
class Member(val name:String){
val contacts = new ArrayBuffer[Network#Member]
}
}<pre name="code" class="java">
在嵌套类中,可以通过外部类.this 的方式来访问外部类的this引用.
class Network(val name:String) { outer -> // outer 变量指向 Network.this
class Member(val name:String){
...
def description = name + "inside " + outer.name
}
}
2、对象
单例对象
Scala没有静态方法或静态字段,可以用objectg 这个语法来达到相同的目的
object Accounts {
privagte var lastNumber = 0
def newUniqueNumber() = { lastNumber += 1; lastNumber }
<pre name="code" class="java">}
Account.newUniqueNumber()
对象的构造器在该对象第一次被使用时调用,这里即首次调用newUniqueNumber时执行,如果对象从未被使用,那么其构造器不会被执行
对象可以拥有类的所有特性, 可以扩展其他类或特质,唯一的例外就是:不能提供构造器参数
常用的方式与 Java中使用单例的地方相似:
1、存放工具函数或常量的地方
2、高效地共享单个不可变实例
3、需要用单个实例来协调某个服务时(单例模式)
在java中,可以既有实例方法又有静态方法,在Scala,就是伴生对象来完成:
class Accout{
val id = Account.newUniqueNumber()
private var balance = 0.0
def deposit(amount: Double) { balance += amount}
...
}
object Account { //伴生对象
private var lastNumber = 0
private def newUniqueNumber() = { lastNumber +=1; lastNumber}
}
类和它的伴生对象可以互相访问私有特性,必须存在于同一个源文件中
扩展类和特质的对象
abstract class UndoableAction(val description: String){
def undo(): Unit
def redo(): Unit
}
object DoNothingAction extends UndoableAction("Do nothing"){
override def undo(){}
override def redo(){}
}
DoNothingAction 对象可以被所有需要这个缺省行为的地方共有。
val actions = Map("open" -> DoNothingAction, "save" -> DoNothingAction, ...)//打开和保存功能尚未实现
apply方法
通常使用类的伴生对形象的apply方法来初始化类,省去new 关键字
object(参数1, ..., 参数n)
Array("Mary","had","a","little","lamb")
Array(Array(1,7), Array(2,9))
class Account private (val id:Int, initialBalance: Double){
private var balance =initialBalance
}
object Account{
def apply(initialBalance : Double) = new Account(newUniqueNumber(), initialBalance)
...
}
val acct = Accounnt(1000.0)
应用启动对象
每个Scala程序都必须从一个对象的main方法开始,这个方法的类型为Array[String] => Unit:
object Hello{
def main(args:Array[String]){
println("Hello, World!")
}
}<pre name="code" class="java">
也可以扩展App特质
object Hello extends App{
prinltn("Hello, World!")
}
如果需要命令行参数,可以通过args属性得到
object Hello extends App{
if(args.length > 0)
println("Hello, " + argss(0))
else
println("Hello, World!")
}
scalac Hello.scala
scala -Dscala.time Hello Fred //scala.time 会显示运行时间
Hello, Fred
[total 4ms]
枚举
定义一个扩展Enumeration类的对象,并以Value方法调用初始化枚举中的所有可选值
object TrafficLightColor extends Enumeration{
val Red, Yellow, Green = value
}
val Red = Value
val Yellow = Value
val Green = Value
每次调用Value方法都返回内部类的新实例,该内部类也叫做Value.
或者 也可以向Value方法传入ID 名称
val Red = Value(0, "Stop")
val Yellow = Value(10) // 名称为Yellow
val Green = Value("Go")// id 为11
如果不指定ID,则ID在前一个枚举值基础上加一,从零开始,缺省名称 为字段名。
可以用TrafficLightColor.Red, 等来引用,如果觉得太冗长,可以直接引入枚举值
import TrafficLightColor._
也可以用一种类型别名:
object TrafficLightColor extends Enumeration{
type TrafficLightColor = Value
val Red, Yellow, Green = Value
}
现在枚举的类型变成了TrafficLightColor.TrafficLightColor,不过仅当使用import 语句这样做才有意义。
import TrafficLightColor._
def doWhat(color: TrafficLightColor) ={
if( color == Red) "stop"
else if (color == Yellow) "hurry up"
else "go"
}
枚举值的ID 可以通过id 方法返回,名称通过toString方法返回
for(c <- TrafficLightColor.values) println( c.id = ": "+ c)
可以通过枚举的ID或名称来进行查找定位,以下两段代码都输出TrafficLightColor.Red对象
TrafficLightColor(0) // 将调用Enumeration.apply
TrafficLightColor.withName("Red")
3、包和引入
package com{
package horstmann{
package impatient{
class Employee
...
}
}
}
packkage com.horstmann.impatient{ //串联式,com和com.horstmann的成员在这里不可见
package people{
classPerson
}
}
源文件的目录和包之间并没有强制的关联关系,不需要将Employee.scala 放在 com/horstmann/impatient目录当中。同时也可以在一个文件当中定义多个包。
作用域规则
package com{
package horstmann{
object Utils{
def percentOf() = ...
}
package impatient{
class Employee{
...
def giveRaise(rate: scala.Double){
salary += Utils.percentOf(salary, rate)
}
}
}
}
}
这里Utils.percentOf使用的是当前包下的内容,是相对路径,而Java一直是 绝对路径
注意:
如果有个包定义为collection , 而在使用scala的collection时,使用相对路径,可能报错,这个时候就要使用绝对路径,以_root_开始,如
val subordinates = new _root_.scala.collection.mutable.ArrayBuffer[Employee]
文件顶部标记法
package com.horstmann.impatient
package people
classPerson
等同于
package com.horstmann.impatient{
package people{
class Person
}
}
文件内的所有内容都属于 com.horstmann.impatient.people,但com.horstmann.impatient的包的内容是可见的,可以被直接引用
包对象
包可以包含类、对象和特质,但不能包含函数或变量,包对象就是为了解决这个局限。
package com.hostmann.impatient
package object people{
val defaultName = "John"
}
package people{
class Person{
var name = defaultName // 从包对象拿到常量
}
}
package com.horstmann.impatient.people
class Person{
private [people] def description = "A person with name" + name
...
}// description在people 包内可见
可以将可见度 延展到上层包
private [impatient] def description = ...
引入
import java.awt.Color
import java.awt._ //等同于java的通配符*
import java.awt.{Color, Font} //选择性引入
import java.util.{HashMap => JavaHashMap} //重命名
import java.util.{HashMap=>_,_} //隐藏HashMap,引入其他成员
任何地方都可以声明引入
class Manager{
import scala.collection.mutable._
}
隐式引入
每个Scala程序都隐式地如下代码
import java.lang._
import scala._
import Predef._
后面引入会覆盖前面重名的,例如
scala.StringBuilder 会覆盖 java.lang.StringBuilder 而不是与之冲突。
当 我们 使用
collection.mutable.Hashmap
其实是在使用
scala.collection.mutable.Hashmap
4、继承
使用extends关键字
class Employee extends Person{
var salary = 0.0
}
final 类无法被继承,final方法无法被重写,final字段无法被覆盖。
注: java的final字段是不可变,scala 即为 val.
重写一个非抽象方法必须使用 override
public class Persion extends xxx{
override def toString = getClass.getName + "[name=" + name + "]"
}
调用超类方法和Java一样,使用super
public class Employee extends Person{
override def toString = super.toString + "[salary=" + salary + ";"
}
类型检查和转换
isInstanceOf, asInstanceOf
if(p.isInstanceOf[Employee]){
val s = p.asInstanceOf[Employee] // s的类型为Employee
}
如果p是 Employee类或其自雷的对象,isInstanceOf 将会成功
如果p是null,则p.isInstanceOf[Employee]返回false, p.asInstanceOf[Employee]返回null
如果p不是一个Employee , 则 p.asInstanceOf[Employee] 将跑出异常
如果想要测试p指向的 是一个Employee对象,但又不是自雷的话,用
p.getClass == classOf[Employee] // classOf定义在 scala.Prede对象中, 所以会被自动引入
Scala | Java |
obj.isInstanceOf[C1] | obj instanceof C1 |
obj.asInstanceOf[C1] | (C1) obj |
classOf[C1] | C1.class |
不过,与类型检查和转换相比, 模式匹配通常是更好的选择:
p match{
case s: Employee => ... // 将s 作为Employee 处理
case _ => // p 不是Employee
}
受保护字段和方法
和Java一样, protected, 可以被任何子类访问,但不能从其他位置看到。
与Java不同,protected的成员对于类所属的包而言,是不看见的, 如果需要这种可见性,可以使用 包修饰:
protected [包名] def xxx = xxx;
超类的构造
class Employee(name: String, age: Int, val salary: Double) extends Person(name, age)
这里定义了子类 和 调用超类的主构造器。
在Scala的构造器中,不能像Java一样 ,调用super(params)。
Scala类可以扩展Java类
class Square(x:Int, y:Int, width:Int) extends java.awt.Rectangle(x, y, width, width)
def 只能重写另一个def
val 只能重写另一个val 或 不带参数的def
var 只能重写另一个抽象的var //换句话说,如果你用了var, 所有的子类只能被动接受
class Person(val name: String){
override def toString = getClass.getName + "[name=" + name + "]"
}
class SecretAgent(codename :String ) extendsd Person(codename){
override val name = "secret" // 覆盖了父类的name
override val toString = "secret"
}
abstract class Person{
def id:Int
}
class Student ( override val id : Int) extends Person // 覆盖了父类的 id
匿名子类
val alien = new Person("Fred") {
def greeting = "Greetings, Earthling! My name is Fred."
}
从技术上讲,这将会创建一个结构类型的对象, 该对象标记为 Person{def greeting : String}
可以用这个类型 作为 参数类型的 定义
def meet(p : Person ( def greeting: String)){
println(p.name + "says: "+ p.greeting)
}
抽象类
abstract class Person (val name : String) {
def id :Int // 没有方法体, 抽象方法,无需像java一样,加上abstract
}
类中如果有一个方法是 抽象的,那么该类必须声明为abstract
子类重写超类的抽象方法,无需override
class Employee(name:String) extends Person(name){
def id = name.hashCode // 不需要override
}
抽象字段
没有初始值的字段,即为抽象字段
abstract class Person{
val id : Int //带有抽象的getter方法
var name: String //带有抽象的 g/s
}
class Employee (val id: Int) extends Person { // 具体的 id
var name = "" //具体的 name , 同样无需 override
}
用 匿名类型 来 定制 抽象字段:
val fred = new Person{
val id = 1729
var name = "Fred"
}
构造顺序和提前定义
class Creature {
val range: Int = 10
val env:Array[Int] = new Array[Int](range)
}
class Ant extends Creature{
override val range = 2
}
执行流程:
range 设置为10
初始化env数组,调用range()取值器
该方法被重写以输出(还未初始化的)Ant类的range字段值
range()方法返回0
env设定长度为0
Ant构造器继续执行,range字段为2
这里的问题是在构造器内不应该依赖val的值。
这里和Java一样,当在超类的构造方法中调用方法时,会遇到相似的问题,被调用的方法可能被子类重写,因此它可能并不会按照你的语气行事。
解决方法:
1、val 声明为final , 安全不灵活
2、超类中val声明为lazy, 安全不高效
3、子类提前定义语法
class Ant extends {
override val range = 2 // 在超类构造器执行之前初始化子类的val字段
}with Creature
与Java中基本类型相对应的类,以及Unit类型,都扩展自AnyVal
所有其他类都是AnyRef的子类,
AnyVal和AnyRef都扩展自Any类
Any类定义了 isInstanceOf、asInstanceOf方法,以及用于相等性判断和哈希码的方法。
AnyRef类追加了来自Object类的wart和notify/notifyAll,同时提供了一个带函数参数的方法synchronized,等同于Java中的synchronized块,如
account.synchronized{account.balanced += amount}

对象相等性
AnyRef的 eq 方法检查两个引用是否指向同一个对象,AnyRef的equals方法调用eq。
在应用程序中,不直接调用eq或equals,只要用==操作符就好
override def equals(other : Any) ={
val that = other.asInstanceOf[Item]
if(that == null) false
else description == that.description && price == that.price
}
override def hashCode = 13 * description.hashCode + 17 * price.hashCode //定义equals时, 记得同时也定义hashCode