final (Java) - final (Java)

V programovacím jazyce Java se final klíčové slovo používá v několika kontextech k definování entity, kterou lze přiřadit pouze jednou.

Jakmile je final proměnná přiřazena, obsahuje vždy stejnou hodnotu. Pokud final proměnná obsahuje odkaz na objekt, pak může být stav objektu změněn operacemi na objektu, ale proměnná bude vždy odkazovat na stejný objekt (tato vlastnost final se nazývá netransitivita ). To platí také pro pole, protože pole jsou objekty; pokud final proměnná obsahuje odkaz na pole, pak mohou být komponenty pole změněny operacemi na poli, ale proměnná bude vždy odkazovat na stejné pole.

Závěrečné třídy

Konečná třída nelze dělit na podtřídy. Jelikož to může přinést výhody zabezpečení a efektivity, mnoho standardních tříd knihoven Java je konečných, například java.lang.System a java.lang.String .

Příklad:

public final class MyFinalClass {...}

public class ThisIsWrong extends MyFinalClass {...} // forbidden

Konečné metody

Konečnou metodu nelze přepsat ani skrýt podtřídami. To se používá k zabránění neočekávaného chování podtřídy, která mění metodu, která může být zásadní pro funkci nebo konzistenci třídy.

Příklad:

public class Base
{
    public       void m1() {...}
    public final void m2() {...}

    public static       void m3() {...}
    public static final void m4() {...}
}

public class Derived extends Base
{
    public void m1() {...}  // OK, overriding Base#m1()
    public void m2() {...}  // forbidden

    public static void m3() {...}  // OK, hiding Base#m3()
    public static void m4() {...}  // forbidden
}

Běžnou mylnou představou je, že deklarování metody jako final zlepšuje účinnost tím, že umožňuje kompilátoru přímo vložit metodu, ať se volá kdekoli (viz inline expanze ). Protože je metoda načítána za běhu , kompilátoři to nemohou udělat. Pouze běhové prostředí a kompilátor JIT přesně vědí, které třídy byly načteny, a tak pouze oni mohou rozhodovat o tom, kdy se mají vložit, bez ohledu na to, zda je metoda konečná.

Překladače strojových kódů, které generují přímo spustitelný strojový kód specifický pro platformu , jsou výjimkou. Při použití statického propojení může kompilátor bezpečně předpokládat, že metody a proměnné vypočítatelné v době kompilace mohou být vložené.

Konečné proměnné

Konečný proměnná může být inicializována pouze jednou, a to buď prostřednictvím inicializátor nebo příkazu přiřazení. Není nutné jej inicializovat v místě deklarace: nazývá se to „prázdná konečná“ proměnná. Prázdná proměnná konečné instance třídy musí být definitivně přiřazena v každém konstruktoru třídy, ve které je deklarována; podobně musí být definitivně přiřazena prázdná konečná statická proměnná ve statickém inicializátoru třídy, ve které je deklarována; jinak dojde v obou případech k chybě při kompilaci. (Poznámka: Pokud je proměnná odkazem, znamená to, že proměnnou nelze znovu spojit s odkazem na jiný objekt. Ale objekt, na který odkazuje, je stále proměnlivý , pokud byl původně proměnlivý.)

Na rozdíl od hodnoty konstanty není hodnota konečné proměnné nutně známa v době kompilace. Za dobrou praxi se považuje reprezentace konečných konstant ve všech velkých písmenech pomocí podtržítka k oddělení slov.

Příklad:

public class Sphere {

    // pi is a universal constant, about as constant as anything can be.
    public static final double PI = 3.141592653589793;

    public final double radius;
    public final double xPos;
    public final double yPos;
    public final double zPos;

    Sphere(double x, double y, double z, double r) {
         radius = r;
         xPos = x;
         yPos = y;
         zPos = z;
    }

    [...]
}

Jakýkoli pokus o přiřazení radius , xPos , yPos nebo zPos bude mít za následek chyby kompilace. Ve skutečnosti, i když konstruktor nenastaví konečnou proměnnou, pokus o její nastavení mimo konstruktor bude mít za následek chybu kompilace.

