Neměnné objekty

Tomáš Pitner, upravil Marek Šabo
tomp@fi.muni.cz

Neměnné objekty

  • 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

Příklad neměnného objektu

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

Výhody a nevýhody

Výhody
  • 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)
Nevýhody
  • chceme-li objekt byť jen drobně změnit, musíme vytvořit nový
  • to stojí čas a paměť

Porovnání

  • Neměnný objekt vs. konstanta

    • konstanta je jenom jedna (je statická)
    • neměnných objektů může být víc s různými hodnotami
    • kdyby jich bylo více a měly stejnou hodnotu, postrádá smysl

  • Neměnný objekt vs. final

    • final prakticky zakáže změnu odkazu
    • nelze tedy do dané proměnné přiřadit odkaz na jiný objekt
    • samotný objekt ale může být dál "vevnitř" modifikován

Příklad final

final Person p = new Person("Marek");
// p = new Person("Jan"); // cannot be done
p.setName("Haha I changed it"); // this works!

Vestavěné neměnné třídy

  • 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í:
String s = "Hello " + "World";
  • Kód vytvoří 3 objekty: "Hello ", "World" a "Hello World".
  • Cože? To je tak neefektivní?
  • Pokud by vysloveně vadilo, lze místo String použít StringBuilder, který je modifikovatelný (mutable), viz dále

String pod lupou

  • Podívejme se na rozdíl "Hello" a new String("Hello").

    new String("Hello")

    vytvoří pokaždé nový objekt

    "Hello"

    funguje na principu: jestli taký objekt zatím neexistuje, tak ho vytvořím, jinak vrátím odkaz na již existující objekt (uložen v paměti String constant pool)

String s1 = "Hello";
String s2 = "Hello";
boolean bTrue = (s1 == s2); // true, identical objects

s1 = new String("Hello");
s2 = new String("Hello");
boolean bFalse = (s1 == s2); // false, different objects though with same value

Metody třídy String

  • char charAt(int index) — vráti prvek na daném indexu
  • static String format(String format, Object…​ args) — stejné jako printf v C
  • boolean isEmpty() — vráti true jestli je prázdný
  • int length() — velikost řetězce
  • matches, split, indexOf, startsWith …​
Více metod najdete v dokumentaci třídy String.
  • Doporučujeme javadoc prostudovat, používaním existujících metod jsi ušetříte spoustu práce!

Třída StringBuilder

StringBuilder builder = new StringBuilder("Hello ");
builder.append("cruel ").append("world"); // method chain
builder.append("!");
String result = builder.toString();
  • StringBuilder se průběžně modifikuje, přidáváme do něj další znaky
  • Na závěr vytvoříme výsledný řetězec
  • StringBuilder není thread safe, proto existuje její varianta StringBuffer.

Objektové obálky nad primitivními hodnotami

  • 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;

Konstanty objektových obálek

  • Wrappers (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;

Metody objektových obálek

  • 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.

Víc hodnot

  • 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

Rozdíl od primitivních typů

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ší
  • 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ě

Doporučení

  • 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

Přechod mezi primitivními a objektovými typy

  • Java podporuje automatické balení (boxing) a vybalení (unboxing) mezi primitivními typy a wrappery.
  • Proto je následující kód je naprosto v pořádku
int primitiveInt = 42;
Integer objectInt = primitiveInt;
primitiveInt = new Integer(43);
  • Jak už bylo řečeno, použití primitivního typu je obvykle lepší nápad - jsme v Javě :-)

Zvláštnosti

  • 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).

/