Scala中的类
在本节中,你将会学习如何用Scala实现类。如果你了解Java中的类,你不会觉得这有多难,并且你会很享受Scala更加精简的表示法带来的便利。
本节的要点包括:
1. 类中的字段自动带有getter方法和setter方法
2. 你可以用定制的getter/setter方法替换掉字段的定义,而不必修改使用类的客户端,这就是所谓的"统一访问原则"
3. 用@BeanProperty注解来生成JavaBeans的getXxx/setXxx()方法(大家比较熟悉的一种编程方式)
4. 每个类都有一个主要的构造器,这个构造器和类定义"交织"在一起。它的参数直接成为类的字段。主构造器执行类体中所有的语句
5. 辅助构造器是可选的,它们叫做this
6. 嵌套类
一、类中的字段自动带有getter方法和setter方法
1、简单类和无参方法
class PersonOps {
private var age = 0 //你必须初始化一个字段
def increment() {age += 1} //方法默认是公有的
def currentAge() = age
}
1)在Scala中,类并不声明为public。Scala源文件可以包含多个类,所有这些类都具有公有可见性。
2)如何进行调用?
object MainOps{
def main(args: Array[String]) {
val personOps = new PersonOps()
personOps.increment()
println("currentAge=> " + personOps.currentAge())
}
}
3)不能用类直接调用age,因为age是私有字段,只能在PersonOps中访问到。
2)无参方法:
println("currentAge=> " + personOps.currentAge())
println("currentAge=> " + personOps.currentAge)
建议:对设置值的时候,即改变对象状态值或者状态时使用(),而对于取值来说(不会改变对象的状态和值)去掉()是个不错的风格,这也是我们事例中的方法。
personOps.increment()
println("currentAge=> " + personOps.currentAge)
3)你可以通过以不带()的方式声明currentAge来强制这种风格:
class PersonOps {
private var age = 0 //你必须初始化一个字段
def increment() {age += 1} //方法默认是公有的
def currentAge = age
}
结果:这样一来在调用时,必须要用不带()的进行方法调用 println(“currentAge=> ” +
personOps.currentAge)
4)带getter和setter的属性
Java中属性定义
public class PersonAge{ //这是Java
private int age;
public int getAge() { return age; }
public void setAge(int age) { this.age=age; }
}
getter和setter方式比共有字段(public)好在什么地方呢?
之所以说getter和setter方法比公有字段更好,是因为它们让你可以从简单的get/set机制出发,并在需要的时候做改进。
5)scala中的getter和setter方法
class PersonA{
var age = 0
}
Scala生成面向JVM的类,其中有一个私有的age字段以及相应的getter方法和setter方法。这两个方法是公有的,因为我们没有将age声明为private。而对私有字段而言,getter和setter方法也是私有的。
字段默认,调用默认set/get方法,如果字段私有,那么默认的getter/setter方法也将会是私有的,无法调用,但是我们可以自定义覆盖掉默认的
1、在任何时候你都可以自己重新定义getter和setter方法。例如:
/**
* 自定义getter和setter方式实现
*/
class CustPerson{
private var privateAge =0 // 变成私有并改名
def age = privateAge
def age_= (newValue: Int) {
if (newValue > privateAge)
privateAge=newValue // 不能变年轻
}
}
注意事项:在自定义setter方法时,等号左边的方法名称和=之间不应该有空格,负责会出现变量未定义的错误。
调用:
val custPerson = new CustPerson
custPerson.age = 30
custPerson.age = 21
custPerson.age_=(50)
println("custPersonAge=> " + custPerson.age)
class SanMaoScala {
var name: String ="";
def getName(): String ={
name
}
}
object SanMaoOps{
def main(args: Array[String]): Unit ={
var sm = new SanMaoScala;
sm.name_=("sanmao")
println(sm.name)
}
}
字段私有,自己编写set/get方法
class SanMaoScala {
private var name: String ="";
def getName(): String ={
name
}
def setName(name: String):Unit = {
this.name = name
}
}
object SanMaoOps{
def main(args: Array[String]): Unit ={
var sm = new SanMaoScala;
sm.setName("sanmao")
println(sm.getName())
}
}
在Scala中,getter和setter分别叫做age和age_=例如:
val personA = new PersonA
println("startAge=> " + personA.age)
personA.age = 21
println("endAge=> " + personA.age)
Scalac PersonOps.scala
\src\main\scala\tw\tw>javap -p PersonA.class
Compiled from "PersonOps.scala"
public class tw.PersonA {
private int age;
public int age();
public void age_$eq(int);
public tw.PersonA();
}
正如你看到的那样,编译器创建了age和age_
eq方法。=号被翻译成
eq,是因为JVM不允许在方法名中出现=
说明:在Scala中,getter和setter方法并非被命名为getXxx和setXxx,不过它们的用意是相同的。后面会介绍如何生成Java风格的getXxx和setXxx方法,以使得你的Scala类可以与Java工具实现互操作
3、如果字段是val,则只有getter方法被生成
有时候你需要一个只读属性,有getter但没有setter。如果属性的值在对象构建完成后就不再改变,则可以使用val字段:
//定义val声明的变量
val totalCount = 0
调用:custPerson.totalCount = 11(会抛出错误)
结论:Scala会生成一个私有的final字段和一个getter方法,但没有setter。
2和3)的一个小总结:
总结一下,在实现属性时你有如下四个选择:
■ var totalCount : Scala自动合成一个getter和一个setter
■ val totalCount : Scala自动合成一个getter
■ 由你来定义totalCount和totalCount _=方法
■ 由你来定义totalCount方法
但在Scala中,你不能实现只写属性,即带有setter但不带getter的属性。当你在Scala类中看到字段的时候,记住它和Java或c++中的字段不同。它是一个私有字段,加上getter方法(对val字段而言)或者getter和setter了法(对var字段而言)
4、私有字段:分为两种,1个是类私有字段,另外一个是对象私有字段
1)在Scala中Java和C++也一样,方法可以访问该类的所有对象的私有字段。例如:
/**
* 类私有字段和对象私有字段的实现
*/
class PersonCounter{
private var value = 0
def increment () {value+=1 }
// 可以访问另一个对象的私有字段
def isLess (other: PersonCounter) = value < other.value
}
2)对象私有字段
如果你不需要任何getter或setter,可以将字段声明为private[this]
private[this] val totalCount = 0
//这是不允许的方式(因为跨实例访问)
def compareAgeBetweenDiffPersons(personC : PersonCounter): Unit ={
if(totalAge > personC.totalAge){**//不能应用在别的实例上面,只能应用在此单一的实例上面**
println("")
}
}
三、用@BeanProperty注解
1、Scala对于你定义的字段提供了getter和setter方法。不过,这些方法的名称并不是Java工具所预期的。
把Java属性定义为一对getName/setName方法或者对于只读属性而言单个getName方法。许多Java工具都依赖这样的命名习惯。当你将Scala字段标注为@BeanProperty时,这样的方法会自动生成。例如:
/**
* 用BeanProperty实现的getter和setter方法
*/
import scala.reflect.BeanProperty
class BeanPropertyPerson{
@BeanProperty var name:String = _
}
调用:
val beanPropertyPerson = new BeanPropertyPerson()
beanPropertyPerson.setName("jack")
println("beanPropertyPerson.name=> " + beanPropertyPerson.getName)
beanPropertyPerson.name = "Garry"
println("scala.beanProperty.person.name=> " + beanPropertyPerson.name)
将会生成四个方法:
■ name:String
■ name_=(newValue: Strmg):Unit
■ getName():String
■ setName(newValue: String): Unit
一、Scala单例对象
1、Scala没有静态方法或静态字段,你可以用object这个语法结构来达到同样目的。对象定义了某个类的单个实例,包含了你想要的特性。例如
object PersonSingle{
private var personNumber = 0
def newUniquePersonNumber:Int = {
personNumber+=1
personNumber
}
}
object ObjectOps {
def main(args: Array[String]) {
println(PersonSingle.newUniquePersonNumber)
println(PersonSingle.newUniquePersonNumber)
}
}
二、Scala伴生对象
在Java或C++中,你通常会用到既有实例方法又有静态方法的类。在Scala中,你可以通过类和与类同名的”伴生”对象来达到同样的目的。例如:
class PersonSingle{
val personId = PersonSingle.newUniquePersonNumber
private var personBalance:Double =0
def personDeposit (amount: Double) { personBalance += amount }
}
类和它的伴生对象可以相互访问私有特性。它们必须存在于同一个源文件中。这说明了类的伴生对象可以被访问,但并不在作用域当中。举例来说, PersonSingle类必须通过
PersonSingle.newUniquePersonNumber
而不是直接用newUniqueNumber来调用伴生对象的方法。
三、对象可以扩展类或特质
1、一个object可以扩展类以及一个或多个特质,其结果是一个扩展了指定类以及特质的类的对象,同时拥有在对象定义中给出的所有特性。
abstract class PersonAccount (val accountName: String) {
def unRegisterPersonAccount() : Unit
def reRegisterPersonAccount() : Unit
}
object PersonAccountImpl extends PersonAccount("AccountName") {
override def unRegisterPersonAccount (): Unit = {
println("===start to logger======")
}
override def reRegisterPersonAccount (): Unit = {
println("===end to logger=========")
}
}
调用:PersonAccountImpl.reRegisterPersonAccount()
四、Apply方法(object和class)
1、我们通常会定义和使用对象的apply方法。当遇到如下形式的表达式时,apply方法就会被调用:
object(参数1,…,参数N)
通常,这样—个apply方法返回的是伴生类的对象
class PersonApplyTest{
private var balance = 0
def apply() = println("your person apply class test")
def initPersonNumber:Int = {
balance += 1
balance
}
}
object PersonApplyTest{
def apply () = {
println("Please init your person information")
new PersonApplyTest
}
}
调用:
1、val personApplyTest = PersonApplyTest()
println(personApplyTest.initPersonNumber)
2、val personApplyTest = new PersonApplyTest
println(personApplyTest.initPersonNumber)
personApplyTest()
五、扩展App类的特征
1、除了每次都提供自己的main方法外,你也可以扩展App特质,然后将程序代码放人构造器方法体内:
object ObjectOps extends App{
if (args.length > 0)
println("Hello, "+args (0))
else
println("Hello, World! ")
}
六、枚举Enumeration
1、Scala并没有枚举类型。不过,标准类库提供了一个Enumeration助手类,可以用于产出枚举。定义一个扩展Enumeration类的对象并以Value方法调用初始化枚举中的所有可选值。
2、枚举定义的初始化
object PersonClothesColor extends Enumeration{
val Red,Yellow,Green = Value
}
3、枚举引用
1)定义完成后,你就可以用PersonClothesColor.Red、PersonClothesColor.Yellow等来引用枚举值了。
2)需要注意的是:枚举的类型是PersonClothesColor.Value而不是PersonClothesColor,后者是握有这些值的对象。有人推荐增加一个类型别名:
4、访问枚举
for(c <- PersonClothesColor.values){
println(c.id +”:” + c)
}
println(PersonClothesColor(0))
println(PersonClothesColor.withName(“Yellow”))