1. 程式人生 > >Scala入門到精通——第六節:類和物件(一)

Scala入門到精通——第六節:類和物件(一)

本節主要內容

1 類定義、建立物件
2 主構造器
3 輔助構造器

類定義、建立物件

//採用關鍵字class定義
class Person {
  //類成員必須初始化,否則會報錯
  //這裡定義的是一個公有成員
  var name:String=null
}

Person類在編譯後會生成Person.class檔案
這裡寫圖片描述
利用javap -prviate Person命令檢視位元組碼檔案內容,可以看得到以下內容

D:\ScalaWorkspace\ScalaChapter06\bin\cn\scala\xtwy>javap -private Person
警告: 二進位制檔案Person包含cn.scala.xtwy.Person
Compiled from
"Person.scala" public class cn.scala.xtwy.Person { private java.lang.String name; public java.lang.String name(); public void name_$eq(java.lang.String); public cn.scala.xtwy.Person(); }

從位元組碼檔案內容可以看到:雖然我們只在Person類中定義了一個類成員(域)name,型別為String,但Scala會預設幫我們生成name()與name_=()及建構函式Person()。其中name()對應java中的getter方法,name_=()對應java中的setter方法(由於JVM中不允許出現=,所以用$eq代替。值得注意的是定義的是公有成員,但生成的位元組碼中卻是以私有的方式實現的,生成的getter、setter方法是公有的
因此,可以直接new操作建立Person物件

//預設已經有構建函式,所以可以直接new
scala> val p=new Person()
p: Person = Person@84c504

//直接呼叫getter和setter方法
//setter方法
scala> p.name_=("john")
//getter方法
scala> p.name
res2: String = john

//直接修改,但其實呼叫的是p.name_=("jonh")
scala> p.name="jonh"
p.name: String = jonh

//getter方法
scala> p.name
res28: String = jonh

你也可以定義自己的getter和setter方法

class Person{
  //定義私有成員
  private var privateName:String=null;

  //getter方法
  def name=privateName
  //setter方法
  def name_=(name:String){
    this.privateName=name
  }

}
D:\ScalaWorkspace\ScalaChapter06\bin\cn\scala\xtwy>javap -private Person
警告: 二進位制檔案Person包含cn.scala.xtwy.Person
Compiled from "Person.scala"
public class cn.scala.xtwy.Person {
  private java.lang.String privateName;
  private java.lang.String privateName();
  private void privateName_$eq(java.lang.String);
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  public cn.scala.xtwy.Person();
}

從生成的位元組碼中可以看出:(1)定義成私有成員,其getter、setter方法也是私有的;(2)直接能訪問的是我們自己定義的getter、setter方法。下面給出的是呼叫方式

scala> val p=new Person()
p: Person = Person@12d0b54

scala> p.name
res29: String = null

//直接賦值法
scala> p.name="john"
p.name: String = john

scala> p.name
res30: String = john

從程式碼執行產生的結果,我們可以知道:通過p.name=“john”這種方式進行賦值,呼叫者並不需要知道是其通過方法呼叫還是欄位訪問來進行操作的,這便是著名的統一訪問原則

如果類的成員域是val型別的變數,則只會生成getter方法

class Person {
  //類成員必須初始化,否則會報錯
  //這裡定義的是一個val公有成員
  val name:String="john"
}

D:\ScalaWorkspace\ScalaChapter06\bin\cn\scala\xtwy>javap -private Person
警告: 二進位制檔案Person包含cn.scala.xtwy.Person
Compiled from "Person.scala"
public class cn.scala.xtwy.Person {
  private final java.lang.String name;
  public java.lang.String name();
  public cn.scala.xtwy.Person();
}

從位元組碼檔案中可以看出:val變數對應的是java中的final型別變數,只生成了getter方法

如果將成員域定義為private[this],則不會生成getter、setter方法

class Person {
  //類成員必須初始化,否則會報錯
  //private[this]修飾
  private[this] var name:String="john"
}

D:\ScalaWorkspace\ScalaChapter06\bin\cn\scala\xtwy>javap -private Person
警告: 二進位制檔案Person包含cn.scala.xtwy.Person
Compiled from "Person.scala"
public class cn.scala.xtwy.Person {
  private java.lang.String name;
  public cn.scala.xtwy.Person();
}

在java語言當中,在定義JavaBean的時候生成的都是setXxx()、getXxx()方法,但scala語言生成的getter方法和setter方法並不是這樣的,如果也需要程式自動會生成getter方法和setter方法,則需要引入 scala.reflect.BeanProperty
然後採用註解的方式修飾變數


class Person {
  //類成員必須初始化,否則會報錯
  //@BeanProperty用於生成getXxx,setXxx方法
  @BeanProperty var name:String="john"
}

D:\ScalaWorkspace\ScalaChapter06\bin\cn\scala\xtwy>javap -private Person
警告: 二進位制檔案Person包含cn.scala.xtwy.Person
Compiled from "Person.scala"
public class cn.scala.xtwy.Person {
  private java.lang.String name;
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  public void setName(java.lang.String);
  public java.lang.String getName();
  public cn.scala.xtwy.Person();
}

下圖給出的是getter、setter方法產生的規則
這裡寫圖片描述
來源:scala for the impatient

類主構造器

主構造器的定義與類的定義交織在一直,將構造器引數直接放在類名稱之後,如下程式碼:

//下列程式碼不但定義了一個類Person,還定義了主構造器,主構造器的引數為String、Int型別
class Person(val name:String,val age:Int)

D:\ScalaWorkspace\ScalaChapter06\bin\cn\scala\xtwy>javap -private Person
警告: 二進位制檔案Person包含cn.scala.xtwy.Person
Compiled from "Person.scala"
public class cn.scala.xtwy.Person {
  private final java.lang.String name;
  private final int age;
  public java.lang.String name();
  public int age();
  public cn.scala.xtwy.Person(java.lang.String, int);
}

//不難看出:上面的程式碼與下列java語言編寫的程式碼等同
public class Person{
  private final String name;
  private final int age;
  public Person(String name,int age){
       this.name=name;
       this.age=age;
  }
  public String getName(){ return name}
  public int getAge() {return age}
}

//具體使用操作如下:
scala> val p=new Person("john",29)
p: Person = Person@abdc0f

scala> p.name
res31: String = john

scala> p.age
res32: Int = 29

主構造器會執行類定義中的所有語句,例如

//當在建立物件時,需要進行相關初始化操作時,可以將初始化語句放在類體中,同樣也可以在類中新增或重寫相關方法
class Person(val name:String,val age:Int){
  //println將作為主構建器中的一部分,在建立物件時被執行
  println("constructing Person ........")
  //重寫toString()方法
  override def toString()= name + ":"+ age
}

scala> val p=new Person("john",29)
constructing Person ........
p: Person = john:29

回過頭來看的話,前面我們定義的Person類是一種無參主構建器

//Person類具有無參主構建器
class Person {
  println("constructing Person....")
  val name:String="john"
}

scala> val p=new Person()
constructing Person....
p: Person = [email protected]79895f

主構建器還可以使用預設引數

//預設引數的主構建器
class Person(val name:String="",val age:Int=18){
  println("constructing Person ........")
  override def toString()= name + ":"+ age
}

scala> val p=new Person
constructing Person ........
p: Person = :18

scala> val p=new Person("john")
constructing Person ........
p: Person = john:18

主構造器中的引數還可以加訪問控制符

//預設引數的主構建器,引數帶訪問控制符號
//age變成私有成員,其getter方法是私有的,外部不能訪問
class Person(val name:String="",private val age:Int=18){
  println("constructing Person ........")
  override def toString()= name + ":"+ age
}

當主構造器的引數不用var或val修飾的時候,引數會生成類的私有val成員,並且不會產生getter和setter方法

//不加變數修飾符
class Person(name:String,age:Int){
  println("constructing Person ........")
  override def toString()= name + ":"+ age
}

D:\ScalaWorkspace\ScalaChapter06\bin\cn\scala\xtwy>javap -private Person
警告: 二進位制檔案Person包含cn.scala.xtwy.Person
Compiled from "Person.scala"
public class cn.scala.xtwy.Person {
  private final java.lang.String name;
  private final int age;
  public java.lang.String toString();
  public cn.scala.xtwy.Person(java.lang.String, int);
}

//與下面類定義等同
class Person(private[this] val name:String,private[this] val age:Int){
  println("constructing Person ........")
  override def toString()= name + ":"+ age
}

D:\ScalaWorkspace\ScalaChapter06\bin\cn\scala\xtwy>javap -private Person
警告: 二進位制檔案Person包含cn.scala.xtwy.Person
Compiled from "Person.scala"
public class cn.scala.xtwy.Person {
  private final java.lang.String name;
  private final int age;
  public java.lang.String toString();
  public cn.scala.xtwy.Person(java.lang.String, int);
}

值得注意的是,將上述Person類中的toString()方法去掉,則類中無任何地方使用了主構造器的引數,此時主構造器引數不會生成類成員

即將
//不加變數修飾符
class Person(name:String,age:Int){
  println("constructing Person ........")
  override def toString()= name + ":"+ age
}
改成:
class Person( val name:String,age:Int){
  println("constructing Person ........")
}

其位元組碼檔案如下:
D:\ScalaWorkspace\ScalaChapter06\bin\cn\scala\xtwy>javap -private Person
警告: 二進位制檔案Person包含cn.scala.xtwy.Person
Compiled from "Person.scala"
public class cn.scala.xtwy.Person {
  public cn.scala.xtwy.Person(java.lang.String, int);
}
//可以看出,主構造器引數不會生成類成員

下面圖給出了Scala中主構建器引數生成類成員和方法時的規則
這裡寫圖片描述
來源:scala for the impatient

在某些情況下,可能需要禁用主構建器,程式碼如下:

//類名後面緊跟private關鍵字可以將主構建器設為私有,不允許外部使用
class Person private(var name:String,var age:Int){
  println("constructing Person ........")
}

//生成的位元組碼檔案如下,可以看到其構建函式已經為private了
D:\ScalaWorkspace\ScalaChapter06\bin\cn\scala\xtwy>javap -private Person
警告: 二進位制檔案Person包含cn.scala.xtwy.Person
Compiled from "Person.scala"
public class cn.scala.xtwy.Person {
  private java.lang.String name;
  private int age;
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  public int age();
  public void age_$eq(int);
  private cn.scala.xtwy.Person(java.lang.String, int);
}

//此時不能直接這麼用
scala> val p=new Person("john",19)
<console>:9: error: constructor Person in class Person cannot be accessed in obj
ect $iw
       val p=new Person("john",19)
             ^

輔助建構函式

前面講了,如果禁用掉了主構建器,則必須使用輔助建構函式來建立物件。輔助建構函式具有兩個特點:(1)輔助構建器的名稱為this,java中的輔助建構函式與類名相同,這常常會導致修改類名時出現不少問題,scala語言避免了這樣的問題;(2)呼叫輔助建構函式時,必須先呼叫主建構函式或其它已經定義好的建構函式。

3.1 我們首先看一下只有輔助建構函式的Person類

//只有輔助建構函式的類
class Person{
  //類成員
  private var name:String=null
  private var age:Int=18
  private var sex:Int=0

  //輔助構造器
  def this(name:String){
    this()
    this.name=name
  }
  def this(name:String,age:Int){
    this(name)
    this.age=age
  }
   def this(name:String,age:Int,sex:Int){
    this(name,age)
    this.sex=sex
  }
}

//位元組碼檔案
D:\ScalaWorkspace\ScalaChapter06\bin\cn\scala\xtwy>javap -private Person
警告: 二進位制檔案Person包含cn.scala.xtwy.Person
Compiled from "Person.scala"
public class cn.scala.xtwy.Person {
  private java.lang.String name;
  private int age;
  private int sex;
  private java.lang.String name();
  private void name_$eq(java.lang.String);
  private int age();
  private void age_$eq(int);
  private int sex();
  private void sex_$eq(int);
  public cn.scala.xtwy.Person();
  public cn.scala.xtwy.Person(java.lang.String);
  public cn.scala.xtwy.Person(java.lang.String, int);
  public cn.scala.xtwy.Person(java.lang.String, int, int);
}

//在定義輔助建構函式時,需要注意建構函式的順序
class Person{
  //類成員
  private var name:String=null
  private var age:Int=18
  private var sex:Int=0

 //輔助構造器
 def this(name:String,age:Int,sex:Int){
    this(name,age)//此處會發生編譯錯誤,這是因為def this(name:String,age:Int)沒有被定義
    this.sex=sex
  }

  def this(name:String){
    this()
    this.name=name
  }
  def this(name:String,age:Int){
    this(name)
    this.age=age
  }

}

3.2 帶主建構函式、輔助建構函式的Person類

//具有主構建函式和輔助構建函式的Person類
class Person(var name:String,var age:Int){
  //類成員
  private var sex:Int=0

  //輔助構造器
   def this(name:String,age:Int,sex:Int){
    this(name,age)
    this.sex=sex
  }
}

生成的位元組碼檔案如下:
D:\ScalaWorkspace\ScalaChapter06\bin\cn\scala\xtwy>javap -private Person
警告: 二進位制檔案Person包含cn.scala.xtwy.Person
Compiled from "Person.scala"
public class cn.scala.xtwy.Person {
  private java.lang.String name;
  private int age;
  private int sex;
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  public int age();
  public void age_$eq(int);
  private int sex();
  private void sex_$eq(int);
  public cn.scala.xtwy.Person(java.lang.String, int);
  public cn.scala.xtwy.Person(java.lang.String, int, int);
}

在主建構函式小節當中我們提到,有時候可能會禁用掉主建構函式,此時只能通過輔助建構函式來建立物件

//禁用主建構函式
class Person private(var name:String,var age:Int){
  //類成員
  private var sex:Int=0

  //輔助構造器
   def this(name:String,age:Int,sex:Int){
    this(name,age)
    this.sex=sex
   }

}

//其位元組碼檔案內容如下
D:\ScalaWorkspace\ScalaChapter06\bin\cn\scala\xtwy>javap -private Person
警告: 二進位制檔案Person包含cn.scala.xtwy.Person
Compiled from "Person.scala"
public class cn.scala.xtwy.Person {
  private java.lang.String name;
  private int age;
  private int sex;
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  public int age();
  public void age_$eq(int);
  private int sex();
  private void sex_$eq(int);
  private cn.scala.xtwy.Person(java.lang.String, int);
  public cn.scala.xtwy.Person(java.lang.String, int, int);
}

新增公眾微訊號,可以瞭解更多最新Spark、Scala相關技術資訊
這裡寫圖片描述