Typy zagnieżdżone

Statyczne typy składowe, klasy wewnętrzne, lokalne, anonimowe oraz wyrażenia lambda


dr inż. Aleksander Smywiński-Pohl

apohllo@o2.pl

http://apohllo.pl/dydaktyka/programowanie-obiektowe

Plan

  • statyczne typy składowe: interfejsy, wyliczenia, adnotacje
  • klasy wewnętrzne
  • klasy lokalne
  • klasy anonimowe
  • wyrażenia lambda
  • interfejs Stream

Statyczny zagnieżdżony interfejs

class LinkedStack {
    static interface Linkable {
        public Linkable getNext();
        public void setNext();
    }

    Linkable head;

    public void push(Linkable node) {
        if(node == null){
            throw new InvalidArgumentException("Element cannot be null!");
        }
        node.setNext(head);
        this.head = node;
    }

    public Linkable pop() {
        Linkable result = this.head;
        if(result != null){
            this.head = result.getNext();
        }
        return result;
    }
}
// import LinkedStack.Linkable;

class LinkableInteger implements LinkedStack.Linkable {
    int i;
    LinkedStack.Linkable next;

    public LinkableInteger(int i) { this.i = i; }

    public LinkedStack.Linkable getNext() { return next; }

    public void setNext(LinkedStack.Linkable next) { this.next = next; }
}

Własności statycznego typu składowego

  • dostęp do prywatnych statycznych składowych typu otaczającego
  • dostęp typu otaczającego do prywatnych składowych typu statycznego

Map.Entry - przykład statycznego zagnieżdżonego interfejsu

Map<String,Integer> numbers = new HashMap<>();
numbers.put("jeden", 1);
numbers.put("dwa", 2);
numbers.put("trzy", 3);
for(Map.Entry<String,Integer> entry : numbers.entrySet()){
    System.out.println("" + entry.key + " : " + entry.value);
}

Klasa wewnętrzna

class BoundedArrayList {
    private Object[] array;
    private int pointer = 0;

    public BoundedArrayList(int size){
        array = new Object[size];
    }

    public boolean add(Object element){
        if(pointer < array.length){
            array[pointer] = element;
            pointer++;
            return true;
        } else {
            return false;
        }
    }
}
class BoundedArrayList {
    protected class ForwardIterator implements Iterator {
        private int index = 0;
        public boolean hasNext(){
            return index < pointer;
        }
        public Object next(){
            if(index >= pointer){
                throw new NoSuchElementException();
            }
            Object result = array[index];
            index++;
            return result;
        }
    }

    public Iterator forwardIterator(){
        return new ForwardIterator();
    }
}
class BoundedArrayList {
    protected class BackwardIterator implements Iterator {
        private int index = pointer-1;
        public boolean hasNext(){
            return index >= 0;
        }
        public Object next(){
            if(index < 0){
                throw new NoSuchElementException();
            }
            Object result = array[index];
            index--;
            return result;
        }
    }

    public Iterator backwardIterator(){
        return new BackwardIterator();
    }
}
BoundedArrayList list = new BoundedArrayList(10);

list.add("Ala");
list.add("ma");
list.add("kota");

for(Iterator iterator = list.forwardIterator(); iterator.hasNext(); ){
    System.out.println(iterator.next());
}

for(Iterator iterator = list.backwardIterator(); iterator.hasNext(); ){
    System.out.println(iterator.next());
}
new BoundedArrayList.BackwardIterator();          // niedozwolone
System.out.println(list.forwardIterator());

BoundedArrayList$ForwardIterator@67784306
new BoundedArrayList.BackwardIterator();

|  Error:
|  an enclosing instance that contains BoundedArrayList.BackwardIterator 
|     is required
|  new BoundedArrayList.BackwardIterator();
|  ^-------------------------------------^

Własności niestatycznego typu składowego

  • dostęp do prywatnych składowych typu otaczającego
  • dostęp typu otaczającego do prywatnych składowych typu niestatycznego
  • klasa składowa nie może mieć takiej samej nazwy jak jakaś klasa nadrzędna lub pakiet
  • klasa składowa nie może zawierać składowych statycznych, z wyjątkiem wartości stałych

Klasy lokalne

class LocalExample {
    public static interface IntHolder { int getValue(); }

    public void run(){
        IntHolder[] holders = new IntHolder[10];
        for(int i = 0; i < 10; i++){
            final int fi = i;
            class MyIntHolder implements IntHolder {
                public int getValue() { return fi; }
            }
            holders[i] = new MyIntHolder();
        }

        for(int i = 0; i < 10; i++){
            System.out.println(holders[i].getValue());
        }
    }
}

