Java dla Testerów 17. Gettery i settery: typy referencyjne

Gettery i settery w typach referencyjnych to druga (i ostatnia) lekcja dotycząca akcesorów i mutatorów. Zobaczysz o czym trzeba pamiętać używając ich z typami referencyjnymi.

Gettery i settery w typach referencyjnych: linki i materiały

Gettery i settery do pól o typie referencyjnym będą zachowywały się inaczej niż to, co już znamy z typów prostych i Stringa. Różnice wynikają z podstawowej cechy typów referencyjnych: będziemy pracować na referencjach, a nie na wartościach.

Porówanie: setter

Popatrz na poniższy kod.

@Test
    public void gettersAndSettersTest(){
        Customer customer = new Customer();
        int age = 15;
        customer.setAge(age);
        System.out.println("Wiek: " + customer.getAge());        
        age = 22;
        System.out.println("Wiek: " + customer.getAge());
    }

W tym przykładzie pracujemy na typie prostym, czyli na wieku naszego klienta, który jest typu int. Zmiana wartości zmiennej age już po przekazaniu jej do settera nie wpływa na wartość pola obiektu klasy Customer. Było 15 i nadal jest 15, a nie 22. Całość zachowa się inaczej, gdy będziemy pracować na referencji.

@Test
public void gettersAndSettersObjectsTest(){
    Customer customer = new Customer();
    Address address = new Address("Kraków", "Fiołkowa", "15/22");
    customer.setAddress(address);    
    System.out.println("Adres: " +
            customer.getAddress().getStreet() + " " +
            customer.getAddress().getApartment() + ", " +
            customer.getAddress().getCity());
    address.setCity("Gdańsk");
    System.out.println("Adres: " +
            customer.getAddress().getStreet() + " " +
            customer.getAddress().getApartment() + ", " +
            customer.getAddress().getCity());
}

Jak widzisz na powyższym przykładzie, tym razem w setterze przekazujemy referencję (address). Następnie „wypluwamy” w konsoli pełen adres. Następnie zmieniamy miasto w adresie i ponownie wypisujemy pełny adres naszego customera, by móc porównać, czy coś się zmieniło. No i właśnie w przeciwieństwie do tego, co widzieliśmy na przykładzie typów prostych, teraz miasto w adresie naszego klienta się zmieni na „Gdańsk”.

Dlaczego tak się dzieje? Dlatego, że zarówno address jak i to co dostaniemy z customer.getAddress() zawiera referencję do tego samego obiektu i w konsekwencji pracujemy na tym samym obiekcie. Dokładniej tłumaczę to na filmie.

Porówanie: getter

Pokazałam Ci jaki jest problem z setterem w typach referencyjnych, a teraz pokażę Ci to od strony gettera. Spójrz na poniższy przykład:

@Test
public void gettersAndSettersTest(){
    Customer customer = new Customer();
    int age = 15;
    customer.setAge(age);
    System.out.println("Wiek: " + customer.getAge());
    int age2 = customer.getAge();
    age2 = 22;
    System.out.println("Wiek: " + customer.getAge());
}

Tym razem nie modyfikujemy wartości oryginalnej zmiennej age. Teraz pobieramy do zmiennej age2 już wcześniej ustawiony wiek klienta za pomocą metody customer.getAge(). Następnie zmieniamy wartość zmiennej age2 na 22. Czy zmieni to wiek dla naszego klienta? Nie.

A teraz spójrz na poniższy przykład.

@Test
public void gettersAndSettersObjectsTest(){
    Customer customer = new Customer();
    Address address = new Address("Kraków", "Fiołkowa", "15/22");
    customer.setAddress(address);
    Address address2 = customer.getAddress();
    System.out.println("Adres: " +
            customer.getAddress().getStreet() + " " +
            customer.getAddress().getApartment() + ", " +
            customer.getAddress().getCity());
    address2.setCity("Gdańsk");
    System.out.println("Adres: " +
            customer.getAddress().getStreet() + " " +
            customer.getAddress().getApartment() + ", " +
            customer.getAddress().getCity());
}

Tutaj robimy coś podobnego ale na zmiennej typu Address. Po przekazaniu do settera zmiennej address, tworzymy nową zmienną address2 i pobieramy adres klienta za pomocą metody customer.getAddress(). Pewnie już wiesz do czego zmierzam. Zmieniam w address2 miasto i sprawdzam czy się zmieniło. Miasto się oczywiście zmieni, bo pracujemy na referencji do tego samego obiektu.

Gettery i settery w typach referencyjnych: jak sobie z tym poradzić?

No to co zrobić, jeżeli chcemy, żeby całość zachowywała się podobnie jak dla typów prostych. W setterze i getterze możemy tworzyć kopię obiektu, w ten sposób nie będziemy pracować na referencji do tego samego obiektu. Więcej na ten temat mówię w filmie, ale poniżej możesz też zobaczyć kod klasy Customer.

package GettersAndSetters;

public class Customer {

    private String name;
    private String lastName;
    private int age;
    private Address address;

    public Address getAddress() {
        return copy(address);
    }

    public void setAddress(Address addressParameter) {
        address = copy(addressParameter);
    }

    public void setAge(int ageParameter) {
        if (ageParameter<0) throw new IllegalArgumentException("Age can't be negative number");
        age = ageParameter;
    }

    public int getAge() {
        return age;
    }

    private Address copy(Address addressToCopy){
        Address address = new Address(addressToCopy.getCity(), addressToCopy.getStreet(), addressToCopy.getApartment());
        return address;
    }
}