Statické proměnné a metody

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

Úvod

  • Se statickou metodou jsme se setkali už u úplně prvního programu - Hello, world!
public class Demo {
  public static void main(String[] args) {
    System.out.println("Hello World!");
  }
}
  • public reprezentuje viditelnost — metoda je veřejná
  • void reprezentuje návratový typ — nic nevrací
  • Co reprezentuje static?
  • Metoda static v Javě (jinde může být chápáno jinak) říká, že metoda nepotřebuje pro své fungování žádný konkrétní existující objekt, s nímž by pracovala
  • To přesně potřebujeme u main, neboť žádný objekt dosud nemáme

Dosud byly nestatické metody a atributy

  • Dosud jsme zmiňovali atributy (proměnné) a metody objektu.
  • Jméno (atribut String name) patří přímo jedné osobě
  • Metoda toString vrátí Person jméno této osoby
public class Person {
  private String name; // name of this person
  ...
  public String toString() {
    return "Person " + name; // returns name of this person
  }
}

Jak fungují statické metody a atributy

  • Lze deklarovat také metody a proměnné patřící celé třídě, tj. všem objektům třídy.
  • Taková proměnná (nebo metoda) existuje pro jednu třídu jen jednou.
  • Označujeme ji static.

Použití statických metod

  • Chceme metodu max, která vrací maximální hodnotu dvou čísel
  • K tomu žádné objekty nepotřebujeme
  • Uděláme ji jako statickou
public class Calculate {
  public static int max(int a, int b) {
    if (a > b) return a;
    return b;
  }
}

Jde i jako nestatická?

  • Ano, takto. Vynecháme static.
public class Calculate {
  public int max(int a, int b) {
    if (a > b) return a;
    return b;
  }
}
  • Na spuštění max budu nyní potřebovat objekt třídy Calculate
Calculate c = new Calculate();
int max = c.max(1, 2); // method needs an object `c`
  • Ovšem ten objekt c je tam úplně k ničemu, s žádnými jeho atributy se nepracuje a ani žádné nemá

Řešení

  • Uděláme metodu statickou.
  • Pak metoda patří celé třídě a zavoláme ji názvem třídy bez konkrétního objektu
public class Calculate {
  public static int max(int a, int b) {
    if (a > b) return a;
    return b;
  }
}
...
int m = Calculate.max(1, 2);

Zpříjemnění použití statických metod

  • Někdy v kódu často používáme statické metody určité třídy, např. naše Calculator.max
  • Pro kratší zápis lze pak využít deklaraci import static dané metody
  • nebo všech metod přes *, např.
import static cz.muni.fi.pb162.Calculator.*;
...
int m = max(1, 2);

Typické použití statických metod

  • Velmi často se používají v obdobných situacích, jako výše uvedené max
  • Tzn. jednoduše pro implementaci funkce, která nevyužívá žádné atributy (data objektu), pouze dostane vstupy a něco vrátí
  • Pak ani logicky žádný objekt nepotřebuje
  • Příklady: metody třídy java.util.Arrays

Statické proměnné (atributy třídy)

  • Doposud jsem měli pouze proměnné (atributy) patřící konkrétnímu objektu
  • Např. ve třídě Person, která reprezentuje člověka, má každý člověk své (obvykle i odlišné) jméno
  • Někdy je ale situace, kdy pro celou třídu stačí určitý údaj jenom jednou
  • Příklad: chceme jsi pamatovat, kolik lidí se nám během chodu programu vytvořilo
  • Jak to udělat?

Počítaní lidí

  • Vytvoříme statickou proměnnou peopleCount a každý člověk ji při svém vzniku zvýší o jedna.
public class Person {
   private String name;
   private static int peopleCount = 0;
   public Person(String name) {
      this.name = name;
      peopleCount++;
   }
   public static int howManyPeople() {
      return peopleCount;
   }
}
  • Logicky na vrácení počtu lidí stačí statická metoda howManyPeople()

Počítaní lidí II

  • Použití bude vypadat následovně
Person.howManyPeople(); // 0
Person jan = new Person("Jan");
Person.howManyPeople(); // 1
Person anna = new Person("Anna");
Person.howManyPeople(); // 2

Voláni statické metody

Můžeme volat statickou metodu nad konkrétním objektem (instancí) dané třídy?

