• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Referencje do obiektów zmienialnych

Object Storage Arubacloud
+1 głos
648 wizyt
pytanie zadane 2 sierpnia 2016 w Java przez itcloud Gaduła (3,380 p.)

Czy może mi ktoś wytłumaczyć, o co tu chodzi, bo czytałem 3x i nie rozumiem za bardzo sensu.

"Pamiętaj, aby nie pisać metod akcesora, które zwracają referencje do obiektów zmienialnych. Złamaliśmy tę zasadę w klasie Employee, w której metoda getHireDay zwraca obiekty klasy Date:

class Employee

{

private Date hireDay;

public Date getHireDay()

{

return hireDay;

}

...

}

 

W ten sposób łamiemy zasadę hermetyzacji! Spójrzmy na ponizszy niesforny fragment kodu:

Employee harry = ...

Date d = harry.getHireDay();

double tenYearsInMiliSeconds = 10 * .....

d.setTime(d.getTime() - ...)

 

Powód nie jest oczywisty. Zarówno zmienna d jak i harry.hireDay() odwołują się do tego samego obiektu. Wywołanie metody mutatora na rzecz obiektu d automatycznie powoduje modyfikację prywatnego obiektu klasy Employee! Jeśli konieczne jest zwrócenie referencji do modyfikowalnego obiektu, należy najpierw sklonować ten obiekt."  / JAVA. Podstawy. (Helion)

 

3 odpowiedzi

+2 głosów
odpowiedź 2 sierpnia 2016 przez Mr. Szanowny Bywalec (2,180 p.)

Musisz wiedzieć, że w Javie bawimy się zmiennymi normalnie, tak jakbyśmy bawili się wskaźnikami w C++. To są referencje - czyli "private Date hireDay;" jest tylko referencją (jeśli oczywiście później utworzyliśmy ten obiekt przez "new"). "hireDay" poprostu ma w sobie adres obiektu Date, czyli jeśli do "Date d" damy " hireDay" to przekażemy adres tego obiektu i wtedy oba będą sie odwoływać do tego samego obiektu.

Edit: Czyli wywołując metody na "d" zmieniamy obiekt do którego "d" się odwołuję tak samo jak "hireDay" - bo oba przechowują adres w pamięci tego obiektu, a nie sam obiekt.

komentarz 2 sierpnia 2016 przez itcloud Gaduła (3,380 p.)
edycja 2 sierpnia 2016 przez itcloud
hmm a nie jest tak, że ok - jest to zmienna referencyjna, ale bardziej działa to w JAVIe jak wskaźnik w c++, bo przecież referencji nie można niby przestawiać, a w Javie jednak można?

Ale reasumując: czyli jeśli tworzę dwie zmienne:

Date a = new Date();

Date b = new Date();

to co, a i b wskazują dokładnie na to samo ?
1
komentarz 2 sierpnia 2016 przez Mr. Szanowny Bywalec (2,180 p.)
Za pomocą "new" w twoim przykładzie inicjalizujesz w pamięci dwa obiekty. a i b to nie to samo. Tworzysz dwa obiekty o różnych adresach.

Jeśli natomiast zrobisz tak:

Date a = new Date();

Date b = a;

To wtedy a i b odwołują się do tego samego obiektu.
komentarz 2 sierpnia 2016 przez itcloud Gaduła (3,380 p.)
Teraz dopiero zrozumiałem (w sumie niby to wiedziałem od początku, ale jednak nie do końca w każdych aspektach). Dziękuję.
+1 głos
odpowiedź 2 sierpnia 2016 przez Porcupine Nałogowiec (31,560 p.)

Chodzi o to, że jeśli pole w Twojej klasie nie jest immutable nie powinieneś zwracać getterem() bezpośrednio tego pola. Ponieważ normalnie mamy sobie jakąś klasę i jeśli chcemy żeby użytkownik mógł zmieniać wartości pól w tej klasie to udostępniamy mu do tego specjalne metody (np. settery). Jeśli nie udostępniamy mu takich metod to znaczy, że nie chcemy aby ingerował w te wartości pól. 

