Apilkacje w środowisku Java - wykład 8 Kolekcje c.d. ( mapy) Podstawy programowania funkcyjnego

Podobne dokumenty
Programowanie w Javie - wykład 13 Kolekcje c.d. ( mapy)

Programowanie w Javie - wykład 14 Podstawy programowania funkcyjnego

Aplikacje w Javie wykład 8 Kolekcje c.d. (komparatory, kolejki, mapy)

Aplikacje w Javie wykład 8 Kolekcje c.d. (komparatory, kolejki, mapy)

Programowanie w języku Java. Kolekcje

Programowanie w Javie- wykład 12 Kolekcje (zbiory)

java.util.* :Kolekcje Tomasz Borzyszkowski

Kolekcje. Na podstawie:

Kolekcje mgr Tomasz Xięski, Instytut Informatyki, Uniwersytet Śląski Katowice, 2011

Java Collections Framework

Kolekcja (kontener) to po prostu obiekt, który grupuje wiele elementów w jeden twór.

Programowanie obiektowe

Kurs programowania. Wykład 9. Wojciech Macyna. 28 kwiecień 2016

Realizacja ekstensji klasy. Paulina Strzelecka, Tomasz Roszkowski

Programowanie Obiektowe (Java)

Podstawy otwartych języków programowania Przechowywanie danych

Programowanie i projektowanie obiektowe

Aplikacje w Javie wykład 7 Kolekcje (listy, zbiory)

Programowanie w Javie- wykład 11 Kolekcje (listy)

Kolekcje - pakiet Java Collections Framework

Kurs programowania. Wykład 9. Wojciech Macyna

Programowanie w środowisku graficznym- wykład 8 Kolekcje c.d. ( mapy) Strumienie Wątki-podstawy

