天天看點

20162330 第三周 藍墨雲班課 泛型類-Bag 練習

目錄

  • 題目及要求
  • 思路分析
  • 遇到的問題和解決過程
  • 代碼實作及托管連結
  • 感想
  • 參考資料

  1. 代碼運作在指令行中,路徑要展現學号資訊,IDEA中,僞代碼要展現個人學号資訊;
  2. 參見Bag的UML圖,用Java繼承BagInterface實作泛型類Bag,并對方法進行單元測試(JUnit),測試要涵蓋正常、異常情況、邊界情況;
  3. 課上送出測試代碼和測試運作的結果截圖,截圖要求全屏截圖,包含自己的學号資訊,否則無效;測試Bag類的代碼中至少包含一個自定義類如Student;
  4. 課下完成碼雲上代碼的上傳。
  • 【附】Bag的UML圖:
    20162330 第三周 藍墨雲班課 泛型類-Bag 練習

【傳回目錄】

  • 首先自定義一個Bag類,使用

    public class Bag<T> implements BagInterface<T>

    實作給出接口的所有方法架構,之後在裡面通過泛型類型 T 自定義一個數組,通過填充方法來完善Bag類。考慮到給出的接口含有

    add

    remove

    isEmpty

    等方法,如果使用 List 建立對象實作有些簡單,是以我選擇了使用 Object 類定義數組實作這些方法。

    在 Bag 類的 UML類圖 中,我們可以清楚地知道每個方法的傳回類型、參數及需要實作的功能,大部分功能的實作比較架構化,比如:

    getCurrentSize()

    isEmpty

    add(T newEntry)

    remove()

    等,其方法填充内容基本符合以下架構:
public <傳回類型> <函數名>(<參數>){
    //初始化傳回值參數對象
    //循環(周遊、修改元素)
    //條件(何時修改、指派)
    //傳回值(參數)
}
           

之是以能用 循環 + 條件 的架構是因為這些方法基本都涉及周遊環節,我也使用了幾種不同的周遊方式。

需要注意的是最後一個方法

toArray()

,這個方法相對陌生,我查找了API,關鍵句如下:

按 适當順序 傳回包含此清單中所有元素的數組;

如果指定的數組能容納隊列,并有剩餘的空間,那麼會将數組中緊接 collection 尾部的元素設定為 null。

  • 所謂的“适當順序”,可以了解為不同與原來數組排列元素的順序,我又繼續看了UML類圖中的這一方法的注釋:
A new array of entries currently in the bag.
  • 我的思路是将原來數組中的空值元素都填補為同一類型的相同的值,然後再重新傳回這個數組,這樣就可以通過一個含空值的數組調用此方法之後的元素容量進行單元測試。實作代碼如下:
/*
      Shows all objects in a new array of food bag entries.
     */
    public T[] toArray() {
        int j = food.length - 1;
        for (int i = 0;i<food.length;i++) {
            food[j] = "apple";
            if (food[i] == null) {  //将空值全部填補
                food[i] = food[j];
                j--;
            }
        }
        return (T[]) food;
    }
           

  • 【問題】在使用Junit測試

    add(T newEntry)

    方法時,Junit測試異常:
    20162330 第三周 藍墨雲班課 泛型類-Bag 練習
  • 【解決方法】我仔細看了一下自己寫的 add 方法:
public boolean add(T newEntry) {
        boolean boo = false;
        for (Object i : food) {
            if (i == null) {
                i = newEntry;  //添加到第一個空值位置
                boo = true;
                break;
            }
        }
        return boo;
    }
           

由于使用foreach周遊比較簡單,我就沒有考慮其他問題。IDEA的提示,給 i 重新指派的那條語句中的 i 是多餘的:

