Ошибка в использовании Object.clone()

У меня есть следующий сценарий:

Я определяю int[][] переменная в моем основном классе. int[][] matrix1 = new int[10][10] и я даю ему некоторые значения. Затем я вызываю метод и отправляю эту переменную в качестве параметра этому методу. Быть объектом, который он отправляет, по ссылке, а не по значению, поэтому внутри метода, потому что я должен изменить значения, содержащиеся в matrix1, но не влиять на объект после того, как он вернется из метода, я делаю клон его следующим образом:

private void myMethod( int[][] matrix1 )
{
    int[][] matrix1Clone = matrix1.clone();
    //And next i do some changes to matrix1Clone
    ......
}

Но проблема в том, что изменения, которые я делаю в matrix1Clone, также происходят в matrix1. Так что он на самом деле не создал клон объекта matrix1, но обе переменные указывают на один и тот же объект.

Почему это? Я не могу понять это. Почему не работает метод клонирования?

Если вам нужна дополнительная информация, пожалуйста, спросите. Но я боюсь, что это об этом, на самом деле не могу дать вам больше, но, возможно, я мог бы попробовать.

Я могу что-то упустить, но я не могу понять, что...

Благодарю.

РЕДАКТИРОВАТЬ

Извините, сделал опечатку. Уже поздно, и я устал. Я действительно использую метод клонирования, поэтому я запутался, потому что он не работает:(.

3 ответа

Решение

Попробуйте клонировать его с помощью clone() http://docs.oracle.com/javase/1.4.2/docs/api/java/lang/Object.html

private void myMethod( int[][] matrix1 )
{
    int[][] matrix1Clone = matrix1.clone();
}

или скопируйте все значения с помощью цикла

EDIT: Api для clone() говорит, что он должен вернуть копию объекта, но поведение может отличаться в зависимости от того, какой объект клонируется. Попробуйте перебрать массив в качестве альтернативы. Поскольку это двумерный массив, вам нужен вложенный цикл:

for(int i=0; i<old.length; i++)
  for(int j=0; j<old[i].length; j++)
    old[i][j]=copy[i][j];

где old это "оригинальный массив", а copy это копия

Вы даете matrix1Clone та же ссылка, что и matrix1, Если вы измените matrix1Clone затем matrix1 тоже меняется.

Вы можете скопировать ваш массив с помощью итерации по исходному массиву:

   public static int[][] clone2DArray(int[][] array) {
        int rows = array.length;

        //clone the 'shallow' structure of array
        int[][] newArray = array.clone();
        //clone the 'deep' structure of array
        for(int row = 0; row < rows; row++){
            newArray[row] = array[row].clone();
        }

        return newArray;
    }

На самом деле, массивы не имеют значений, но имеют указатели на объектные или примитивные типы данных. Если вы хотите получить подробный ответ, вы должны прочитать мой комментарий здесь: Java НИКОГДА не передается по ссылке, верно?... верно? или здесь: в Java что такое мелкая копия?

Итак, поскольку массивы являются указателями, что произойдет, если вы клонируете указатель с указателями в нем? Сначала указатели копируются по-настоящему, но эти указатели указывают только на другой объект, который не клонируется. Поэтому, если вы хотите клонировать, я предлагаю не использовать массивы, а "более сложные" структуры данных: классы. Другая возможность - никогда не хранить массив внутри массива... как я использую массивы только для контейнеров!

Но я не могу дать вам подробности о многомерных обобщениях Java, поскольку я никогда не имею с ними дело, не только из-за их возможной несогласованности, потому что они являются массивами (они в любом случае нарушают некоторые принципы ОО и делают код уродливым).

РЕДАКТИРОВАТЬ

Я провел несколько тестов, как метод клонирования работает для массивов внутри класса, в чем проблема и какие у нас есть обходные пути.

Сначала тестовая структура данных:

public class Foobar implements Cloneable {
    String[] array;

    public Foobar() {
        this.array = new String[10];
    }

    public String getValue(){
        return array[0];
    }

    public String[] getArray(){
        return array;
    }

    public void setArray(String[] array){
        this.array = array;
    }

    @Override
    public Object clone(){
        try{
            Foobar foobar = (Foobar) super.clone();
            foobar.setArray(array);
            return foobar;
        }
        catch(Exception e){
            return null;
        }
    }
}

Теперь контроллер:

String[] array = new String[10];
array[0] = "111";
Foobar foo1 = new Foobar();  
foo1.setArray(array);
Foobar foo2 = foo1; //Alternation: Foobar foo2 = (Foobar) foo1.clone();  
System.out.println("Instance: "+foo1.getArray()+" with value: "+foo1.getValue());
System.out.println("Instance: "+foo2.getArray()+" with value: "+foo2.getValue());
array[0] = "999";
System.out.println("Instance: "+foo1.getArray()+" with value: "+foo1.getValue());
System.out.println("Instance: "+foo2.getArray()+" with value: "+foo2.getValue());

Результаты теста всегда будут выглядеть так - независимо от того, использую я = или clone():

Instance: [Ljava.lang.String;@42e816 with value: 111
Instance: [Ljava.lang.String;@42e816 with value: 111
Instance: [Ljava.lang.String;@42e816 with value: 999
Instance: [Ljava.lang.String;@42e816 with value: 999 

Это не хорошо!!

Так что же такое обходной путь? Я предлагаю делать это в каждом классе структуры данных:

public class Foobar implements Serializable {
    //any class variables...it doesn't matter which!

    public Foobar() {
        //do initialisation here...it doesn't matter what you do!
    }

    public Foobar copy(){
        try{
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            Foobar foobar = (Foobar) ois.readObject();
            return foobar;
        }
        catch(Exception e){
            return null;
        }
    }
}

Таким образом, вы получите полную копию, реализовав всего одну строку кода:

Foobar foo2 = foo1.copy(); //nice and easy!!

Преимущество этого решения: обычно достаточно реализовать интерфейс Serializable, чтобы сделать класс "копируемым". А если нет, вы можете решить любые проблемы, прочитав то, что написано в Сериализуемый Javadoc!

Более того: не имеет значения, какие объекты в классе вы хотите сделать "копируемыми", поэтому вам не нужно больше тратить время на эту проблему. В конце концов, приведенный выше код является самым простым и быстрым решением, глубоко внедренным в Java с тех пор, и использует только оперативную память! (спасибо ByteArrayOutputStream)

Наслаждайтесь!

ОБНОВЛЕНИЕ: обратите внимание, что вам нужно использовать копию объекта, только если вы хотите временный стек или вы имеете дело с потоками (в общем: если вам нужно, чтобы объекты были полностью независимы друг от друга). В противном случае вы не должны делать никаких копий! Также, если вы записываете некоторые данные в файл или сокет, вам не нужна копия. Еще больше я предлагаю реализовывать метод копирования только тогда, когда он действительно используется: для структур данных (модель). Поэтому будьте осторожны, используя этот мощный метод (в противном случае он может замедлить работу вашего приложения или даже заполнить хранилище Java VM, если вы делаете миллионы копий без причины, это действительно вызовет переполнение стека:o).

РЕДАКТИРОВАТЬ

Я работал над этой проблемой немного больше. Потому что я неожиданно обнаружил, что существует публичный метод clone () для "примитивных" массивов, которых нет в Java API!! ("пасхальное яйцо" от SUN для массивов типа String[] или int[];-)

И так как я использую реальные массивы в качестве базовой структуры данных Foobar (не ArrayLists!), Я могу изменить метод клонирования (из приведенного выше класса) следующим образом:

@Override
public Object clone(){
    try{
        Foobar foobar = (Foobar) super.clone();
        String[] arrayClone = array.clone(); //who thought that this is possible?!
        foobar.setArray(arrayClone);
        return foobar;
    }
    catch(Exception e){
        return null;
    }
}

И теперь мы получаем этот результат прямо из коробки:

Instance: [Ljava.lang.String;@42e816 with value: 111
Instance: [Ljava.lang.String;@9304b1 with value: 111
Instance: [Ljava.lang.String;@42e816 with value: 999
Instance: [Ljava.lang.String;@9304b1 with value: 111

Проблема решена с "двойными" объектами!!! Как видите, клоны имеют разные объекты независимо от оригинала... поэтому foo1.equals(foo2)) будет ложным!

Решение: в методе clone класса вам также необходимо клонировать все его переменные класса! (Но если некоторые переменные класса являются ArrayLists или многомерными массивами, даже это решение не будет работать!)

Наконец, в чем реальная проблема? Класс ArrayList не клонирует свои массивы, он только вызывает метод copyOf в классе Array, что вредно. Поэтому никогда не используйте метод clone класса ArrayList и никогда не наследуйте какой-либо класс от ArrayList, потому что его метод clone не будет работать! (Это работает, только если класс ArrayList содержит только примитивы и не содержит объектов... в противном случае просто используйте простое решение ByteArray, описанное выше!).

Обратите внимание, что с массивами большей размерности, такими как Object[][], вам всегда нужно реализовать решение ByteArray, описанное выше, их нельзя клонировать! И если ваш массив огромен, это может занять некоторое время и также потребовать немного оперативной памяти.

Теперь вы эксперт по клонированию!:-D

Другие вопросы по тегам