Pro ilustraci, že konečnost nezaručuje neměnnost: Předpokládejme, že tři poziční proměnné nahradíme jedinou:

    public final Position pos;

kde pos je objekt s třemi vlastnostmi pos.x , pos.y a pos.z . Pak pos nelze přiřadit, ale tři vlastnosti mohou, pokud nejsou samy o sobě konečné.

Stejně jako plná neměnnost má použití konečných proměnných velké výhody, zejména při optimalizaci. Například Sphere pravděpodobně bude mít funkci, která vrací svůj objem; vědění, že jeho poloměr je konstantní, nám umožňuje zapamatovat si vypočítaný objem. Pokud máme relativně málo Sphere sa potřebujeme jejich objemy velmi často, může být nárůst výkonu značný. Vytvoření poloměru a Sphere final informuje vývojáře a překladače, že tento druh optimalizace je možný ve všech kódech, které používají Sphere s.

I když se zdá, že tato final zásada porušuje , je to následující právní prohlášení:

for (final SomeObject obj : someList) {
   // do something with obj
}

Vzhledem k tomu, že proměnná obj vychází z rozsahu s každou iterací smyčky, je ve skutečnosti znovu deklarována každá iterace, což umožňuje použít stejný token (tj. obj ) K reprezentaci více proměnných.

Konečné proměnné ve vnořených objektech

Konečné proměnné lze použít ke konstrukci stromů neměnných objektů. Jakmile jsou tyto objekty zkonstruovány, je zaručeno, že se již nezmění. Aby toho bylo možné dosáhnout, musí mít neměnná třída pouze finální pole a tato konečná pole mohou mít pouze neměnné typy. Primitivní typy Java jsou neměnné, stejně jako řetězce a několik dalších tříd.

Pokud je výše uvedená konstrukce porušena tím, že má ve stromu objekt, který není neměnný, nedrží se očekávání, že vše, co je dosažitelné pomocí konečné proměnné, je konstantní. Například následující kód definuje souřadnicový systém, jehož počátek by měl být vždy na (0, 0). Původ je implementován pomocí a java.awt.Point a tato třída definuje svá pole jako veřejná a upravitelná. To znamená, že i když se k origin objektu dostanete přes přístupovou cestu pouze s konečnými proměnnými, lze tento objekt stále upravovat, jak ukazuje níže uvedený ukázkový kód.

import java.awt.Point;

public class FinalDemo {

    static class CoordinateSystem {
        private final Point origin = new Point(0, 0);

        public Point getOrigin() { return origin; }
    }

    public static void main(String[] args) {
        CoordinateSystem coordinateSystem = new CoordinateSystem();

        coordinateSystem.getOrigin().x = 15;

        assert coordinateSystem.getOrigin().getX() == 0;
    }
}

Důvodem je to, že deklarace proměnné final znamená pouze to, že tato proměnná bude kdykoli ukazovat na stejný objekt. Objekt, na který proměnná ukazuje, však není ovlivněn touto konečnou proměnnou. Ve výše uvedeném příkladu lze souřadnice x a y počátku libovolně upravovat.

Aby se zabránilo této nežádoucí situaci, běžným požadavkem je, že všechna pole neměnného objektu musí být konečná a že typy těchto polí musí být neměnné samy o sobě. To diskvalifikuje java.util.Date a java.awt.Point několik dalších tříd z použití v takových neměnných objektech.

Závěrečná a vnitřní třída

Když je v těle metody definována anonymní vnitřní třída , všechny proměnné deklarované final v rozsahu této metody jsou přístupné z vnitřní třídy. U skalárních hodnot se hodnota final proměnné po přiřazení nemůže změnit. U hodnot objektů se reference nemůže změnit. To umožňuje kompilátoru Java „zachytit“ hodnotu proměnné za běhu a uložit kopii jako pole ve vnitřní třídě. Jakmile je vnější metoda ukončena a její rámec zásobníku byl odstraněn, původní proměnná je pryč, ale soukromá kopie vnitřní třídy přetrvává ve vlastní paměti třídy.

import javax.swing.*;

public class FooGUI {