Weźmy prostszy przykład: 

 

class ListKeeper {

    private List<Integer> list;

    // other methods and fields

    public List<Integer> getList() {
        return list;
    }
}

Mamy sobie klasę, która ma w sobie listę intów. Nie chcemy żeby użytkownik tej klasy mógł edytować tą listę, chcemy jedynie aby mógł przeglądać zawarte w niej przedmioty, dlatego udostępniamy mu metodę getList(). 
Ale co się stało !?
Użytkownik może teraz zrobić coś takiego:

List<Integer> list = new ListKeeper.getList();
list.add(5); // MUHAHAHAHAHA

Niby nie udostępniliśmy mu metody w rodzaju addElement() ani setList(), a jednak podstępny użytkownik i tak nam popsuł naszą listę dodając do niej 5. 
Dlaczego? 

Ponieważ zwracając: return list; zwróciliśmy bezpośrednią referencję do listy znajdującej się w naszym obiekcie klasy ListKeeper. 

Jak temu zapobiec? 
Najprościej tak:

public List<Integer> getList() {
        return Collections.unmodifiablelist(list);
}

Teraz gdy nasz podstępny gagatek zrobi:
 

List<Integer> list = ListKeeper.getList();
list.add(5); // WHAT? -->  Exception: java.lang.UnsupportedOperationException


I możemy spać spokojnie, bo nikt nie popsuje nam naszej drogocennej listy intów :)


 

komentarz 2 sierpnia 2016 przez itcloud Gaduła (3,380 p.)

Nie wiem, co to znaczy "immutable".

Poza tym, czy dobrze rozumiem, a nie korzystałem nigdy z tzw. list: błędem w poniższym kodzie było to, że:

List<Integer> list = new ListKeeper.getList();

a jeśliby było:

List<Integer> list = new ListKeeper;

list.getList();   // to już jest ok?

No bo rozumiem, że tworzymy referencję(wskaźnik, patrząc od strony c++) tzn. zmienną referencyjną list i jest ona typu listy intów. A nizej w tym moim przykładzie wywołujemy metodę (accesora). No i zawsze jest w takiej metodzie return. A tego niżej to nie rozumiem, bo co to jest to add() ? To metoda tak jakby wbudowana w funkcjonalność list? Nie wiem, czy to konkretnie odpowiada na moje pytanie zadane w wątku.

 

edit: tak czy inaczej, jakbyś jeszcze znalazł chwilę i odpowiedział mi na inny problem w tym wątku http://forum.pasja-informatyki.pl/163727/polimorfizm-java-vs-c - będę wdzięczny (pytanie z wczoraj). Bo widzę czytając co piszesz, że masz wyższy poziom wtajemniczenia ;)

1
komentarz 2 sierpnia 2016 przez Porcupine Nałogowiec (31,560 p.)

list.getList();   // to już jest ok?

Nie ;) To co napisałeś jest w tym przypadku praktycznie równoważne z tym co ja. Postaram się wieczorem dokładniej wyjaśnić o co chodzi :) 
 

Co do drugiego tematu - nie siedzę za bardzo w C++ i nie wiem jak to tam wygląda z obiektowością, więc jeśli chodzi o takie porównanie pomiędzy językami to za bardzo raczej nie pomogę.  

1
komentarz 3 sierpnia 2016 przez Porcupine Nałogowiec (31,560 p.)

