Vnořené třídy


Materiály

Vnořené třídy

Java umožňuje definovat třídu uvnitř jiné třídy. Takové třídy nazýváme vnořené (angl.: nested class). Vnořené třídy mohou byt statické a nestatické. Nestatické vnořené třídy nazýváme vnitřní třídy a statické nazýváme statické vnořené třídy.

    
    class OuterClass {
        ...
        static class StaticNestedClass {
            ...
        }

        class InnerClass {
            ...
        }
    }
    

Statické vnořené třídy

Podobně jako statické metody a atributy, jsou i statické vnořené třídy spojené se třídou. Statické vnořené třídy jsou přístupné pomocí jména vnější třídy která je "zapouzdřuje": OuterClass.StaticNestedClass.

    
    //Vytvoření instance objektu vnořené statické třídy
    OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();
    

Vnitřní třídy

Na rozdíl od statických vnořených tříd jsou vnitřní třídy vázané na instanci vnější třídy. Vnitřní trída má také přístup ke všem atributům a metodám vnější třídy a to ikdyž jsou deklarovány jako private. Pokud tedy chceme vytvořit instanci vnitřní třídy tak musíme nejprve vytvořit instanci vnější třídy.

    
        OuterClass outerObject = new OuterClass();
        OuterClass.InnerClass innerObject = outerObject.new InnerClass();
    

Lokální třídy

Speciálním případe vnořených jsou tzv. lokální třídy. Tyto třídy jsou definované uvnitř bloku. Typicky se tyto třídy vyskytují v těle metody. Více informací lze nalézt zde.

Anonymní třídy

Anonymní třídy umožňují současně deklarovat a vytvořit instanci. Tyto třídy nemají jméno. Používáme je tam, kde jejich instanci použijeme pouze jednou.

    
    interface HelloWorld {
        public void greet();
        public void greetSomeone(String someone);
    }

    HelloWorld frenchGreeting = new HelloWorld() {
        String name = "tout le monde";
        public void greet() {
            greetSomeone("tout le monde");
        }

        public void greetSomeone(String someone) {
            name = someone;
            System.out.println("Salut " + name);
        }
    };

    HelloWorld spanishGreeting = new HelloWorld() {
        String name = "mundo";
        public void greet() {
            greetSomeone("mundo");
        }
        public void greetSomeone(String someone) {
            name = someone;
            System.out.println("Hola, " + name);
        }
    };

    frenchGreeting.greetSomeone("Fred");
    spanishGreeting.greet();
    

Shadowing

Deklarace vnořených tříd mohou překrýt vlastnosti nadřazené třídy. Nelze se tedy na ně odkazovat pouze jejich jménem.

    
    public class ShadowTest {
        public int x = 0;

        class FirstLevel {
            public int x = 1;

            void methodInFirstLevel(int x) {
                System.out.println("x = " + x);
                System.out.println("this.x = " + this.x);
                System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
            }
        }

        public static void main(String... args) {
            ShadowTest st = new ShadowTest();
            ShadowTest.FirstLevel fl = st.new FirstLevel();
            fl.methodInFirstLevel(23); // -> x = 23, this.x = 1, ShadowTest.this.x = 0
        }
    }
    

Lambda výrazy

Při vytváření anonymních tříd které mají jednu metodu je zápis zbytečně složitý. Tento problém řeší lambda výrazy. Lambda výrazy v Javě vypadají následovně:

    
        (int x, int y) -> x * y
        () -> System.out.print("Hello World")
        (String s) -> { System.out.println(s); }
        () -> { return 42 };
    

Spustitelné lambda výrazy

    

    Runnable r1 = new Runnable() {
        @Override
        public void run() {
            System.out.println("Runnable using anonymous class.");
        }
    };

    // Lambda Runnable
    Runnable r2 = () -> System.out.println("Runnable using lambda expression.");
    r1.run();
    r2.run();
    

Runnable představuje funkční interface. Mimo ty které obsahuje Java si můžeme definovat své vlastní a to následovně:

    
    @FunctionalInterface
    public interface WorkerInterface {
        public void doSomeWork();
    }
    

Funckční interface může obsahovat pouze jednu metodu.

    

    List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

    // Pomocí for cyklu
    for(Integer n: list) {
        System.out.println(n);
    }

    // Pomocí lambda výrazu
    list.forEach(n -> System.out.println(n));

    // Syntaktický cukr v Java 8
    list.forEach(System.out::println);
    

Úkoly

  1. S využitím rozhraní

        
        public interface Mapping {
            Object map(Object o);
        }
        
    
    implementujte metodu List<Object> map(List<Object> list, Mapping m);, která se chová stejně jako funkce map, známá z jazyka Scheme/Lisp.

  2. Stejným způsobem navrhněte metodu List<Object> filter(List<Object> list, Condition c), která ze seznamu vybere hodnoty splňující danou podmínku. Navrhňete vhodnou třídu/rozhraní Condition. Metodu otestujte výběrem lichých celých čísel ze seznamu. Podmínku vytvořte jako vnitřní třídu, jako anonymní třídu i jako lambda výraz.

  3. Stejným způsobem navrhněte metodu Object reduce(Object terminator, List<Object> list, Reducing c), která se chová obdobně jako procedura foldr v jazyce Scheme. Metodu otestujte součtem celých čísel v seznamu. Reducing vytvořte jako vnitřní třídu, jako anonymní třídu i jako lambda výraz.

Úkoly posílejte na email r.vyjidacek@gmail.com s předmětem ZP3JV05. Termín odevzdání do půlnoci 1. 11. 2017. Odevzdávejte pouze zdrojové kódy v archivu zip.