首页 > 代码库 > Scala具体解释---------类
Scala具体解释---------类
Scala中的类 |
摘要:
在本篇中。你将会学习怎样用Scala实现类。
假设你了解Java或C++中的类,你不会认为这有多难。而且你会非常享受Scala更加精简的表示法带来的便利。本篇的要点包含:
1. 类中的字段自己主动带有getter方法和setter方法
2. 你能够用定制的getter/setter方法替换掉字段的定义,而不必改动使用类的client,这就是所谓的"统一訪问原则"
3. 用@BeanProperty注解来生成JavaBeans的getXxx/setXxx()方法
4. 每一个类都有一个基本的构造器,这个构造器和类定义"交织"在一起。它的參数直接成为类的字段。主构造器运行类体中全部的语句
5. 辅助构造器是可选的,它们叫做this
简单类和无參方法 |
简单类
Scala类最简单的形式看上去和Java或c+++的非常相似:
class Counter {
private var value=http://www.mamicode.com/0 //你必须初始化字段
def increment() {value+=1} //方法默认是公有的
def current()=value
}
在Scala中,类并不声明为public。Scala源文件能够包括多个类,全部这些类都具有公有可见性。使用该类须要做的就是构造对象并依照通常的方式来调用方法:
val myCounter=new Counter // 或new Counter()
myCounter.increment()
println (myCounter.current) // 1
无參方法
调用无參方法比方current时,你能够写上圆括号。也能够不写:
myCounter.current //OK
myCounter.current() //相同OK
应该用哪一种形式呢,我们觉得对于改值器方法,即改变对象状态的方法使用()。而对于取值器方法不会改变对象状态的方法去掉()是不错的风格。
这也是我们在演示样例中的做法:
myCounter.increment() //对改值器使用()
println (myCounter.current) //对取值器不使用()
你能够通过以不带()的方式声明current来强制这样的风格:
class Counter {
def current=value //定义中不带()
}
这样一来类的使用者就必须用myComter.current,不带圆括号。
带getter和setter的属性 |
Java中的字段属性
编写Java类时。我们并不喜欢使用公有字段:
public class Person{ // 这是Java
public int age; //Java中不鼓舞这样做
}
使用公有字段的话,不论什么人都能够写入fred.age,让Fred更年轻或更老。这就是为什么我们更倾向于使用getter和setter方法:
public class Person{ //这是Java
private int age;
public int getAge() { return age; }
public void setAge{int age) { this.age=age; }
}
像这种一对getter/setter通常被称做属性(property)。我们会说Person类有一个age属性。这究竟好在哪里呢,仅从它自身来说,并不比公有字段来得更好。不论什么人都能够调用fred.setAge(21),让他永远停留在21岁。只是假设这是个问题,我们能够防止它发生:
public void setAge (nt newValue) {// 不能变年轻
if ( newValue>age )
age=newValue;
}
之所以说getter和setter方法比公有字段更好,是由于它们让你能够从简单的get/set机制出发。并在须要的时候做改进。须要注意的是:只由于getter和setter方法比公有字段更好,并不意味着它们总是好的。通常,假设每一个客户瑞都能够对一个对象的状态数据进行获取和设置。这明显是非常糟糕的。以下。会向展示怎样用Scala实现属性。但要靠你自己决定,能够取值和改值的字段是否是合理的设计
Scala中的字段属性
Scala对每一个字端都提供getter和setter方法。
在这里,我们定义一个公有字段:
class Person {
var age=0
}
Scala生成面向JVM的类。当中有一个私有的age字段以及对应的getter方法和setter方法。这两个方法是公有的,由于我们没有将age声明为private。
而对私有字段而言。getter和setter方法也是私有的。
Scala中的getter和setter
在Scala中,getter和setter分别叫做age和age_=比如:
println (fred.age) // 将调用方fred.age()
fred.age= 21 // 将调用fred.age=(21)
假设想亲眼看到这些方法,能够编译Person类,然后用javap查看字节码:
scalac Person.scala
javap -private Person
Compiled from "Person.scala"
public class Person extends java.lang.Object implements scala.ScalaObject {
private int age;
public int age()
public void age_$eq(int)
public Person()
}
正如你看到的那样,编译器创建了age和age_$eq方法。=号被翻译成$eq,是由于JVM不同意在方法名中出现=
说明:在Scala中,getter和setter方法并不是被命名为getXxx和setXxx。只是它们的用意是同样的。
后面会介绍怎样生成Java风格的getXxx和setXxx方法,以使得你的Scala类能够与Java工具实现互操作
Scala中的自己定义getter和setter
在不论什么时候你都能够自己又一次定义getter和setter方法。
比如:
class Person {
private var privateAge =0 // 变成私有并改名
def age = privateAge
def age_= (newValue: Int) {
if (newValue > privateAge)
privateAge=newValue // 不能变年轻
}
}
你的类的使用者仍然能够訪问fred.age。但如今Fred不能变年轻了:
fred.age = 30
fred.age = 21
println (fred.age) // 30
颇具影响的Eiffel语言的发明者Bertrand Meyer提出了统一訪问原则,内容例如以下:"某个模块提供的全部服务都应该能通过统一的表示法訪问到。至于它们是通过存储还是通过计算来实现的,从訪问方式上应无从获知"。在Scala中,fred.age的调用者并不知道age是通过字段还是通过方法来实现的。当然了,在JVM中,该服务总是通过方法来实现的,要么是编译器合成。要么由程序猿提供。
还需注意的是:Scala对每一个字段生成getter和setter方法听上去有些恐怖,只是你能够控制这个步骤例如以下:
■ 假设字段是私有的,则getter和setter方法也是私有的
■ 假设字段是val。则仅仅有getter方法被生成
■ 假设你不须要不论什么getter或setter,能够将字段声明为private[this]
仅仅带getter的属性 |
Scala类中的常量
有时候你须要一个仅仅读属性。有getter但没有setter。假设属性的值在对象构建完毕后就不再改变,则能够使用val字段:
class Message {
val timeStamp=new java.util.Date
……
}
Scala会生成一个私有的final字段和一个getter方法,但没有setter。
私有字段的getter和setter
只是,有时你须要这样一个属性,client不能任意改值,但它能够通过某种其它的方式被改变。前面中的Counter类就是个非常好的样例,从概念上讲。counter有一个current属性,当increment方法被调用时更新。但并没有相应的setter
class Counter {
private var value=http://www.mamicode.com/0 //你必须初始化字段
def increment() {value+=1} //方法默认是公有的
def current()=value
}
须要注意的是。你不能通过val来实现这样一个属性。val永不改变。你须要提供一个私有字段和一个属性的getter方法,像这样:
class Counter {
private var value=http://www.mamicode.com/0 //你必须初始化字段
def increment() {value+=1} //方法默认是公有的
def current=value //声明中没有()
}
在getter方法的定义中并没有()。因此。你必须以不带圆括号的方式来调用:
val n=myCounter.current // myCounter.current()这种调用方式是语法错误
总结
总结一下,在实现属性时你有例如以下四个选择:
■ var foo: Scala自己主动合成一个getter和一个setter
■ val foo: Scala自己主动合成一个getter
■ 由你来定义foo和foo_=方法
■ 由你来定义foo方法
但在Scala中,你不能实现仅仅写属性,即带有setter但不带getter的属性。当你在Scala类中看到字段的时候,记住它和Java或c++中的字段不同。
它是一个私有字段,加上getter方法(对val字段而言)或者getter和setter了法(对var字段而言)
对私有字段 |
类私有字段
在Scala中Java和C++也一样。方法能够訪问该类的全部对象的私有字段。
比如:
class Counter {
private var value=http://www.mamicode.com/0
def increment () {value+=1 }
def isLess (other: Counter) = value < other.value // 能够訪问还有一个对象的私有字段
}
之所以訪问other.value是合法的,是由于othert也相同是Cormter对象。
对象私有字段
除此之外Scala同意我们定义更加严格的訪问限制,通过private[this]这个修饰符来实现:
private [this] var value=http://www.mamicode.com/0 // 类似某个对象.value这种訪问将不被同意
这样一来,Counter类的方法仅仅能訪问到当前对象的value字段,而不能訪问相同是Counter类型的其它对象的该字段。这种訪问有时被称为对象私有的,这在某些OO语言。比方SmaIITalk中十分常见。
对于类私有的字段,Scala生成私有的getter和setter方法。但对于对象私有的字段,Scala根本不会生成getter或setter方法。
权限指定
Scala同意你将訪问权赋予指定的类。private[类名]修饰符能够定义仅有指定类的方法能够訪问给定的字段。这里的类名必须是当前定义的类。或者是包括该类的外部类。在这样的情况下,编译器会生成辅助的getter相setter方法,同意外部类訪问该字段。这些类将会是公有的,由于JVM并没有更细粒度的訪问控制系统,而且它们的名称也会随着JVM实现不同而不同。
Bean属性 |
正如你在前面所示,Scala对于你定义的字段提供了getter和setter方法。
只是,这些方法的名称并非Java工具所预期的。JavaBeans规范
www.oracle.com/technetwork/java/javase/tech/index-jsp-138795.html
把Java属性定义为一对getFoo/setFoo方法或者对于仅仅读属性而言单个getFoo方法。
很多Java工具都依赖这种命名习惯。当你将Scala字段标注为@BeanProperty时。这个方案会自己主动生成。比如:
import scala.reflect.BeanProperty
class Person {
@BeanProperty var name: String=_
}
将会生成四个方法:
■ name:String
■ name_=(newValue: Strmg):Unit
■ getName():String
■ setName(newValue: String): Unit
下表显示了在各种情况下哪些方法会被生成:
假设你以主构造器參数的方式定义了某字段。而且你须要JavaBeans版的getter和setter方法,像例如以下这样给构造器參数加上注解就可以:
class Person (@BeanProperty var name: String)
辅助构造器 |
和Java或C++一样。Scala能够有随意多的构造器。只是Scala类有一个构造器比其它全部构造器都更为重要,它就是主构造器(primary constructor)。除了主构造器之外,类还能够有随意多的辅助构造器( auxiliary constructor)我们将首先讨论辅助构造器。这是由于它们更easy理解。
它们同Java或C++的构造器十分相似,仅仅有两处不同。
■ 辅助构造器的名称为this。
在Java或C++中,构造器的名称和类名同样,当你改动类名时就不那么方便了
■ 每个辅助构造器都必须以一个对先前已定义的其它辅助构造器或主构造器的调用開始
这里有一个带有两个辅助构造器的类。
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
}
}
和Java、C++一一样,类假设没有显式定义主构造器则自己主动拥有一个无參的主构造器就可以。你能够以三种方式构建对象:
val p1 = new Person //主构造器
val p2 = new Person("Fred")//第一个辅助构造器
val p3 = new Person ("Fred",42) //第二个辅助构造器
主构造器 |
主构造器的參数直接放置在类名之后
在Scala中,每一个类都有主构造器。主构造器并不以this方法定义。而是与类定义交织在一起
class Person (val name:String, val aqe:Int){
// (…)中的内容就是主构造器的參数
}
主构造器的參数被编译成字段,其值被初始化成构造时传入的參数。
在本例中name和age成为Person类的字段。
如new Person("Fred",42)这种构造器调用将设置name和age字段。我们仅仅用半行Scala就完毕了七行Java代码的工作:
public class Person{ //这是Java
private String name;
private int age;
public Person(String name,int age) {
this.name=name
this.age=age
}
public String name() {return this.name;}
public int age() {raturn this.age;}
}
主构造器会运行类定义中的全部语句。比如在下面类中:
class Person (val name: String, val age: Int) {
println ("Just constructed anther person")
def description=name+"is"+age+"years old"
}
println语句是主构造器的一部分。每当有对象被构造出来时。上述代码就会被运行。当你须要在构造过程其中配置某个字段时这个特性特别实用。
比如:
class MyProg {
private val props=new Properties
props.load ( new FileReader ( "myprog.properties" ) ) // 上述语句是主构造器的一部分
}
类名之后没有參数
假设类名之后没有參数,则该类具备一个无參主构造器。这样一个构造器不过简单地运行类体中的全部语句而已。你通常能够通过在主构造器中使用默认參数来避免过多地使用辅助构造器。比如:
class Person (val name:String="",val age: Int =0 )
主构造器參数
主构造器的參数能够採用下表中列出的随意形态
比如:
class Person (val name : String, privite var age: Int)
这段代码将声明并初始化例如以下字段:
val name: String
private var age: Int
构造參数也能够是普通的方法參数。不带val或var。这种參数怎样处理取决于它们在类中怎样被使用。假设不带val或var的參数至少被一个方法所使用,它将被升格为字段。
比如:
class Person(name: String, age: Int) {
def description=name+"is"+age+"years old"
}
上述代码声明并初始化了不可变字段name和age,而这两个字段都是对象私有的。
类似这种字段等同于private[this] val字段的效果。否则,该參数将不被保存为字段。它不过一个能够被主构造器中的代码訪问的普通參数。严格地说,这是一个详细实现相关的优化。
主构造器參数生成字段
下表总结了不同类型的主构造器參数相应会生成的字段和方法:
假设主构造器的表示法让你困惑,你不须要使用它。你仅仅要依照常规的做法提供一个或多个辅助构造器就可以,只是要记得调用this(),假设你不和其它辅助构造器串接的话。话虽如此,很多程序猿还是喜欢主构造器这种精简的写法。
Martin Odersky建议这样来看待主构造器:在Scala中,类也接受參数,就像方法一样。当你把主构造器的參数看做是类參数时,不带val或var的參数就变得易于理解了,这种參数的作用域涵盖了整个类。
因此,你能够在方法中使用它们。而一旦你这样做了,编译器就自己主动帮你将它保存为字段。
类定义与主构造器
Scala设计者们觉得每敲一个键都是珍贵的,因此他们让你能够把类定义和主构造器结合在一起。当你阅读一个Scala类时。你须要将它们分开。举例来说,当你看到例如以下代码时:
class Person (val name: String) {
var age=0
def description=name+"is"+age+"years old"
}
把它拆开成一个类定义:
class Person (val name: String) {
var age = 0
def description = name+"is"+age+"years old"
}
和一个构造器定义:
class Person(val name: String) {
var age = 0
daf description= nama+"is"+age+"years old"
}
假设想让主构造器变成私有的,能够像这样放置privatekeyword:
class Person private ( val id: Int ) { … }
这样一来类用户就必须通过辅助构造器来构造Person对象了
嵌套类 |
Scala内嵌类
在Scala中。你差点儿能够在不论什么语法结构中内嵌不论什么语法结构。
你能够在函数中定义函数,在类中定义类。下面代码是在类中定义类的一个演示样例:
import scala.collection.mutable.ArrayBuffer
class Network {
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
}
}
在Scala中,每一个实例都有它自己的Member类,就和它们有自己的members字段一样,考虑有例如以下两个网络:
val chatter = new Network
val myFace = new Network
也就是说,chatter.Member和myFace.Member是不同的两个类。
这和Java不同,在Java中内部类从属于外部类。
Scala採用的方式更符合常规,举例来说,要构建一个新的内部对象。你仅仅须要简单的new这个类名:new chatter.Member。而在Java中,你须要使用一个特殊语法:chatter.new Member()。
拿我们的网络演示样例来讲,你能够在各自的网络中加入成员。但不能跨网加入成员:
val fred = chatter.join("Fred")
val wilma=chatter.join ("Wilma")
fred.contacts+=wilma //OK
val barney=myFace.join ("Barney") // 类型为myFace .Member
fred.contacts+=barney // 不能够这样做,不能将一个myFace.Member加入到chatter.Member元素缓冲其中
Scala内嵌类訪问
对于社交网络而言,这种行为是讲得通的。
假设你不希望是这个效果,有两种解决方案。首先。你能够将Member类移到别处,一个不错的位置是Network的伴生对象。
object Network {
class Member (val name: String) {
val contacts=new ArrayBuffer[Member]
}
}
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]
}
}
假设你仅仅想在某些地方,而不是全部地方,利用这个细粒度的"每一个对象有自己的内部类"的特性,则能够考虑使用类型投影。
内嵌类訪问外部类
在内嵌类中,你能够通过外部类.this的方式来訪问外部类的this引用。就像Java那样。
假设你认为须要。也能够用例如以下语法建立一个指向该引用的别名:☆☆
class Network(val name: String){ outer=>
class Member (val name: String) {
def dascription=name+"inside"+outer.name
}
}
class Network { outer=>语法使得outer变量指向Network.this。对这个变量。你能够用不论什么合法的名称。self这个名称非经常见。但用在嵌套类中可能会引发歧义。这种语法和"自身类型"语法相关,将会后面内容继续介绍
类与对象
类是一个对象的蓝图。
一旦定义一个类能够创建从类蓝图使用keywordnew创建对象。以下是一个简单的语法在Scala中定义一个类:
class Point(xc: Int, yc: Int) { var x: Int = xc var y: Int = yc def move(dx: Int, dy: Int) { x = x + dx y = y + dy println ("Point x location : " + x); println ("Point y location : " + y); } }
这个类定义了两个变量x和y和方法:move,没有返回值。类变量被调用,类的字段和方法被称为类方法。
类名能够作为一个类的构造函数,能够採取一些參数。上面的代码定义了两个构造函数的參数:xc和yc;它们都在类的主体内可见。
正如前面提到的,能够使用keywordnew创建对象,然后能够依照以下的样例所看到的訪问类的字段和方法:
import java.io._ class Point(val xc: Int, val yc: Int) { var x: Int = xc var y: Int = yc def move(dx: Int, dy: Int) { x = x + dx y = y + dy println ("Point x location : " + x); println ("Point y location : " + y); } } object Test { def main(args: Array[String]) { val pt = new Point(10, 20); // Move to a new location pt.move(10, 10); } }
当上述代码被编译和运行时,它产生了下面结果:
C:/>scalac Test.scala C:/>scala Test Point x location : 20 Point y location : 30 C:/>
扩展一个类:
能够扩展scala类以类似的方式,如在Java中的一样。但有两个限制:方法重载须要overridekeyword,仅仅有主构造能够传递參数给基构造。
如今扩展上面的类,并添加一个类的方法:
class Point(val xc: Int, val yc: Int) { var x: Int = xc var y: Int = yc def move(dx: Int, dy: Int) { x = x + dx y = y + dy println ("Point x location : " + x); println ("Point y location : " + y); } } class Location(override val xc: Int, override val yc: Int, val zc :Int) extends Point(xc, yc){ var z: Int = zc def move(dx: Int, dy: Int, dz: Int) { x = x + dx y = y + dy z = z + dz println ("Point x location : " + x); println ("Point y location : " + y); println ("Point z location : " + z); } }
extends子句有两种作用:它使类Location继承类Point全部非私有成员,它使Location类作为Point类的子类。
因此。这里的Point类称为超类,而Location类被称为子类。扩展一个类,继承父类的全部功能。被称为继承,但scala同意继承。仅仅能从一个唯一的类。
让我们看看完整的样例,显示继承的使用方法:
import java.io._ class Point(val xc: Int, val yc: Int) { var x: Int = xc var y: Int = yc def move(dx: Int, dy: Int) { x = x + dx y = y + dy println ("Point x location : " + x); println ("Point y location : " + y); } } class Location(override val xc: Int, override val yc: Int, val zc :Int) extends Point(xc, yc){ var z: Int = zc def move(dx: Int, dy: Int, dz: Int) { x = x + dx y = y + dy z = z + dz println ("Point x location : " + x); println ("Point y location : " + y); println ("Point z location : " + z); } } object Test { def main(args: Array[String]) { val loc = new Location(10, 20, 15); // Move to a new location loc.move(10, 10, 5); } }
须要注意的是方法move,不会覆盖 move 方法对应的定义,由于它们是不同的定义(比如,前两个參数,而后者则须要三个參数)。
让我们编译和执行上面的程序,这将产生下面结果:
C:/>scalac Test.scala C:/>scala Test Point x location : 20 Point y location : 30 Point z location : 20 C:/>
单例对象:
Scala比Java更面向对象,由于在Scala中不能有静态成员。相反,Scala有单例的对象。单例就是仅仅能有一个实例。即。类的对象。能够使用keywordobject取代classkeyword,而不是创建单例。由于不能实例化一个单独的对象。不能将參数传递给主构造。前面已经看到所有採用单一对象,调用Scala的main方法的样例。
下面是单例显示的同样的样例:
import java.io._ class Point(val xc: Int, val yc: Int) { var x: Int = xc var y: Int = yc def move(dx: Int, dy: Int) { x = x + dx y = y + dy } } object Test { def main(args: Array[String]) { val point = new Point(10, 20) printPoint def printPoint{ println ("Point x location : " + point.x); println ("Point y location : " + point.y); } } }
当上述代码被编译和运行时,它产生了下面结果:
C:/>scalac Test.scala C:/>scala Test Point x location : 10 Point y location : 20 C:/>
http://www.cnblogs.com/sunddenly/p/4427551.html
http://www.yiibai.com/scala/scala_classes_objects.html
Scala具体解释---------类