Рейтинговые книги
Читем онлайн Философия Java3 - Брюс Эккель

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 80 81 82 83 84 85 86 87 88 ... 132

String s;

while((s = Iss.popO) != null) System.out.printin(s);

}

} /* Output-

stun!

on

Phasers *///:-

Внутренний класс Node тоже является параметризованным и имеет собственный параметр типа.

Для определения наличия элементов в стеке в этом примере используется предохранитель (end sentinel). Он создается при конструировании LinkedStack, а затем при каждом вызове push() новый объект Node<T> создается и связывается с предыдущим Node<T>. При вызове рор() всегда возвращается top.item, после чего текущий объект Node<T> уничтожается и происходит переход к следующему — если только текущим элементом не является предохранитель; в этом случае переход не выполняется. При повторных вызовах рор() клиент будет получать null, что свидетельствует об отсутствии элементов в стеке.

RandomList

Рассмотрим еще один пример контейнера: допустим, вам понадобилась особая разновидность списка, которая случайным образом выбирает один из своих элементов при вызове select(). Так как класс должен работать для любых объектов, мы воспользуемся параметризацией:

//• generics/RandomList.java import java.util.*;

public class RandomList<T> {

private ArrayList<T> storage'= new ArrayList<T>(), private Random rand = new Random(47); public void add(T item) { storage.add(item); } public T selectО {

return storage.get(rand.nextInt(storage.si ze()));

}•

public static void main(String[] args) {

RandomList<String> rs = new RandomList<String>(); for(String s: ("The quick brown fox jumped over " + "the lazy brown dog").splitC "))

rs. add (s); продолжение & for(int i = 0; i < 11; i++)

System.out.printers.select О + " ");

}

} /* Output:

brown over fox quick quick dog brown The brown lazy brown

Параметризованные интерфейсы

Параметризация работает и с интерфейсами. Например, класс, создающий объекты, называется генератором. В сущности, генератор представляет собой специализированную версию паттерна «метод-фабрика», но при обращении к нему никакие аргументы не передаются, тогда как метод-фабрика обычно получает аргументы. Генератор умеет создавать объекты без дополнительной информации.

Обычно генератор определяет всего один метод — тот, который создает объекты. Назовем его next() и включим в стандартный инструментарий:

//• net/mi ndvi ew/uti1/Generator.java

// Параметризованный интерфейс

package net.mi ndvi ew.uti1;

public interface Generator<T> { T nextO; } ///:-

Возвращаемое значение метода next() параметризовано по типу Т. Как видите, механизм параметризации работает с интерфейсами почти так же, как с классами.

Чтобы продемонстрировать, как работает реализация Generator, мы воспользуемся иерархией классов, представляющих разные виды кофе:

//: generics/coffee/Coffee.java package generics.coffee;

public class Coffee {

private static long counter = 0; private final long id = counter**; public String toStringO {

return getClassO.getSimpleNameО + " " + id:

}

} Hill-. generics/coffee/Latte.java package generics.coffee; public class Latte extends Coffee {} Hill generics/coffee/Mocha.java package generics.coffee; public class Mocha extends Coffee {} Hill : generics/coffee/Cappuccino.java package generics.coffee; public class Cappuccino extends Coffee {} Hill : 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-

package generics coffee.

import java util *,

import net mindview util.*,

public class CoffeeGenerator

implements Generator<Coffee>, Iterable<Coffee> {

private Class[] types = { Latte class. Mocha.class.

Cappuccino class, Americano.class. Breve class. }; private static Random rand = new Random(47); public CoffeeGeneratorО {} //Для перебора-private int size = 0.

public CoffeeGenerator(int sz) { size = sz;' } public Coffee next О { try {

return (Coffee)

types[rand nextInt(types.length)] newlnstance(). // Сообщение об ошибках во время выполнения: } catch(Exception е) {

'throw new RuntimeException(e);

}

}

class Coffeelterator implements Iterator<Coffee> { int count = size;

public boolean hasNextO { return count > 0. } public Coffee next О { count--;

return CoffeeGenerator.this.next().

}

public void removeO { // He реализован

throw new UnsupportedOperationExceptionO;

}

}:

public Iterator<Coffee> iteratorO { return new CoffeeIterator();

}

public static void main(String[] args) {

CoffeeGenerator gen = new CoffeeGenerator(); for (int i =0. i <5; i++)

System.out println(gen.nextO); for(Coffee с : new CoffeeGenerator(5)) System.out println(c),

}

} /* Output-Americano 0 Latte 1

Americano 2 продолжение &

Mocha 3 Mocha 4 Breve 5 Americano 6 Latte 7 Cappuccino 8 Cappuccino 9 *///:-

Параметризованный интерфейс Generator гарантирует, что next() вернет параметр типа. CoffeeGenerator также реализует интерфейс Iterable и поэтому может использоваться в синтаксисе foreach. Аргумент, по которому определяется момент прекращения перебора, передается при вызове второго конструктора.

