Можно ли написать конструкторы копирования для классов с переменными-членами интерфейса в Java?

Как бы вы написали конструктор копирования для класса с переменными-членами интерфейса?

Например:

public class House{

    // IAnimal is an interface
    IAnimal pet;

    public House(IAnimal pet){
        this.pet = pet;
    }

    // my (non-working) attempt at a copy constructor
    public House(House houseIn){
        // The following line doesn't work because IAnimal (an interface) doesn't 
        // have a copy constructor
        this.pet = new IAnimal(houseIn.pet);
    }
}

Я вынужден иметь бетон Animal ? Если так, то повторное использование класса для домов с собаками против домов с кошками становится запутанным!

4 ответа

Решение

У вас есть один из трех вариантов:

  1. Есть метод на IAnimal для глубокого клонирования объекта (используется библиотеками, такими как интерфейсы DOM, такие как Node.cloneNode(boolean))
  2. Создайте конструктор копирования во всех реализациях IAnimal это берет конкретный тип и делает это требованием в контракте интерфейса, затем использует отражение для доступа к нему
  3. Создайте фабрику копирования, которая будет копировать каждую реализацию вручную
  4. Используйте стороннюю библиотеку, которая реализует глубокое клонирование для вас со своими собственными контрактами, такими как конструкторы без аргументов, неконечные поля, Serializable классы и т. д., как перечисленные здесь

Метод копирования

Для #1 сделайте что-то вроде:

public interface IAnimal {
    IAnimal cloneDeep();
}

Реализуйте это в ваших конкретных типах, затем вызовите этот метод, чтобы скопировать его:

this.pet = pet.cloneDeep();

Затем запишите требование в интерфейсе, сказав что-то вроде:

Реализации этого интерфейса должны возвращать объект, который не является == в этом случае, и должен быть глубоко клонирован, чтобы манипулирование этим объектом не приводило к манипулированию возвращаемым и наоборот.

Реализации должны будут следовать этому контракту, чтобы соответствовать интерфейсу, но это не будет применено во время компиляции.

Копировать конструктор

Попробуйте рефлексивно получить доступ к конструктору копирования, затем укажите, что конструктор копирования требуется во всех конкретных реализациях интерфейса, который становится частью контракта интерфейса. Каждая реализация будет выглядеть так:

public class Dog implements IAnimal {

    private String name;

    public Dog(Dog dog) {
        this.name = dog.name;
    }
}

И тогда все, что вам нужно, это один метод для копирования каждой реализации:

public static <A extends IAnimal> A copy(A animal) {
    Class<?> animalType = animal.getClass();
    // This next line throws a number of checked exceptions you need to catch
    return (A) animalType.getConstructor(animalType).newInstance(animal);
}

Если у вас есть это, добавьте заявление об этом в документации вашего интерфейса:

Реализации этого интерфейса должны определять конструктор копирования, который принимает аргумент того же типа или супертипа своего класса. Этот конструктор должен сделать глубокую копию аргумента, чтобы манипулирование этим объектом не привело к манипулированию возвращаемым и наоборот.

Опять же, это принудительно во время выполнения. copy метод выше бросает NoSuchMethodException ошибки, когда конструктор не существует.

Копировальная фабрика

Это занимает IAnimal и использует instanceof решить, какой метод передать, например:

public static IAnimal copyAnimal(IAnimal animal) {
    if (animal instanceof Dog)
        return copyDog((Dog) animal);
    if (animal instanceof Cat)
        return copyCat((Cat) animal);
    //...
    else
        throw new IllegalArgumentException("Could not copy animal of type: "
                + animal.getClass().getName());
}

Затем сделайте глубокое копирование в copy методы для каждого типа вручную.

Если я понимаю вашу проблему, поскольку вы не можете указывать конструкторы в интерфейсе, вам нужно объявить метод глубокого копирования в вашем интерфейсе и реализовать его в ваших классах. Вы не можете создать экземпляр интерфейса. Возможно, вы также захотите глубоко скопировать что-нибудь в House а также, в зависимости от ваших потребностей.

public interface IAnimal {
    ...
    IAnimal deepCopy(); 
}


public House(House houseIn){
    this.pet = houseIn.pet.deepCopy();
}

Проблема, конечно, в том, что вы должны сделать так, чтобы не сделать что-то не так. Это пахнет так, будто вам не нужен интерфейс, а скорее абстрактный класс.

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

В этом случае я бы предложил использовать абстрактный класс:

public class House {

AbstractAnimal pet;

public House(AbstractAnimal pet) {
    this.pet = pet;
}

public House(House houseIn) {
    this.pet = new AbstractAnimal(houseIn.pet) {
        //implement the abstract methods that are required for anonymous class
    };
}

private abstract class AbstractAnimal {
    //Abstract class attributes 

    public AbstractAnimal(AbstractAnimal Parent) {
        //Constructor code, can also call abstract methods if required
    }
    //declare Abstract methods if required.
}

Насколько мне известно, в Java нет прямого эквивалента этому.

"Правильный" способ - сделать интерфейс самостоятельно реализующим Cloanable.

И самый простой способ - использовать отражение. Я знаю, что есть библиотека, которая обрабатывает глубокие копии произвольных объектов, но я не могу вспомнить имя в данный момент.

Связанный: Java: рекомендуемое решение для глубокого клонирования / копирования экземпляра

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