public class Vertex1D {
private int x;
public Vertex1D(int x) {
this.x = x;
}
public int getX() {
return x;
}
}
...
Vertex1D v = new Vertex1D(1);
// x of value 1 cannot be changed anymore
Tomáš Pitner, Radek Ošlejšek
Neměnný (immutable) objekt nemůže být po jeho vytvoření modifikován
Bezpečně víme, co v něm až do konce života bude
Tudíž může být souběžně používán z více míst, aniž by hrozily nekonzistence
Jsou prostředkem, jak psát robustní a bezpečný kód
K jejich vytvoření nepotřebujeme žádný speciální nástroj
Od nových verzí Javy (14+) lze pro tento účel s výhodou využít typy record
(záznam)
public class Vertex1D {
private int x;
public Vertex1D(int x) {
this.x = x;
}
public int getX() {
return x;
}
}
...
Vertex1D v = new Vertex1D(1);
// x of value 1 cannot be changed anymore
record
)Namísto třídy class
můžeme použít record
Tím se definuje třída, kde každý objekt bude mít vlastnosti name
a address
,
které se nastaví jednou a už nejdou změnit, čili jako výše Vertex1D
.
public record Person (String name, String address) {}
//...
Person p = new Person("Pavel Holec", "Lipová 3, Brno");
record
?record
je vlastně typ tříd neměnných objektů.
Překladač pro nás automaticky pro tuto třídy vytvoří:
konstruktor mající parametr pro nastavení každého atributu (hodnoty) v objektu
přístupové metody pro čtení atributů, např. person.name()
record
Dále pak se "samy vytvoří":
metoda equals
pro porovnání objektů: dva záznamy budou stejné <⇒ jsou stejné všechny odpovídající si atributy
metoda hashCode()
konzistentní s equals()
"inteligentní" metoda toString
vracející např. Person[name=John Doe, address=100 Linda Ln.]
Jména metod jsou odlišná od konvence používané u JavaBeans nebo obecně u javových objektů:
tradičně by bylo person.getName() , ale u záznamu person.name() |
je to vláknově bezpečné (thread safe) — objekt může být bezpečně používán více vlákny naráz
programátor má jistotu, že se mu obsah objektu nezmění — silný předpoklad
kód je čitelnější, udržovanější i bezpečnější (např. útočník nemůže změnit náš token)
chceme-li objekt byť jen drobně změnit, musíme vytvořit nový
to stojí čas a paměť
final
) odkazfinal Person p = new Person("Marek");
// reference cannot be changed
// p = new Person("Jan");
// but object itself can be changed
p.setName("Haha I changed it"); // this works!
record Person(String name) {}
...
Person p = new Person("Marek");
// reference can be changed
p = new Person("Jan");
// but object itself cannot be changed
// p.setName("Haha I changed it");
Neměnnou třídou v Javě je String
.
Má to řadu dobrých důvodů - tytéž jednou definované řetězce lze používat souběžně z více míst programu
Nicméně i negativní stránky - někdy větší režie spojená s nemodifikovatelností
Velkou skupinou vestavěných neměnných objektů jsou tzv. objektové obálky primitivních hodnot.
Java má primitivní typy — int
, char
, double
, …
Ke každému primitivnímu typu existuje varianta objektového typu — Integer
, Character
, Double
, …
Tyto objektové typy se nazývají wrappers.
Objekty jsou neměnné
Při vytváření takových objektů není nutné používat new
,
využije se tzv. autoboxing, např. Integer five = 5;
Obdobně autounboxing, int alsoFive = five;
Integer objectInt = new Integer(42);
Integer objectInt2 = 42;
Objektové obálky (např. Double
) mají různé konstanty:
MIN_VALUE
je minimální hodnota jakou může double
obsahovat
POSITIVE_INFINITY
reprezentuje konstantu kladné nekonečno
NaN
je zkratkou Not-a-Number — dostaneme ji např. dělením nuly
Protože konstanty jsou statické, jejich hodnoty získáme přes název třídy:
double d = Double.MIN_VALUE;
d = Double.NEGATIVE_INFINITY;
např. pro Double
existuje static double parseDouble(String s)
— udělá ze String
číslo, z "1.0"
, vytvoří číslo 1.0
obdobně pro Integer
a další číselné typy
pro převody na číselné typy dále int intValue()
převod double
do typu int
boolean isNaN()
— test, jestli není hodnotou číslo
Více konstant a metod popisuje javadoc. |
Test: Objektové typy (Integer
) mají od primitivních (int
) jednu hodnotu navíc — uhádněte jakou!
je to null
Integer
je objektový typ, proměnná je odkaz na objekt
Proč tedy vůbec používat primitivní typy, když máme typy objektové?
int i = 1
zabere v paměti právě jen 32 bitů
používáme přímo danou paměť, jednička je uložena přímo v i
Integer i = 1
je třeba alokace paměti pro objekt, zkonstruování objektu s obsahem 1
v proměnné i
je pouze odkaz, je to (nepatrně) pomalejší
[NOTE] Výkon může být u velkého počtu objektů problém, např. vytvoření miliónu proměnných typu Integer
namísto int
může mít dopad na výkon a zcela jistě zabere dost paměti, asi zbytečně.
Používejte hlavně primitivní typy
Využívejte metody objektových typů, hlavně statické, kde není třeba mít objekt
Řada objektových jazyků vůbec primitivní typy jako v Javě nemá, vše jsou objekty
Java podporuje automatické zabalení (boxing) a vybalení (unboxing) mezi primitivními typy a wrappery.
Proto je následující kód je naprosto v pořádku:
Nicméně použití primitivního typu je obvykle lepší nápad.
int primitiveInt = 42;
// jakoby new Integer(primitiveInt),
// tedy zabalení hodnoty do objektu
Integer objectInt = primitiveInt;
// vybalení hodnoty z objektu
primitiveInt = new Integer(43);
Zajímavost, anebo spíš podraz Javy:
Integer i1 = 100; // between -127 and 128
Integer i2 = 100;
boolean referencesAreEqual = (i1 == i2); // true
i1 = 300;
i2 = 300;
boolean referencesNotEqual = (i1 == i2); // false
Poučení: objekty pomocí ==
obvykle neporovnáváme (budeme se učit o equals
).
Optimalizace využití paměti: "valueOf() returns an Integer instance representing the specified int value. This method will always cache values in the range -128 to 127, inclusive, and may cache other values outside of this range."
Důsledek použití návrhové vzoru Flyweight (muší váha), kdy konstruktor (respektive autoboxing kód v Javě) někdy vrací již dříve vytvořenou instanci (de facto Singleton), jindy zas zcela novou instanci.