Żeby nie zaciemniać z tymi listami (bo do javo'wych kolekcji i ich metod zapewne dojdziesz w dalszej części książki) zrobimy inny przykład. 

Wyobraź sobie, że mamy sobie psa. Pies ma obrożę, którą nadaje mu właściciel w momencie gdy tego psa kupi / przygarnie. Nie chcemy, żeby ktokolwiek mógł zmienić psu obrożę, ponieważ jest to taki symbol jego powiązania z właścicielem (nie rozważamy jakichś mrocznych scenariuszy w rodzaju, że właściciel przedwcześnie umrze i pies będzie musiał zmienić swojego pana). 

Chcemy jednak żeby każdy mógł sprawdzić dane, które pies ma na obroży. Tak, że jeśli przypadkiem pies by się zgubił, będziemy mogli znaleźć właściciela. 

Zacznijmy od zdefiniowania tego czym jest obroża: 

 

class Collar {

    private String owner;
    private String address;

    public Collar(String owner, String address) { 
        this.owner = owner;
        this.address = address;
    }

    public String setOwner(String owner) { 
         this.owner = owner;
    }
 
    public String getOwner() {
         return owner;
    }
}

Dodatkowe metody (w tym getter i setter dla adresu) pominąłem w celu zachowania większej czytelności.

O klasie takiej możemy powiedzieć, że jest mutable (PL. mutowalna), najprościej ujmując znaczy to, że po stworzeniu obiektu klasy Collar możemy za pomocą metody setOwner() zmienić jej stan. Można sobie wyobrazić, że w obroży jest miejsce na karteczkę z danymi właściciela psa i że karteczkę tą można wymienić bez zmiany psu obroży.

Teraz mamy sobie klasę pies: 

 

class Dog {
     private Collar myCollar;
     
     // other methods and fields
  
     // constructor
     public Dog(Collar collar) {
          myCollar = collar;
     }

     public Collar getCollar() {
          return myCollar;
     }
}

Zauważ, że nie mamy tutaj metody setCollar(), ponieważ tak jak wcześniej ustaliliśmy NIE CHCEMY aby ktoś zmienił naszemu pieskowi obrożę i sobie go przygarnął.

Teraz mamy sobie naszego kochanego szczeniaka: 

Dog scooby = new Dog(new Collar("Shaggy", "Green Street 14/27");

I wszystko fajnie, dopóki ktoś nie zrobi tak:

Collar scoobiesCollar = scooby.getCollar();
scoobiesCollar.setOwner("Fred"); // MUAHAHAHAHHAHA

Teraz gdybyśmy sprawdzili obrożę naszego Scoobiego: 

System.out.println(scooby.getCollar()); // we should have toString() method in the Collar class

Ku naszej rozpaczy okaże się, że właścicielem Scoobiego jest teraz Fred! Mimo, że wprost nie udostępniliśmy żadnej metody, która służyłaby do zmiany obroży u naszego pieska...

Na razie chętnie poznam Twoje zdanie co do tego co napisałem i postaram się odpowiedzieć na pytania. Później możemy pokazać jak temu zapobiec pokazując przy okazji, że klasy immutable (PL. niemutowalne) to strasznie fajna rzecz. 

komentarz 3 sierpnia 2016 przez itcloud Gaduła (3,380 p.)

Próbuję rozgryźć to co napisałeś, krok po kroku. Natrafiłem na problem. Może wkleję cały kod, najpierw pierwsza klasa (jeden plik), potem druga klasa (drugi plik).

import java.util.*;

//klasa mutowalna, mozna zmieniac jej stan
class Collar {
	
    private String owner;
    private String address;
     
    //konstruktor
    Collar(String owner, String address) { 
        this.owner = owner;
        this.address = address;
    }
 
    public void setOwner(String owner) { 
         this.owner = owner;
    }
 
    public void setAddress(String address) { 
        this.address = address;
   }
    
    public String getOwner() {
         return owner;
    }
 
    public String getAddress() {
        return address;
   }
    
    public String toString(){
    	String nazwa_klasy = this.getClass().getName();
    	return "class="+nazwa_klasy+", owner="+owner+", address="+address;
    }
   
    public static void main(String[] args){
    	
    	//tworze pierwszy obiekt, obroze
    	Collar obroza1 = new Collar("Mieciu","Topolowa 47");
    	System.out.println(obroza1);
    	
    	//tworze obiekt psa, z obroza juz utworzona wyzej
    	
    	//Dog pies1 = new Dog(obroza1); //to cos nie bangla !!!! a moze jest ok, patrzac na to co nizej sie dzieje..
    	//System.out.println(pies1); 
    	Dog pies1 = new Dog(new Collar("Fredek","Zamczysko 8"));
    	System.out.println(pies1);  //w sumie tu tez wypisuje cos dzinwego: Dog@1f9dc36 
    	
    	Collar wrong1 = pies1.getCollar();
    	pies1.setOwner("Oszuścik"); // hmm gdy to odkomentuje jest blad: The method setOwner(String) is undefined for the type Dog

    }

}

i druga:

import java.util.*;

class Dog {
     private Collar myCollar;
   
     //konstruktor (argumentem jest inna klasa - tzn jej obiekt)
     public Dog(Collar collar) {
          myCollar = collar;
     }
 
     public Collar getCollar() {
          return myCollar;
     }
}

Nie ukrywam, że jest dla mnie problemem ogarnięcie tutaj tego, że:

1. nagle nie tworzę drugiej klasy (DOG) jako klasy rozszerzającej tę klasę Collar. Ale tu dziedziczenie intuicyjnie domyślam się, że nie ma sensu, bo jak może obroża rozszerzać psa albo odwrotnie. Cały czas byłem przy temacie polimofrizmu i tutaj się jego doszukiwałem heh.. Jak byś mógł mi te kwestie pomóc uporządkować?

2. pierwszy raz spotykam się w praktyce (sic!) ze składową w klasie, która jest jakimś obiektem i muszę się z tym oswoić.

komentarz 4 sierpnia 2016 przez itcloud Gaduła (3,380 p.)
Jeszcze jedno, jakbyś mógł podpowiedzieć, czy nie przyszedł już czasem moment np. u mnie (siedzę i czytam książki..), aby coś konkretnego stworzyć? Bo jakoś nie potrafię sobie wyobrazić teraz programu, który miałbym napisać i który zawierałby np. 5 klas ale bez dziedziczenia, albo 5 klas ale z dziedziczeniem, albo z interfejsem. Kiedy co wybrać? To kwestia praktyki (czyli trzeba się zatrudnić jako programista i z czasem wszystko się opanuje), czy też tego, że jednak inni programiści niechętnie dzielą się takimi szczegółami?

Tak samo jak nie mam pojęcia, po co się tworzy interfejsy, jeśli w klasach które korzystają z nich wpisuje się tę metodę (jej implrementację), która w ogólnym zarysie jest przedstawiona (jej deklaracja) właśnie w interfejsie. Czyli nie dość że piszę ogólny zarys w interfejsie, to potem w każdej z klas.
1
komentarz 4 sierpnia 2016 przez Porcupine Nałogowiec (31,560 p.)
Masz rację, rozszerzanie klasy Collar przez Dog nie ma sensu. Ogólnie dziedziczenie nie jest zawsze dobrym rozwiązaniem. Większość książek na początku porusza ten temat żeby przedstawić ogólnie na czym to programowanie obiektowe polega, ale tak na prawdę czasem właśnie chodzi o to żeby uniknąć dziedziczenia. Jeden z rozdziałów książki Effective Java, która jest uznawana za prawdopodobnie najważniejszą książkę dla programistów Javy mówi:
"Favour composition over inheritance" (Preferuj kompozycję ponad dziedziczenie).
Gdzie owa kompozycja to właśnie "zjawisko" gdzie jedna klasa jako pole ma obiekt innej klasy. Kiedyś na pewno poznasz to głębiej i będziesz wiedzieć kiedy jedna klasa powinna dziedziczyć po innej, a kiedy od tego uciekać. (Jakbyś chciał o tym poczytać to warto zacząć od tego czym jest: "Liskov substitution principle", ale wydaje mi się, że na początku nauki języka nie musisz zagłębiać się tak szybko w takie rzeczy.)

Ja javy uczyłem się na początku samu zaczynając przygodę od "Head First - Java". Później pracowałem ze znajomymi w kole naukowym na studiach, co też było (i jest) bardzo fajnym doświadczeniem - więc jeśli znasz kogoś kto też się uczy programować to wspólna praca nad projektem jest super.

Co do indywidualnych projektów - im wcześniej, tym lepiej. Taki just-in-time learning jest bardzo pozytywny. Pisz na co masz ochotę, może być to jakaś prosta gra albo jakaś aplikacja mająca na celu zarządzanie planem dnia / przypominanie Ci o nowych odcinkach serialu / cokolwiek co Ci przyjdzie do głowy :)

Interfejsy można powiedzieć, że głównie służą do definicji typów, też taki polimorfizm tylko "luźniejszy" i bardziej preferowany. Moim zdaniem takie rzeczy najlepiej poznaje się z czasem, wraz z pisaniem kolejnych projektów. Trudno osiągnąć takie pełne zrozumienie z książki :)
komentarz 4 sierpnia 2016 przez itcloud Gaduła (3,380 p.)
>Ja javy uczyłem się na początku samu zaczynając przygodę od "Head First - Java".