new LocalExample().run();
0
1
2
3
4
5
6
7
8
9

Własności klas lokalnych

  • klasy lokalne mają dostęp do zmiennych prywatnych klas otaczających
  • klasy lokalne mają dostep do finalnych zmiennych lokalnych (w tym argumentów metod oraz wyjątków)
  • odwołanie do zmiennych lokalnych tworzy domknięcie
  • nazwa klasy lokalnej jest dostępna tylko w bloku, w którym jest ona zdefiniowana

Klasy anonimowe

class BoundedArrayList {
    private Object[] array;
    private int pointer = 0;

    public Iterator backwardIterator(){
        return new Iterator() {
            private int index = pointer-1;
            public boolean hasNext(){
                return index >= 0;
            }
            public Object next(){
                if(index < 0){
                    throw new NoSuchElementException();
                }
                Object result = array[index];
                index--;
                return result;
            }
        };
    }
}

Comparator

class NumberCollection {
    private SortedSet<String> numbers;
    public NumberCollection(){
        numbers = new TreeSet<>(new Comparator<String>(){
            public int compare(String a, String b){
                return a.length() - b.length();
            }
        });
    }

    public boolean add(String number){
        return numbers.add(number);
    }

    public String toString(){
        return numbers.toString();
    }
}
NumberCollection numbers = new NumberCollection();
numbers.add("1111");
numbers.add("1");
numbers.add("111111");

System.out.println(numbers);

"Instancja" klasy abstrakcyjnej

abstract class AbstractClass {
}

AbstractClass abstractValue = new AbstractClass(){};

Wyrażenia lambda - motywacja

import java.io.*;

File dir = new File("/home/apohllo");

String[] fileList = dir.list(new FilenameFilter() {
    public boolean accept(File file, String fileName){
        return fileName.endsWith(".java");
    }
});
for(String s : fileList){
    System.out.println(s);
}

Wyrażenie lambda zamiast klasy anonimowej

import java.io.*;

String[] fileList = new File("/home/apohllo").
    list((f,s) -> { return s.endsWith(".java"); });
for(String s : fileList){
    System.out.println(s);
}
import java.io.*;

String[] fileList = new File("/home/apohllo").
    list((f,s) -> s.endsWith(".java"));
for(String s : fileList){
    System.out.println(s);
}
Arrays.asList(new File("/home/apohllo").list((f,s) -> s.endsWith(".java"))).
    stream().forEach(System.out::println);

Interfejs Stream i techniki funkcyjne

  • interfejs Collection został rozszerzony o metodę stream
  • metoda ta została wprowadzona aby ograniczyć wsteczną niekompatybilność
  • metoda ta jest domyślna w interfejsie Collection
  • metoda zwraca elemet typu Stream
  • interfejs Stream umożliwia wykonywania metod funkcyjnych:
    • allMatch
    • anyMatch
    • collect
    • concat
    • count
    • distinct
    • empty
    • filter
    • findAny
    • findFirst
    • flatMap
    • forEach
    • map
    • itd.

fliter, map i collect

import java.util.stream.*;

Collection<String> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
numbers.stream().filter(e -> e % 2 == 0).
                 map(e -> e.toString()).
                 collect(Collectors.toList());

map

import java.util.stream.*;

Collection<String> numbers = Arrays.asList("jeden", "dwa", "trzy", "cztery");
numbers.stream().map(String::length).
                 collect(Collectors.joining(", "));

forEach

Collection<String> numbers = Arrays.asList("jeden", "dwa", "trzy", "cztery");
numbers.stream().forEach(System.out::println);
// jeden
// dwa
// trzy
// cztery

map i reduce

Collection<String> numbers = Arrays.asList("jeden", "dwa", "trzy", "cztery");
double sum = numbers.stream().map(String::length).
                              reduce(0, (x,y) -> { return x + y; });
System.out.println(sum / numbers.size());

Wartościowanie leniwe

import java.util.function.*;
import java.util.stream.*;

public class SquareGenerator implements IntSupplier {
    private int current = 1;

    @Override
    public synchronized int getAsInt(){
        int thisResult = current * current;
        current++;
        return thisResult;
    }
}
IntStream squares = IntStream.generate(new SquareGenerator());
PrimitiveIterator.OfInt stepThrough = squares.iterator();
for(int i = 0; i < 10; i++){
    System.out.println(stepThrough.nextInt());
}

for(int i = 0; i < 10; i++){
    System.out.println(stepThrough.nextInt());
}

Pytania?