Scala練習五Scala中的類
Scala中的類 |
摘要:
在本篇中,你將會學習如何用Scala實現類。如果你瞭解Java或C++中的類,你不會覺得這有多難,並且你會很享受Scala更加精簡的表示法帶來的便利。本篇的要點包括:
1. 類中的欄位自動帶有getter方法和setter方法
2. 你可以用定製的getter/setter方法替換掉欄位的定義,而不必修改使用類的客戶端,這就是所謂的"統一訪問原則"
3. 用@BeanProperty註解來生成JavaBeans的getXxx/setXxx()方法
4. 每個類都有一個主要的構造器,這個構造器和類定義"交織"在一起。它的引數直接成為類的欄位。主構造器執行類體中所有的語句
5. 輔助構造器是可選的,它們叫做this
簡單類和無參方法 |
簡單類
Scala類最簡單的形式看上去和Java或c+++的很相似:
class Counter {
private var value=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 //定義中不帶()
}
這樣一來類的使用者就必須用
帶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
不過,有時你需要這樣一個屬性,客戶端不能隨意改值,但它可以通過某種其他的方式被改變。前面中的Counter類就是個很好的例子,從概念上講,counter有一個current屬性,當increment方法被呼叫時更新,但並沒有對應的setter
class Counter {
private var value=0 //你必須初始化欄位
def increment() {value+=1} //方法預設是公有的
def current()=value
}
需要注意的是,你不能通過val來實現這樣一個屬性,val永不改變。你需要提供一個私有欄位和一個屬性的getter方法,像這樣:
class Counter {
private var value=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=0
def increment () {value+=1 }
def isLess (other: Counter) = value < other.value // 可以訪問另一個物件的私有欄位
}
之所以訪問other.value是合法的,是因為othert也同樣是Cormter物件。
物件私有欄位
除此之外Scala允許我們定義更加嚴格的訪問限制,通過private[this]這個修飾符來實現:
private [this] var value=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規範
把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)我們將首先討論輔助構造器,這是因為它們更容易理解。它們同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"
}
如果想讓主構造器變成私有的,可以像這樣放置private關鍵字:
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()。拿我們的網路示例來講,你可以在各自的網路中新增成員,但不能跨網新增成員:
相關推薦
Scala練習五Scala中的類
Scala中的類摘要:在本篇中,你將會學習如何用Scala實現類。如果你瞭解Java或C++中的類,你不會覺得這有多難,並且你會很享受Scala更加精簡的表示法帶來的便利。本篇的要點包括:1. 類中的欄位自動帶有getter方法和setter方法2. 你可以用定製的gette
Scala練習五
1. 改進Counter類,讓它不要在Int.MaxValue時變成負數程式程式碼:class Counter {private var value=100def increment(){if(value<Int.MaxValue)value+=1elsevalue}d
Scala練習六Scala物件
Scala中的物件摘要:在本篇中,你將會學到何時使用Scala的object語法結構。在你需要某個類的單個例項時,或者想為其他值或函式找一個可以掛靠的地方時,你就會用到它。本篇的要點包括:1. 用物件作為單例或存放工具方法2. 類可以擁有—個同名的伴生物件3. 物件可以擴充套
Scala練習(十五)
\1. 編寫四個Junit測試案例,分別使用帶或不帶某個函式的@Test註解。用 Junit執行這些測試。 1 2 3 4 5 6 7 8 9 10 11 12 13 import org.junit.Test class ScalaTest { @Test
《快學Scala》第5章 類 練習
1. 改進5.1節的Counter類,讓它不要在Int.MaxValue時變成負數。 /** * Created by Ibuki Suika on 2014/5/26. */ class Counter { private var value = 0 de
redis數據類型四之hash的指令操作(五種數據類型中最重要的一種)
redis數據類型 hash的指令操作 1、老規矩,看看redis官方文檔怎麽寫: 這裏說呢,hashes 這種數據類型容易代表對象,實際上你可以無限制地在 hash 中放給定數量的字段。(除了可用的內存)(其實這個我理解有些模糊,是內存能夠滿足就可以無限制的放入嗎?)所
scala 基礎九 scala 類的擴展和繼承extends override
rgs 繼承 res div cal def ack obj extends 1.scala 類的繼承 package smart.iot //父類 animal 有三個參數 class animal(var name:String,var age:Int
快學Scala 第六課 (類構造函數)
ora per 如果 輔助 text log ring nbsp string 類 主構造器: class Person (var name: String){ } 主構造參數可以不帶val或者var,如果沒有被其他方法使用,則不保存為字段。 如果被其他方法
scala模式匹配及樣本類
cas 增加 符號 size 成功 沒有 類類型 種類 返回 樣本類 1、帶有case關鍵字的類被稱為樣本類; 例如:abstract class Expr case class Var(name: String) extends E
Scala學習筆記(4)—— scala 練習
1 練習 1.1 建立一個List scala> val lst0 = List(1,7,9,8,0,3,5,4,6,2) lst0: List[Int] = List(1, 7, 9, 8, 0
SCALA練習
scala> val lines = List("hello tom hello jerry","hello tom hello hello hello") lines: List[String] = List(hello tom hello jerry,
Scala練習二控制結構和函式
控制結構和函式摘要:本篇主要學習在Scala中使用條件表示式、迴圈和函式,你會看到Scala和其他程式語言之間一個根本性的差異。在Java或C++中,我們把表示式(比如3+4)和語句(比如if語句)看做兩樣不同的東西。表示式有值,而語句執行動作。在Scala中,幾乎所有構造出
Scala練習(二)
Scala控制結構和函式&練習 1. 一個數字如果為正數,則它的signum為1;如果是負數,則signum為-1;如果為0,則signum為0;編寫一個函式來計算這個值 簡單邏輯判斷: 測試結果如下: Scala中已經有此方法了,如下: printl
Scala練習(九)
檔案正則表示式&練習 1. 編寫一小段Scala程式碼,將某個檔案中的行倒轉順序,將最後一行作為第一行,依此類推 程式程式碼: import scala.io.Source import java.io.PrintWriter object Re
Scala練習(十二)
\1. 編寫函式 values(fun: (Int) => Int, low:Int, high: Int), 該函式輸出一個集合,對應給定區間內給定函式的輸入和輸出。比如,values(x=> x * x, -5, 5)應該產生一個對偶的集合(-5, 25),
Scala練習(一)
1. 在Scala REPL中鍵人3.,然後按Tab鍵。有哪些方法可以被應用 在Scala REPL中需要按3. 然後按Tab才會提示。 直接按3加Tab是沒有提示的。下面是結果: 列出的方法並不全,需要查詢全部方法還是需要到Scaladoc中的Int,Double,R
scala例項——五
import scala.util.control.Breaks._ import org.apache.spark.sql._ import org.apache.spark.SparkConf import org.apache.spark.SparkCon
Scala練習二
1. 一個數字如果為正數,則它的signum為1;如果是負數,則signum為-1;如果為0,則signum為0;編寫一個函式來計算這個值簡單邏輯判斷:測試結果如下:Scala中已經有此方法了,如下:println(BigInt(10).signum)測試結果如下:2. 一個
Scala練習一基礎學習
摘要:在篇主要內容:如何把Scala當做工業級的便攜計算器使用,如何用Scala處理數字以及其他算術操作。在這個過程中,我們將介紹一系列重要的Scala概念和慣用法。同時你還將學到作為初學者如何瀏覽Scaladoc文件1. 使用Scala直譯器2. 用var和val定義變數3
Scala練習八繼承
Scala繼承摘要:在本篇中,你將瞭解到Scala的繼承與Java和C++最顯著的不同。要點包括:1. extends、final關鍵字和Java中相同2. 重寫方法時必須用override3. 只有主構造器可以呼叫超類的主構造器4. 你可以重寫欄位在本篇中,我們只探討類繼承