Mam polską edycję. Momentami fajne, ale zbyt rozwlekłe i nie podobają mi się te programy, które tam próbuje autor rozkładać na czynniki pierwsze - a wszystko ciągnie się jak flaki z olejem. Poza tym książka mówi o java5 - czyli staroć. Nic dziwnego, że książka jest na allegro wyprzedawana za 50% jej ceny początkowej, bo za moment w ogóle nikt nie będzie tego kupować.

Dla przeciwwagi polecam książkę: JAVA - podstawy. Świetna - są nawiązania do c++. Mowa m.in. o tym, że w javie są interfejsy, a w c++ to klasy abstrakcyjne (metody virtual).

No i kwestia interfejsów. Dotąd na tym forum nikt mi tego nie wytłumaczył w taki sposób, jak tłumaczy ojciec synowi, który wkracza w programowanie (ojcu zależy, żeby syn go zrozumiał). Tutaj na forum często czytam różne watki i fajnie, jak ktoś próbuje coś wytłumaczyć, ale załamuje się, jak widzę sposób, w jaki to robi. Mnóstwo określeń, które ON SAM na tym poziomie co jest, rozumie. Gorzej z tym, komu te informacje przekazuje.

Próbowano mi wytłumaczyć, co to są interfejsy, ale nie wytłumaczono, po co je stosować. Przed momentem natrafiłem na filmik na ytb o wzorcach projektowych. I dopiero chyba po tym filmiku SAM DOSZEDŁEM DO TEGO, po co te interfejsy.

