Kategorie szkoleń | Egzaminy | Kontakt
  • 1
  • 3
  • 310

W pakiecie java.lang istnieje interfejs Cloneable, który powinien być implementowany przez klasę umożliwiającą kopiowanie. Jednak metoda clone() istnieje w klasie Object, która nie implementuje Cloneable. 

Rozważmy więc poniższy kod:

 

public class MojaKlasa {

   @Override
   protected Object clone() {
      // Moja implementacja klonowania
   }
}   

 

Czy w takim przypadku klasa MojaKlasa jest poprawnie zaimplementowana?

Karol_Antczak
  • Zapytał
  • @ Karol_Antczak | 22.12.2014
    • 2
    • 2
    • 5

Odpowiedź (1)

  • 13

Gdyby klasa Object implementowała interfejs Cloneable, to wtedy każda klasa również by go implementowała (gdyż każda klasa - jawnie lub niejawnie - dziedziczy po Object). Wtedy interfejs nie spełniałby swojej roli wyróżnienia klas, których obiekty mogą być klonowane.

Dzięki temu, że klasa Object posiada metodę clone(), klasy potomne, które implementują Cloneable mogą wykorzystać tę domyślną implementację, a jeśli ona im nie odpowiada to mogą ją przedefiniować.

Przyjrzyjmy się następującemu przykładowi:

 

public class Punkt implements Cloneable {
  int x; // współrzędna X
  int y; // współrzędna Y

  public Punkt(int x, int y) {
    this.x = x;
    this.y = y;
  }

  @Override
  public String toString() {
    return String.format("(%d,%d)", x, y);
  }

  @Override
  protected Punkt clone() {
    try {
      return (Punkt) super.clone();
    } catch (CloneNotSupportedException e) {
      System.out.println(this.getClass().getName() + " nie implementuje Cloneable...");
      return null;
    }
  }
}

 

Klasa Punkt implementuje interfejs Cloneable. Implementacja metody clone() jest poprawna, gdyż:

  • wyjątek CloneNotSupportedException został obsłużony, więc nie wymaga dalszej propagacji
  • od Javy SE5 nadpisywana metoda może zwracać typ bardziej szczegółowy (potomny) w stosunku do typu zwacanego przez metodę nadpisywaną (tzw. covariant return)

Teraz można stworzyć klasę Okrag:

 

public class Okrag implements Cloneable {
  Punkt srodek;
  int promien;

  public Okrag(int ox, int oy, int r) {
    this.srodek = new Punkt(ox, oy);
    this.promien = r;
  }

  public Okrag(Punkt p, int r) {
    this.srodek = p;
    this.promien = r;
  }

  @Override
  protected Okrag clone() {
    try {
      return (Okrag) super.clone();
    } catch (CloneNotSupportedException e) {
      System.out.println(this.getClass().getName()
                         + " nie implementuje Cloneable...");
      return null;
    }
  }

  @Override
  public String toString() {
    return String.format("[okrąg]: współrzędne środka: %s, promień: %d",
                         srodek, promien);
  }
}

 

Teraz można spróbować sklonować Okrag:

 

public class Test {

  public static void main(String[] args) {
    Okrag oryginal = new Okrag(2, 7, 3);
    Okrag kopia = oryginal.clone();
    System.out.println("ORYGINAŁ: " + oryginal);
    System.out.println("KOPIA   : " + kopia);
    System.out.println("Czy różne referencje do okręgów? "
                       + (oryginal != kopia));
    System.out.println("Czy różne referencje do punktów? "
                       + (oryginal.srodek != kopia.srodek));
  }
}

 

Wyniki powyższego testu są następujące:

 

ORYGINAŁ: [okrąg]: współrzędne środka: (2,7), promień: 3
KOPIA   : [okrąg]: współrzędne środka: (2,7), promień: 3
Czy różne referencje do okręgów? true
Czy różne referencje do punktów? false

 

W wyniku klonowania otrzymaliśmy drugą instancję obiektu Okrag. Obiekt posiada tę samą wartość promienia i tę samą referencję do środka okręgu. Standardowa implementacja metody clone() tworzy PŁYTKĄ kopię obiektu. W efekcie, gdy zmienimy współrzędne środka oryginalnego okręgu, to te same zmiany będą dotyczyć jego kopii.

Jeśli takie zachowanie nie jest pożądane, to wtedy trzeba nadpisać standardową implementację metody clone().

Aby otrzymać GŁĘBOKĄ kopię obiektu wystarczy zmienić implementację metody clone() w klasie Okrag na następującą (w klasie Punkt nie musimy niczego zmieniać, gdyż nie zawiera ona atrybutów referencyjnych):

 

@Override
protected Okrag clone() {
  try {
    Okrag kopia =  (Okrag) super.clone();
    kopia.srodek = srodek.clone();
    return kopia;
  } catch (CloneNotSupportedException e) {
    System.out.println(this.getClass().getName()
                       + " nie implementuje Cloneable...");
    return null;
  }
}

 

Tym razem poprzedni test da następujące wyniki:

 

ORYGINAŁ: [okrąg]: współrzędne środka: (2,7), promień: 3
KOPIA   : [okrąg]: współrzędne środka: (2,7), promień: 3
Czy różne referencje do okręgów? true
Czy różne referencje do punktów? true

 

Na koniec warto zauważyć, że podobną funkcjonalność do metody clone() można uzyskać, definiując konstruktor kopiujący (przyjmujący jako argument oryginalny obiekt) lub metodę fabryki.

  • Odpowiedział
  • @ | 27.12.2014
  • TRENER MODERATOR ALTKOM AKADEMII