Параметризация работает и с интерфейсами. Например, класс, создающий объекты, называется генератором. В сущности, генератор представляет собой специализированную версию паттерна «метод-фабрика», но при обращении к нему никакие аргументы не передаются, тогда как метод-фабрика обычно получает аргументы. Генератор умеет создавать объекты без дополнительной информации.
Обычно генератор определяет всего один метод — тот, который создает объекты. Назовем его next() и включим в стандартный инструментарий:
//: net/mindview/util/Generator.java
// Параметризованный интерфейс
package net.mindview.util;
public interface Generator<T> {
T next(); }
Возвращаемое значение метода next() параметризовано по типу Т. Как видите, механизм параметризации работает с интерфейсами почти так же, как с классами.
Чтобы продемонстрировать, как работает реализация Generator, мы воспользуемся иерархией классов, представляющих разные виды кофе:
//: generics/coffee/Coffee.java
package generics.coffee;
public class Coffee {
private static long counter = 0;
private final long id = counter++;
public String toString() {
return getClass().getSimpleName() + " " + id;
}
}
//: generics/coffee/Latte.java
package generics.coffee;
public class Latte extends Coffee {}
//: generics/coffee/Mocha.java
package generics.coffee;
public class Mocha extends Coffee {}
//: generics/coffee/Cappuccino.java
package generics.coffee;
public class Cappuccino extends Coffee {}
//: generics/coffee/Americano.java
package generics.coffee;
public class Americano extends Coffee {}
//: generics/coffee/Breve.java
package generics.coffee;
public class Breve extends Coffee {}
Теперь мы можем реализовать интерфейс Generator<Coffee>, который создает случайные типы объектов из иерархии Coffee:
//: generics/coffee/CoffeeGenerator.java
// Генератор случайных объектов из иерархии Coffee
class CoffeeIterator implements Iterator<Coffee> {
int count = size;
public boolean hasNext() { return count > 0; }
public Coffee next() {
count--;
return CoffeeGenerator.this.next();
}
public void remove() {// He реализован
throw new UnsupportedOperationException();
}
};
public Iterator<Coffee> iterator() {
return new CoffeeIterator();
}
public static void main(String[] args) {
CoffeeGenerator gen = new CoffeeGenerator();
for(int i = 0; i < 5; i++)
System.out.println(gen.next());
for(Coffee c : new CoffeeGenerator(5))
System.out.println(c);
}
}
<spoiler text="Output:">
Americano 0
Latte 1
Americano 2
Mocha 3
Mocha 4
Breve 5
Americano 6
Latte 7
Cappuccino 8
Cappuccino 9
</spoiler> Параметризованный интерфейс Generator гарантирует, что next() вернет параметр типа. CoffeeGenerator также реализует интерфейс Iterable и поэтому может использоваться в синтаксисе foreach. Аргумент, по которому определяется момент прекращения перебора, передается при вызове второго конструктора.
А вот как выглядит другая реализация Generator<T>, предназначенная для получения чисел Фибоначчи:
//: generics/Fibonacci.java
// Построение чисел Фибоначчи
import net.mindview.util.*;
public class Fibonacci implements Generator<Integer> {
</spoiler> Хотя и внутри, и снаружи класса мы работаем с int, в параметре типа передается Integer. В этом проявляется одно из ограничений параметризации в языке Java: примитивные типы не могут использоваться в качестве параметров типа. Впрочем, в Java SE5 была добавлена удобная автоматическая упаковка (распаковка) для перехода от примитивных типов к объектным «оберткам», и наоборот. Можно сделать следующий шаг вперед и создать генератор чисел Фибоначчи с реализацией Iterable. Конечно, можно изменить реализацию класса и добавить интерфейс Iterable, но исходные коды не всегда находятся в вашем распоряжении, и вообще там, где это возможно, лучше обойтись без их модификации. Вместо этого мы воспользуемся «адаптером» для получения нужного интерфейса (этот паттерн уже упоминался ранее в книге). Существует несколько вариантов реализации адаптеров. Например, для получения адаптируемого класса можно воспользоваться наследованием:
//: generics/IterableFibonacci.java
// Adapt the Fibonacci class to make it Iterable.
import java.util.*;
public class IterableFibonacci
extends Fibonacci implements Iterable<Integer> {
private int n;
public IterableFibonacci(int count) { n = count; }
</spoiler> Для использования IterableFibonacci в синтаксисе foreach мы передаем конструктору границу, чтобы метод hasNext() знал, когда следует возвращать false.