>Interfejsy można powiedzieć, że głównie służą do definicji typów, też taki polimorfizm >tylko "luźniejszy" i bardziej preferowany. Moim zdaniem takie rzeczy najlepiej poznaje się >z czasem, wraz z pisaniem kolejnych projektów. Trudno osiągnąć takie pełne >zrozumienie z książki :)

Sorki :) Ale to mi wiele nie mówi. Domyślam się - choć mogę się mylić - że interfejs buduje się po to, żeby ten kto to zaprojektował mógł tak jakby "podyktować warunki" budowania (implementowania) klasy/klas w oparciu własnie o ten "wzorzec" / szablon. Widziałm w Eclipsie, że gdy koleś podczepiał klasę pod interfejs (implements..) to od razu (tak jak buduje się automatycznie gettery i settery) wyskoczyły mu funkcje/metody. Nie wyłamywał się ponad to, co otrzymał. Nie zastanawiał się, tylko samemu ew. stworzył swoją implementację metod. W sumie tylko cały czas się zastanawiam, po co to, jeśli ktos może poprzez swoją implementację stworzyć potworka. Jeden zrobi tak, jak chciałby aby to wyglądało projektant interfejsu a inny niekoniecznie?
1
komentarz 5 sierpnia 2016 przez Porcupine Nałogowiec (31,560 p.)

Ogólnie, jak zapewne już wiesz, w Javie klasa może extendować bezpośrednio tylko jedną klasę. Co jest trochę ograniczające, bo czasem chcielibyśmy się odwoływać do danego obiektu tak jakby z różnych perspektyw, co za raz spróbuję wyjaśnić. To jest główny powód powstania interfejsów. 

