工作中偶然發現Scala構造方法中的參數,無論是否有val/var修飾都可以順利編譯運行,如下: 那麼兩者的區別在哪裡呢?對於case class呢?其區別又在哪裡?其應用場景又在哪裡呢?下麵就辨析一下如下幾個類的區別 單純的從代碼中來看,發現不了什麼區別,只是簡單的多了一個val的修飾符。為了一探 ...
工作中偶然發現Scala構造方法中的參數,無論是否有val/var修飾都可以順利編譯運行,如下:
1 class AA(name: String) 2 class BB(val name: String)
那麼兩者的區別在哪裡呢?對於case class呢?其區別又在哪裡?其應用場景又在哪裡呢?下麵就辨析一下如下幾個類的區別
1 class AA(name: String) 2 class BB(val name: String) 3 class CC(var name: String)
4 class DD(private val name: String) 5 class EE(private[this] val name: String)
6 case class FF(name: String) 7 case class GG(val name: String) 8 case class HH(var name: String) 9 case class II(private val name: String) 10 case class JJ(private[this] val name: String)
單純的從代碼中來看,發現不了什麼區別,只是簡單的多了一個val的修飾符。為了一探究竟,先對源碼進行編譯,然後通過javap對其class文件進行反編譯,查看其與源碼的區別。
一、普通類構造器中val/var 存在和不存在的區別
源碼:
1 class AA(name: String) 2 class BB(val name: String) 3 class CC(var name: String)
反編譯結果:
1 Compiled from "Test.scala" 2 public class AA { 3 public AA(java.lang.String); 4 } 5 6 Compiled from "Test.scala" 7 public class BB { 8 private final java.lang.String name; 9 public java.lang.String name(); 10 public BB(java.lang.String); 11 } 12 13 Compiled from "Test.scala" 14 public class CC { 15 private java.lang.String name; 16 public java.lang.String name(); 17 public void name_$eq(java.lang.String); 18 public CC(java.lang.String); 19 }
結論:構造器中val修飾的參數,編譯之後會在該類中添加一個private final的全局常量,同時提供一個用於獲取該常量的public方法,無設置方法。
構造器中var修飾的參數,編譯之後會在該類中添加一個private的全局變數,同時提供兩個獲取和設置該變數值的public方法。
構造器中無修飾符的參數,則該參數屬於構造函數內的局部參數,僅僅在該構造函數內部訪問。
二、普通類構造器中private和private[this] 修飾參數的區別
源碼:
1 class DD(private val name: String) 2 class EE(private[this] val name: String)
反編譯結果:
Compiled from "Test.scala" public class DD { private final java.lang.String name; private java.lang.String name(); public DD(java.lang.String); } Compiled from "Test.scala" public class EE { public EE(java.lang.String); }
結論:private 修飾的構造器參數,編譯之後會在該類中添加一個private final的全局常量,同時提供一個private的訪問該參數的方法。即該參數可在該類範圍內訪問。
private[this]修飾的構造器參數,不存在全局變數,只能在該構造方法中訪問,在該類中無法訪問。同 class AA(name: String)
註意:Scala整個類體就是其構造函數,所以,站在Scala角度看,private[this]修飾的構造器參數能夠在整個類中訪問,而站在Java角度看,該參數僅僅能夠在構造函數中訪問,在類中無法訪問。而站在Scala角度看,private[this]和 private的主要區別在於,private[this]修飾的參數無法通過e.name的方式訪問,即使在該類的內部。註意下圖:
圖中,在EE#show中是無法訪問that.name的,即使that的類型本身就是EE也不行的。這才是private[this]和private在Scala中的主要區別。
三、普通class和case class的區別
源碼:
1 case class FF(name: String)
編譯之後會發現在文件夾下麵多出兩個class文件,一個為FF.class,另一個為FF$.class文件。對兩個文件進行反編譯
反編譯結果:
1 Compiled from "Test.scala" 2 public class FF implements scala.Product,scala.Serializable { 3 //private final 修飾的name常量 4 private final java.lang.String name; 5 //public修飾獲取name的方法,可用於外部訪問 6 public java.lang.String name(); 7 //public修飾的構造函數 8 public FF(java.lang.String); 9 public static scala.Option<java.lang.String> unapply(FF); 10 public static FF apply(java.lang.String); 11 public static <A> scala.Function1<java.lang.String, A> andThen(scala.Function1<FF, A>); 12 public static <A> scala.Function1<A, FF> compose(scala.Function1<A, java.lang.String>); 13 public FF copy(java.lang.String); 14 public java.lang.String copy$default$1(); 15 public java.lang.String productPrefix(); 16 public int productArity(); 17 public java.lang.Object productElement(int); 18 public scala.collection.Iterator<java.lang.Object> productIterator(); 19 public boolean canEqual(java.lang.Object); 20 public int hashCode(); 21 public java.lang.String toString(); 22 public boolean equals(java.lang.Object); 23 } 24 25 Compiled from "Test.scala" 26 public final class FF$ extends scala.runtime.AbstractFunction1<java.lang.String, FF> implements scala.Serializable { 27 //靜態的FF$對象 28 public static FF$ MODULE$; 29 //構造函數為private 30 private FF$(); 31 32 //返回FF對象的 apply方法 33 public FF apply(java.lang.String); 34 35 public static {}; 36 public final java.lang.String toString(); 37 public scala.Option<java.lang.String> unapply(FF); 38 private java.lang.Object readResolve(); 39 public java.lang.Object apply(java.lang.Object); 40 }
分析:
先看FF.class
1、對比class AA(name: String)的結果來看,case class 自動實現了scala.Product,scala.Serializable兩個特質(介面),因此,case class類中自然也會實現這兩個特質中的抽象方法/覆寫一些方法(11~22行)。
1 public class AA 2 public class FF implements scala.Product,scala.Serializable
2、對比 public class BB,在類內部都具有一個全局常量name和一個獲取該常量的public方法,同時兩者都具有一個public的構造函數。
3、實現了一些特質中的一些方法,覆寫了Java Object中的一些方法。例如:andThen() toString() hashCode()等
再看FF$
1、有一個public static修飾名為 MODULE$ 的 FF$對象。
2、構造器被私有化,用private修飾。
3、組合1、 2、兩點可知,FF$是一個單例類。其矢志不渝就是Scala中FF類的伴生對象。
結論:
Scala編譯器在對case class進行編譯的時候做了特殊處理,擴展了其方法和功能,加入了scala.Product,scala.Serializable的特性,同時為其提供了該類的伴生對象。另外,對於case class 構造器參數,其預設以public修飾,可允許外部調用。
四、其他
上面幾個對比理解了,下麵這幾個類之間的區別也就可以舉一反三了。
1 case class GG(val name: String) 2 case class HH(var name: String) 3 case class II(private val name: String) 4 case class JJ(private[this] val name: String)
=========================================
原文鏈接:case class 和class的區別以及構造器參數辨析 轉載請註明出處!
=========================================
-----end