Diskuze: Pomoc s osvělením zájkladů OOP - konkrétně equals/hashcode a referenční typ - Object

Java Java Pomoc s osvělením zájkladů OOP - konkrétně equals/hashcode a referenční typ - Object

Avatar
Zdeněk Zemek:

Ahoj všem, prosím o pomoc. Študuju Javu poctivě dle Vašich tutoriálů. Je to skvělé čtení, baví mě to. Mám trošku problém ve dvou článcích a potřeboval bych poradit, zda byste byl někdo tak hodný. Konkrétně
ZDE: http://www.itnetwork.cz/…cky-doplneni
a
ZDE:http://www.itnetwork.cz/…uals-a-clone

Je to v diskusi - autor: Zdeněk Zemek

Díky moc za Vaši práci.

 
Odpovědět 20. října 10:46
Avatar
Lubor Pešek
Člen
Avatar
Lubor Pešek:

a s čím bys chtěl poradit? jako vysvětlit, co znamená equals a jak pracuje Java s pamětí?

Nahoru Odpovědět 20. října 11:55
Existují dva způsoby, jak vyřešit problém. Za prvé vyhoďte počítač z okna. Za druhé vyhoďte okna z počítače.
Avatar
Atrament
Člen
Avatar
Odpovídá na Zdeněk Zemek
Atrament:

Tebou odkazované články jsou prémiové, jejich obsah včetně diskusí je tudíž dostupný pouze lidem, kteří si je koupí. Piš své dotazy přímo sem do fóra kde je to dostupné každému a je tak větší šance, že se najde někdo schopný a ochotný poradit.

 
Nahoru Odpovědět 20. října 13:02
Avatar
Zdeněk Zemek:

Ajo, to jsem si neuvědomil, že to není vidět..pardon.

Tedy k tomu equals();

Narazil jsem tam na něco čemu nerozumím. Píše se, že metodu equals() obsahuje každý objekt - vychází to z dědictví po třídě object a že porovnává hashcody, čili pokud bychom u dvou instancí svojí třídy dali porovnání equals() bylo by to stále TRUE. Ok, tomu rozumím.
Ale dále se píše - U své třídy je tedy občas vhodné překrýt i metodu equals(). Spolu s equals() je třeba překrýt i metodu hashCode(), která vrací výsledný hash kód objektu. A tomu nerozumím. Proč přepisovat hashcode?

Do dotazu k článku jsem psal toto:
Zkousel jsem změnit hashcody u jednotlivých instancí a stejně mi to funguje. Jak je to možný? Chápal jsem to tak, že se porovnávají hashkody také, čili hash by měl být stejný, čehož dosáhneme pomocí vygenerovaného kodu @Overrride hashcode- zkusil jsem si hashcody vypsat u každé instance a opravdu ty hashe byly stejné. Pokud ale metodu pro přepsání hashcodu zruším, funguje to taky. Není to náhodou tím, že je přepsána právě i metoda equals? Která původně porovnává hashe... jak bylo napsáno v článku..
Pokud to tak je, pak se mi ukazuje jako zbytečné přepisovat metodu hashcode()
Poradíte mi někdo prosím? :-)

 
Nahoru Odpovědět 20. října 15:01
Avatar
Zdeněk Zemek:

A k tomu druhému - tedy Boxingu mám tento dotaz:

Ahoj... já teda nevím jestli jsem to asi špatně pochopil, ale mě se zdá, že mi to v Javě 8 nejde.

Zopakuji co je psáno: S proměnnou zabalenyInt nyní pracujeme tak, jako by v ní byl typ referenční. Můžeme ji přiřadit do několika dalších proměnných a pokud se její hodnota změní.

int a = 10;
Object zabalenyInt = a;

Mě to ale nejde. Pokud změním proměnou a, tak zabalený int už má svoji vlastní hodnotu, čili se jedná o novou proměnou typu Object, která není ukazatelem.
Zkoušel jsem i vytvořit novou proměnou Object druhyObject a té přiřadit jako hodnotu Object zabalenyInt. To stejné. Nebyla referenční.
Pokud jsem po přiřazení:

Object druhyObject = zabalenyInt;

změnil hodnotu objektu zabalenyInt, tak druhyObject se nezměnil a měl již svoji vlastní hodnotu. Mohl by mi to prosím někdo osvětlit?

Zasílám zdroják:

int a = 10;

Object zabalenyInt = a;

System.out.prin­tln(a);
System.out.prin­tln(zabalenyIn­t);

System.out.prin­tln("");

a = 12;
System.out.prin­tln(a);
System.out.prin­tln(zabalenyIn­t);

 
Nahoru Odpovědět 20. října 15:04
Avatar
Lubor Pešek
Člen
Avatar
Odpovídá na Zdeněk Zemek
Lubor Pešek:

Nejsem si zcela jistý, ale tipoval bych, že je to kvůli té změně toho equals.
Hashcode se vypočítává z několika faktorů (proto je tak jedinečný).
Například (ale takhle to nemusí být, jen fakt příklad:D) když máš název proměnné, její datový typ, modifikátor přístupu, název třídy, obsah metody toString() a equals(), (metody, které musí obsahovat všechny třídy, co existují) tak se ti z těchto údajů vypočítá hash code. No a stačí, abys měl v metodě equals() místo písmene a -> e a už máš úplně jiný hash.

Proto, kdybys nezměnil i hash, tak ty upravíš metodu equals(), ale instance této třídy se nebudou patrně shodovat, protože každá bude nejspíš vypočítávat tu část hashe, podle které se to porovnává, jinak než druhá instance.

Pokud bys ovšem jen přepisoval metodu equals() na nějakou statickou podobu, tak by ten problém nastat nemusel. Otázka by ovšem zněla jinak - proč to děláš?:)

Nahoru Odpovědět  -1 20. října 17:32
Existují dva způsoby, jak vyřešit problém. Za prvé vyhoďte počítač z okna. Za druhé vyhoďte okna z počítače.
Avatar
Lubor Pešek
Člen
Avatar
Lubor Pešek:

a u toho druhého případu mám takový dojem, že si pleteš primitivní a objektové datové typy.

Nahoru Odpovědět 20. října 18:11
Existují dva způsoby, jak vyřešit problém. Za prvé vyhoďte počítač z okna. Za druhé vyhoďte okna z počítače.
Avatar
Lubor Pešek
Člen
Avatar
Odpovídá na Zdeněk Zemek
Lubor Pešek:

A jinak ten tvůj příklad, tak tady je vysvětlení:
Ty si nejdřív vytvoříš proměnnou datového typu "int" a s názvem "a" a přiřadíš mu nějakou hodnotu "10".
Tohle všechno se uloží do zásobníku paměti

*Jjestli nevíš co je zásobník a halda, znovu si to pročti), lepší tutoriál na to nikde na netu nenajdeš, tady to popsali tak, že by to pochopila i moje máti;)
*

No a pak vytváříš nový objekt, který má referenci "zabalenyInt". Tato reference se znovu ukládá do zásobníku, jako další údaj, ale objekt, na který tato reference se uloží do haldy (do toho druhého obrázku)

No a ty sice změníš tu hodnotu v zásobníku u proměnné int a, ale toho objektu se nedotkneš. Je to tak i dobře, protože ta proměnná a odkazuje na konkrétní hodnotu v zásobníku, ne na objekt v haldě.

Nahoru Odpovědět  -1 20. října 18:22
Existují dva způsoby, jak vyřešit problém. Za prvé vyhoďte počítač z okna. Za druhé vyhoďte okna z počítače.
Avatar
Lubor Pešek
Člen
Avatar
Lubor Pešek:

no a ještě ta tvoje poznámka - Pokud jsem po přiřazení: Object druhyObject = zabalenyInt
změnil hodnotu objektu zabalenyInt, tak druhyObject se nezměnil a měl již svoji vlastní hodnotu

pročti si znovu tenhle článek
http://www.itnetwork.cz/…ge-collector
Referenční a primitivní datové typy

Nahoru Odpovědět 20. října 18:25
Existují dva způsoby, jak vyřešit problém. Za prvé vyhoďte počítač z okna. Za druhé vyhoďte okna z počítače.
Avatar
Zdeněk Zemek:

pojmy zásobník - halda jsou mi známé a rozumím jim, děkuji. Ještě jednou i pro TEBE:

Toto je napsané v článku:
S proměnnou zabalenyInt nyní pracujeme tak, jako by v ní byl typ referenční. Můžeme ji přiřadit do několika dalších proměnných a pokud se její hodnota změní, změní se ve všech těchto proměnných. Dále může nabývat hodnoty null. Obyčejný int by se jen kopíroval bez jakékoli závislosti.

Logicky tedy pokud si vytvořím další proměnou např. Object druhyInt = zabalenyInt tak pokud bych změnil hodnotu zabalenyInt, tak druhy by se měl změnit také ne? Jelikož se jedná o referenční typ proměnné? Tak by to mělo podle mě být.

S tím hashem si vedle jak jedle. Nejdřív si pořádně přečti co píšu.

 
Nahoru Odpovědět 21. října 10:14
Avatar
coells
Redaktor
Avatar
Odpovídá na Zdeněk Zemek
coells:

hashCode() je metoda, pomocí které se v Javě implementuje technika celočíselných tříd ekvivalence.

Co to znamená?

Tvoje třída reprezentuje typ, který s sebou nese určitou tebou definovanou sémantiku, například že se dva různé objekty mohou rovnat, což můžeš vyjádřit vlastní definicí metody equals() s požadovaným chováním.
Jenže si představ, že máš miliony složitých objektů a chceš je vzájemně porovnávat - a porovnání v tomhle případě bude trvat dlouho.

Potom se dá použít následující zkratka.
Definuješ si současně s equals() také metodu hashCode(), která objektu přiradí celé číslo a budeš vyžadovat následující chování od objektů x,y: x.equals(y) => x.hashCode() == y.hashCode()

Neboli, pokud se mají dva objekty rovnat, pak musí mít stejný hashcode.
Opačně to neplatí, objekty se stejným hashcodem mohou být různé.

Pokud je objekt neměnný, stačí hashcode spočítat jen jednou a v případě, že máš milion složitých objektů k porovnání, můžeš v prvním kroku místo úplného porovnání pomocí equals() porovnat jen jejich hodnotu hashCode().
Tím se dá předejít velké spoustě složitého porovnávání, které bys jinak dělal zbytečně, protože rovnou víš, že budou objekty různé.
Nepomůžeš si, samozřejmě, v případě, že dva různé objekty mají stejný hashcode, tomu se říká false positive, ale to už bychom brousili příliš daleko (ve skutečnosti je za hashcode rozsáhlá a složitá matematická teorie).

Vztah mezi hashcode a equals spočívá pouze v implikaci, kterou jsem uvedl výše.
Není obecně pravda, že předefinováním hashcode přestane equals fungovat.

Takže proč bys měl vždy definovat obě metody naráz?

Některé kolekce v Javě využívají techniku hashování, což je rychlá operace vyhledání objektu na základě jeho celočíselné třídy ekvivalence.
Tyhle třídy vyžadují, aby byly obě metody implementované současně a v opačném případě budou vykazovat hodně podivné chování, které se špatně hledá.
A protože se jedná o často používané třídy a programátoři pravidelně zapomínali implementovat obě metody, zavedlo se pravidlo o současné implementaci obou metod.

Co se týká autoboxingu, představ si to takhle:

int a = 1;
Object b = a;
Object c = Integer.valueOf(a);

Java neumí hodnotové typy vkládat do referenčních, ale docela často se to hodí.
Proto začala podporovat autoboxing, což znamená, že kompilátor zjistí, že proměnnou a "chybně" ukládáš do proměnné b a zabalí ji do vhodného referenčního typu.
To znamená, že kompilátor pro proměnné b i c vygeneruje identický kód, abys zbytečné nemusel psát navíc něco, co je zjevné.

Integer je imutabilní typ a jeho hodnotu nemůžeš změnit, to je výhodné z řady dalších důvodů.
A změna proměnné a se nijak nemůže projevit na b nebo na c.

Akceptované řešení
+20 Zkušeností
+1 bodů
Řešení problému
 
Nahoru Odpovědět  +1 22. října 1:36
Avatar
Zdeněk Zemek:

Moc děkuju za vysvětlení... ještě bych měl dotaz... asi jsem fakt hloupej, tak mi to promiňte, ale v článku se píše:

S proměnnou zabalenyInt nyní pracujeme tak, jako by v ní byl typ referenční. Můžeme ji přiřadit do několika dalších proměnných a pokud se její hodnota změní, změní se ve všech těchto proměnných.

... mě to prostě nejde... vytvořím:

int a = 5;
Object zabalenyInt = a;

Object druhyInt = zabalenyInt;
System.out.prin­tln(zabalenyIn­t);
System.out.prin­tln(druhyInt);

// sem to je OK
/*
Vystup:
5
5
*/

// Nyni zmenim:

zabalenyInt = 14;
System.out.prin­tln(zabalenyIn­t);
System.out.prin­tln(druhyInt);

/*
Vysledek>
14
5

Podle mne by to - dle clanku - melo byt tedy, kdyz ma byt Object referencni promenou, ktera ukazuje na hodnotu v halde>
14
14
*/

 
Nahoru Odpovědět 24. října 11:18
Avatar
coells
Redaktor
Avatar
Odpovídá na Zdeněk Zemek
coells:

Autoboxing (česky auto-matické box-zabalení) jenom zabaluje hodnotu int do neměnného objektu Integer.
Je to takový malý trik, abychom mohli s hodnotou pracovat stejně jako s objektem, ale stále můžeme předstírat, že se jedná o hodnotu.

V tomhle případě určitě nečekáme, že by se změna proměnné a projevila na b, že?

int a = 1;
int b = a;
a = 3;

Když se do toho vloží autoboxing, je to podobné.

int a = 1;
Object b = a;
// Object b = Integer.valueOf(a);  <-- identicke jako predchozi radek
a = 3;

Pro proměnnou b se vygeneruje kód Integer.valueOf(a).
Hodnota proměnné a se vkládá do konstruktoru pro typ Integer a vytvořený objekt se už nikdy nezmění.
Sice teď máme v b referenci na objekt, ale po vytvoření už nemá s proměnnou a nic společného.

int a = 1;
Object zabalenyInt = a;
Object druhyInt = zabalenyInt;  // obsahuje stejnou! instanci jako `zabalenyInt`

a = 3;  // protoze `a` je hodnota, zmena se nedotkne ostatnich promennych

zabelenyInt = 14;  // prelozi se jako `Integer.valueOf(14)`, tzn. vytvori se novy objekt

// `druhyInt` stale obsahuje referenci na puvodni objekt ze `zabalenyInt`

Volání zabalenyInt = 14; způsobí, že kompilátor opět vygeneruje kód zabalenyInt = Integer.valueOf(14);
Původní reference z proměnné se zahodí a vytvoří se nová instance typu Integer.
V proměnných zabalenyInt a druhyInt teď máme dva různé objekty, které spolu už nemají nic společného.

Jak jsem říkal na začátku, pomocí autoboxingu předstíráme, že pracujeme s hodnotou, i když držíme objekt.
Aby bylo chování konzistentní, nesmí se takový objekt měnit a musí kopírovat chování hodnotového typu.
Změna proměnné zabalenyInt se tedy nesmí projevit na ostatních proměnných.

Článek jsem nečetl, takže nedokážu říct, jestli to autor myslel nějak jinak nebo je tam chyba.
Ale korektní chování je, že se proměnné zabalenyInt a druhyInt nesmí ovliňovat.

 
Nahoru Odpovědět  +1 24. října 12:07
Avatar
Zdeněk Zemek:

Moc děkuju, Jasné rozumím, Jsou to prostě nové objekty, které vzájemně nemají nic společného :-), Právě, že z toho článku to vyzní, aspoň pro mě trochu jinak, proto se na to jako trouba pořád ptám. Ale tak to budu brát, že to je takhle, což jsem si prakticky i ověřil a nebudu to řešit. :-).

Ještě jednou děkuji za vyčerpávající odpověď.
Z.

 
Nahoru Odpovědět 24. října 12:38
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 14 zpráv z 14.