1

I found many topics suggesting and discussing cloning but I have been unable to actually implement a method which can duplicate my Clip object.

Here is what I have tried:

// ... setting up class ...

MyClip GunClip = new MyClip();

GunClip.set(AudioSystem.getClip());

AudioInputStream inputStream = AudioSystem.getAudioInputStream(new BufferedInputStream(getClass().getResourceAsStream("/Resources/sound/Laser.wav")));

GunClip.dummy.open(inputStream);
// ...

Then later when an event is triggered I want to play that sound repeatedly. So I try to duplicate it:

class MyClip implements Cloneable {

    Clip dummy;

    public MyClip() {

    }

    public Clip get() {
        return dummy;
    }

    public void set(Clip c) {
        this.dummy = c;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

}

I implement the Cloneable class as suggested in this topic.

And then I clone it before I play it:

MyClip c = (MyClip) GunClip.clone();

c.dummy.setFramePosition(0);
c.dummy.start();

But even now it doesn't work...

EDIT: I have figured out why it doesn't work, which is due to the fact that it is not a deep clone and the InputStream which is being used by the original GunClip is not being cloned. But since Clip is an abstract interface it may be harder than normal to clone it.

JFreeman
  • 974
  • 1
  • 10
  • 26
  • you haven't shown any 'cloning' in your code, only lousy code. in your code, you'll never be able to create an instance of MyClip, unless you already have an instance of MyClip. Have MyClip implement the Cloneable interface, and implement that. – Stultuske Mar 26 '19 at 06:39
  • I tried that as well but it always says `clone() has protected access in Object`, the code I wrote was a copy of what I found here in answer #1 https://stackoverflow.com/questions/869033/how-do-i-copy-an-object-in-java – JFreeman Mar 26 '19 at 06:52
  • how did you try that? you should show what you've tried – Stultuske Mar 26 '19 at 06:58
  • in that question, you've picked the answer with the Clone constructor. yes, you can do that, but you should at the very least have one other constructor for your class, or your code will be useless. If you want an example of how to use the Cloneable interface correctly: check the same thread, but the answer posted by Bhasker Tiwari – Stultuske Mar 26 '19 at 07:00
  • Edited but still doesn't work. I wonder why? – JFreeman Mar 26 '19 at 07:12
  • because you don't think about the code you implement. – Stultuske Mar 26 '19 at 07:15
  • Like I said, I am clearly missing something. I don't understand something in relation to how these classes work. Do you know what it could be? – JFreeman Mar 26 '19 at 07:16
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/190673/discussion-between-jfreeman-and-stultuske). – JFreeman Mar 26 '19 at 07:17

2 Answers2

2
class MyClip implements Cloneable {

    Clip dummy;

    public MyClip() {

    }

    public Clip get() {
        return dummy;
    }

    public void set(Clip c) {
        this.dummy = c;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }    
}

if you write code like this, you are basically saying "The Object class knows everything about my custom class, including how to build it". Of course that is not the case.

You'll need to adapt the clone method to your needs:

class MyClip implements Cloneable {

    Clip dummy;

    public MyClip() {

    }

    public Clip get() {
        return dummy;
    }

    public void set(Clip c) {
        this.dummy = c;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        MyClip clone = new MyClip(); // create a new instance of your class
        clone.set(this.dummy);       // make sure it has the same value for 'dummy'
                                     // I would suggest improvement on your setter and getter name, though
        return clone;  // This returns an instance of MyClip, which has the exact same
                       // state as your current, but is a clone, and not the same instance.
    }

}

In order to call this, you would need something like this:

public void getClone(MyClip original) {
  MyClip Clone = (MyClip)original.clone();
}

EDIT: As per your issue, where the 'dummy' of the original is also infected, if you want to prevent this, just have your Clip class also implement Cloneable, and turn your clone method into this:

@Override
        protected Object clone() throws CloneNotSupportedException {
            MyClip clone = new MyClip(); 
            clone.set(this.dummy == null ? null : this.dummy.clone());     
            return clone;                            
        }
