Programowanie w języku Java WYKŁAD dr inż. Piotr Zabawa Certyfikowany Konsultant IBM/Rational e-mail: pzabawa@pk.edu.pl www: http://www.pk.edu.pl/~pzabawa 17.03.2014
WYKŁAD 4 Uzupełnienia Wykładu 2 Klasy zagnieżdżone Domknięcia dr inż. Piotr Zabawa Instytut Informatyki Wydział Fizyki, Matematyki i Informatyki
Polimorfizm metody wirtualne Wszystkie metody w Javie są wirtualne, za wyjątkiem: metod statycznych (bo przecież nie dotyczą obiektów), metod deklarowanych ze specyfikatorem final (co oznacza, że postać metody jest ostateczna i nie może być ona przedefiniowana w klasie pochodnej, a jak nie ma przedefiniowania, to niepotrzebna jest wirtualność), metod prywatnych (do których odwołania z innych metodach danej klasy nie są polimorficzne, bo metody prywatne nie mogą być przedefiniowane).
Polimorfizm metody wirtualne Przedefiniowując metody w podklasach warto używać adnotacji @Override. Daje ona sygnał kompilatorowi, że intencją programisty jest przedefiniowanie metody.
Adnotacje Zakład Inżynierii Oprogramowania Adnotacje są mechanizmem uzupełniania kodu o informacje wykraczające poza możliwości języka Java. Zostały wprowadzone w Java SE 5 jako reakcja na mechanizmy wcześniej udostępnione w C#. Adnotacje są zarówno wbudowane w pakiecie java.lang: @Override przesłonięcie metody z klasy bazowej @Deprecated ostrzeżenie o użyciu przestarzałego elementu języka @SupressWarnings wygaszanie ostrzeżeń kompilatora jak i możliwe do definiowania przez programistę. Adnotacje są wykorzystywane do dokumentowania kodu źródłowego. Składniowo stoją na równi z modyfikatorami, np. public, static.
Adnotacje Zakład Inżynierii Oprogramowania Przykład definicji adnotacji @Test jako tzw. markera (pusta): import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Test{ Definicja adnotacji przypomina interfejs.
Adnotacje Zakład Inżynierii Oprogramowania Przykład definicji adnotacji @Usecase: import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Usecase { public int id(); public String description() default brak opisu Powyższa adnotacja może być wykorzystana do oznaczania w kodzie metod stanowiących implementację przypadków użycia: @Usecase(id=47, decription= Realizacja zamówienia ) public void processorder(){
Adnotacje Zakład Inżynierii Oprogramowania Metaadnotacje Służą do definiowania adnotacji przez programistę. @Target @Retention @Documented @Inherited Do interpretowania kodu zawierającego adnotacje wykorzystuje się procesory adnotacji. Opierają się one o mechanizm refleksji, który omówimy poźniej. Są narzędzia wspomagające przetwarzanie adnotacji, takie jak np. apt. Będziemy intensywnie korzystali z adnotacji wbudowanych w plugin przy omawianiu testów jednostkowych i narzędzia JUnit4.
Adnotacje Zakład Inżynierii Oprogramowania Adnotacje można też wykorzystać do wygodniejszej obsługi zdarzeń, np. w GUI niż można to osiągnąć za pomocą samych klas wewnętrznych. Do tego zagadnienia wrócimy przy okazji omawiania biblioteki Swing.
Klasy i metody abstrakcyjne Metoda abstrakcyjna nie ma implementacji (ciała) i powinna być zadeklarowana ze specyfikatorem abstract. abstract int getsomething(); Klasa w której zadeklarowano jakąkolwiek metodę abstrakcyjną jest klasą abstrakcyjną i musi być opatrzona specyfikatorem abstract. Jeśli klasa jest abstrakcyjna, to nie musi mieć metod abstrakcyjnych. Klasa dziedzicząca klasę abstrakcyjną musi zdefiniować wszystkie abstrakcyjne metody tej klasy, albo sama będzie klasą abstrakcyjną i wtedy jej definicja musi być opatrzona specyfikatorem abstract.
Konstruktory Zakład Inżynierii Oprogramowania Konstruktory w Java: Domyślne mogą nie występować jawnie, są zawsze bezargumentowe Własne definiowane przez programistę Podobnie jak inne metody o tej samej nazwie muszą różnić się listą argumentów. W przypadku konstruowania obiektów, których klasy uwikłane są w hierarchie dziedziczenia, należy posłużyć się słowem kluczowym super. Dzięki niemu można przeprowadzić poprawną inicjalizację obiektów.
Konstruktory Zakład Inżynierii Oprogramowania Przykład listy inicjalizacyjnej ze słowem kluczowym super: public class Superclass { public String a; public int b; public Superclass(String a, int b) { this.a = a; this.b = b;
Konstruktory Zakład Inżynierii Oprogramowania public class Subclass extends Superclass { public float x; public Subclass(String a, int b, float x) { super(a, b); this.x = x;
Klasy zagnieżdżone Są one charakterystycznym elementem języka Java. Zapewne nie mają odpowiednika w żadnym innym języku programowania.
Klasy zagnieżdżone Czym są klasy wewnętrzne Klasa wewnętrzna to klasa zdefiniowana wewnątrz innej klasy. class A { class B { Klasa B jest klasą wewnętrzną w klasie A. Klasa A jest klasą otaczającą klasy B lub jej klasą zewnętrzną. Konstrukcja składniowa dla przekazywania this do konstruktora klasy wewnętrznej i późniejszego odwoływania się do niego.
Klasy zagnieżdżone Klasa zagnieżdżona - wewnętrzna - lokalna Terminologia: klasa zagnieżdżona każda klasa zdefiniowana wewnątrz innej klasy klasa wewnętrzna taka klasa zagnieżdżona, która nie jest statyczna klasa lokalna klasa zagnieżdżona zdefiniowana w bloku class OuterClass { static class StaticNestedClass { class InnerClass { void method(){ class LocalClass {;
Klasy zagnieżdżone Relacje między klasą otaczającą a klasą zagnieżdżoną istniejące w kodzie pośrednim.
Klasy zagnieżdżone Co może klasa wewnętrzna być zadeklarowana ze specyfikatorem private (normalne klasy nie!), uniemożliwiając wszelki dostęp spoza klasy otaczającej odwoływać się do niestatycznych składowych klasy otaczającej (jeśli nie jest zadeklarowana ze specyfikatorem static), być zadeklarowana ze specyfikatorem static (normalne klasy nie!), co powoduje, że z poziomu tej klasy nie można odwoływać się do składowych niestatycznych klasy otaczającej (takie klasy nazywają się zagnieżdżonymi, ich rola sprowadza się wyłacznie do porządkowania przestrzeni nazw i ew. lepszej strukturyzacji kodu)
Klasy zagnieżdżone Co może klasa wewnętrzna mieć nazwę (klasa nazwana), nie mieć nazwy (wewnętrzna klasa anonimowa), być lokalna zdefiniowana w bloku (metodzie lub innym bloku np. w bloku po instrukcji if), odwoływać się do zmiennych lokalnych (o ile jest lokalna, a zmienne są deklarowane ze specyfikatorem final).
Klasy zagnieżdżone Do czego są przydatne Klasy wewnętrzne mogą być ukryte przed innymi klasami pakietu (względy bezpieczeństwa). Klasy wewnętrzne pozwalają unikać kolizji nazw (np. nazwa klasy wewnętrznej nie koliduje nazwą z klasą zewnętrzną o tej samej nazwie). Klasy wewnętrzne pozwalają (czasami) na lepszą, bardziej klarowną strukturyzację kodu, bo można odwoływać się z nich do składowych (nawet prywatnych) klasy otaczającej, a przy tym zlokalizować pewne działania.
Klasy zagnieżdżone Do czego są przydatne Klasy wewnętrzne (w szczególności anonimowe) są intensywnie używane przy implementacji standardowych interfejsów Javy. Anonimowe klasy wewnętrzne pozwalają na traktowanie fragmentów kodu do wykonania (ściślej: metod przedefiniowywanych w tych klasach) jak obiektów, a wobec tego np. umieszczanie ich w tablicach, kolekcjach, czy przekazywanie innym metodom jako argumentów. Można to traktować jako bardzo częściową (ograniczoną co do semantyki i nieco żmudną w składni) realizację koncepcji domknięć (closures).
Klasy zagnieżdżone Klasa zaprzyjaźniona Java nie wspiera bezpośrednio możliwości tworzenia klas zaprzyjaźnionych (metod/atrybutów). Jednak można zasymulować ten mechanizm z wykorzystaniem klas wewnętrznych. public class A { private int privateint = 31415; public class SomePrivateMethods { public int getsomethingprivate() { return privateint; private SomePrivateMethods() { public void givekeyto(b other) { other.receivekey(new SomePrivateMethods());
Klasy zagnieżdżone public class B { private A.SomePrivateMethods key; public void receivekey(a.someprivatemethods key) { this.key = key; public void usageexample() { A ana = new A(); //int foo = ana.privateint; // doesn't work ana.givekeyto(this); int fii = key.getsomethingprivate(); System.out.println(fii);
Klasy zagnieżdżone Widoczność (visibility) klas wewnętrznych Sposób odwołania się do klasy wewnętrznej spoza klasy otaczającej jeśli klasa wewnętrzna jest: Prywatna brak możliwości Publiczna - NazwaKlasyOtaczającej.NazwaKlasyWewnętrznej Sposób odwołania się do klasy wewnętrznej z klasy otaczającej: Niezależnie od widoczności brak Sposób odwołania się z klasy otaczającej do klasy wewnętrznej: Statycznej: NazwaKlasyWewnętrznej.atrybut Niestatycznej:
Klasy zagnieżdżone Klasa anonimowa Jest to klasa wewnętrzna nie posiadająca nazwy. Najczęściej tworzymy klasy wewnętrzne po to, by przedefiniować jakieś metody klasy dziedziczonej przez klasę wewnętrzną bądź zdefiniować metody implementowanego przez nią interfejsu na użytek jednego obiektu. Referencję do tego obiektu chcemy traktować jako typu klasy dziedziczonej lub typu implementowanego interfejsu. Nazwa klasy wewnętrznej jest więc nam niepotrzebna i nie chcemy jej wymyślać. Wtedy stosujemy anonimowe klasy wewnętrzne.
Klasy zagnieżdżone Wykorzystuje się ją wtedy, gdy chcemy uniknąć tworzenia klas dla pojedynczych obiektów. Wszystko co o tej klasie wiemy to to, że implementuje ona pewien interfejs i właśnie za jego pośrednictwem możemy się do jej jedynego obiektu odwoływać. Typowy przykład zastosowania, to obsługa zdarzeń w GUI z wykorzystaniem prostych metod, jeśli nie muszą być współdzielone między komponentami. Jest zwykle wykorzystywana jako jedyna instancja implementacji interfejsu zarówno w pakietach bibliotek standardowych jak i we własnych implementacjach. UWAGA: odnośniki do domknięć (clojure),których ona jest namiastką.
Klasy zagnieżdżone Definicję anonimowej klasy wewnętrznej dostarczamy w wyrażeniu new. new NazwaTypu( parametry ) { // pola i metody klasy wewnętrznej gdzie: NazwaTypu nazwa nadklasy (klasy dziedziczonej w klasie wewnętrznej) lub implementowanego przez klasę wewnętrzną interfejsu, parametry argumenty przekazywane konstruktorowi nadklasy; w przypadku gdy Typ jest nazwą interfejsu lista parametrów jest oczywiście pusta (bo chodzi o implementację interfejsu).
Klasy zagnieżdżone Klasy wewnętrzne wspierają do pewnego stopnia brakujący w Java mechanizm wielokrotnego dziedziczenia. Jest on rozwiązany w pełni na poziomie interfejsów i częściowo na poziomie implementacji za pomocą klas wewnętrznych. Przykład symulacji wielokrotnego dziedziczenia za pomocą klas wewnętrznych pokazano na następnym slajdzie.
Klasy zagnieżdżone interface A { interface B { class X implements A, B { class Y implements A { B makeb() {return new B() {; static void takesb(b b){ main(){ Y y = new Y(); takesb(y.makeb()); // skrót
Klasy zagnieżdżone Kompilator Java generuje dla każdej klasy odpowiadający jej plik.class zawierający kod pośredni możliwy do wykonania na JVM. W przypadku klas wewnętrznych generuje on plik o nazwie: NazwaKlasyZewnętrznej$NazwaKlasyWewnętrznej.class a dla klas wewnętrznych anonimowych NazwaKlasyZewnętrznej$<i>.class gdzie i kolejny numer klasy anonimowej. Konstruowanie klas wewnętrznych jest dość skomplikowane, szczególnie w połączeniu z dziedziczeniem, ze względu na konieczność zapewnienia inicjalizacji niejawnej asocjacji z klasy wewnętrznej do klasy zewnętrznej.
Klasy zagnieżdżone Przykład konstrukcji klasy wewnętrznej: class Extern { class Inner { public class SuperInner extends Extern.Inner { SuperInner(Extern wi){ wi.super(); public static void main(string[] args) { Extern wi = new Extern(); SuperInner ii = new SuperInner(wi);
Klasy zagnieżdżone Przykład ilustrujący sposób korzystania z klas wewnętrznych oraz ich wybrane własności. UWAGA: przykład ten stanowi zarazem ilustrację zjawiska znanego z programowania obiektowego, które polega na propagacji statyczności.
Java SE 8
Java SE 8 Zakład Inżynierii Oprogramowania Omówione zostaną tutaj w zarysie te elementy języka, które mają zostać wprowadzone do Java SE w wersji 8.
Java SE 8 Zakład Inżynierii Oprogramowania W języku Java istnieją dwa szczególne rodzaje interfejsów: Marker interfejs, który nie ma pól ani metod Interfejs funkcyjny interfejs, który deklaruje dokładnie jedną metodę i żadnego pola. Przykłady: java.lang.runnable java.awt.event.actionlistener Implementacje interfejsów funkcyjnych wprowadzamy w kodzie za pomocą anonimowych klas wewnętrznych.
Java SE 8 wyrażenia Lambda Nazwy powiązane z pojęciem wyrażenia Lambda: Domknięcia (closures) Metody anonimowe (anonymous methods) W nawiązaniu do klas wewnętrznych, które mogą stanowić namiastkę domknięć omówione zostanie to pojęcie i sposób jego realizacji wprowadzony przez Java 8. Wyrażenie Lambda pozwala przenieść do metody fragment kodu zamiast obiektu. Dawniej można było używać jedynie idei callbacków wspieranej w Java za pomocą anonimowych klas wewnętrznych. Pozwala również na wykonanie tego samego kodu na grupie danych.
Java SE 8 wyrażenia Lambda Różnica między pojęciami Lambda i Domknięcie: Lambda jest funkcją anonimową funkcją zdefiniowaną bez nazwy. W niektórych językach jest równoważna funkcji nazwanej. W rzeczywistości definicja funkcji jest wewnętrznie przepisywana w czasie dopasowywania jej do zmiennej (binding). Domknięcie jest dowolną funkcją, która domyka (rozpościera się ponad) środowisko, w którym została zdefiniowana. Oznacza to, że może ona mieć dostęp do zmiennych, które nie są na jej liście parametrów.
Java SE 8 wyrażenia Lambda W jakiej sytuacji wybierać jaki rodzaj klas. Klasy lokalne warto używać gdy potrzebujemy wielu instancji jednej klasy, dostępu dok konstruktora, nazwanego typu Klasy anonimowe warto używać gdy chcemy zadeklarować pola lub dodatkowe metody Wyrażenia Lambda Warto używać w celu ukrycia fragmentu zachowania w celu przekazania go do innego fragmentu kodu. Np., jeśli chcemy wykonać tę samą akcję na każdym elemencie kolekcji, kiedy zakończy się przetwarzanie, kiedy wystąpi błąd Warto używać jeśli potrzebna jest prosta instancja interfejsu funkcyjnego i nie pasują pozostałe tu omawiane kryteria
Java SE 8 wyrażenia Lambda Klasy zagnieżdżone warto używać w podobnych okolicznościach jak klasy lokalnej, gdy potrzebujemy typu szerzej dostępnego i nie potrzebujemy dostępu do zmiennych lokalnych ani do parametrów metod Klasy wewnętrzne warto używać gdy potrzebujemy dostępu do niepublicznych pól i metod klasy otaczającej Statyczne klasy zagnieżdżone można używać gdy nie potrzebujemy dostępu do niepublicznych pól ani metod klasy otaczającej
Java SE 8 wyrażenia Lambda Ogólna postać wyrażenia lambda: (argument) -> (body) Przykłady: (int a, int b) -> { return a + b; () -> System.out.println("Hello World"); (String s) -> { System.out.println(s); () -> 42 () -> { return 3.1415 ;
Java SE 8 wyrażenia Lambda Do Java 8 dodano nową adnotację pozwalającą na definiowanie interfejsów funkcyjnych: @FunctionalInterface public interface WorkerInterface { public void dosomework();
Java SE 8 wyrażenia Lambda Sposób wykorzystania tak zdefiniowanego interfejsu: public class WorkerInterfaceTest { public static void execute(workerinterface worker) { worker.dosomework(); public static void main(string [] args) { execute(new WorkerInterface() { @Override public void dosomework() { System.out.println("Worker invoked using Anonymous class"); ); Zakład Inżynierii Oprogramowania execute( () -> System.out.println( Invoked!") );
Java SE 8 wyrażenia Lambda Na następnym slajdzie pokazano przykład ilustrujący sposób wykorzystania wyrażeń lambda do uzyskania w GUI efektu, jaki do tej pory był możliwy z wykorzystaniem anonimowych klas wewnętrznych.
Java SE 8 wyrażenia Lambda public class ListenerTest { public static void main(string[] args) { JButton testbutton = new JButton("Test Button"); testbutton.addactionlistener(new ActionListener(){ ); @Override public void actionperformed(actionevent ae){ System.out.println("Click Detected by Anon Class"); testbutton.addactionlistener(e -> System.out.println("Click Detected by Lambda Listner")); JFrame frame = new JFrame("Listener Test"); frame.setdefaultcloseoperation(jframe.exit_on_close); frame.add(testbutton, BorderLayout.CENTER); frame.pack(); frame.setvisible(true); Zakład Inżynierii Oprogramowania
Java SE 8 wyrażenia Lambda Jedna z głównych różnic w użyciu klas anonimowych i wyrażeń lambda leży w sposobie wykorzystania słowa kluczowego this. Klasa anonimowa odnosi się do klasy anonimowej Wyrażenie Lambda odnosi się do klasy, w której zostało zapisane Kolejna różnica tkwi w sposobie kompilacji. Klasa anonimowa kompilowana w sposób wyjaśniony wcześniej Wyrażenie Lambda kompilowane i konwertowane do prywatnej metody w klasie (z wykorzystaniem invokedynamic z Java 7)
Java SE 8 wyrażenia Lambda Przykład z klasą wewnętrzną przekazujemy kod do miejsca wywołania metody. btn.setonaction(new EventHandler<ActionEvent>() { @Override public void handle(actionevent event) { System.out.println("Hello World!"); ); btn.setonaction( event -> System.out.println("Hello World!") );
Java SE 8 wyrażenia Lambda Przykład z referencją do metody statycznej przekazujemy kod do miejsca wywołania metody. public class Utils { public static int comparebylength(string in, String out){ return in.length() - out.length(); public class MyClass { public void dosomething() { String[] args = new String[] {"microsoft","apple","lin ux","oracle" Arrays.sort(args, Utils::compareByLength);
Java SE 8 wyrażenia Lambda Przykład z referencją do metody nie-statycznej przekazujemy kod do miejsca wywołania metody. public class MyClass implements Comparable<MyObject> { @Override public int compareto(myobject obj) {return... public void dosomething() { MyObject myobject = new MyObject(); Arrays.sort(args, myobject::compareto);
Java SE 8 wyrażenia Lambda Przykład wykonywania tej samej metody na różnych danych. List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7); // z operatorem -> list.foreach(n -> System.out.println(n)); // z operatorem :: nowym w Java 8 list.foreach(system.out::println);
Java SE 8 wyrażenia Lambda UWAGA: W kontekście tego co mówiliśmy o wzorcach projektowych i różnych możliwościach ich implementowania, w tym z wykorzystaniem: Interfejsów Klas abstrakcyjnych Klas zagnieżdżonych/wewnętrznych Programowania aspektowego warto rozważyć możliwość wykorzystania w implementacji wzorców: Wyrażeń lambda Zakład Inżynierii Oprogramowania
Java SE 8 Type Annotations Jest to kolejny element dodany do Java 8. Prowadzi on do znacznego wzbogacenia języka o adnotacje, których zarówno zasięg, znaczenie, jak i sposób definiowania uległy zmianom. Ograniczymy się tu tylko do przykładów użycia i do ilustracji nowego sposobu definiowania adnotacji. Możliwość określania listy typów targetów @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER) public @interface Test { Przykład użycia Map<@NonNull String, @NonEmpty List<@Readonly Document>> documents;
Java SE 8 - nowości Nowe cechy Java 8: Lambda expressions Remove the Permanent Generation Small VM Parallel Array Sorting Bulk Data Operations for Collections Define a standard API for Base64 encoding and decoding New Date & Time API Provide stronger Password-Based-Encryption (PBE) algorithm implementations in the SunJCE provider
Koniec dr inż. Piotr Zabawa Instytut Informatyki Wydział Fizyki, Matematyki i Informatyki