А вот как выглядит другая реализация Generator<T>, предназначенная для получения чисел Фибоначчи:

//. generics/Fibonacci java // Построение чисел Фибоначчи import net mindview util *;

public class Fibonacci implements Generator<Integer> { private int count = 0;

public Integer nextO { return fib(count++); } private int fibCint n) {

if(n < 2) return 1, return fib(n-2) + fib(n-l),

}

public static void main(String[] args) { Fibonacci gen = new Fibonacci(); for(int i = 0; i < 18: i++)

System.out.print(gen.nextО + " ");

}

} /* Output-

1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 *///:-

Хотя и внутри, и снаружи класса мы работаем с int, в параметре типа передается Integer. В этом проявляется одно из ограничений параметризации в языке Java: примитивные типы не могут использоваться в качестве параметров типа. Впрочем, в Java SE5 была добавлена удобная автоматическая упаковка (распаковка) для перехода от примитивных типов к объектным «оберткам», и наоборот.

Можно сделать следующий шаг вперед и создать генератор чисел Фибоначчи с реализацией Iterable. Конечно, можно изменить реализацию класса и добавить интерфейс Iterable, но исходные коды не всегда находятся в вашем распоряжении, и вообще там, где это возможно, лучше обойтись без их модификации. Вместо этого мы воспользуемся «адаптером» для получения нужного интерфейса (этот паттерн уже упоминался ранее в книге).

Существует несколько вариантов реализации адаптеров. Например, для получения адаптируемого класса можно воспользоваться наследованием:

//: generi cs/IterableFi bonacci.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; } public Iterator<Integer> iteratorO {

return new Iterator<Integer>0 {

public boolean hasNextO { return n > 0; } public Integer next О { n- -;

return Iterabl eFibonacci .this nextO.

}

public void removeO { // He реализован

throw new UnsupportedOperationExceptionO;

}

}:

}

public static void main(String[] args) {

for(int i . new IterableFibonacci(18)) System out printO + " ");

}

} /* Output-

1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584

*///.-

Для использования IterableFibonacci в синтаксисе foreach мы передаем конструктору границу, чтобы метод hasNext() знал, когда следует возвращать false.

Параметризованные методы

До настоящего момента мы рассматривали параметризацию целых классов, однако параметризация может применяться и к отдельным методам классов. Сам класс при этом может быть параметризованным, а может и не быть — это не зависит от наличия параметризованных методов.

Параметризованный метод может изменяться независимо от класса. В общем случае параметризованные методы следует использовать «по мере возможности». Иначе говоря, если возможно параметризовать метод вместо целого класса, вероятно, стоит выбрать именно этот вариант. Кроме того, статические методы не имеют доступа к параметрам типа параметризованных классов; если такие методы должны использовать параметризацию, это должно происходить на уровне метода, а не на уровне класса.

Чтобы определить параметризованный метод, следует указать список параметров перед возвращаемым значением:

//• generics/GenericMethods.java

public class GenericMethods { public <T> void f(T x) {

System out println(x.getClass().getNameO);

}

public static void main(String[] args) {

GenericMethods gm = new GenericMethodsО,

gm.f(""); продолжение &

gm f(l); gm.f(l.O); gm.f(l.OF); gm f('c'); gm.f(gm);

}

} /* Output: java.lang.String java.lang Integer java.lang.Double java.lang.Float java.lang.Character GenericMethods *///:-

Класс GenericMethods не параметризован, хотя и класс, и его методы могут быть параметризованными одновременно. Но в данном случае только метод f() имеет параметр типа, обозначаемый списком параметров перед возвращаемым значением метода.

Учтите, что при использовании параметризованного класса параметры типов должны указываться при создании экземпляра. Но при использовании параметризованного метода указывать параметры типа йе обязательно, потому что компилятор способен «вычислить» их за вас. Таким образом, вызов f() выглядит как обычный вызов метода; создается впечатление, что метод f() существует в бесконечном количестве перегруженных версий. При вызове ему даже может передаваться аргумент типа GenericMethods.

Для вызовов f(), использующих примитивные типы, в действие вступает механизм автоматической упаковки — примитивные типы автоматически преобразуются в соответствующие объекты. Это позволяет исключить некоторые фрагменты кода, которые были необходимы прежде из-за явного выполнения преобразований.

Вычисление типа аргумента

Параметризацию иногда упрекают в том, что она увеличивает объем кода. Для наглядности возьмем пример holding/MapOfList.java из главы 11. Создание контейнера Map с List выглядит так:

1 ... 80 81 82 83 84 85 86 87 88 ... 132
На этой странице вы можете бесплатно читать книгу Философия Java3 - Брюс Эккель бесплатно.
Похожие на Философия Java3 - Брюс Эккель книги

Оставить комментарий