Stultuske
  • 9,296
  • 1
  • 25
  • 37
  • Thank you very much. That makes sense, however, the clone still is not working properly? – JFreeman Mar 26 '19 at 07:24
  • Okay, I got your class working with a String (instead of a Clip) I am going to see if I can work backwards to getting a Clip working also – JFreeman Mar 26 '19 at 07:26
  • define "not working properly", and what do you expect it to do? – Stultuske Mar 26 '19 at 07:27
  • With the String when I clone it and modify the resulting object it does not modify the original as well. With the clip it seems still to be doing so. – JFreeman Mar 26 '19 at 07:28
  • that doesn't mean the clone doesn't work. I'll edit my answer – Stultuske Mar 26 '19 at 07:30
  • Is it possible to modify javax.sound.sampled.Clip to be cloneable? – JFreeman Mar 26 '19 at 07:35
  • If the class is not final, you can extend it, and use the subclass – Stultuske Mar 26 '19 at 07:40
  • It is abstract so It might be harder to do than normal. If anyone else is having the same problem look here https://stackoverflow.com/questions/19124984/how-to-extend-a-abstract-class-in-java – JFreeman Mar 26 '19 at 07:48
  • @JFreeman if it is abstract, it means it has to be subclassed, it can't work otherwise. Did you create your own Clip class? – Stultuske Mar 26 '19 at 07:51
  • I tried but it has too many methods that need overriding to be a convenient solution. I am going to give you the correct answer because you have come closest to the solution to this problem, and have pointed me in the correct direction to solve this. – JFreeman Mar 27 '19 at 02:18
2

I like to write a copy constructor in my objects. That gives me a way to clone the object without having to cast the result. You can use that constructor to implement Cloneable in a clean way and so still have your object support that interface. Here's what I mean, including sample code to clone your object in both these ways:

static class MyClip implements Cloneable {

    Clip dummy;

    public MyClip() {

    }

    public MyClip(MyClip toCopy) {
        dummy = toCopy.dummy;
    }

    public Clip get() {
        return dummy;
    }

    public void set(Clip c) {
        this.dummy = c;
    }

    @Override
    public Object clone() {
        return new MyClip(this);
    }

}

public static void main(String... args) {
    MyClip original = new MyClip();
    MyClip clone1 = (MyClip)original.clone();
    MyClip clone2 = new MyClip(original);
}

Another good thing about writing a copy constructor to copy your object is that there's a natural way to have your superclass contribute to the copy operation, by calling its copy constructor with 'super(toCopy);' first, and then copying the fields in your own subclass. That doesn't apply here, because you're implementing an interface, but is important if you're extending another class.

CryptoFool
  • 21,719
  • 5
  • 26
  • 44
  • Thank you! I have implemented this but when I modify clone2 it is also modifying the original. Is there a way to avoid this? – JFreeman Mar 26 '19 at 07:38
  • Why would it modify the original? My code won't. All it's doing is referencing the copy's dummy value in the **dummy = toCopy.dummy;** line. – CryptoFool Mar 26 '19 at 07:39
  • `clone2.dummy.start()` -> when I print the line both original and clone2 have started. I believe it might be due to the fact that the orignial loaded an `inputStream` which may be being passed by reference? – JFreeman Mar 26 '19 at 07:41
  • @Steve it's because both objects are pointing to the same dummy. If the Clip class has a copy constructor, use that one – Stultuske Mar 26 '19 at 07:41
  • Yes, that's why. Copying an object along with what it points to is called a "deep copy". That takes more work. I can change my code to do that, assuming that Clip has a copy constructor... – CryptoFool Mar 26 '19 at 07:42
  • Yes I think I am going to need to do a deep copy. Or what might be easier is if I clone my inputStream and then create a clip from it after. That would require 1 less copy. – JFreeman Mar 26 '19 at 07:46
  • 1
    I backed off of that when I realized you were talking about a well defined Clip interface. Since a Clip is just an interface, I can't clone it that way. It doesn't seem to have a 'clone' method either, so I don't know how to clone a Clip. – CryptoFool Mar 26 '19 at 07:47
  • 1
    Yes. You might have to do that anyway, if there's no way to clone a Clip, and you need to have two unique Clip objects. The key thing is that to do a deep copy with this code, you'd need to change this line: **dummy = toCopy.dummy;** to a line that copied the **toCopy.dummy** object instead of just copying the reference to it. – CryptoFool Mar 26 '19 at 07:48
  • Yes, I agree. Thank you for your help though, I am confused by the designers of the Clip and InputStream not adding a clone option. – JFreeman Mar 26 '19 at 07:50
  • Since Clip is just an interface, there has to be a concrete class implementing it. It's possible that that class can be copied, even though the interface doesn't support that via a clone method. It might, for example, have a copy constructor. This is something that can't be mandated by an interface. - I don't know enough about your surrounding code to know if this is might be an option. – CryptoFool Mar 26 '19 at 07:52