![](http://images2017.cnblogs.com/blog/1062725/201710/1062725-20171007184208240-2031952014.png)

我就想不通為什麼是多餘的,于是又開始檢查 foreach 的結構架構:
           
for(<元素類型> <元素變量> : <周遊對象>){
     引用元素變量的相關語句;
}
           

修改了幾次數組類型之後還是不對,于是我認為是foreach方法出現問題,就做了一個foreach的測試類,發現果然是foreach方法的問題,同樣的修改指派語句,使用for循環周遊就正常,使用foreach就不能進行相應指派:

![](http://images2017.cnblogs.com/blog/1062725/201710/1062725-20171007185209661-1263550744.png)

接着,我開始使用debug單步調試:(單擊圖檔可放大)

[![](http://images2017.cnblogs.com/blog/1062725/201710/1062725-20171007185542708-250193647.png)](http://images2017.cnblogs.com/blog/1062725/201710/1062725-20171007185542708-250193647.png)

剛開始設斷點,我并沒有注意觀察細節,隻是跟着步驟走了一遍,又跟蹤了一遍才發現給 i 指派的語句好像并沒有效果,這一句出現了問題。我又仔細地跟蹤了一遍,發現add元素的位址和原來空值的位址不一樣,而最後數組添加的是空值的位址:

[![](http://images2017.cnblogs.com/blog/1062725/201710/1062725-20171007185832974-1747717902.png)](http://images2017.cnblogs.com/blog/1062725/201710/1062725-20171007185832974-1747717902.png)

[![](http://images2017.cnblogs.com/blog/1062725/201710/1062725-20171007185845349-1292468436.png)](http://images2017.cnblogs.com/blog/1062725/201710/1062725-20171007185845349-1292468436.png)

是以總結起來就是,i 原來對應的位址就不是指向數組元素的,是以隻要用其他循環(for循環)替代即可正常修改原數組的元素:

![](http://images2017.cnblogs.com/blog/1062725/201710/1062725-20171007190315911-1971919464.png)

如果一定要使用foreach修改數組元素的話,那隻能再另外加一層循環:
           
public boolean add(T newEntry) {
        boolean boo = false;
        for (Object i : food) {
            if (i == null) {  //foreach不能修改數組元素
                for (int j = 0; j < food.length; j++) {  //重新使用for循環指派
                    if (food[j] == i) {
                        food[j] = newEntry;  //添加到第一個空值位置
                        break;
                    }
                }
                boo = true;
                break;
            }
        }
        return boo;
    }
           

至于我的驗證是否正确,我又查找了相關資料,一種說法是:

foreach結構中的元素變量是個基本資料類型,在周遊時不指向數組元素的位址,它隻代表數字它自己。
  • 還有一個執行個體可以更好地證明我的驗證:
for (Integer temp : list)
{
    if (temp == 1)
    {
        temp = temp * 2;
    }
}
           

根據oracle的官方文檔,正式翻譯應該如下:

for (Iterator i = list.iterator(); i.hasNext(); )
{
    float i0 = (Integer)i.next();
        if(i0 == 1)
          i0 = i0*2;
}
           

是以foreach中的 temp變量隻是一個局部變量(i0),而且還是集合中元素的一個副本,并不是元素本身。這樣才導緻輸出“修改過的數組”時,仍然輸出原數組。

綜上所述,我的驗證正确,foreach隻适合周遊數組,在實作涉及到修改數組元素的功能時,不宜使用,會造成指派失敗。

  • 這裡隻貼出Bag類代碼和運作成功截圖,其餘代碼見相關代碼托管連結:

    【BagInterface類】

    【Bag類】

    【BagTest類】

  • Bag類如下:
/**
     * A finite number of objects,not necessarily distinct,in no particular order,
     * and having the same data type(collection).
     *
     * @author 20162330
     */
public class Bag<T> implements BagInterface<T> {
    private Object food[] = new Object[5];

    /*
      Returns the current number of objects in the bag(except null).
     */
    public int getCurrentSize() {
        int foodSize = 0;
        for (Object i : food) {  //foreach周遊
            if (i != null)
                foodSize++;
        }
        return foodSize;
    }

    /*
      Demonstrates if the food bag is empty(null).
     */
    public boolean isEmpty() {
        boolean boo = true;  //預設為空
        for (Object i : food) {
            if (i != null) {
                boo = false;
                break;
            }
        }
        return boo;
    }

    /*
     Adds a given object to the food bag,according to whether the addition succeeds,
     return true or false.
    */
    public boolean add(T newEntry) {
        boolean boo = false;
        for (Object i : food) {
            if (i == null) {  //foreach不能修改數組元素
                for (int j = 0; j < food.length; j++) {  //重新使用for循環指派
                    if (food[j] == i) {
                        food[j] = newEntry;  //添加到第一個空值位置
                        break;
                    }
                }
                boo = true;
                break;
            }
        }
        return boo;
    }

    /*
      Removes an unspecified object from the food bag,if possible.
     */
    public T remove() {
        Object n = null;
        for (int i = 0; i < food.length; i++) {
            if (food[i] != null) {
                n = food[i];
                food[i] = null;  //移除第一個不為空的元素
                break;
            }
        }
        return (T) n;
    }

    /*
      Removes an occurrence of a particular object from the food bag,if possible.
     */
    public boolean remove(T anEntry) {
        boolean boo = false;
        int i = 0;
        while (i < food.length) {
            if (food[i] == anEntry) {
                food[i] = null;
                boo = true;
                break;
            }
            i++;
        }
        return boo;
    }

    /*
      Removes all objects from the food bag.
     */
    public void clear() {
        int i = 0;
        do {  //do-while方式周遊
            food[i] = null;
            i++;
        }
        while (i < food.length);
    }

    /*
      Counts the number of times an object occurs in the food bag.
     */
    public int getFrequencyOf(T anEntry) {
        int t = 0;
        for (Object i : food) {
            if (i == anEntry)
                t++;
        }
        return t;
    }

    /*
      Tests whether the food bag contains a particular object.
     */
    public boolean contains(T anEntry) {
        boolean boo = false;
        for (Object i : food) {
            if (i == anEntry) {
                boo = true;
                break;
            }
        }
        return boo;
    }

    /*
      Shows all objects in a new array of food bag entries.
     */
    public T[] toArray() {
        int j = food.length - 1;
        for (int i = 0;i<food.length;i++) {
            food[j] = "apple";
            if (food[i] == null) {  //将空值全部填補
                food[i] = food[j];
                j--;
            }
        }
        return (T[]) food;
    }
}
           

Junit單元測試截圖:(單擊圖檔可放大)

[![](http://images2017.cnblogs.com/blog/1062725/201710/1062725-20171007193120865-790803238.png)](http://images2017.cnblogs.com/blog/1062725/201710/1062725-20171007193120865-790803238.png)
           

  • 這次實踐中解決問題确實花費了不少時間,不過我一直堅持獨立思考,最終順利解決問題,同時又練習了一下幾種不同的循環(周遊)方式,Junit測試與之前相比也更全面了,也算是又體驗了一回“做中學”。

  • foreach寫失效的問題
  • 藍墨雲班課上的示例BagInterface類