<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    隨筆 - 3, 文章 - 152, 評論 - 17, 引用 - 0
    數據加載中……

    Java的基礎問題(三)

    問題1
    我聲明了什么!

    String s = "Hello world!";

    許多人都做過這樣的事情,但是,我們到底聲明了什么?回答通常是:一個String,內容
    是“Hello world!”。這樣模糊的回答通常是概念不清的根源。如果要準確的回答,一半
    的人大概會回答錯誤。
    這個語句聲明的是一個指向對象的引用,名為“s”,可以指向類型為String的任何對象,
    目前指向"Hello world!"這個String類型的對象。這就是真正發生的事情。我們并沒有聲
    明一個String對象,我們只是聲明了一個只能指向String對象的引用變量。所以,如果在
    剛才那句語句后面,如果再運行一句:

    String string = s;

    我們是聲明了另外一個只能指向String對象的引用,名為string,并沒有第二個對象產生
    ,string還是指向原來那個對象,也就是,和s指向同一個對象。
    String s1 = "This is cool!"; // 生成一個字符串對象[OBJ_1],將其引用賦值給s1
    String s2 = "This is cool!"; // 將同樣的對象[OBJ_1]的引用賦值給s2
    String s3 = new String("This is cool!"); // 新建一個字符串對象[OBJ_2],其引用賦
    值給s3;
    s1 = s2; // 將s2保存的[OBJ_1]的引用賦值給s1,沒有實際效果
    s3 = "This is cool!"; // 將[OBJ_1]的引用賦值給s3,s3原先引用的[OBJ_2]不再被引用


    這樣的代碼共有產生2個String實例,有1對象可以被GC。

    問題2,"=="和equals方法究竟有什么區別?

    ==操作符專門用來比較變量的值是否相等。比較好理解的一點是:
    int a=10;
    int b=10;
    則a==b將是true。
    但不好理解的地方是:
    String a=new String("foo");
    String b=new String("foo");
    則a==b將返回false。

    根據前一帖說過,對象變量其實是一個引用,它們的值是指向對象所在的內存地址,而不
    是對象本身。a和b都使用了new操作符,意味著將在內存中產生兩個內容為"foo"的字符串
    ,既然是“兩個”,它們自然位于不同的內存地址。a和b的值其實是兩個不同的內存地址
    的值,所以使用"=="操作符,結果會是false。誠然,a和b所指的對象,它們的內容都是"
    foo",應該是“相等”,但是==操作符并不涉及到對象內容的比較。
    對象內容的比較,正是equals方法做的事。

    看一下Object對象的equals方法是如何實現的:
    boolean equals(Object o){

    return this==o;

    }
    Object對象默認使用了==操作符。所以如果你自創的類沒有覆蓋equals方法,那你的類使
    用equals和使用==會得到同樣的結果。同樣也可以看出,Object的equals方法沒有達到eq
    uals方法應該達到的目標:比較兩個對象內容是否相等。因為答案應該由類的創建者決定
    ,所以Object把這個任務留給了類的創建者。

    看一下一個極端的類:
    Class Monster{
    private String content;
    ...
    boolean equals(Object another){ return true;}

    }
    我覆蓋了equals方法。這個實現會導致無論Monster實例內容如何,它們之間的比較永遠返
    回true。

    所以當你是用equals方法判斷對象的內容是否相等,請不要想當然。因為可能你認為相等
    ,而這個類的作者不這樣認為,而類的equals方法的實現是由他掌握的。如果你需要使用
    equals方法,或者使用任何基于散列碼的集合(HashSet,HashMap,HashTable),請察看一
    下 java doc以確認這個類的equals邏輯是如何實現的。

    問題3
    String到底變了沒有

    沒有。因為String被設計成不可變(immutable)類,所以它的所有對象都是不可變對象。請
    看下列代碼:

    String s = "Hello";
    s = s + " world!";

    s所指向的對象是否改變了呢?從本系列第一篇的結論很容易導出這個結論。我們來看看發
    生了什么事情。在這段代碼中,s原先指向一個String對象,內容是"Hello",然后我們對
    s進行了+操作,那么s所指向的那個對象是否發生了改變呢?答案是沒有。這時,s不指向
    原來那個對象了,而指向了另一個String對象,內容為"Hello world!",原來那個對象還
    存在于內存之中,只是s這個引用變量不再指向它了。
    通過上面的說明,我們很容易導出另一個結論,如果經常對字符串進行各種各樣的修改,
    或者說,不可預見的修改,那么使用String來代表字符串的話會引起很大的內存開銷。因
    為String對象建立之后不能再改變,所以對于每一個不同的字符串,都需要一個String對
    象來表示。這時,應該考慮使用StringBuffer 類,它允許修改,而不是每個不同的字符串
    都要生成一個新的對象。并且,這兩種類的對象轉換十分容易。
    同時,我們還可以知道,如果要使用內容相同的字符串,不必每次都new一個String。例如
    我們要在構造器中對一個名叫s的String引用變量進行初始化,把它設置為初始值,應當這
    樣做:
    public class Demo {
      private String s;
      ...
      public Demo {
        s = "Initial Value";
      }
      ...
    }
    而非
    s = new String("Initial Value");
    后者每次都會調用構造器,生成新對象,性能低下且內存開銷大,并且沒有意義,因為St
    ring對象不可改變,所以對于內容相同的字符串,只要一個String對象來表示就可以了。
    也就說,多次調用上面的構造器創建多個對象,他們的String類型屬性s都指向同一個對象

    上面的結論還基于這樣一個事實:對于字符串常量,如果內容相同,Java認為它們代表同
    一個String對象。而用關鍵字new調用構造器,總是會創建一個新的對象,無論內容是否相
    同。
    至于為什么要把String類設計成不可變類,是它的用途決定的。其實不只String,很多Ja
    va標準類庫中的類都是不可變的。在開發一個系統的時候,我們有時候也需要設計不可變
    類,來傳遞一組相關的值,這也是面向對象思想的體現。不可變類有一些優點,比如因為
    它的對象是只讀的,所以多線程并發訪問也不會有任何問題。當然也有一些缺點,比如每
    個不同的狀態都要一個對象來代表,可能會造成性能上的問題。所以Java標準類庫還提供
    了一個可變版本,即 StringBuffer。

    問題4,final關鍵字到底修飾了什么?
    final使得被修飾的變量"不變",但是由于對象型變量的本質是“引用”,使得“不變”也
    有了兩種含義:引用本身的不變,和引用指向的對象不變。

    引用本身的不變:
    final StringBuffer a=new StringBuffer("immutable");
    final StringBuffer b=new StringBuffer("not immutable");
    a=b;//編譯期錯誤

    引用指向的對象不變:
    final StringBuffer a=new StringBuffer("immutable");
    a.append(" broken!"); //編譯通過

    可見,final只對引用的“值”(也即它所指向的那個對象的內存地址)有效,它迫使引用只
    能指向初始指向的那個對象,改變它的指向會導致編譯期錯誤。至于它所指向的對象的變
    化,final是不負責的。這很類似==操作符:==操作符只負責引用的“值”相等,至于這個
    地址所指向的對象內容是否相等, ==操作符是不管的。

    理解final問題有很重要的含義。許多程序漏洞都基于此----final只能保證引用永遠指向
    固定對象,不能保證那個對象的狀態不變。在多線程的操作中,一個對象會被多個線程共享
    或修改,一個線程對對象無意識的修改可能會導致另一個使用此對象的線程崩潰。一個錯
    誤的解決方法就是在此對象新建的時候把它聲明為final,意圖使得它“永遠不變”。其實
    那是徒勞的。

    問題5
    到底要怎么樣初始化!

    本問題討論變量的初始化,所以先來看一下Java中有哪些種類的變量。
    1. 類的屬性,或者叫值域
    2. 方法里的局部變量
    3. 方法的參數

    對于第一種變量,Java虛擬機會自動進行初始化。如果給出了初始值,則初始化為該初始
    值。如果沒有給出,則把它初始化為該類型變量的默認初始值。

    int類型變量默認初始值為0
    float類型變量默認初始值為0.0f
    double類型變量默認初始值為0.0
    boolean類型變量默認初始值為false
    char類型變量默認初始值為0(ASCII碼)
    long類型變量默認初始值為0
    byte類型變量默認初始值為0
    short類型變量默認初始值為0
    所有對象引用類型變量默認初始值為null,即不指向任何對象。注意數組本身也是對象,
    所以沒有初始化的數組引用在自動初始化后其值也是null。

    對于兩種不同的類屬性,static屬性與instance屬性,初始化的時機是不同的。instance
    屬性在創建實例的時候初始化, static屬性在類加載,也就是第一次用到這個類的時候初
    始化,對于后來的實例的創建,不再次進行初始化。這個問題會在以后的系列中進行詳細
    討論。

    對于第二種變量,必須明確地進行初始化。如果再沒有初始化之前就試圖使用它,編譯器
    會抗議。如果初始化的語句在try塊中或if塊中,也必須要讓它在第一次使用前一定能夠得
    到賦值。也就是說,把初始化語句放在只有if塊的條件判斷語句中編譯器也會抗議,因為
    執行的時候可能不符合if后面的判斷條件,如此一來初始化語句就不會被執行了,這就違
    反了局部變量使用前必須初始化的規定。但如果在else塊中也有初始化語句,就可以通過
    編譯,因為無論如何,總有至少一條初始化語句會被執行,不會發生使用前未被初始化的
    事情。對于try-catch也是一樣,如果只有在try塊里才有初始化語句,編譯部通過。如果
    在catch或finally里也有,則可以通過編譯。總之,要保證局部變量在使用之前一定被初
    始化了。所以,一個好的做法是在聲明他們的時候就初始化他們,如果不知道要出事化成
    什么值好,就用上面的默認值吧!

    其實第三種變量和第二種本質上是一樣的,都是方法中的局部變量。只不過作為參數,肯
    定是被初始化過的,傳入的值就是初始值,所以不需要初始化。

    問題6
    instanceof是什么東東?

    instanceof是Java的一個二元操作符,和==,>,<是同一類東東。由于它是由字母組成的
    ,所以也是Java的保留關鍵字。它的作用是測試它左邊的對象是否是它右邊的類的實例,
    返回boolean類型的數據。舉個例子:

    String s = "I AM an Object!";
    boolean isObject = s instanceof Object;

    我們聲明了一個String對象引用,指向一個String對象,然后用instancof來測試它所指向
    的對象是否是Object類的一個實例,顯然,這是真的,所以返回true,也就是isObject的
    值為True。
    instanceof有一些用處。比如我們寫了一個處理賬單的系統,其中有這樣三個類:

    public class Bill {//省略細節}
    public class PhoneBill extends Bill {//省略細節}
    public class GasBill extends Bill {//省略細節}

    在處理程序里有一個方法,接受一個Bill類型的對象,計算金額。假設兩種賬單計算方法
    不同,而傳入的Bill對象可能是兩種中的任何一種,所以要用instanceof來判斷:

    public double calculate(Bill bill) {
      if (bill instanceof PhoneBill) {
        //計算電話賬單
      }
      if (bill instanceof GasBill) {
        //計算燃氣賬單
      }
      ...
    }
    這樣就可以用一個方法處理兩種子類。

    然而,這種做法通常被認為是沒有好好利用面向對象中的多態性。其實上面的功能要求用
    方法重載完全可以實現,這是面向對象變成應有的做法,避免回到結構化編程模式。只要
    提供兩個名字和返回值都相同,接受參數類型不同的方法就可以了:

    public double calculate(PhoneBill bill) {
      //計算電話賬單
    }

    public double calculate(GasBill bill) {
      //計算燃氣賬單
    }

    所以,使用instanceof在絕大多數情況下并不是推薦的做法,應當好好利用多態。

    參考:http://community.csdn.net/Expert/topic/3364/3364544.xml?temp=8.037966E-0
    2

    posted on 2005-03-05 18:07 閱讀(206) 評論(0)  編輯  收藏 所屬分類: J2se

    主站蜘蛛池模板: 全部免费国产潢色一级| 午夜视频免费成人| 久久国产亚洲电影天堂| 丝瓜app免费下载网址进入ios| 亚洲欧洲一区二区三区| 一二三区免费视频| 亚洲精品中文字幕无码蜜桃| 三级网站免费观看| 亚洲av色影在线| 日本最新免费网站| 亚洲色精品VR一区区三区| 在线免费观看视频你懂的| 老司机午夜免费视频| 亚洲人成无码网WWW| 中文字幕日本人妻久久久免费| 亚洲AV综合色区无码一区爱AV| 在线播放免费人成毛片乱码| 亚洲精品成人图区| 国产成人免费爽爽爽视频| 看成年女人免费午夜视频| 亚洲一级特黄大片在线观看| 久久综合九色综合97免费下载| 亚洲精品网站在线观看你懂的| 成人毛片18女人毛片免费96| 一级特黄色毛片免费看| 亚洲日本一区二区| 成年女人18级毛片毛片免费观看| 亚洲爆乳成av人在线视菜奈实| 亚洲视频在线免费| 精品无码国产污污污免费网站| 亚洲欧美日韩中文无线码| 亚洲另类激情专区小说图片| 午夜理伦剧场免费| 亚洲精华国产精华精华液网站| 国产精品亚洲二区在线观看 | 女人18毛片特级一级免费视频| 老司机福利在线免费观看| 久久久久亚洲精品日久生情| 国产又大又黑又粗免费视频| 久久精品成人免费看| 亚洲欧美成人一区二区三区 |