0

I am an experienced programmer but a Java beginner. I have a benchmarking method that accepts a parameter of type Map and performs some tests on it. It can be invoked on a HashMap, Hashtable, IdentityHashMap, TreeMap etc because these all implement Map. They also all implement Cloneable, but Eclipse tells me I am not allowed to invoke the clone() method.

private static double[] timeMapRemoves(Map<String,Integer>  map, 
                                       Collection<String> data, 
                                       int reps) {
  Map<String,Integer> map_clone = map.clone(); // OOPS -- "clone not accessible"

So I delve into the Oracle website and I come up with a solution of sorts

Map<String,Integer> map_clone = null;
Method clone = null;
try {
    clone = map.getClass().getMethod("clone", null);
    map_clone = (Map<String,Integer>)clone.invoke(map, null);
} catch (NoSuchMethodException | SecurityException 
         | IllegalAccessException | IllegalArgumentException
     | InvocationTargetException e) {
    e.printStackTrace();
}

I feel that I may, like Drool Rockworm, have delved too deep and missed the canonical solution.

1 Answers1

1

clone() is protected which means it is only accessible from a subclass or that very same package.

Reiteration from the comments:

It all depends on the context from which it is called, and if that context is the same type then you can call the protected method. Here the context is a different type so it cannot call it.

When you change the parameter to HashMap<K, V> for example you can call it because HashMap overrides the clone() method with a public modifier. So in short: you can't do that with a simple Map<K, V> declaration.

This means a situation like this will work:

class X {
    public X(){
        X newX = new X().clone();
    }
}

but this won't:

class X {
    public X(){
        String newString = "hello".clone();
    }
}

But then again, this will:

class X implements Map<String, String>{
    public X(){
        Map<String, String> map = new HashMap<>().clone();
    }
}

And so will this:

private static double[] timeMapRemoves(HashMap<String,Integer>  map, 
                                   Collection<String> data, 
                                   int reps) {

    Map<String, String> someMap = (Map<String, String>) map.clone();
}

Notice how I changed the parameter to HashMap<String,Integer>.

The reason for why this works is very simple: HashMap defines its own clone() method.

public Object clone() {
    HashMap<K,V> result = null;
    try {
        result = (HashMap<K,V>)super.clone();
    } catch (CloneNotSupportedException e) {
        // assert false;
    }
    result.table = new Entry[table.length];
    result.entrySet = null;
    result.modCount = 0;
    result.size = 0;
    result.init();
    result.putAllForCreate(this);

    return result;
}
Jeroen Vannevel
  • 43,651
  • 22
  • 107
  • 170
  • `clone()` is `protected` only in `Object`, and everything is a subclass of `Object`. The `Map` implementing classes that implement `clone()` all appear to make `clone()` `public`. – ajb Apr 05 '14 at 00:40
  • I think you've missed the point of the question. Most of the `Map` implementations define `clone()`, but `Map` itself does not, and his parameter is a `Map` and therefore he can't use it to access `clone()`. His question is, how can he use the `clone()`, if any, defined for the particular `Map` type he gets as a parameter. – ajb Apr 05 '14 at 00:42
  • @ajb: yes, but unless I'm mistaking it all depends on the context from which it is called, and if that context is the same type then you can call the `protected` method. Here the context is a different type so it cannot call it. When you change the parameter to `HashMap` for example you can call it because `HashMap` overrides the `clone()` method with a `public` modifier. So in short: you can't do that with a simple `Map<>` declaration. – Jeroen Vannevel Apr 05 '14 at 00:45
  • @Jeroen Vannevel That was a reasonable explanation of things I know already. It does not, however, provide a solution to the problem. ajb understands the problem. I would like to be able to call clone() on objects that implement it in a method which accepts Map implementations. Every Map implementation I am working with (indeed all the ones I know of) implements Cloneable. I can forcibly invoke clone() using reflection, but I am seeking a more principled solution. – Tim J. Benham Apr 05 '14 at 03:08
  • @TimJ.Benham: Indeed, I just layed out what the problem was. As for possible solutions: reflection is always a way when you're encountering limitations native to the libraries. One other possibility I see here is for you to create your own helper function that takes a `Map` as argument and creates a new one based on its values. Basically: create your own `clone` method. – Jeroen Vannevel Apr 05 '14 at 03:46
  • @JV OK. To check my understanding: if the library writers had defined an interface CloneableMap extends Map implements Cloneable, and had made all their cloneable Map implementations implement CloneableMap instead of Map, then I wouldn't have this problem? – Tim J. Benham Apr 05 '14 at 04:08
  • @TimJ.Benham: yes, providing they added a `public clone()` method to it. The entire problem here is simply that the standard `clone()` is `protected` and you want to use it as `public`. Usually you'd simply create a new interface that implements a `public` version of it but since you don't have access to these classes, that's not option. What remains is either reflection (as you suggest) or creating your own `clone()` method. If they would have provided a `public clone()` in `Map` then that would have solved it as well. – Jeroen Vannevel Apr 05 '14 at 12:06