Np. masz sobie klasy: 

class Bird extends Animal {

}
class Airplane extends MeansOfTransport {

}

No i tak wyszło, że piszesz jakiś program, gdzie potrzebujesz przejść na wyższy poziom abstrakcji i móc odwoływać się jakoś do wszystkich rzeczy / zwierząt, które umieją latać. Właśnie wtedy możesz sobie stworzyć interface: 
 

interface Flyable {
    void fly(); 
}
class Bird extends Animal implements Flyable {

    @Override 
    void fly() {
         System.out.println("Flap, flap, moving my wings");
     }
}
class Airplane extends MeansOfTransport implements Flyable {

    @Override 
    void fly() {
         System.out.println("Wrrrr, engines are working");
     }
}

Dzięki temu możesz zrobić polimorficznie:

 

public static void main(String[] args) { 

    Flyable somethingThatCanFly = new Bird();
    somethingThatCanFly.fly();

    Flyable somethingThatCanFly2 = new Airplane();
    somethingThatCanFly2.fly();
}

Dzięki temu patrzysz zarówno na samolot jak i na ptaka tylko z perspektywy, że jest to coś, co umie latać. Nie obchodzą Cię ich inne cechy / metody. Wyabstrachowałeś tylko tą umiejętność, na której Ci zależało. 

Wspominałeś o wzorcach projektowych - dokładnie, na ich przykładzie można poznać prawdziwą siłę i znaczenie polimorfizmu, interfejsów itp. Ale to już trochę bardziej zaawansowane zagadnienie, powstają całe książki opisujące po kilka - kilkanaście wzorców :) 

 

komentarz 5 sierpnia 2016 przez itcloud Gaduła (3,380 p.)
Dziękuję dobry człowieku. Zajarzyłem wszystko :)
0 głosów
odpowiedź 2 sierpnia 2016 przez itcloud Gaduła (3,380 p.)

Kucze, no nie ogarniam tego, nie ogarniam. Do tej pory gdzie były przykłady klas ze składowymi i z metodami typu setter,getter nie było nigdzie mowy o jakimś niebezpieczeństwie "returna" przy getterach. A tutaj piszą:

"Jeśli konieczne jest zwrócenie referencji do modyfikowalnego obiektu, należy najpierw sklonować ten obiekt."

Co to znaczy do modyfikowalnego obiektu? Przecież każda składowa w klasie jest modyfikowalna, mogę ją zmienić (no chyba że napisze final). Jakiś najprostszy przykład możecie podać na to co zacytowałem i na antyprzykład, gdzie będzie zwracana referencja do NIEmodyfikowalnego obiektu ? Chciałbym się uczyć dalej, ale jak już tego nie rozumiem, to nie widzę sensu.

komentarz 2 sierpnia 2016 przez itcloud Gaduła (3,380 p.)
up...
1
komentarz 4 sierpnia 2016 przez Porcupine Nałogowiec (31,560 p.)

Jeśli masz jakąś klasę i ona ma pola private (które samo w sobie jest obiektem niemodyfikowalnym), do których nie udostępniasz setterów (lub innych metod mogących zmienić wartość tego pola) to taka składowa jest niemodyfikowalna dla innych klas. I to, że klasa sama w sobie może edytować swoje składowe to jest spoko. Tylko powinieneś ograniczyć żeby inne klasy nie miały zbyt dużego dostępu do jej pól. 

Podobne pytania

0 głosów
1 odpowiedź 1,416 wizyt
pytanie zadane 12 listopada 2016 w JavaScript przez ReksetoDev Gaduła (4,530 p.)
0 głosów
2 odpowiedzi 1,470 wizyt
pytanie zadane 14 marca 2018 w Java przez mibdbz Gaduła (4,300 p.)
0 głosów
1 odpowiedź 2,132 wizyt
pytanie zadane 8 września 2016 w Java przez nemezisso Użytkownik (860 p.)

92,572 zapytań

141,422 odpowiedzi

319,644 komentarzy

61,959 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...