簡體   English   中英

在Java中保留泛型的封裝

[英]Preserving encapsulation of a generic in Java

晚上好。

我有一個相當復雜的問題。 為了練習Java,我一直在重新實現標准庫中的某些數據結構。 堆棧,LinkedList,樹等。通過一個非常簡單的示例,我剛剛確定了使用peek()pop()方法時java.util.Stack類將執行深層復制。 這是可以理解的,因為目標是保護類的內容不受外界干擾。 到目前為止,在我自己的Stack實現中(一個具有簡單數組的幼稚實現,稍后將提供鏈接列表),我一點也不在乎:

public class ArrayStack<T> implements Stack<T> {
    private T[] data; // Will expand the array when stack is full.
    private int top; // serves as both top and count indicator.
    ...
    ...
   @Override
   public T pop() throws EmptyStackException {
    if(top == -1)
        throw new EmptyStackException("Stack is empty.");
    return data[top--]; // Shallow copy, dangerous!
}

不幸的是,由於無法實例化泛型,因此我無法假設有復制構造函數,並且無法執行諸如return new T(data[top--]); 我一直在尋找SO,並且找到了兩個相關的線程,它們試圖通過使用clone()某些變體來解決問題。 線程建議將該類的簽名擴展為:

public class ArrayStack<T extends DeepCloneableClass> implements Stack<T>
...

DeepCloneableClass是一個類,該類實現允許“深度克隆”的接口(有關詳細信息,請參見該線程中的最高響應)。 當然,這種方法的問題是我真的不能期望標准類(例如StringInteger擴展我的自定義類,並且,當然,我所有現有的jUnit測試現在都在編譯時抱怨-時間,因為它們依賴於這樣的整數和字符串堆棧。 因此,我覺得這種解決方案似乎不可行。

線程建議使用第三方庫克隆幾乎所有對象。 盡管該庫似乎仍受支持(最新的錯誤修復發生在不到一個月前),但我寧願不依賴第三方工具也不使用Java可以為我提供的一切。 原因是這些ADT的源代碼有一天可能會與大學生共享,而我寧願不要讓他們負擔安裝額外工具的負擔。

因此,我正在尋找一種簡單且可能的高效方法,以維護通用Java數據結構的內部完整性,同時仍允許與pop()peek()popFront()等方法的簡單接口。

非常感謝您的幫助!

賈森

為什么需要克隆對象?

您的堆棧中只有一組引用。 您可能不需要克隆它們,只需創建一個新數組並在其中放入適當的引用,然后丟棄舊數組即可。

IntegerStrings等都是不可變的,因此從設計上講它們的內容是安全的。

至於自定義對象,盡管經驗豐富的Java程序員一定會對它有不同的感覺,但是實現自定義接口無疑是解決問題的一種方法。

另一個方法是使<T extends Serializable> (由IntegerString等實現)並通過序列化“克隆”

但是,如果您想教給學生“正確的方式”,那么我肯定會使用第三方庫...您可以在項目中創建一個lib文件夾,並配置您的構建工具/ IDE,使用以下命令將所需的jar添加到Classpath中相對路徑,因此您的本科生無需安裝或設置任何東西。

僅供參考, 這個問題可能非常有用。

我一直在使用這種方法來講授Java入門課程(作為IT講師,而不是大學教授),它的痛苦程度遠沒有聽起來的那么大。

這些評論幫助我了解我的錯。 我使用以下示例向我和其他人“證明” Java標准庫的集合在提供對集合中對象的引用時會進行深層復制:

import java.util.Stack;

public class StackTestDeepCopy {
    public static void main(String[] args){
        Stack<String> st = new Stack<String>();
        st.push("Jim");
        st.push("Jill");
        String top = st.peek();
        top = "Jack";
        System.out.println(st); 
    }
}

在打印st時,我看到對象沒有改變,並得出結論:已經進行了深拷貝。 錯誤! String是不可變的,因此語句top = "Jack"不會以任何方式修改String (不是任何Object都會被這樣的語句“修改”,但我並不是直覺),它只是使參考點指向堆上的新位置。 一個涉及實際可變類的新示例使我以自己的方式理解該錯誤。

現在已經解決了這個問題,我對標准庫允許這樣做的事實感到困惑。 為什么將標准庫中的訪問元素實現為淺表副本? 聽起來很不安全。

java.util.Stack不會進行深層復制:

import java.util.Stack;
public class Test {
    String foo;
    public static void main(String[] args) {
        Test test = new Test();
        test.foo = "bar";
        Stack<Test> stack = new Stack<Test>();
        stack.push(test);
        Test otherTest = stack.pop();
        otherTest.foo = "wibble";
        System.out.println("Are the same object: "+(test.foo == otherTest.foo));
    }
}

結果是:

Are the same object: true

如果確實進行了復制,則test和otherTest將指向另一個對象。 典型的堆棧實現只是返回對添加到堆棧中的同一對象的引用,而不是副本。


您可能還想將數組項設置為null,然后再返回,否則數組仍將保留對該對象的引用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM