Diskuze: Pomoc s osvělením zájkladů OOP - konkrétně equals/hashcode a referenční typ - Object
V předchozím kvízu, Online test znalostí Java, jsme si ověřili nabyté zkušenosti z kurzu.
a s čím bys chtěl poradit? jako vysvětlit, co znamená equals a jak pracuje Java s pamětí?
Atrament:20.10.2016 13:02
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.
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?
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.println(a);
System.out.println(zabalenyInt);
System.out.println("");
a = 12;
System.out.println(a);
System.out.println(zabalenyInt);
Lubor Pešek:20.10.2016 17:32
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áš?:)
a u toho druhého případu mám takový dojem, že si pleteš primitivní a objektové datové typy.
Lubor Pešek:20.10.2016 18:22
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ě.
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
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.
coells:22.10.2016 1:36
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
.
+20 Zkušeností
+2,50 Kč
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.println(zabalenyInt);
System.out.println(druhyInt);
// sem to je OK
/*
Vystup:
5
5
*/
// Nyni zmenim:
zabalenyInt = 14;
System.out.println(zabalenyInt);
System.out.println(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
*/
coells:24.10.2016 12:07
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.
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.
Zobrazeno 14 zpráv z 14.