Person anna = new Person("Anna");
anna.howManyPeople();
  • Ano, není to problém.
  • Přes třídu Person je to však správnější.

Voláni nestatické metody

  • Můžeme volat nestatickou metodu jako statickou?
Person.getName();
  • Logicky ne!
  • Co by mohlo volání Person.getName() vrátit? Nedává to smysl.
  • Jde nám přece o jméno konkrétního člověka, tj. atribut v konkrétním objektu Person
  • Atribut name se nastaví až při zavolání konstruktoru

Přístup ze statické metody k nestatickému atributu?

  • Obdobně platí pro atributy, tj. NELZE toto:
public class NonStaticTest {
  private int count = 0;
  public static void main(String args[]) {
    count++; // compiler error
  }
}
  • Java ohlásí při překladu chybu: non-static variable count cannot be referenced from a static context.
  • Metoda main je statická — může používat pouze statické metody a proměnné.

Přístup k nestatickému atributu

  • Jedině po vytvoření konkrétní instance:
public class NonStaticTest {
  private int count = 0;
  public static void main(String args[]) {
    NonStaticTest test = new NonStaticTest();
    test.count++;
  }
}
  • NOTE: Všimněte si, že ve třídě mohu vytvořit objekt té stejné třídy.
  • Dalším řešením by bylo udělat count statický.

Problémy se statickými metodami? NE

  • Ano, použití static není tak prosté, jak jsme dosud prezentovali :-)
  • Statické metody většinou problém nejsou
  • Jednoduše slouží k realizaci nějaké činnosti, které stačí předané vstupy (parametry) a která nepotřebuje žádný "svůj" objekt s atributy
  • Důkazem je řada použití statických metod v Java Core API, kde jsou třídy, které mají jen statické metody
  • Takovým třídám se říká utility classes (jakési "účelové" třídy)

Problémy se statickými proměnnými? ANO

  • U statických proměnných je to složitější
  • Jejich použití musí být hodně dobře zdůvodněné
  • Opravdu potřebujeme danou hodnotu pro danou třídu právě jednou???
  • Nestane se dobudoucna, že jich budeme potřebovat více???

Možné řešení — singleton

  • Velmi často je daleko lepší aplikovat návrhový vzor singleton (jedináček)
  • Tj. vyrobit si třídu, která bude soustřeďovat (jako nestatické atributy) ta data, která jsme nechtěli dávat do ostatních tříd
  • Např. náš výše uvedený počet osob, kromě toho i další údaje, např. nějaký další souhrn za všechny osoby, třeba proměrný plat, věk…​
  • U singletonu musíme zajistit, aby objekt existoval nejvýše jedenkrát, a to jakmile ho potřebujeme.
  • Musíme tedy zabránit přímému vytváření objektů zvnějšku pomocí new.
  • Na získání instance zvnějšku se pak použije volání statické metody, obvykle něco jako getInstance().

Příklad singletonu

public class PersonCounter {
   // here will be the singleton instance (object)
   private static PersonCounter counter = null;
   private int peopleCount = 0;
   // "private" prevents creation of instance via new PersonCounter()
   private PersonCounter() {}
   // creates the singleton unless it exists
   public static PersonCounter getInstance() {
      if(counter == null) {
         counter = new PersonCounter();
      }
      return counter;
   }
   public void addPerson() {
      peopleCount++;
   }
   public int howManyPeople() {
      return peopleCount;
   }
}

Import statických prvků

  • Už jsme ukázali výše, že statické třídy i metody můžeme importovat:
import static java.lang.System.out;
public class HelloWorld {
  public static void main(String[] args) {
    out.println("Hello World!");
  }
}
  • relevantní pouze pro statické metody a proměnné
  • vhodně použitelné pro některé věci z Java Core API, např. Math
  • TIP: Více informací: Wikipedia:Static import

Problémy importu statických prvků

import static java.lang.System.out;
// developer is reading the code
out.println("Hello World!"); // what is out?
// few lines above:
PrintStream out;
// ahh ok, I thought it was System.out
  • Takže někdy pak nevíme, jestli jde o statický import a nebo jen o lokální proměnnou/metodu.
  • A jakmile něco nevíme na první pohled, JE TO ŠPATNĚ! :-)

/