    public static void main(String[] args) {
        //initialize GUI components
        final JFrame jf = new JFrame("Hello world!"); //allows jf to be accessed from inner class body
        jf.add(new JButton("Click me"));

        // pack and make visible on the Event-Dispatch Thread
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                jf.pack(); //this would be a compile-time error if jf were not final
                jf.setLocationRelativeTo(null);
                jf.setVisible(true);
            }
        });
    }
}

Prázdné finále

Blank finále , který byl představen v Javě 1.1, je konečná proměnná jejíž prohlášení postrádá inicializátor. Před verzí Java 1.1 byla finální proměnná vyžadována, aby měla inicializátor. Prázdné finále, podle definice „finále“, lze přiřadit pouze jednou. tj. při přiřazení musí být nepřiřazeno. Aby to bylo možné, kompilátor Java spustí analýzu toku, aby zajistil, že pro každé přiřazení k prázdné konečné proměnné bude proměnná před přiřazením definitivně nepřiřazena; jinak dojde k chybě při kompilaci.

final boolean hasTwoDigits;
if (number >= 10 && number < 100) {
  hasTwoDigits = true;
}
if (number > -100 && number <= -10) {
  hasTwoDigits = true; // compile-error because the final variable might already be assigned.
}

Kromě toho musí být před přístupem definitivně přiděleno prázdné finále.

final boolean isEven;

if (number % 2 == 0) {
  isEven = true;
}

System.out.println(isEven); // compile-error because the variable was not assigned in the else-case.

Všimněte si však, že před přístupem je třeba definitivně přiřadit i nefinální místní proměnnou.

boolean isEven; // *not* final

if (number % 2 == 0) {
  isEven = true;
}

System.out.println(isEven); // Same compile-error because the non-final variable was not assigned in the else-case.

C / C ++ analog konečných proměnných

V C a C ++ je analogickým konstruktem const klíčové slovo . To se podstatně liší od prostředí final Java, v zásadě se jedná o kvalifikátor typu : const je součástí typu , nejen součástí identifikátoru (proměnné). To také znamená, že stálost hodnoty lze změnit přetypováním (explicitní převod typu), v tomto případě známým jako „přetypování“. Odhazování konstance a následná úprava objektu má však za následek nedefinované chování, pokud byl objekt původně deklarován const . Java final je přísné pravidlo, takže není možné kompilovat kód, který přímo rozbíjí nebo obchází konečná omezení. Pomocí reflexe je však často možné stále měnit konečné proměnné. Tato funkce se většinou využívá při deserializaci objektů s konečnými členy.

Dále, protože C a C ++ vystavují ukazatele a odkazy přímo, existuje rozdíl mezi tím, zda je samotný ukazatel konstantní, a zda jsou data, na která ukazatel ukazuje, konstantní. Použití const na samotný ukazatel, jako v SomeClass * const ptr , znamená, že obsah, na který se odkazuje, lze upravit, ale samotný odkaz nemůže (bez přetypování). Toto použití má za následek chování, které napodobuje chování final odkazu na proměnnou v Javě. Naproti tomu při použití const pouze na odkazovaná data, jako v const SomeClass * ptr , obsah nelze upravit (bez přetypování), ale samotný odkaz ano. Odkaz i odkazovaný obsah lze deklarovat jako const .

C # analogy pro konečné klíčové slovo

C # lze považovat za podobný Java, pokud jde o jeho jazykové vlastnosti a základní syntaxi: Java má JVM, C # má .Net Framework; Java má bytecode, C # má MSIL; Java nemá podporu ukazatelů (skutečná paměť), C # je stejný.

Pokud jde o konečné klíčové slovo, má C # dvě související klíčová slova:

  1. Ekvivalentní klíčové slovo pro metody a třídy je sealed
  2. Ekvivalentní klíčové slovo pro proměnné je readonly

Všimněte si, že klíčovým rozdílem mezi klíčovým slovem odvozeným z C / C ++ const a klíčovým slovem C # readonly je to, že const je vyhodnocováno v době kompilace, zatímco readonly je vyhodnocováno za běhu, a tedy může mít výraz, který se vypočítá a opraví až později (za běhu).

Reference