0

I have a project that I need to do very specifically and I need some help. I have looked basically everywhere for an answer and can't find it, not even on Stack Overflow. It has to do with cloning hashtables. (Both shallow and deep.)

I have pasted the code I have below, but in short, I have a class called EHashtable, which extends Hashtable. I then add some string keys and values of various custom class types. In the EHashtable class, there are methods called Clone() and dClone(). Clone() is supposed to create a shallow clone of its Hashtable - meaning the Hashtable is cloned but its values are not. dClone() is supposed to create a new Hashtable and clone each of the original hashtable's values to it (meaning each value points to different memory references than the first). It is also supposed to throw an exception if the custom made object is not cloneable.

Right now, even on a shallow clone (the Clone() method), changes one value in one Hashtable and will NOT change the value in the other. It seems as if each value is pointing to different references. I don't understand how to make the Clone() and dClone() methods do the two different things I want them to do. One other thing, the hashtable cannot have Generics. It needs to be Hashtable and not Hashtable<K, V>.

I have looked up how to for loop through a hashtable. That only works on an Object type, and Object types can't clone() due to the method's protected status. Below is my code, starting with the main method. I realize this is very specific, and all help is greatly appreciated.

import java.util.Hashtable;
import java.util.Iterator;

public class _Test {
    public static void main(String[] arguments) {
        Circle cr1 = new Circle(1);
        Circle cr2 = new Circle(2);
        Point po1 = new Point(10, 10);
        Point po2 = new Point(20, 20);
        PlaneCircle pcr1 = new PlaneCircle(po1, 11f);
        PlaneCircle pcr2 = new PlaneCircle(po2, 12f);

        EHashtable eh = new EHashtable(20);
        eh.add(new String("C1"), cr1);
        eh.add(new String("C2"), cr2);
        eh.add(new String("PC1"), pcr1);
        eh.add(new String("PC2"), pcr2);

        try {
            EHashtable ehCloned = (EHashtable) eh.Clone();

            System.out.println("/***--Before Alteration--***/");
            System.out.println("eh:");
            System.out.println(eh);
            System.out.println();
            System.out.println("ehCloned:");
            System.out.println(ehCloned);
            System.out.println();

            Circle cr3 = new Circle(99);
            Point po3 = new Point(99, 99);
            PlaneCircle pcr3 = new PlaneCircle(po3, 9999f);

            eh.add(new String("C1"), cr3);
            eh.add(new String("PC1"), pcr3);

            System.out.println("/***--After Alteration--***/");
            System.out.println("eh:");
            System.out.println(eh);
            System.out.println();
            System.out.println("ehCloned:");
            System.out.println(ehCloned);
            System.out.println();
        }

        catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
        } 

        catch (ClassCastException e) {
            System.out.println(e.toString());
        }

        catch (Exception e) {
            System.out.println("\nError Message:" + e.getMessage());
        }
    }
}


import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;

public class EHashtable extends Hashtable {
    public EHashtable() {

    }

    public EHashtable(int capacity) {

    }

    // Done
    public boolean add(Object key, Object value) {
        if (!(containsKey(key) && containsValue(value))) {
            put(key, value);
            return true;
        }

        else {
            return false;
        }
    }

    // Done
    public void Clear() {
        clear();
    }

    // Done
    public Object Clone() throws CloneNotSupportedException {
        EHashtable eh = (EHashtable) this.clone();
        return eh;
    }

    public Object dClone() {
        EHashtable eh = new EHashtable();
        for (Object key : keySet())
            eh.put(key, get(key));
        return eh;
    }

    // Done
    public boolean isNotEmpty() {
        return !isEmpty();
    }

    // Done
    public Iterator iterate() {
        return entrySet().iterator();
    }
}


public class Point {
    private int x;
    private int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public String toString() {
        return "[x=" + x + ", y=" + y + "]";
    }
}


public class PlaneCircle {
    private Point p;
    private float radius;

    public PlaneCircle (Point p, float radius) {
        this.p = p;
        this.radius = radius;
    }

    public String toString() {
        return "[p=" + p.toString() + ", radius=" + radius + "]";
    }
}


public class Circle {
    private float radius;

    public Circle(float radius) {
        this.radius = radius;
    }

    public String toString() {
        return "[radius=" + radius + "]";
    }
}
JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
user1567060
  • 179
  • 1
  • 3
  • 11

5 Answers5

0

Found this in effective java

public class HashTable implements Cloneable {
    private Entry[] buckets = ...;

    private static class Entry {
        final Object key;
        Object value;
        Entry next;
        Entry(Object key, Object value, Entry next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }

        // Recursively copy the linked list headed by this Entry
        Entry deepCopy() {
            return new Entry(key, value,
                    next == null ? null : next.deepCopy());
        }
    }
    @Override public HashTable clone() {
        try {
            HashTable result = (HashTable) super.clone();
            result.buckets = new Entry[buckets.length];
            for (int i = 0; i < buckets.length; i++)
                if (buckets[i] != null)
                    result.buckets[i] = buckets[i].deepCopy();
            return result;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }

One more way to do is:

SerializationUtils.clone() serializes and de-serializes the entire object-graph referenced by the Map, that's why it takes so long. It creates a true deep-copy, however (provided you don't have funny serialization-stuff going on in your classes).

constantlearner
  • 5,157
  • 7
  • 42
  • 64
0

Simplest way is to serialize and deserialize the HashMap. However this requires to implement Serializable on all key and value classes. However wether its Cloneable or Serializable .. there is some work ´to be done in order to deep-copy object graphs anyway

Preferably with a fast serializer like http://code.google.com/p/fast-serialization/ :-). This way performance should be ok'ish

R.Moeller
  • 3,436
  • 1
  • 17
  • 12
0

If all the elements are Serializable than serialize the hashMap and deserialize by ObjectInputStream , which will be a deep copy.

Please refer to this.

Some important point from this link:

Cloning through serialization

One solution to these problems is to clone using serialization. Usually, serialization is used to send objects off somewhere (into a file or over the network) so that somebody else can reconstruct them later. But you can abuse it to reconstruct the object yourself immediately. If the object is serializable at all, then the reconstruction should be a faithful copy. In normal uses of serialization, the original object is nowhere near; it could be on the other side of the world at the far end of a network connection. So you can be sure that changing the copy will have no effect on the original.

Before going any further, I should caution that this technique is not to be used lightly. First of all, serialization is hugely expensive. It could easily be a hundred times more expensive than the clone() method. Secondly, not all objects are serializable. Thirdly, making a class serializable is tricky and not all classes can be relied on to get it right. (You can assume that system classes are correct, though.)

Community
  • 1
  • 1
Trying
  • 14,004
  • 9
  • 70
  • 110
0

Note that it is a poor idea to have methods which differ only by case (clear, Clear; clone, Clone). It isn't necessary because you can use the super keyword to call the superclass implementation of a method.

Hashtable itself is cloneable, so overriding clone is not necessary; the existing clone method already does what you need for a shallow clone, although for convenience you could override it to give a return type of EHashtable instead of Object:

public EHashtable clone() {
    return (EHashtable)super.clone();
}

For a deep clone, you're right, since Object.clone is protected, you can't call it. It's a historical design mistake in Java. There are messy ways around it (serialization, or reflection, or defining an ActuallyCloneable interface that makes the method public) but I don't think you need to do these things, because (unless you left mutator methods out for brevity when pasting the question) all of the types that you're storing in your table seem to be immutable. There is no use in cloning them if they cannot change.

If they're not immutable in your real code, making them immutable is actually a very good idea for these simple value types. It enables you to simplify other code by not worrying about when you need to copy them.

Right now, even on a shallow clone (the Clone() method), changes one value in one Hashtable and will NOT change the value in the other.

You're not changing values; you're putting new ones. Changing values would be something like:

cr1.radius = 123;

That's not possible if your objects are immutable, but it would change the object as seen from both the original hashtable and from its shallow clone.

A couple of minor suggestions: (1) Printing the toString() of an exception will only print its name and message; the stacktrace information will be missing. To get the stacktrace too, use the printStackTrace() method. This also has the benefit of making sure it's printed to stderr instead of stdout (which may make it more visible; it's bright red in Eclipse). E.g.,

catch (CloneNotSupportedException e) {
    e.printStackTrace();
}

Or if there is nothing useful that can be done with a particular checked exception, it is better to simply rethrow it as an unchecked exception, so that surrounding code does not plow on in spite of errors:

catch (CloneNotSupportedException e) {
    throw new RuntimeException(e);
}

(2) Doing eh.add(new String("C1"), cr1); (creating a new string) is a silly anti-pattern that has no benefits and adds unnecessary object creation and typing overhead. You can change it to just eh.add("C1", cr1);.

Boann
  • 48,794
  • 16
  • 117
  • 146
  • Right now, your answer is my favorite. One request: Despite what you said about mutable vs immutable, I still need to do a deep clone. Some of my classes ARE mutable. Would you mind explaining how to clone each value? Thank you. – user1567060 Nov 03 '13 at 01:56
  • @user1567060 As mentioned, Java's clone() method is a weak spot in the API, but see http://stackoverflow.com/questions/64036/how-do-you-make-a-deep-copy-of-an-object-in-java, and all of the related questions, for lots of alternative ideas. – Boann Nov 03 '13 at 13:35
0

In addition to the Serialization ideas, if you have existing code to persist your Map to/from a file, a database, XML/json etc., that would be a "quick and dirty" option for you. Not quick performance wise, but quick to code.

user949300
  • 15,364
  • 7
  • 35
  • 66