posts - 241,  comments - 116,  trackbacks - 0
          1. 前言
          寫這基礎復習系列是覺得工作中自己的基礎太差了,很多東西都沒想透徹,沒研究明白??戳恕禞ava基礎16課》總結出其中的一些知識點,用于以后自己復習用,以前的一些知識盲點也明白了。當然,基礎這東西很難說,什么是基礎?有人認為將Java的SDK源碼中重要的類研究一遍,并且能按其規范(接口)實現了自己的類才算是真正掌握了基礎。其實一點都沒錯,只有通過去看微觀的實現,才能提升自己的認識。

          2. 數組在內存中的存儲狀態

          先看看數組,數組咱們平時經常用,從用法來看,數組相當于普通變量,只不過它可以狀態多個相同類的多個對象容器而已。在內存中,數組向內存申請的空間是一段連續的物理空間。
          public class ArrayTest {
              public static void main(String[] args) {
                  String[] array = new String[] { "1", "2", "3" };
                  for (String str : array) {
                      System.out.println(str.hashCode());
                  }
              }
          }
          3個字符串實際上占用的是一段連續的內存空間地址。需要說明的一點就是數組是引用型變量,數組中的元素僅僅是指向內存地址的指針,而指針指向的目的地才是實際的數據對象。內存中的情況如下:
          所謂的數組聲明,實際上就是按照指定長度,為數組在內存開辟了一段連續的空間,如果不是Java基本原型數據則附給這些內存空間的指針與默認初始地址null,如果是原型數據,則這些空間不再是指針,而是實實在在的原型值(例如int是0)。
          而實際上多維數組的實現也是基于上面一維數組實現的,所以二維數組在我們來看,邏輯上可以當成矩陣,而在實實在在物理內存上則是如下:
          例如
          public static void main(String[] args) {
                  String[][] str2 = new String[3][4];
                  for (int i = 0; i < str2.length; i++) {
                      int numOuter = i + 1;
                      System.out.println("外層執行了" + numOuter + "次");
                      for (int j = 0; j < str2[i].length; j++) {
                          int numIuter = j + 1;
                          System.out.println("內層執行了" + numIuter + "次");
                          str2[i][j] = "素" + i + j;
                      }
                  }

                  for (String[] strArray1 : str2) {
                      for (String str : strArray1) {
                          System.out.print(str + " ");
                      }
                      System.out.println();
                  }
              }
          看似二維數組存儲的元素是這樣的矩陣形式
          素00 素01 素02 素03
          素10 素11 素12 素13
          素20 素21 素22 素23
          實際上這個二維數組的分配如下圖
          再復雜的三維數組、多維數組同樣以此類推,呈現出一個倒樹結構,以根擴展,葉子節點才是真正存儲數據(原型)或者是真正指向應用數據對象的指針(復雜對象)。
          3.對象的產生
          對象的產生和JVM的運行機制息息相關,我們使用一個對象為我們服務實際上歸根結底最后都是得用new出來的對象為我們所用,而這個對象是通過類對象產生的,這就是Java思想中的萬事萬物接對象的概念。首先得有一個模板對象,這個模板對象就是類對象,每一個new出來的實例對象實際上都是由這個模板對象而產生出來的,所以我們定義類的時候如果具有類變量,那么所有因它而創建的實例對象中的static變量都會因為類變量的改變而改變。因為static本身就是類對象所擁有的,模板都變了,你實例對象中的相關變量當然要變嘍。
          無論是通過哪個實例對象去訪問類變量,底層都是用類對象直接訪問該類變量,所以大家使用static變量時得出來的值都是一樣的。
          還要說明的一點就是final變量,如果在編譯時就能確定該變量的值,則此值在程序運行時不再是個變量,而是一個定值常量。至于實例變量的初始化時機以及JVM的一些初始化內幕
          4. 父子對象
          使用Java不可能不使用繼承機制,現在來看看new一個子類的時候是如何初始化父類的。假如有如下的類結構
          所有類如果不指定父類那么就都是Object的子類,如果指定了父類,則間接地會繼承Object的,可能是它的孫子,也可能是它的曾孫子,也可能是它的孫子的孫子。如下例
          class Parent{
              static{
                  System.out.println("老子的靜態塊");
              }
              {
                  System.out.println("老子的非靜態塊");
              }
              public Parent(){
                  System.out.println("老子的無參構造函數");
              }
          }

          class Sub extends Parent{
              static{
                  System.out.println("兒子的靜態塊");
              }
              {
                  System.out.println("兒子的非靜態塊");
              }
              public Sub(){
                  System.out.println("兒子的無參構造函數");
              }
          }

          public class ParSubTest {

              /**
               * @param args
               */
              public static void main(String[] args) {
                  new Sub();
              }
          }
          執行之后的結果是
          老子的靜態塊
          兒子的靜態塊
          老子的非靜態塊
          老子的無參構造函數
          兒子的非靜態塊
          兒子的無參構造函數
          由此可以得出結論:
          0.靜態代碼塊總會在實例對象創建之前執行,因為它是屬于類對象級別的代碼塊,JVM先在內存中分配好了類對象的空間,執行完靜態塊后再去理會實例對象作用域的東西。
          1.總是執行父類的非靜態塊
          2.隱式調用父類的無參構造函數,或者現實調用父類的有參構造函數
          3.執行子類的非靜態塊
          4.根據程序需要(就是new后面的構造器函數)調用子類的構造函數
          下面來看看一個不太規范的父子程序引發的問題。
          package se01;

          class Par1 {

              private int num = 20;

              public Par1() {
                  System.out.println("par-num:" + num);
                  this.display();
              }

              public void display() {
                  System.out.println("num:" + num + "   class:"
                          + this.getClass().getName());
              }

          }

          class Sub1 extends Par1 {
              private int num = 40;

              public Sub1() {
                  num = 4000;
              }

              public void display() {
                  System.out.println("sub-num:" + num + "   class:"
                          + this.getClass().getName());
              }
          }

          public class ParSubErrorTest {

              public static void main(String[] args) {
                  new Sub1();
              }
          }
          當然,一般在實際項目開發中也不會這么寫代碼,不過這代碼給咱們的啟示是揭示了JVM的一些內幕。執行結果是
          par-num:20
          sub-num:0   class:se01.Sub1
          就像剛剛得出的5條結論一樣,在new Sub1();的時候先要對父類進行構造函數的調用,而父類的構造函數又調用了方法display(),這個時候問題就出現了,父類究竟調用的是誰的構造方法?是父類自己的,還是子類重寫的?結論很簡單了,就是子類若重寫了該方法,那么直接調用子類的重寫方法,如果沒有重寫該方法,那么直接由父類對象直接調用自己的方法即可。由上面程序可以看出子類重寫了該display()方法,那么在調用子類的構造函數之前是先調用了父類的無參構造函數,之后在父類無參構造函數中調用了子類重寫后的display()方法,而此時,子類對象還沒實例化完畢呢,僅僅在內存中分配了相應的空間而已,實例變量僅僅有系統默認值而已,并沒有完成賦值的過程,所以,此時子類的實例變量num是默認值0,導致調用子類方法時顯示num也是0。而父類的實例變量當然此時已經初始化完畢了,實例對象也有了,自然它的num是賦予初始值后的20嘍。
          而這程序的問題,或者說不規范的地方在哪里呢?就是它將構造函數用于了其他用途,構造函數實際上就是為了初始化數據用的,而不是用于調用其他方法用的,此程序在構造函數中調用了自己聲明的一個public方法,無異于扭曲了構造函數本身的作用,雖然說這么寫編譯器不會報錯,但是無異于給繼承機制帶來了隱患。
          5.繼承機制在處理成員變量和方法時的區別
          package se01;

          class Parent2 {
              int a = 1;
              public void test01() {
                  System.out.println(a);
              }
          }

          class Sub2 extends Parent2 {
              int a = 2;C#字符串比較Compare使用指南
              public void test01() {
                  System.out.println(a);
              }
          }

          public class ParSubPMTest {

              public static void main(String[] args) {
                  Parent2 sub2 = new Sub2();
                  Sub2 sub3 = (Sub2)sub2;
                  System.out.println(sub2.a);
                  sub2.test01();
                  System.out.println(sub3.a);
                  sub3.test01();
              }
          }
          輸出結果是
          1
          2
          2
          2
          也就是說通過直接訪問實例變量的時候是顯示父類特性的,當使用方法的時候則顯示運行時特性。實際上父子關系在內存中存儲是這樣的
          就是說實例對象雖然都是同一個,但是這個實例實際上既存儲了自己的變量,也存儲了父類的變量,當使用父類聲明的對象訪問變量時呈現父親的變量值,使用子類的對象直接訪問變量時呈現子類的值。也就是說當我們初始化一個子類對象時,會將它所有的父類(這里是單繼承的意思,所有的父類就是說父親、爺爺、曾祖、曾曾祖父……)的實例變量分配內存空間。如果子類定義的實例變量與父類同名,那么會隱藏父類的變量,并不是完全覆蓋,通過父類.變量依然能夠獲得父類的實例變量。6.      Java內存管理技巧1:盡量使用直接量,而盡量不要用new的方式建立這些對象,比如
          String string = "1";
          Long longlong = 1L;
          Byte bytebyte = 1;
          Short shortshort = 1;
          Integer integer = 22;
          Float floatfloat = 2.2F;
          Double doubledouble = 0.333333;
          Boolean booleanboolean = false;
          Character character = 'm';
          2:盡量使用StringBuffer和StringBuilder來進行字符串的的鏈接和使用,這個就不用解釋了吧,很常用,尤其是拼接SQL的時候。
          3:養成習慣,盡早釋放無用對象
          例如如下程序:
          public void test(){
                  StringBuilder stringBuilder = new StringBuilder();
                  
                  stringBuilder = null;
                  //很消耗時間………………………………
              }
          在很消耗時間的程序執行前將變量就盡量釋放掉,讓JVM垃圾回收期去回收去。
          4:不到萬不得以,不要輕易使用static變量,雖然static變量很常用,不過這個類變量會常駐內存,從對象復用的角度講,倒是省了資源了,但是如果不是經常復用的對象而聲明了static變量就會常駐內存,只要程序還在運行就永不會回收。
          5:避免創建重復對象變量
                  for(int i=0;i<10;i++){
                      Use use = new Use();
                  }
          如上代碼創建了很多個臨時對象變量use,實際上可以改進成
                  Use use = null;
                  for(int i=0;i<10;i++){
                      use = new Use();
                      use = null;
                  }
          6:盡量不要自己使用對象的finalize方法
          不到萬不得以,千萬不要在此方法中進行變量回收等等操作。
          7:如果運行時環境要求空間資源很嚴格,那么可以考慮使用軟引用SoftReference對象進行引用。當內存不夠時,它會犧牲自己,釋放軟引用對象。軟引用對象適用于比較瞬時的處理程序,處理完了就完了,內存不夠會先將此對象控件騰出來而不回內存溢出的報錯誤。(關于垃圾回收和對象各種方式的引用會在之后學習筆記中體現)
          7.總結
          主要復習了數組的內存形式、父子對象的一些調用陷阱、父子關系在內存中的形式、內存的使用技巧。
          posted on 2011-08-15 10:45 墻頭草 閱讀(281) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          人人游戲網 軟件開發網 貨運專家
          主站蜘蛛池模板: 洞头县| 县级市| 东城区| 库车县| 磐石市| 涿鹿县| 涟水县| 绥中县| 阿拉善盟| 伊吾县| 盐池县| 康定县| 茶陵县| 山东省| 共和县| 潍坊市| 班戈县| 庐江县| 威信县| 巴林左旗| 香港 | 阳高县| 修水县| 武功县| 游戏| 昌乐县| 佛冈县| 玛纳斯县| 胶州市| 巴塘县| 施秉县| 尼玛县| 永登县| 兰州市| 玉树县| 孟村| 竹溪县| 资溪县| 奈曼旗| 裕民县| 威海市|