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

          2. 數(shù)組在內(nèi)存中的存儲狀態(tài)

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

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

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

          public class ParSubTest {

              /**
               * @param args
               */
              public static void main(String[] args) {
                  new Sub();
              }
          }
          執(zhí)行之后的結果是
          老子的靜態(tài)塊
          兒子的靜態(tài)塊
          老子的非靜態(tài)塊
          老子的無參構造函數(shù)
          兒子的非靜態(tài)塊
          兒子的無參構造函數(shù)
          由此可以得出結論:
          0.靜態(tài)代碼塊總會在實例對象創(chuàng)建之前執(zhí)行,因為它是屬于類對象級別的代碼塊,JVM先在內(nèi)存中分配好了類對象的空間,執(zhí)行完靜態(tài)塊后再去理會實例對象作用域的東西。
          1.總是執(zhí)行父類的非靜態(tài)塊
          2.隱式調用父類的無參構造函數(shù),或者現(xiàn)實調用父類的有參構造函數(shù)
          3.執(zhí)行子類的非靜態(tài)塊
          4.根據(jù)程序需要(就是new后面的構造器函數(shù))調用子類的構造函數(shù)
          下面來看看一個不太規(guī)范的父子程序引發(fā)的問題。
          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();
              }
          }
          當然,一般在實際項目開發(fā)中也不會這么寫代碼,不過這代碼給咱們的啟示是揭示了JVM的一些內(nèi)幕。執(zhí)行結果是
          par-num:20
          sub-num:0   class:se01.Sub1
          就像剛剛得出的5條結論一樣,在new Sub1();的時候先要對父類進行構造函數(shù)的調用,而父類的構造函數(shù)又調用了方法display(),這個時候問題就出現(xiàn)了,父類究竟調用的是誰的構造方法?是父類自己的,還是子類重寫的?結論很簡單了,就是子類若重寫了該方法,那么直接調用子類的重寫方法,如果沒有重寫該方法,那么直接由父類對象直接調用自己的方法即可。由上面程序可以看出子類重寫了該display()方法,那么在調用子類的構造函數(shù)之前是先調用了父類的無參構造函數(shù),之后在父類無參構造函數(shù)中調用了子類重寫后的display()方法,而此時,子類對象還沒實例化完畢呢,僅僅在內(nèi)存中分配了相應的空間而已,實例變量僅僅有系統(tǒng)默認值而已,并沒有完成賦值的過程,所以,此時子類的實例變量num是默認值0,導致調用子類方法時顯示num也是0。而父類的實例變量當然此時已經(jīng)初始化完畢了,實例對象也有了,自然它的num是賦予初始值后的20嘍。
          而這程序的問題,或者說不規(guī)范的地方在哪里呢?就是它將構造函數(shù)用于了其他用途,構造函數(shù)實際上就是為了初始化數(shù)據(jù)用的,而不是用于調用其他方法用的,此程序在構造函數(shù)中調用了自己聲明的一個public方法,無異于扭曲了構造函數(shù)本身的作用,雖然說這么寫編譯器不會報錯,但是無異于給繼承機制帶來了隱患。
          5.繼承機制在處理成員變量和方法時的區(qū)別
          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
          也就是說通過直接訪問實例變量的時候是顯示父類特性的,當使用方法的時候則顯示運行時特性。實際上父子關系在內(nèi)存中存儲是這樣的
          就是說實例對象雖然都是同一個,但是這個實例實際上既存儲了自己的變量,也存儲了父類的變量,當使用父類聲明的對象訪問變量時呈現(xiàn)父親的變量值,使用子類的對象直接訪問變量時呈現(xiàn)子類的值。也就是說當我們初始化一個子類對象時,會將它所有的父類(這里是單繼承的意思,所有的父類就是說父親、爺爺、曾祖、曾曾祖父……)的實例變量分配內(nèi)存空間。如果子類定義的實例變量與父類同名,那么會隱藏父類的變量,并不是完全覆蓋,通過父類.變量依然能夠獲得父類的實例變量。6.      Java內(nèi)存管理技巧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:養(yǎng)成習慣,盡早釋放無用對象
          例如如下程序:
          public void test(){
                  StringBuilder stringBuilder = new StringBuilder();
                  
                  stringBuilder = null;
                  //很消耗時間………………………………
              }
          在很消耗時間的程序執(zhí)行前將變量就盡量釋放掉,讓JVM垃圾回收期去回收去。
          4:不到萬不得以,不要輕易使用static變量,雖然static變量很常用,不過這個類變量會常駐內(nèi)存,從對象復用的角度講,倒是省了資源了,但是如果不是經(jīng)常復用的對象而聲明了static變量就會常駐內(nèi)存,只要程序還在運行就永不會回收。
          5:避免創(chuàng)建重復對象變量
                  for(int i=0;i<10;i++){
                      Use use = new Use();
                  }
          如上代碼創(chuàng)建了很多個臨時對象變量use,實際上可以改進成
                  Use use = null;
                  for(int i=0;i<10;i++){
                      use = new Use();
                      use = null;
                  }
          6:盡量不要自己使用對象的finalize方法
          不到萬不得以,千萬不要在此方法中進行變量回收等等操作。
          7:如果運行時環(huán)境要求空間資源很嚴格,那么可以考慮使用軟引用SoftReference對象進行引用。當內(nèi)存不夠時,它會犧牲自己,釋放軟引用對象。軟引用對象適用于比較瞬時的處理程序,處理完了就完了,內(nèi)存不夠會先將此對象控件騰出來而不回內(nèi)存溢出的報錯誤。(關于垃圾回收和對象各種方式的引用會在之后學習筆記中體現(xiàn))
          7.總結
          主要復習了數(shù)組的內(nèi)存形式、父子對象的一些調用陷阱、父子關系在內(nèi)存中的形式、內(nèi)存的使用技巧。
          posted on 2011-08-15 10:45 墻頭草 閱讀(282) 評論(0)  編輯  收藏

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


          網(wǎng)站導航:
           
          人人游戲網(wǎng) 軟件開發(fā)網(wǎng) 貨運專家
          主站蜘蛛池模板: 长武县| 榆中县| 台东市| 田东县| 沭阳县| 汤阴县| 收藏| 鲁山县| 大兴区| 阿尔山市| 黎平县| 藁城市| 台前县| 温泉县| 梅河口市| 舒城县| 常州市| 肥西县| 阳新县| 昆山市| 怀远县| 邯郸县| 中西区| 昭苏县| 昌宁县| 微山县| 民县| 客服| 邓州市| 大厂| 大方县| 乌拉特后旗| 银川市| 祁阳县| 本溪| 新绛县| 错那县| 屯门区| 卓资县| 鱼台县| 大理市|