Возможно ли в Java реализовать что-то похожее на Object.clone ()? - PullRequest
2 голосов
/ 25 марта 2010

Метод Object.clone () в Java довольно специфичен, так как вместо возврата копии объекта, который должен быть клонирован с типом объекта, он возвращает правильный тип объекта. Это можно лучше описать с помощью следующего кода:

class A implements Cloneable
{
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

class B extends A {
}

public class MainABC {
    public static void main(String[] args) throws CloneNotSupportedException {
        B b = new B();
        B b1 = (B)b.clone(); //see here that we are using A's .clone(). The only
                             //thing it does is call Object's clone().
        System.out.println(b1.getClass()); //but as we see here, its not an Object
                                           //its a B!
    }
}

Итак, может ли кто-нибудь объяснить, если это возможно, есть ли возможность повторить то, что происходит внутри метода Object.clone ()?

Ответы [ 5 ]

2 голосов
/ 27 марта 2010

Совершенно верно, что Object.clone() делает несколько вещей, которые просто не могут быть достигнуты в Java.

С Джош Блох о дизайне: конструктор копирования и клонирование (выделено):

Метод клонирования объекта очень сложен. Он основан на полевых копиях и является «экстралингвистическим». Он создает объект без вызова конструктора. Нет гарантий, что он сохранит инварианты, установленные конструкторами.

Object.clone() делает то, что не разрешено языком. Вот почему, среди многих других причин, clone() не работает.

(Если вы еще этого не сделали, вам также следует прочитать его книгу Эффективная Java , чтобы понять, почему он (и многие другие) считают, что Java clone() и Cloneable не работает).


Если вы просто хотите создать объект того же класса, что и другой произвольный объект, то это на самом деле вполне достижимо с некоторой оговоркой (а именно, что не все типы являются общедоступными для создания экземплярами) с помощью отражения.

Вот пример того, как использовать отражение для:

  • Узнать класс объекта во время выполнения
  • Список объявленных им полей, методов и конструкторов
  • Найти его конструктор копирования (если есть) и пытается вызвать его, используя заданный объект как параметр.

import java.lang.reflect.*;

public class NewInstance {
   static void print(String label, Object[] arr) {
      System.out.println(label);
      for (Object o : arr) {
         System.out.println(o);
      }
      System.out.println("---");
   }

   static Object newInstance(Object o) {
      Class<?> c = o.getClass();
      System.out.println("Class is " + c);
      print("FIELDS:", c.getDeclaredFields());
      print("METHODS:", c.getDeclaredMethods());
      print("CONSTRUCTORS:", c.getDeclaredConstructors());

      try {
         Constructor<?> cc = c.getDeclaredConstructor(c);
         o = cc.newInstance(o);
      } catch (NoSuchMethodException e) {
         System.out.println("No copy constructor found!");
      } catch (IllegalAccessException e) {
         System.out.println("Copy constructor inaccessible!");
      } catch (InstantiationException e) {
         System.out.println("Instantiation failed!");
      } catch (InvocationTargetException e) {
         System.out.println("Copy constructor threw " + e.getCause());
      }
      return o;
   }

   public static void main(String args[]) {
      Object o1 = "hello";
      Object o2 = newInstance(o1);
      boolean success = (o1 != o2) && (o1.equals(o2));
      System.out.println("Attempt " + (success ? "succeeded!" : "failed :("));
   }
}

Выход:

Class is class java.lang.String
FIELDS:
// (omitted)
METHODS:
// (omitted)
CONSTRUCTORS:
public java.lang.String()
public java.lang.String(java.lang.String) // this is what we're looking for!
// (rest omitted)
---
Attempt succeeded!

Обратите внимание, что это всего лишь пример, показывающий, что тип можно проверить во время выполнения, а также можно найти и вызвать конструктор копирования. Таким образом, он не работает, если o является ArrayList, потому что у него нет конструктора, который принимает ArrayList (у него есть конструктор, который принимает Collection, что ArrayList).

Я оставлю это вам в качестве упражнения о том, как расширить поиск конструктора копирования, чтобы включить эти совместимые перегрузки.

1 голос
/ 25 марта 2010

Библиотека Objenesis может использоваться для создания экземпляров произвольных классов, даже если у них нет конструктора без аргументов. Для этого он использует различные приемы для каждой JVM. Вы можете использовать это вместе с небольшим количеством кода отражения, чтобы скопировать все значения полей из исходного объекта в место назначения.

1 голос
/ 25 марта 2010

Я никогда не слышал и не видел языковой конструкции, которая предоставляет вам функциональность, которую clone предоставляет вам бесплатно.

Вы можете имитировать это, но я не верю, что вы можете повторить поведение.

0 голосов
/ 27 марта 2010

Как насчет этого?

public class A {
    public static void main(String[] args) {
        B b = new B();
        B b1 = (B)b.getNew();
        System.out.println(b1.getClass());
    }

    public Object getNew() {
        try {
            return getClass().newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }
}

class B extends A {

}
0 голосов
/ 25 марта 2010

Я думаю, что вы даже не тестировали введенный здесь код!

Если вы попытаетесь скомпилировать этот код, вы получите ошибки.Во-первых, возврат super.clone () выдает ошибку «Несоответствие типов: невозможно преобразовать объект в A»

Во-вторых (я буду считать, что это был опечатка), вы не создали экземпляр BВы сказали

B = new B();

Даже если мы изменим это значение на

B b = new B();
B b1 = b.clone();

Вы получите ошибку, потому что b.clone () вернет экземпляр класса A.

Извините, но вы описываете, что не происходит ... Вам нужен бросок, чтобы получить то, что вы хотите.

Итак, чтобы подвести итог:

public class A extends Object {
    public A clone() {
        return super.clone(); // Error "Type mismatch: cannot convert from Object to A"
    }

    public static void main(String[] args) {
        B b = new B();
        B b1 = b.clone(); // Error "Type mismatch: cannot convert from A to B"
    }
}

class B extends A {
}

РЕДАКТИРОВАТЬ: Я 'Боюсь, ты снова ошибся.То, что вы сделали, возвращает экземпляр B, потому что вы приведете его к экземпляру B.Он вернет B, даже если вы разыграете что-то совершенно другое ... Например,

B b = (B)(new JLabel());
System.out.println(b.class);

Это выведет класс B. На самом деле, он бы напечатал класс B, если когда-либопопал туда ... Вы получите исключение до того, как оно туда придет ... Чтобы получить то, что вы хотите, вам нужно вручную переопределить метод клона и предоставить собственную реализацию.Ваш вопрос вообще недействителен. Вы должны удалить его, но не можете, так как вы проголосовали против ... Я бы проголосовал, чтобы закрыть хотя бы ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...