import java.util.*; public class ListExample { public static void main(string args[]) { List<String> lista1= new ArrayList<String> ();

Tworzenie aplikacji w języku Java

Kolekcje - pakiet Java Collections Framework

Platformy Programistyczne Podstawy języka Java

Programowanie w środowisku graficznym- wykład 7 Kolekcje (listy, zbiory)

API STREAM WYRAŻENIA LAMBDA

Dokumentacja do API Javy.

Lista, Stos, Kolejka, Tablica Asocjacyjna

Dawid Gierszewski Adam Hanasko

Język JAVA podstawy. Wykład 4, część 3. Jacek Rumiński. Politechnika Gdańska, Inżynieria Biomedyczna

Aplikacje w Javie- wykład 11 Wątki-podstawy

Języki i metody programowania Java INF302W Wykład 3 (część 2)

Aplikacje w Javie wykład 13 Elementy programowania funkcyjnego

Java SE Laboratorium nr 7. Temat: Kolekcje

PHP: bloki kodu, tablice, obiekty i formularze

Kolekcje w Javie cz. 1

Java niezbędnik programisty spotkanie nr 8. Kolekcje c.d.

Java. język programowania obiektowego. Programowanie w językach wysokiego poziomu. mgr inż. Anna Wawszczak

Aplikacje w Javie wykład 13 Elementy programowania funkcyjnego

Podstawy programowania skrót z wykładów:

Informatyka I. Klasy i obiekty. Podstawy programowania obiektowego. dr inż. Andrzej Czerepicki. Politechnika Warszawska Wydział Transportu 2018

dr inż. Piotr Czapiewski Tworzenie aplikacji w języku Java Laboratorium 1

Wydział Fizyki i Informatyki Stosowanej, Uniwersytetu Łódzkiego Łódź. Java podstawy języka, wykład 4 1

Wykład 4. Klasa List Kolejki Stosy Słowniki

Laboratorium 03: Podstawowe konstrukcje w języku Java [2h]

TYPY GENERYCZNE (GENERICS)

Klasy abstrakcyjne i interfejsy

Język JAVA podstawy. Wykład 3, część 3. Jacek Rumiński. Politechnika Gdańska, Inżynieria Biomedyczna

Interfejsy. Programowanie obiektowe. Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki Politechniki Wrocławskiej

Programowanie w języku Java - Wyjątki, obsługa wyjątków, generowanie wyjątków

Obiekt klasy jest definiowany poprzez jej składniki. Składnikami są różne zmienne oraz funkcje. Składniki opisują rzeczywisty stan obiektu.

Programowanie obiektowe

znajdowały się różne instrukcje) to tak naprawdę definicja funkcji main.

Laboratorium z przedmiotu: Inżynieria Oprogramowania INEK Instrukcja 7

Wątki. Definiowanie wątków jako klas potomnych Thread. Nadpisanie metody run().

Kurs programowania. Wykład 1. Wojciech Macyna. 3 marca 2016

Instrukcja 2 Laboratorium z Podstaw Inżynierii Oprogramowania

GUI - projektowanie interfejsów cz. II

Języki i techniki programowania Ćwiczenia 2

Programowanie obiektowe. Literatura: Autor: dr inŝ. Zofia Kruczkiewicz

Rozdział 4 KLASY, OBIEKTY, METODY

Podstawy i języki programowania

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

KOLEKCJE JAVY API: NAJPROSTSZE PODSTAWY

Języki i metody programowania Java INF302W Wykład 2 (część 1)

Wykład 8: Obsługa Wyjątków

Wykład 7: Pakiety i Interfejsy

Wyjątki. Streszczenie Celem wykładu jest omówienie tematyki wyjątków w Javie. Czas wykładu 45 minut.

Laboratorium z przedmiotu: Inżynieria Oprogramowania INEK Instrukcja 6

JAVA W SUPER EXPRESOWEJ PIGUŁCE

Podstawy programowania. Podstawy C# Tablice

Comparable<Klasa_uzytkownika>

Podejście obiektowe. Tablice obiektów Przykład 1 metody i atrybuty statyczne oraz niestatyczne

Podstawy programowania obiektowego

Pierwsze kroki. Algorytmy, niektóre zasady programowania, kompilacja, pierwszy program i jego struktura

Programowanie obiektowe

Języki i techniki programowania Ćwiczenia 4 Wzorce

Programowanie w Javie wykład 15 Elementy programowania funkcyjnego cd.

Programowanie obiektowe

Programowanie obiektowe

Klasy abstrakcyjne, interfejsy i polimorfizm

Interfejsy i klasy wewnętrzne

Tablice (jedno i wielowymiarowe), łańcuchy znaków

Kolekcje. object that groups multiple elements into a single unit


Metody Metody, parametry, zwracanie wartości

Konstruktory. Streszczenie Celem wykładu jest zaprezentowanie konstruktorów w Javie, syntaktyki oraz zalet ich stosowania. Czas wykładu 45 minut.

10.1. Wprowadzenie. List<jakiś_typ> target = creat e( src, tu podać warunek wyboru elementów z listy src, tu podać operację na wybranych elementach);

Java niezbędnik programisty spotkanie nr 9. Java 2 Platform, Standard Edition 5.0

2. Tablice. Tablice jednowymiarowe - wektory. Algorytmy i Struktury Danych

Kiedy potrzebne. Struktura (rekord) Struktura w języku C# Tablice struktur. struktura, kolekcja

Wykład 3 Składnia języka C# (cz. 2)

Programowanie, algorytmy i struktury danych

Laboratorium z przedmiotu Programowanie obiektowe - zestaw 04

Zadanie polega na stworzeniu bazy danych w pamięci zapewniającej efektywny dostęp do danych baza osób.

Programowanie w Javie wykład 8 Interfejsy

Listy, krotki, słowniki, funkcje

Tabela wewnętrzna - definicja

Transkrypt:

1 Apilkacje w środowisku Java - wykład 8 Kolekcje c.d. ( mapy) Podstawy programowania funkcyjnego Treści prezentowane w wykładzie zostały oparte o: Barteczko, JAVA Programowanie praktyczne od podstaw, PWN, 2014 http://docs.oracle.com/javase/8/docs/ http://docs.oracle.com/javase/9/docs/ C. S. Horstmann, G. Cornell, Java. Podstawy, Helion, Gliwice 2013 C. S. Horstmann, Java, Techniki zaawansowane(wydanie10), Helion, Gliwice 2017

Przetwarzanie list i zbiorów - przykłady Zmiana wartości elementów listy. Do każdego elementu listy firm (zmienna list), dodać przyrostek "-Polska". ArrayList<String> list = new ArrayList<>( Arrays.asList(new String[]{"Opel", "Fiat", "Audi")); for (int i=0; i<list.size(); i++) list.set(i, list.get(i) + " - Polska"); System.out.println(list); lub za pomocą iteratora listowego for (ListIterator<String> it = list.listiterator(); it.hasnext(); ) it.set(it.next() + " - Polska"); System.out.println(list); Usunięcie podanego obiektu z kolekcji. Usuniemy Opel z listy firm list. System.out.println("Przed: " + list); list.remove("opel"); System.out.println("Po: " + list); remove(ref) usuwa tylko pierwszy element, który został znaleziony ten sposób działa dla dowolnych modyfikowalnych kolekcji Można też usuwać element o podanym indeksie np. list.remove(3); W przypadku błędnego indeksu - IndexOutOfBoundsException 2

Przetwarzanie list i zbiorów - przykłady 3 Usuwanie za pomocą metody remove() iteratora System.out.println("Przed: " + list); for (Iterator<String> it = list.iterator(); it.hasnext();){ if(it.next().equals("opel")) it.remove(); System.out.println("Po: " + list); Ten sposób działa dla dowolnych modyfikowalnych kolekcji Operacje grupowe (bulk-operations) Metody addall(), removeall(), retainall(), to operacje grupowe (jednocześnie są wykonywane działania, które musielibyśmy programować za pomocą iteracji i metod contains(), remove(), add()). Zmienna list1 zawiera listę firm wczytanych z pliku, a list2 listę firm z innego pliku. Należy zmienić listę list1 tak, by zawierała dodatkowo, wszystkie te firmy z drugiego pliku, których nie było w pierwszym pliku. Przykładowe rozwiązanie:

Przetwarzanie list i zbiorów - przykłady 4 System.out.println("Na poczatku list1 : " + list1); System.out.println("Na poczatku list2 : " + list2); System.out.println("Usuwamy z list2, te które są na list1"); list2.removeall(list1); System.out.println("Teraz list2 : " + list2); System.out.println("Dodamy list2 do list1" ); list1.addall(list2); System.out.println("Na koncu list1 : " + list1); Gdyby chodziło tylko o podanie nazw firm bez powtórzeń: System.out.println("Dane z dwóch plików: "); System.out.println("list1 : " + list1); System.out.println("list2 : " + list2); HashSet<String> set = new HashSet<>(list1); set.addall(list2); System.out.println("Wynikowy zbiór : " + set);

Przetwarzanie list i zbiorów - przykłady W klasie Collections dostępne są metody statyczne, ułatwiające wykonywanie różnych operacji na kolekcjach. Oprócz wspomnianych wcześniej metod dostępne są dodatkowo metody odwracania list Collections.reverse(lista), wypełniania kolekcji i wiele innych. W trakcie przeglądania kolekcji za pomocą iteratora nie wolno jej modyfikować innymi środkami niż metody stosowanego właśnie do iterowania. Zatem // coll dowolna kolekcja napisów Iterator<String> it1=coll.iterator(); Iterator<String> it2=coll.iterator(); while(it1.hasnext()){ it1.next(); it2.next(); it2.remove(); // BŁĄD FAZY WYKONANIA wyjątek // ConcurrentModificationException Dotyczy to również pętli for-each dla kolekcji (stosowany jest w niej "pod spodem" iterator) for (String e : coll) coll.add(e+"1");//spowoduje ConcurrentModificationException 5

Kolejki 6 Wszystkie kolejki oferują następujące operacje, określane przez interfejs Queue. Operacja Metody zgłaszające wyjątki przy braku miejsca lub elementów Metody nie zgłaszające wyjątków przy braku miejsca lub elementów Wstawianie boolean add(t e) zwraca true, gdy sukces lub zgłasza wyjątek IllegalStateException przy braku miejsca Usuwanie Pobieranie bez usuwania T remove() zwraca usunięty z początku element typu T lub zgłasza wyjątek: NoSuchElementException jeśli kolejka jest pusta T element() zwraca element z początku lub zgłasza wyjątek: NoSuchElementException jeśli kolejka jest pusta boolean offer(t e) zwraca: true - gdy sukces, false - gdy nie ma miejsca. Zgłasza wyjątek IllegalArgumentException, jesli nie dopuszcza wstawienia danego rodzaju elementu T poll() zwraca usunięty z początku element typu T lub null, jesli kolejka jest pusta T peek() zwraca element z początku lub null, jesli kolejka jest pusta

Kolejki podwójne Kolejka podwójna (ang. deque) jest liniową sekwencją elementów, na której operacje wstawiania, pobierania i usuwania elementów są możliwe na obu końcach sekwencji. Operacje na kolejce podwójnej są określane przez interfejs Deque, który dodaje do Queue metody Wstawianie Usuwanie Pobieranie bez usuwania Z wyjątkiem przy braku miejsca lub elementów boolean addfirst(e) boolean addlast(e) T removefirst() T removelast() T getfirst() T getlast() Bez tego wyjątku boolean offerfirst(e) boolean offerlast(e) T pollfirst() T polllast() T peekfirst() T peeklast() Wśród gotowych implementacji kolejek w JCF są dostępne następujące klasy: LinkedList - lista, która jest jednocześnie kolejką podwójną (bez ograniczeń na miejsce), ArrayDeque - kolejka podwójna, zrealizowana jako rozszerzalna tablica PriorityQueue - kolejka z priorytetami. 7

Kolejki - przykład 8 import java.util.*; public class Kolejki { public static void main(string[] args) { ArrayDeque<Integer> deq = new ArrayDeque<>(); // Działa jak kolejka FIFO deq.offer(1); deq.add(20); deq.add(12); System.out.println("FIFO: " + deq);//fifo: [1, 20, 12] System.out.println("Podglądamy (z początku): " + deq.peek());//podglądamy (z początku): 1 // Działa jak kolejka LIFO deq.clear(); deq.addfirst(1); deq.addfirst(20); deq.addfirst(12); System.out.println("LIFO: " + deq);//lifo: [12, 20, 1] System.out.println("Podglądamy (z początku): " + deq.peek());//podglądamy (z początku): 12

Kolejki - przykład 9 System.out.println("Możemy używać iteratora"); for (Integer i : deq) System.out.println(i); // Kolejka z priorytetami PriorityQueue<Integer> pq = new PriorityQueue<>(); pq.add(1); pq.add(20); pq.add(12); System.out.println("PRIOR QUE: " + pq); //PRIOR QUE: [1, 20, 12] System.out.println("Z kolejki z priorytetami" +" pobieramy kolejne elementy zawsze z początku"); System.out.println("Początek-domyślnie najmniejszy " + "element"); Integer n; while((n = pq.poll())!= null) { System.out.println(n);// 1 12 20

Mapy (interfejs Map) 10 Tablica asocjacyjna jest zestawem elementów, do których zapewniono swobodny, bezpośredni (czyli bez konieczności przeglądania elementów zestawu) dostęp za pomocą kluczy. Można sobie wyobrażać, że jest to uogólnienie zwykłej tablicy: w zwykłej tablicy kluczami są indeksy całkowite, a w tablicy asocjacyjnej kluczami mogą być dowolne obiekty. Efektywne realizacje tablic asocjacyjnych opierają się na odpowiedniej implementacji słowników (odwzorowujących klucze w odpowiadające im wartości). W Javie słownikowe implementacje tablic asocjacyjnych określane są słowem map, pochodzącym od terminu mapping, oznaczającego jednoznaczne odwzorowanie (w tym przypadku zbioru kluczy w zbiór wartości). Również po polsku krótko będziemy nazywać tablice asocjacyjne mapami.

Mapy Mapa jest jednoznacznym odwzorowaniem zbioru kluczy w zbiór wartości. O mapach możemy myśleć jako o takich kolekcjach par: klucz - wartość, które zapewniają odnajdywanie wartości związanej z podanym kluczem. Zarówno klucze, jak i wartości mogą być referencjami do dowolnych obiektów (jak również wartościami null). Oczywiście, wartości kluczy nie mogą się powtarzać (odwzorowanie musi być jednoznaczne). Natomiast pod różnymi kluczami można zapisać te same wartości (odwzorowanie nie musi być wzajemnie jednoznaczne). Przykłady: Zestaw danych zawierający: Klucz: NIP ->Wartość : Dane o podatniku Klucz: Kraj ->Wartość : Stolica Klucz: Nazwa kontaktu ->Wartość : Numer telefonu i pozwalający na odnajdywanie poszukiwanej wartości po kluczu. Istotą zastosowania map jest możliwość łatwego i jednocześnie szybkiego odnajdywania informacji w powiązanych zestawach danych. 11

Mapy - interfejs Map<K,V> 12 W JCF ze względu na specyfikę działania na mapach (jako zestawach par klucze-wartości) implementują one interfejs Map, a nie Collection. Podstawowe operacje na mapie (niezależnie od jej implementacji) są określone przez metody interfejsu Map<K,V>, gdzie K typ kluczy, V typ wartości. Należą do nich: void clear() - usuwa wszystkie elementy mapy (operacja opcjonalna). boolean containskey(object key) - zwraca true jeżeli mapa zawiera podany klucz (i związaną z nim wartość). boolean containsvalue(object value) - zwraca true jeśli mapa zawiera podaną wartość (do ktorej może prowadzić wiele kluczy). V get(object key) - zwraca wartość dla danego klucza. V put(k key, V value) - dodaje parę klucz-wartość do mapy (lub zastępuje poprzednie odwzorowanie tego klucza podaną wartością), zwraca obiekt, który uprzednio znajdował się pod danym kluczem lub null. void putall(map<...> t)- dodaje do mapy wszystkie elementy mapy t. V remove(object key) - usuwa odwzorowanie: klucz-wartość z tej mapy (operacja opcjonalna). Zwraca usuniętą wartość. int size() - zwraca liczbę par klucz-wartość w mapie Uwaga. Metody put..., ze względu na jednoznaczność, powodują zastąpienie istniejących w mapie odwzorowań, które mają takie same klucze jak dodawane odwzorowania.

Mapy - interfejs Map<K,V> Metoda containskey(..) wydaje się duplikować jakby działanie metody get(..). Ale pamietajmy, że mapy mogą zawierać pod danym kluczem wartość null. Wtedy metoda get zwraca null i nie wiemy czy oznacza to brak odwzorowania w mapie, czy też odwzorowanie klucza w wartość null. Metoda containskey pozwala to rozstrzygnąć: zwróci true jeśli odwzorowanie jest (nawet jeśli wartością "pod" kluczem jest null), natomiast zwrócona wartość false świadczy na pewno o tym, że w mapie nie ma poszukiwanego odwzorowania. Ponadto w interfejsie Map istnieją metody : Set<K> keyset() - zwraca widok na zbiór kluczy. Collection<V> values() - zwraca widok na kolekcję wartości. Set<Map.Entry<K,V>> entryset() - zwraca widok na zbiór par kluczewartości jako zbiór obiektów typu Map.Entry<K,V>. K jest typem klucza, V - typem wartości. Ze względu na to, że mapy nie implementują interfejsu Collection, nie można od nich uzyskać bezpośrednio iteratorów. Można natomiast uzyskać kolekcje, które są widokami na zestawy kluczy (Set, bo bez powtórzeń), zestawy wartości (Collection, bo bez porządku i z możliwością powtórzeń elementów), oraz par klucze-wartośći (Set, bo bez powtórzeń). Od tych kolekcji - naturalnie - możemy uzyskać iteratory. 13

Mapy - interfejs Map<K,V> 14 Uwaga: przy tworzeniu map podajemy dwa argumenty typu (typ klucza i typ wartości) np. Map<String, Integer>. W tym przypadku metoda get(...) zwróci obiekt właściwego typu i nie będą potrzebne konwersje zawężające. Jesli używamy surowego typu Map lub argumenty typu są Object, to metoda get(...) będzie zwracać Object, zatem uzyskując wartość "po kluczu" musimy dokonać konwersji zawężającej do odpowiedniego typu. Hierarchia interfejsów i klas w JCF: Interface: Map SortedMap NavigableMap HashMap WeekHashMap LinkedHashMap TreeMap

Mapy konkretne implementacje W mapach istotne jest szybkie odnajdywanie kluczy. Klucze (poprzez umieszczenie ich razem z wartościami w odpowiednich strukturach danych) związane są już bezpośrednio i niejako natychmiastowo z wartościami. Podobnie zatem jak w przypadku zbiorów istnieją dwie podstawowe implementacje, pozwalające na szybkie wyszukiwanie kluczy - implementacja oparta na tablicy mieszającej (HashMap) oraz na drzewie czerwono-czarnym (TreeMap). Niejako ubocznym skutkiem zastosowania tej ostatniej jest możliwość przeglądania kluczy mapy w naturalnym porządku rosnącym lub w porządku, określanym przez komparator podany przy konstrukcji mapy. Reguły stosowania komparatorów są takie same jak w przypadku zbiorów uporządkowanych. Mówimy, że mamy do czynienia z mapą uporządkowaną, a zatem mamy - tak jak w przypadku zbiorów uporządkowanych - dodatkowe właściwości takie jak "pierwszy" i "ostatni" klucz lub ich podzbiór "od" - "do" (określane przez metody interfejsu SortedMap) oraz operacje znajdowania bliskich kluczy (z interfejsu NavigableMap) Implementacja LinkedHashMap pozwala na odtworzenie kolejności dodawania elementów do mapy. 15

Mapy HashMap przykład 1 Przykład. Wyobraźmy sobie, że w pliku znajdują się nazwy i adresy firm. Nasz program po wczytaniu pliku ma za zadanie dostarczenie prostego interfejsu wyszukiwania adresu dla podanej nazwy firmy. Zauważmy, że proste sposoby rozwiązania tego problemu (np. prowadzenie dwóch list - nazw i adresów - i liniowe wyszukiwanie nazwy na liście nazw po to by otrzymać pozycję adresu na liście adresów) są bardzo nieefektywne. Klasa HashMap, która reprezentuje zestaw par klucz-wartość, efektywnie pozwala rozwiązać ten problem. Zatem jeśli postać pliku wejściowego jest następująca: nazwa_firmy1 adres nazwa_firmy2 adres... nazwa_firmyn adres to problem wyszukiwania adresów dla firm podawanych w dialogach wejściowych można oprogramować w następujący sposób: 16

import java.util.*; import java.io.*; import javax.swing.*; Mapy HashMap przykład 1 class TestMap1 { public static void main(string args[]) throws IOException { // mapa odwzorowań : nazwa -> adres // argumenty typu są dwa: dla klucza(nazwy) i wartości(adresu) Map<String, String> map = new HashMap<>(); // Wczytywanie danych Scanner scan = new Scanner(new File("firmsAddr.txt")); String firmname; String address; while (scan.hasnextline()) { firmname = scan.nextline(); address = scan.hasnextline()? scan.nextline() : ""; //nazwa firmy to klucz pod którym w mapie będzie jej adres map.put(firmname, address); //dodanie pary klucz-wartość do mapy 17

Mapy HashMap przykład 1 // Interakcyjna część programu: dla podanej w dialogu // nazwy firmy pokazywany jest jej adres while ((firmname = JOptionPane.showInputDialog ("Nazwa firmy"))!= null) { address = map.get(firmname); if (address == null) address = "?"; JOptionPane.showMessageDialog(null, "Firma: " + firmname + '\n' + "Adres: " + address); w Javie 8 dla map dostępna jest metoda: getordefault(key, value), która zwraca wartość pod kluczem key, a jeśli jej nie ma (w mapie brak klucza i wartości z nim związanej), to zwraca domyślną wartość value. Używając tej metody powyższy fragment programu mógłby wyglądać następująco: while ((firmname = JOptionPane.showInputDialog ("Nazwa firmy"))!= null) { address = map.getordefault(firmname, "?"); JOptionPane.showMessageDialog(null, "Firma: " + firmname + '\n' + "Adres: " + address); 18

Mapy keyset() i values() przykład 1 19 Metody keyset() i values() zwracają odpowiednio zbiór kluczy i kolekcję wartości mapy. Iterując po kluczach możemy sięgać po kolejne pary klucz-> wartość. Dla mapy map firma-> adres: System.out.println(map); Set<String> keys = map.keyset(); System.out.println("Klucze: " + keys); Collection<String> vals = map.values(); System.out.println("Wartości: " + vals); Set<Map.Entry<String,String>> entries = map.entryset(); System.out.println("Pary: " + entries); for (String name : map.keyset()){ String addr = map.get(name); System.out.println(name + " - adres: " + addr); Uzyskana poprzez keyset() kolekcja kluczy jest widokiem (a nie jakimś nowym obiektem-zbiorem) na klucze mapy, co oznacza, że operując na elementach tego zbioru faktycznie operujemy na kluczach mapy. Podobne widoki są zwracane poprzez values() i entryset(). Widok na klucze jest typu wyznaczanego przez klasę implementującą interfejs Set, ale jest to inna klasa niż znane nam HashSet i TreeSet

Mapy TreeMap przykład 1 Parę klucz-> wartość w mapie można usunąć za pomocą metody remove(key) albo za pomocą metody remove() iteratora zbioru kluczy. System.out.println(map); map.remove("microsoft"); System.out.println(map); map.put("microsoft", "Redmont"); System.out.println(map); for (Iterator<String> it = map.keyset().iterator(); it.hasnext(); ) { if(it.next().equals("microsoft")) it.remove(); Jeśli chcemy mieć posortowane klucze tworzymy TreeMap //mapa w porządku naturalnym (klucze alfabetycznie) Map<String, String> mapsort = new TreeMap<>(map); // Pusta mapa z komparatorem odwracającym // naturalny porządek Map<String, String> mapro = new TreeMap<>(Collections.reverseOrder()); // Wpisujemy do niej pary z mapy map mapro.putall(map); System.out.println(mapRo); 20

Mapy entryset - przykład 2 Efektywniejszym od keyset()(gdyż keyset wymaga każdorazowego pobierania wartości spod klucza) sposobem przeglądania map jest użycie metody entryset. Uzyskana kolekcja par klucze-wartości (widok na klucze-wartości) jest typu wyznaczanego przez klasę implementującą interfejs Map.Entry (wewnętrzny interfejs interfejsu Map). Dzięki temu - ale tylko w trakcie iteracji po elementach tej kolekcji - mamy dodatkowe możliwości działania, a mianowicie: uzyskanie klucza (metoda getkey()), wartości dla danej pary (metoda getvalue()) oraz zmianę wartości dla danej pary (metoda setvalue(object). Przykład: Map<String, Integer> map = new HashMap<>(); for(char c = 'a'; c <= 'd'; c++) map.put("" + c, (int) c); Set<Map.Entry<String, Integer>> entries = map.entryset(); System.out.println(entries);//[a=97, b=98, c=99, d=100] //Efektywny sposób przeglądania map for(map.entry<string, Integer> e : entries) { Integer val = e.getvalue(); System.out.print(e.getKey() + " " + val + "' " ); //a 97, b 98, c 99, d 100, e.setvalue(val + 1000); System.out.println(entries);//[a=1097, b=1098, c=1099, d=1100] 21

Mapy przykład 3 Rozważmy napisanie programu diagnostycznego (np. jakichś napraw), który na podstawie wprowadzonego przez użytkownika symptomu podaje niezbędną akcję do wykonania. Możemy to zrobić np. przy pomocy instrukcji switch z ilością case odpowiadającą ilości symptomów (np. 100). Takie rozwiązanie jest niefektywne, wszelkie zmiany powodują konieczność modyfikacji kodu i ponownej kompilacji. Lepiej będzie oddzielić dane od kodu źródłowego (zapisać je w pliku np. w kolejnych wierszach symptom-akcja) i zastosować mapę. HashMap<String, String> diagmap = new HashMap<>(); for (Scanner sc = new Scanner(new File("diagnose.txt")); sc.hasnextline(); ) { diagmap.put(sc.nextline(), sc.nextline()); String symptom = showinputdialog("podaj symptom"); String act = diagmap.containskey(symptom)? diagmap.get(symptom) : "Nieznany symptom"; showmessagedialog(null, act); Zamiast wielu linijek kodu mamy kilka, a program jest niezależny od zestawu symptomów i odpowiednich akcji. Istotą zastosowania map jest możliwość szybkiego odnajdywania informacji w powiązanych zestawach danych, a także tworzenia zwięzłego i elastycznego kodu. 22

Klasa Collections i Arrays JCF zawiera dwie użyteczne klasy, wspomagające działania na kolekcjach (a także tablicach) - klasę Collections i klasę Arrays. Klasa Collections dostarcza statycznych metod, operujących na kolekcjach lub zwracających kolekcje. Do metod tych należą: algorytmy kolekcyjne, metody zwracające widoki kolekcji, metody tworzące specjalne kolekcje. Wybrane algorytmy kolekcyjne: static int binarysearch(list<... > list, K key)- wyszukiwanie binarne na liście. Zwraca indeks elementu, który jest równy podanemu obiektowi key (wedle metody compareto z klas(y) elementów listy, przy czym zarówno elementy listy, jak i obiekt key muszą być porównywalne). Lista musi być najpierw posortowana według naturalnego porządku np. metodą Collections.sort(List l). Przy braku elementu zwraca wartość < 0 (a nie -1!) static int binarysearch(list<...> list, K key, Comparator<...> c) - wyszukiwanie binarne według podanego komparatora (porównywanie obiektu key z elementami listy następuje za pomocą metody compare komparatora). Lista musi być posortowana zgodnie z porządkiem definiowanym przez komparator np. metodą Collections.sort(List l, Comparator c). Zwraca indeks odnalezionego elementu lub wartość < 0, gdy brak jest elementu. 23

Klasa Collections i Arrays 24 static void sort(list<t> list)- sortowanie listy według naturalengo porządku elementów (rosnąco) static void sort(list<t> list, Comparator<...> c) - sortowanie listy wedle porządku określanego przez komparator. W klasie Arrays dostarczono statycznych metod, m.in. implementujących algorytmy sortowania i binarnego wyszukiwania dla tablic zarówno typów pierwotnych, jak i obiektów (w tym ostatnim przypadku również z możliwością użycia komparatorów). Metody klasy Collections zwracające widoki kolekcji nakładają na kolekcje "opakowania" w celu zmiany ich niektórych właściwości. W szczególności możemy uzyskać niemodyfikowalne oraz synchronizowane widoki dowolnej kolekcji. Przypomnijmy, że niemodyfikowalność kolekcji oznacza niemożliwość wszelkich modyfikacji (zarówno strukturalnych - dodawanie usuwanie elementów, jak i zmian wartości elementów). Czasami jest to potrzebne, gdy chcemy zagwarantować, by po utworzeniu kolekcja w ogóle nie mogła podlegać zmianom, lub gdy chcemy zróżnicować prawa dostępu do kolekcji dla różnych grup użytkowników naszej aplikacji (ktoś może zmieniać kolekcje, kto inny - tylko przeglądać). Gotowe implementacje kolekcji w JCF są modyfikowalne.

Klasa Collections i Arrays 25 Niemodyfikowalne widoki kolekcji możemy uzyskać za pomocą metod (dla ułatwienia pominięto parametry typu): public static Collection unmodifiablecollection(collection c); public static Set unmodifiableset(set s); public static List unmodifiablelist(list list); public static Map unmodifiablemap(map m); public static SortedSet unmodifiablesortedset(sortedset s); public static SortedMap unmodifiablesortedmap(sortedmap m); Szczególnym rodzajem niemodyfikowalności jest brak możliwości zmian rozmiaru kolekcji. Takich kolekcji (których elementy możemy zmieniać, ale nie możemy do nich dodawać nowych elementów ani usuwać istniejących) dostarcza statyczna metoda aslist z klasy Arrays, zwracająca listę, elementami której są elementy tablicy przekazanej jako argument. Uwaga: lista ta jest widokiem na tablicę! Metoda ta może służyć nie tylko do łatwego tworzenia list z istniejących tablic, ale również do tworzenia nowych, pustych list o niezmiennych rozmiarach: // Tworzy listę o ustalonym (i niezmiennym) // rozmiarze 1000 elementów List l = Arrays.asList(new Object[1000]);

Java 9 metoda of dla List, Set i Map 26 W Java 9 została dostarczona nowa statyczna metoda fabrykująca ( factory methods ) of tworząca instancje kolekcji (list i zbiorów) oraz map o małej liczbie elementów. // for empty list static <E> List<E> of() // for list containing one element static <E> List<E> of(e e1) // for list containing two element static <E> List<E> of(e e1, E e2) // // for list containing an arbitrary number of elements static <E> List<E> of(e... elements); Przykład: List<String> immutablelist = List.of(); immutablelist = List.of("one", "two", "three"); //jeśli dodamy null do listy to będzie wyrzucony wyjątek //NullPointerException //List<String> immutablelist = List.of("one", "two", null);

Java 9 metoda of dla List, Set i Map 27 Lista stworzona przy pomocy metody of jest niezmienialna (immutable), dlatego jeśli spróbujemy dodać element do listy zostanie wyrzucony wyjątek java.lang.unsupportedoperationexception List<String> immutablelist = List.of("one", "two", "three"); immutablelist.add("four"); //wyjatek Rozwiązanie problemu: List<String> mutablelist = new ArrayList<>(List.of("one", "two", "three")); mutablelist.add("four"); mutablelist.add(null); // Result: [one, two, three, four, null] Set // for empty Set static <E> Set<E> of() // for Set containing one element static <E> Set<E> of(e e1) // for Set containing two element static <E> Set<E> of(e e1, E e2) // // for Set containing an arbitrary number of elements static <E> Set<E> of(e... elements)

Java 9 metoda of dla List, Set i Map 28 Map Map.of() Aby stworzyć mapę korzystamy z metod statycznych: // for empty Map static <K, V> Map<K, V> of() // for Map containing a single mapping static <K, V> Map<K, V> of(k k1, V v1) // for Map containing two mappings static <K, V> Map<K, V> of(k k1, V v1, K k2, V v2) // // for Map containing up to ten mappings static <K, V> Map<K, V> of(k k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9, K k10, V v10) Przykład: Map<Integer, String> immutablemap = Map.of(); immutablemap = Map.of(1, "one", 2, "two", 3, "three");

Java 9 metoda of dla List, Set i Map 29 Map.ofEntries() Jeśli chcemy stworzyć mapę z więcej niż 10 mapowaniami korzystamy z metody Map.ofEntries(). static <K, V> Map<K, V> ofentries( Entry<? extends K,? extends V>... entries) Aby z niej skorzystać pary klucz- wartość (entry) można stworzyć metodą static <K, V> Entry<K, V> entry(k k, V v) Np.: Map<Integer, String> newimmutablemap = Map.ofEntries(Map.entry(1, "one"), Map.entry(2, "two"), Map.entry(3, "three")); System.out.println(newImmutableMap);//{1=one, 3=three, 2=two Map<Integer, String> newimmutablemap1 = Map.ofEntries(); System.out.println(newImmutableMap1);//{

Programowanie funkcyjne - wprowadzenie 30 Języki programowania takie jak C/C++/Java/Python są nazywane imperatywnymi językami programowania, ponieważ zawierają sekwencje zadań do wykonania. Programista jawnie, krok po kroku definiuje jak wykonać zadanie. Programowanie funkcyjne działa inaczej. Zamiast sekwencyjnie wykonywać zadania, języki funkcyjne wyznaczają jedynie wartości poszczególnych wyrażeń. Programy funkcyjne składają się jedynie z funkcji. Funkcje są podstawowymi elementami języka funkcyjnego. Główny program jest funkcją, której podajemy argumenty, a w zamian otrzymujemy wyznaczoną wartość wynik działania programu. Główna funkcja składa się tylko i wyłącznie z innych funkcji, które z kolei składają się z jeszcze innych funkcji. Funkcje takie dokładnie odpowiadają funkcjom w czysto matematycznym znaczeniu przyjmują pewną liczbę parametrów i zwracają wynik. Każda operacja wykonywana podczas działania funkcji, a nie mająca związku z wartością zwracaną przez funkcję to efekt uboczny (np. operacje wejścia wyjścia, modyfikowanie zmiennych globalnych). Funkcje które nie posiadają efektów ubocznych nazywane są funkcjami czystymi (pure function).

Programowanie funkcyjne - wprowadzenie Ideą programowania funkcyjnego jest pisanie programów w kategoriach "co ma być osiągnięte", a nie - jak w programowaniu imperatywnym - poprzez specyfikowanie kolejnych kroków algorytmu do wykonania. Takie podejście jest możliwe, gdy w języku programowania możemy traktować fragmenty kodu (funkcje) jako pełnoprawne obiekty, które mogą być przekazywane innym funkcjom i zwracane z innych funkcji. W czystych językach funkcyjnych tak właśnie się dzieje, co więcej - funkcje nie zmieniają żadnych danych (stanów), ważne jest jedynie wyliczanie ich wyników na podstawie podanych argumentów (co ma duże znaczenie np. przy przetwarzaniu równoległym). Rozważmy przykład. Mamy listę liczb src. Na jej podstawie utworzyć listę target zawierającą kwadraty tych liczb z src, które są mniejsze od 10. List<Integer> src = Arrays.asList(5, 72, 10, 11, 9); List<Integer> target = new ArrayList<>(); for (Integer n : src) { if (n < 10) target.add(n * n) Ale kod nie jest zbyt ogólny, uniwersalny. Jest imperatywny - bo musieliśmy dokładnie zapisać każdy krok naszego "algorytmu". Co się stanie, jeśli zmienimy warunek selekcji liczb (np. biorąc pod uwagę tylko liczby parzyste) albo operację, która na wybranych liczbach ma być wykonana (np. zamiast podniesienia do kwadratu - dodanie jakiejś liczby)? Musielibyśmy za każdym razem pisać podobne pętle jak wyżej. 31

Programowanie funkcyjne - wprowadzenie 32 A gdyby trzeba było z listy napisów wybrać te o określonej długości i przekształcić je np. do wielkich liter? Znowu trzeba by było pisać podobne pętle, tym razem operujące na listach napisów. A przecież zawsze chodzi tu o wybór elementów listy src do przetwarzania, wykonanie na nich określonej operacji i dodanie wyników do listy target. Przydałaby się pewnie metoda create, której moglibyśmy używać np. tak: List<jakiś_typ> target = create(src, tu podać warunek wyboru elementów z listy src, tu podać operację na wybranych elementach); To już przypomina styl funkcyjny: określamy tylko co chcemy osiągnąć i - najczęściej - nie musimy pisać imperatywnego kodu. No ale jak podać warunek wyboru i operację? Jasne jest, że to muszą być jakieś fragmenty kodu, które wykonują odpowiednie obliczenia i zwracają wyniki. Czyli - ogólnie mówiąc - funkcje, które przekazujemy metodzie create, a ona będzie je wywoływać. W Javie przed wersją 8 moglibyśmy coś takiego zrobić korzystając z interfejsów (widzieliśmy to już niejednokrotnie, gdy np. przekazywaliśmy kod do wykonania w wątku jako obiekt implementujący interfejs Runnable czy Callable, albo podając komparatory (obiekty implementujące interfejs Comparator) przy sortowaniu list.) (np. przez wykorzystanie wzorca projektowego Strategia).

Programowanie funkcyjne - wprowadzenie Zdefiniujmy interfejs Filter, którego metoda test() będzie odpowiedzialna za wybór elementów listy (ogólniej - zwraca true, jeśli przekazana jej wartość spełnia zapisane w kodzie tej metody warunki, false - w przeciwnym razie): public interface Filter<V> { boolean test(v v); oraz interfejs Transformer, którego metoda transform() ma wykonać operację na przekazanej wartości i zwrócić jej wynik: public interface Transformer<T,S> { T transform(s v); Napisanie metody create() w kategoriach tych interfejsów jest całkiem łatwe static <T, S> List<T> create(list<s> src, Filter<S> f, Transformer<T, S> t) { List<T> target = new ArrayList<>(); for (S e : src) if (f.test(e)) target.add(t.transform(e)); return target; 33

Programowanie funkcyjne - wprowadzenie W programach, pisanych w Javie przed wersją 8, takiej metodzie create(...) trzeba było przekazywać obiekty implementujące podane interfejsy, np: List<Integer> src = Arrays.asList(5, 72, 10, 11, 9); List<Integer> target = create(src, new Filter<Integer>() { public boolean test(integer n) { return n < 10;, new Transformer<Integer, Integer>() { public Integer transform(integer n) { return n*n; ); To już jest trochę w stylu funkcyjnym, ale mało czytelne, obarczone liniami kodu, które nic nie wnoszą (tworzenie obiektów anonimowych klas wewnętrznych tylko po to, by przekazać kod do wykonania). Tak naprawdę ważne są tu przecież tylko dwie linijki: return n < 10; i return n*n; 34

Programowanie funkcyjne - wprowadzenie M.in. dlatego w Javie w wersji 8 wprowadzono, co prawda w ograniczonym zakresie, wsparcie dla programowania funkcyjnego. Obejmuje ono przede wszystkim lambdawyrażenia oraz przetwarzanie strumieniowe. Zanim poznamy lambda-wyrażenia w szczegółach, możemy o nich myśleć jako o bezpośrednio podawanych fragmentach kodu, swoistych funkcjach bez nazwy, które są traktowane "jak prawdziwe obiekty", czyli np. mogą być przekazywane metodom. Dzięki zastosowaniu lambda-wyrażeń poprzedni przykład wywołania metody create() upraszczamy praktycznie do jednej linijki: List<Integer> target = create(src, n -> n < 10, n -> n*n); Tu zapis: n -> n < 10 jest lambda-wyrażeniem, które możemy traktować jako anonimową funkcję, której parametrem jest n, i która zwraca wynik wyrażenia n<10. Nie wchodząc na razie w szczegóły, można powiedzieć, że kompilator "dopasowuje" to lambda-wyrażenie do drugiego parametru (Filter<S>) metody create(...), w wyniku czego użyta tam metoda test(...) interfejsu Filter "pod spodem" uzyskuje odpowiednią implementację: boolean test(integer n) { return n < 10; Podobnie dzieje z lambda-wyrażeniem: n -> n*n pasującym do wywołania metody transform(...) interfejsu Transformer. 35

Programowanie funkcyjne - wprowadzenie Przykłady wykorzystania lambda-wyrażeń w różnych kontekstach: List<Integer> num = Arrays.asList( 1, 3, 5, 10, 9, 12, 7); List<String> txt = Arrays.asList("ala", "ma", "kota", "aleksandra", "psa", "azora" ); List<Employee> emp = Arrays.asList( new Employee("Kowal", "Jan", 34, 3400.0), new Employee("As", "Ala", 27, 4100.0), new Employee("Kot", "Zofia", 33, 3700.0), new Employee("Puchacz", "Jan", 41, 3600.0) ); System.out.println(create(num, n-> n%2!=0, n->n*100)); //[100, 300, 500, 900, 700] System.out.println( create(txt, s -> s.startswith("a"), s -> s.touppercase() + " " + s.length())); //[ALA 3, ALEKSANDRA 10, AZORA 5] 36

Programowanie funkcyjne - wprowadzenie List<Employee> dopodwyzki = create(emp, e -> e.getage() > 30 && e.getsalary() < 4000, e -> e ); System.out.println("Podwyzki powinni uzyskac:"); System.out.println(doPodwyzki); Uwaga: w powyższym kodzie wykorzystano prostą klasę Employee z polami lname (nazwisko), fname (imię), age (wiek) i salary (pensja) oraz metodą tostring() zwracającą nazwisko i imię. Otrzymamy następujące wyniki: [100, 300, 500, 900, 700] [ALA 3, ALEKSANDRA 10, AZORA 5] Podwyzki powinni uzyskac: [Kowal Jan, Kot Zofia, Puchacz Jan] Jak widać, stworzona wcześniej metoda create() wykorzystywana jest tu dosyć elastycznie (dla różnych typów danych, różnych warunków, różnych operacji), a dzięki lambda-wyrażeniom sformułowanie tego, co chcemy osiągnąć, jest bardzo proste i czytelne. 37

38 Programowanie funkcyjne - wprowadzenie Jednak metoda create(...) nie do końca jest uniwersalna. Być może chcielibyśmy najpierw przetworzyć dane, a filtrować wyniki tego przetwarzania. A może filtrować, przetwarzać i znowu filtrować? Te wszystkie przypadki możemy łatwo obsłużyć za pomocą przetwarzania strumieniowego. Pokażemy teraz szczególne przypadki jego użycia, dobrze zastępujące i znacznie poszerzające funkcjonalność, stworzonej wcześniej metody create(...) i to bez konieczności definiowania jakichkolwiek własnych interfejsów. W kontekście wcześniejszych przykładowych kodów, sekwencja działań jest następująca. Od listy metodą stream() uzyskujemy tzw. strumień (inaczej zwany sekwencją), a na danych strumienia możemy wykonywać m.in. operacje przetwarzania (metoda map), filtrowania (metoda filter) oraz uzyskiwać nowe kolekcje wyników tych operacji (metoda collect). Metodzie map podajemy lambda-wyrażenie, którego wynikiem jest przetworzenie jego parametru (działa to tak jak transform() z naszego interfejsu Transformer). Metoda zwraca strumień, na którym można wykonywać dalsze operacje. Metodzie filter podajemy lambdę przekształcającą parametr w wartość boolowską (tak jak nasz filter()). Filtrowanie zwraca strumień, który pozwala wykonywać ew. dalsze operacje tylko na tych danych, dla których wynik lambdy jest true. Możemy zatem wywoływać sekwencje map-filter, realizujac kolejne etapy przetwarzania strumienia danych. Metoda collect natomiast kończy przetwarzanie strumienia i ma za parametr obiekt klasy Collector, który tworzy nową kolekcję i dodaje do niej dane ze strumienia, na rzecz którego została wywołana. W szczególności taki kolektor, budujący listę, można uzyskać za pomocą statycznej metody Collectors.toList().

Programowanie funkcyjne - wprowadzenie Możemy zatem zapisać poprzedni przykład uzyskania listy kwadratów tych elementów listy src, które są mniejsze od 10: import static java.util.stream.collectors.tolist; //... List<Integer> src = Arrays.asList(5, 72, 10, 11, 9); List<Integer> target = src.stream().filter(n -> n < 10).map(n -> n * n).collect(tolist()); Może trochę więcej tu kodu, niż poprzednio, ale za to nie musimy definiować własnych interfejsów (metody strumieniowe korzystają z interfejsów z pakietu java.util.function, które pokrywają częste przypadki użycia lambda-wyrażeń). Ale co najważniejsze mamy dużo większą swobodę działania. Możemy np. filtrować kwadraty liczb, czyli najpierw wykonać map(), a później filter(): List<Integer> num = Arrays.asList(1, 3, 5, 10, 9, 12, 7); List<Integer> out = num.stream().map(n -> n*n).filter(n -> n > 80).collect(toList()); co da w wyniku listę liczb: [100, 81, 144] 39

Programowanie funkcyjne - wprowadzenie Możemy też ulepszyć poprzedni przykładowy fragment, który selekcjonował pracowników do podwyżki. Teraz nie tylko zostanie wybrana odpowiednia grupa pracowników, ale też podwyżki zostaną zrealizowane: List<Employee> emp =... emp.stream().filter(e -> e.getage() > 30 && e.getsalary() < 4000).forEach(e -> e.setsalary(e.getsalary()*1.1)); emp.foreach( e -> System.out.printf("%s %.0f\n", e, e.getsalary()) ); Ten fragment wyprowadzi na konsolę: Kowal Jan 3740 Warto zwrócić uwagę, że zastosowano tu dwa razy metodę foreach. Pierwsze jej użycie dotyczyło strumienia zwracanego przez filter(...) i polegało na wykonaniu podanej operacji (ustalenia nowej pensji) na wszystkich elementach zwróconej sekwencji. Takie foreach, tak jak collect, jest jedną z metod, która kończy operacje strumieniowe. Drugie foreach zastosowano wobec kolekcji (listy pracowników) dla wyprowadzenia informacji. Jest to jeden z przykładów nowych (domyślnych) metod w interfejsach kolekcyjnych, którym jako argumenty można podawać lambda-wyrażenia odpowiednich rodzajów. 40

Interfejsy funkcyjne i lambda-wyrażenia 41 Jeżeli zestaw metod, deklarowanych w interfejsie, zawiera tylko jedną metodę abstrakcyjną (wyłączając dodane deklaracje abstrakcyjnych publicznych niefinalnych metod z klasy Object), to taki interfejs nazywa się funkcyjnym (SAM - Single Abstract Method). Przykłady interfejsów funkcyjnych: interface SAMA { void dowork(int n);//zawiera jedną metodę abstrakcyjną, interface SAMB { void dowork(string s); @Override boolean equals(object); // przedefiniowuje metodę equals z klasy Object, interface SAMC { void dowork(collection c);//metoda abstrakcyjna default public void done() { System.out.println("Done"); //metoda domyślna (default), ma implementację

Interfejsy funkcyjne i lambda-wyrażenia 42 Interfejsy funkcyjne zostały wybrane jako środek realizacji lambda-wyrażeń w Javie 8. Każde lambda-wyrażenie musi odpowiadać jakiemuś interfejsowi funkcyjnemu i jest typu wyznaczanego przez tego rodzaju interfejsy. W doborze odpowiedniego interfejsu funkcyjnego do lambdawyrażenia kompilator stosuje deskryptor funkcji (function descriptor) interfejsu funkcyjnego, który opisuje parametry typu (gdy interfejs jest sparametryzowany) oraz typy parametrów, typ wyniku i ew. typ zgłaszanego wyjątku metody interfejsu. Definiując interfejsy funkcyjne, warto stosować adnotację @FunctionalInterface, dzięki której kompilator może sprawdzić, czy rzeczywiście dany interfejs jest funkcyjny i poinformować nas o ew. błędach; w dokumentacji i kodzie widać od razu, że dany interfejs może być stosowany z lambdawyrażeniami. Składnia lambda-wyrażenia: gdzie (lista_parametrów) -> ciało_lambda lista_parametrów oznacza rozdzielone przecinkami deklaracje argumentów przekazywanych lambda-wyrażeniu (jak w nagłówku metody); ciało_lambda to jedno wyrażenie lub blok instrukcji, ujęty w nawiasy {. Uwaga. Jeśli typy parametrów mogą być określone przez kompilator w wyniku konkludowania typów (typy inferring), to w deklaracji parametrów można pominąć typy, a wtedy, gdy parametr jest jeden, można pominąć nawiasy okalające listę parametrów.