Zeyomir's Blog Bo piękniej jest wiedzieć coś o wszystkim…

26Gru/10Off
» «

Klonowanie obiektów w Javie

Tworzenie kopii obiektów teoretycznie powinno być dość proste. Jak się jednak okazuje, nie działa do końca tak jak można by się spodziewać. Ale od początku:

Pisząc projekt na zaliczenie na uczelni (proste szachy), natrafiłem na pewien problem. Sprawa prosta, mamy szachownicę- tablicę pól. Pole może mieć na sobie bierkę. Chcemy przenieść bierkę z jednego pola na inne. Jednak najpierw trzeba sprawdzić, czy ruch jest zgodny z zasadami. Ogólnie nie wymaga to cudów i w większości wypadków da się 'wyliczyć' czy wszystko jest w porządku.

Gorzej jednak ze sprawdzeniem, czy dany ruch nie postawi gracza w szachu. Potrzebowałem więc wykonać ten ruch 'testowo', sprawdzić czy król jest pod obstrzałem i następnie albo ruch zatwierdzić, albo wycofać. Najłatwiej oczywiście zrobić kopię szachownicy, na niej przeprowadzić sprawdzany ruch i jeżeli wszystko się zgadza to wykonać ten sam ruch na szachownicy oryginalnej. I tu właśnie pojawił się problem:

Podejście pierwsze

Napisałem szachownica. i poczekałem na podpowiedzi, mając nadzieję, że znajdę tam coś ciekawego ;). Na pierwszym miejscu metoda clone, czytamy opis i już cieszymy się, że znaleźliśmy to czego szukaliśmy. Zacząłem więc (upraszczając) od czegoś takiego, from i to to indeksy odpowiednio pola z którego rusza się bierka oraz pola docelowego.

Pole[] szachownica2 = szachownica.clone();
wykonajRuch(szachownica2);
if(krolBezpieczny(szachownica2)) 
    wykonajRuch(szachownica);

void wykonajRuch(Pole[] szachownica){
    szachownica[to].setBierka(szachownica[from].getBierka());
    szachownica[from].setBierka(null);
}

Brzmi nieźle- szkoda tylko, że nie działa ;). Po powyższym, aplikacja zaczęła "wariować". O co chodzi?

Po krótkiej zabawie z debuggerem widzimy, że zmiana wprowadzona w polu szachownicy testowej pojawia się automatycznie w szachownicy oryginalnej. Kiedy później program próbuje przenieść bierkę w oryginalnej szachownicy, to pole from jest już puste. Teraz już chyba jasne, że metoda clone nie działa do końca tak jak można by się spodziewać.

Różne poziomy kopiowania

Spowodowane jest to tym, że clone wykonuje kopię samej tablicy, ale nie obiektów w niej trzymanych. Jest to tak zwane 'shallow copy' (kopiowanie powierzchowne, płytkie). Efekt jest taki, że mamy 2 osobne tablice, ale obie odwołują się do tych samych obiektów. W naszym przypadku- bezużyteczne. Alternatywą jest 'deep copy' (głębokie kopiowanie)- tworzone są kopie wszystkich obiektów agregowanych przez obiekt klonowany, obiektów agregowanych przez te obiekty itd.

Niestety trzeba sobie to napisać samemu przesłaniając metodę clone, co nie jest ani łatwe, ani przyjemne, a jeżeli gdzieś po drodze agregujemy obiekty klasy której nie możemy zmieniać, to zaczyna się robić naprawdę nieprzyjemnie. Na szczęście w powyższym problemie nie istnieje potrzeba zrobienia pełnego 'deep copy'.

Podejście drugie

Uzbrojeni w tę wiedzę tworzymy rozwiązanie 'wystarczające':

Pole[] szachownica2 = szachownica.clone();
for(byte i = 0; i < szachownica.length; i++){
    szachownica2[i] = szachownica[i].clone();
}
wykonajRuch(szachownica2);
if(krolBezpieczny(szachownica2)) 
    wykonajRuch(szachownica);

Niestety nasza klasa Pole nie do końca wie jak się klonować. Rozwiązujemy sprawę implementując interfejs Cloneable w następujący sposób:

public class Pole implements Cloneable{
    public Pole clone(){
        try{
            return (Pole)super.clone();
        }
        catch (Exception e){
            return null;
        }        
    }
    //reszta klasy
}
Podsumowanie

Wniosek jest krótki: z pozoru proste zadanie klonowania obiektów może być dość upierdliwe jeżeli nie będziemy pamiętali o tym, że domyślnie wykonywana jest tylko 'shallow copy' i jeżeli chcemy zmienić to zachowanie to musimy napisać rozwiązanie sami. Należy też dobrze się zastanowić, czy aby na pewno potrzebujemy całego 'deep copy' żeby nie napracować się na darmo  i niepotrzebnie nie śmiecić pamięci ;).

» «
Tagged as: , Komentarze
Komentarze (2) Trackbacks (0)
  1. Jak już klonujesz to wystarczy:
    if (krolBezpieczny(szachownica2))
    – wykonajRuch(szachownica);
    + szachownica = szachownica2;
    o/

    I nie bardzo czaję czemu w Twoim przypadku płytkie kopiowanie nie działało.
    W końcu Tobie wystarczy widze czy na polu [X][Y] coś stoi (i co) czy nie. Więc obiektów (figur, bo pole to figura?) IMHO nie trzeba kopiować. Figury się nie zmienają po ruchu.

    • co do tego, że wystarczy przypisać testową wersję szachownicy do nowej- teoretycznie tak :) u mnie takie rozwiązanie podyktowane było trzymaniem informacji o zbitych figurach- przy testowaniu ruchu nie dodaję figury do puli zbitych 😛 ale w kontekście tylko tego co zawarte we wpisie, faktycznie masz rację :)

      co do płytkiego kopiowania- tablica jest typu Pole, a klasa Pole ma atrybut Figura. Przy płytkim kopiowaniu miałem kopię tablicy ale nie zawartych w niej obiektów (pól). Kopii figur faktycznie nie potrzebuję, dlatego też nie potrzebuję pełnego deep copy :) ale kopię pól trzymanych w tablicy (a nie tylko wskaźników na te obiekty) jak najbardziej potrzebowałem :) po ruchu zmienia się „zawartość” pola 😉


Trackbacks are disabled.